This is 3d map simulation only and adding feature navigating to selected object
I use AI Navigation and adding line renderer as my guide to the target
If you look at the picture below the line is not flat and I don't know how to make it flat at the surface
This is my code
[SerializeField]
private Collectable Prefab;
[SerializeField]
private Transform Player;
[SerializeField]
private LineRenderer Path;
[SerializeField]
private float PathHeightOffset = 1f;
[SerializeField]
private float SpawnHeightOffset = 1f;
[SerializeField]
private float PathUpdateSpeed = 0.25f;
private Collectable ActiveInstance;
private UnityEngine.AI.NavMeshTriangulation Triangulation;
private Coroutine DrawPathCoroutine;
private void Awake()
{
Triangulation = UnityEngine.AI.NavMesh.CalculateTriangulation();
}
private void Start()
{
SpawnNewObject();
}
public void SpawnNewObject()
{
ActiveInstance = Instantiate(Prefab,
Triangulation.vertices[Random.Range(0, Triangulation.vertices.Length)] + Vector3.up * SpawnHeightOffset,
Quaternion.Euler(90, 0, 0)
);
ActiveInstance.Spawner = this;
if (DrawPathCoroutine != null)
{
StopCoroutine(DrawPathCoroutine);
}
DrawPathCoroutine = StartCoroutine(DrawPathToCollectable());
}
private IEnumerator DrawPathToCollectable()
{
WaitForSeconds Wait = new WaitForSeconds(PathUpdateSpeed);
UnityEngine.AI.NavMeshPath path = new UnityEngine.AI.NavMeshPath();
while (ActiveInstance != null)
{
if (UnityEngine.AI.NavMesh.CalculatePath(Player.position, new Vector3(55.6f, 32.04f, 17.37f), UnityEngine.AI.NavMesh.AllAreas, path))
{
Path.positionCount = path.corners.Length;
for (int i = 0; i < path.corners.Length; i++)
{
Path.SetPosition(i, path.corners[i] + Vector3.up * PathHeightOffset);
}
}
else
{
Debug.LogError($"Unable to calculate a path on the NavMesh between {Player.position} and {ActiveInstance.transform.position}!");
}
yield return Wait;
}
}
That's a general issue that there is only the option to align to the Z axis
But - even though it is a bit uncanny - you can simply rotate it and modify the line positions accordingly so it basically aligns with the local Z axis, but you rotate it so it again aligns flat with the world Y axis
So basically
rotate the LineRenderer object to -90 on the X axis
set it to align view with Z
then when assigning positions simply switch them so that (x, y, z) -> (x, z, -y)
Using a little extension for this
public static class Vector3Extensions
{
public Vector3 FlipForLine(this Vector3 v)
{
return new Vector3 (v.x, v.z, -v.y);
}
}
You could do
for (int i = 0; i < path.corners.Length; i++)
{
Path.SetPosition(i, (path.corners[i] + Vector3.up * PathHeightOffset).FlipForLine());
}
though in general it is cheaper to have a single call to SetPositions passing the entire array at once.
Typed on the phone but I hope the idea gets clear
Related
It says it all in the title, really. I have a blend tree set up with 4 directions and the player moves and rotates perfectly, but if I turn to run directly in (for example) the left direction, the left strafe animation plays while running in that direction. The player is facing in the right direction it is just his legs are playing the wrong animation.
I have tried using "normalized" and "transform.Translate" but nothing seems to make a difference.
public class TwinStickMovement : MonoBehaviour
{
[SerializeField] private float playerSpeed = 5f;
[SerializeField] private float graityValue = -9.81f;
[SerializeField] private float controllerDeadZone = 0.1f;
[SerializeField] private float gamepadRotateSmoothing = 1000f;
[SerializeField] private bool isGamePad;
private CharacterController controller;
private Animator animator;
private Vector2 movement;
private Vector2 aim;
private Vector3 playerVelocity;
private Vector3 lookPoint;
private PlayerControls playerControls;
private PlayerInput playerInput;
private bool animLocked = false;
private void Awake()
{
controller = GetComponent<CharacterController>();
playerControls = new PlayerControls();
playerInput = GetComponent<PlayerInput>();
animator = GetComponent<Animator>();
}
private void FixedUpdate()
{
if (!animLocked && movement != Vector2.zero)
{
animator.SetFloat("moveX", movement.x);
animator.SetFloat("moveY", movement.y);
}
}
private void OnEnable()
{
playerControls.Enable();
}
private void OnDisable()
{
playerControls.Disable();
}
private void Update()
{
HandleInput();
HandleMovement();
HandleRotation();
}
private void HandleInput()
{
movement = playerControls.Controls.Movement.ReadValue<Vector2>();
aim = playerControls.Controls.Aim.ReadValue<Vector2>();
}
private void HandleMovement()
{
Vector3 move = new Vector3(movement.x, 0, movement.y);
controller.Move(move * Time.deltaTime * playerSpeed);
playerVelocity.y += graityValue * Time.deltaTime;
controller.Move(playerVelocity * Time.deltaTime);
}
private void HandleRotation()
{
if (isGamePad)
{
if (Mathf.Abs(aim.x) > controllerDeadZone || Mathf.Abs(aim.y) > controllerDeadZone)
{
Quaternion newrotation = Quaternion.LookRotation(new Vector3(aim.x, 0f, aim.y),Vector3.up);
transform.rotation = Quaternion.RotateTowards(transform.rotation, newrotation, gamepadRotateSmoothing * Time.deltaTime);
}
}
else
{
Ray ray = Camera.main.ScreenPointToRay(aim);
Plane groundPlane = new Plane(Vector3.up, Vector3.zero);
float rayDistance;
if (groundPlane.Raycast(ray, out rayDistance))
{
Vector3 point = ray.GetPoint(rayDistance);
LookAt(point);
}
}
}
private void LookAt(Vector3 point)
{
Vector3 heightCorrectPoint = new Vector3(lookPoint.x, transform.position.y, lookPoint.z);
transform.LookAt(heightCorrectPoint);
}
public void OnDeviceChange(PlayerInput pi)
{
isGamePad = pi.currentControlScheme.Equals("Gamepad") ? true : false;
}
}
I assume that you're using logic that is equivalent to if moveX > 0 and moveY approximatly 0 then play strafe animation. However this doesn't take the players orientation into account and as such only works correctly when you look up. You will need to transform your moveX and moveY into the localspace of lookX and lookY so that the animator can make the appropriate choice.
The left panel shows the information your animator is receiving and based on that it is making the wrong choice and says that your character is strafing. The right panel shows the vector after it was transformed. Here your animator would recognize that you are moving forward stronger than sideways and therefore the strafe is inappropriate and the forward walk animation should play.
I'll abridge the math to make it all work and skip to the interesting bits. Basically you want to transform a uv coordinate system into the xy system. For that you have two given perpendicular vectors (look direction and the "right") in the xy coordinates which you can note in two equations like so:
x = au+bv
y = cu+dv
You can use these to find u and v, which if you do it will be:
u = (by-dx)/(bc-da)
v = (ay-cx)/(cd-ab)
Plugging in all the values and inserting the coordinates that you want to transform into x and y will give you an answer in uv space, which is actually what you need to give to the animator.
Since math is hard and I probably didn't do a particularly good job explaining it, here's the code that does it in Unity:
private Vector2 MovementRelativeToLookdirection(){
float x = movement.x;
float y = movement.y;
float a = transform.forward.x;
float b = transform.forward.z;
float c = transform.right.x;
float d = transform.right.z;
float u = (b*y-d*x)/(b*c-d*a);
float v = (a*y-c*x)/(c*d-a*b);
Vector2 transformedVector = new Vector2(u, v);
return transformedVector;
}
private void FixedUpdate()
{
if (!animLocked && movement != Vector2.zero)
{
Vector2 actualDir = MovementRelativeToLookdirection();
animator.SetFloat("moveX", actualDir.x);
animator.SetFloat("moveY", actualDir.y);
}
}
If my assumption is actually the problem this should fix it.
I made a Camera Script that follows the player using a lerp for making it look more smooth, but for some reason, it looks laggy when walking in some sudden areas.
The ground the player is walking on is tilemap, and at first, I thought there were tiny cell gaps that caused the problem, but it still happens even when I changed the ground to one solid block. So I concluded it must be something with Camera Script that cause this problem.
Here is a video clip of what I'm talking about: https://youtu.be/mmBMHWuHpxo
I think the problem stems from this part of my camera script:
void SetTargetPos()
{
// By default the target x and y coordinates of the camera are it's current x and y coordinates.
targetX = transform.position.x;
targetY = transform.position.y;
// If the player has moved beyond the x margin...
if (CheckXMargin())
{
// ... the target x coordinate should be a Lerp between the camera's current x position and the player's current x position.
targetX = Mathf.Lerp(transform.position.x, transformPlayer.position.x, xSmooth * Time.deltaTime);
}
// If the player has moved beyond the y margin...
if (CheckYMargin())
{
// ... the target y coordinate should be a Lerp between the camera's current y position and the player's current y position.
targetY = Mathf.Lerp(transform.position.y, transformPlayer.position.y, ySmooth * Time.deltaTime);
}
// The target x and y coordinates should not be larger than the maximum or smaller than the minimum.
targetX = Mathf.Clamp(targetX, currentMinBounds.x, currentMaxBounds.x);
targetY = Mathf.Clamp(targetY, currentMinBounds.y, currentMaxBounds.y);
// Set the camera's position to the target position with the same z component.
transform.position = new Vector3(targetX, targetY, transform.position.z);
TestOutOfCamBounds();
}
Btw I have the exact same script on different Unity Projects with the same variable inputs. There is a small difference, and that just the overall size of everything in the second project is a lot smaller than the first one. But it works completely fine on that project. I have also tried Smoothdamp instead of Lerp, still no success. Here is a video clip of the other project: https://youtu.be/baJmKehYfG0
Any help will be much appreciated.
If you want to look at the entire script here it is:
public class Camera_Controller : MonoBehaviour
{
private static Camera_Controller _instance;
public static Camera_Controller instance;
[Header("Put player here")]
public GameObject player;
public Transform transformPlayer;
[Space]
Vector2 currentMinBounds;
Vector2 currentMaxBounds;
public Vector3 targetPos;
[Space]
[Header("Camera Properties")]
public float xMargin = 1f;
public float yMargin = 1f;
public float xSmooth = 8f;
public float ySmooth = 8f;
public Vector3 velocity = Vector3.zero;
private float targetY;
private float targetX;
[Header("Smooth Transition")]
public Vector3 oldPosition;
public Vector3 targetPosition;
public float transitionsTime;
public bool switchingCamera;
private void Awake()
{
if (_instance != null && _instance != this)
{
Destroy(gameObject);
return;
}
else
{
_instance = this;
}
instance = _instance;
}
void Start()
{
targetPos = transform.position;
}
private bool CheckXMargin()
{
// Returns true if the distance between the camera and the player in the x axis is greater than the x margin.
return Mathf.Abs(transform.position.x - transformPlayer.position.x) > xMargin;
}
private bool CheckYMargin()
{
// Returns true if the distance between the camera and the player in the y axis is greater than the y margin.
return Mathf.Abs(transform.position.y - transformPlayer.position.y) > yMargin;
}
void Update()
{
if (!switchingCamera)
{
SetTargetPos();
}
}
public void SetCamBounds(Vector2 minBounds, Vector2 maxBounds) //Called from Game Events Trough WarpController as trigger
{
currentMinBounds = minBounds;
currentMaxBounds = maxBounds;
}
//SetTargetPos() should be causing the problem
void SetTargetPos()
{
// By default the target x and y coordinates of the camera are it's current x and y coordinates.
targetX = transform.position.x;
targetY = transform.position.y;
// If the player has moved beyond the x margin...
if (CheckXMargin())
{
// ... the target x coordinate should be a Lerp between the camera's current x position and the player's current x position.
targetX = Mathf.Lerp(transform.position.x, transformPlayer.position.x, xSmooth * Time.deltaTime);
}
// If the player has moved beyond the y margin...
if (CheckYMargin())
{
// ... the target y coordinate should be a Lerp between the camera's current y position and the player's current y position.
targetY = Mathf.Lerp(transform.position.y, transformPlayer.position.y, ySmooth * Time.deltaTime);
}
// The target x and y coordinates should not be larger than the maximum or smaller than the minimum.
targetX = Mathf.Clamp(targetX, currentMinBounds.x, currentMaxBounds.x);
targetY = Mathf.Clamp(targetY, currentMinBounds.y, currentMaxBounds.y);
// Set the camera's position to the target position with the same z component.
transform.position = new Vector3(targetX, targetY, transform.position.z);
TestOutOfCamBounds();
}
void TestOutOfCamBounds() //Set Camera some boundaries.
{
if (targetPos.x <= currentMinBounds.x)
{
targetPos.x = currentMinBounds.x;
}
if (targetPos.x >= currentMaxBounds.x)
{
targetPos.x = currentMaxBounds.x;
}
if (targetPos.y <= currentMinBounds.y)
{
targetPos.y = currentMinBounds.y;
}
if (targetPos.y >= currentMaxBounds.y)
{
targetPos.y = currentMaxBounds.y;
}
}
}
Try doing the call for the cammer movement in Late update, Example bellow.
private void LateUpdate() {
SetTargetPos();
}
This is called after the update method and might help reduce the cammer jitter.
Your second video is 2d and your first is 3d. try lerping the z position also.
targetZ = Mathf.Lerp(transform.position.z, transformPlayer.position.z, zSmooth * Time.deltaTime);
I found a solution to it all!
I changed the update function to LateUpdate()... Sorry for wasting your time
I'm making a tower defense game in Unity 3D C#. The goal is for players to place objects in a grid (grid code below). I'm having trouble getting the "Corner" object to get placed on the ground, it's just appearing anywhere in the Y axis that I click. I'm confused because I'm using the same exact script for a different item and it's working fine (I just changed out the words "wall" for "corner"). The only difference between the items is that one is a straight line and one is an L shape (I did this through making two cubes, combining them, and putting them as a child Empty game object called "corner"). Any ideas?
using UnityEngine;
public class Grid : MonoBehaviour
{
[SerializeField]
private float size = 1f;
public Vector3 GetNearestPointOnGrid(Vector3 position)
{
position -= transform.position;
int xCount = Mathf.RoundToInt(position.x / size);
int yCount = Mathf.RoundToInt(position.y / size);
int zCount = Mathf.RoundToInt(position.z / size);
Vector3 result = new Vector3(
(float)xCount * size,
(float)yCount * size,
(float)zCount * size);
result += transform.position;
return result;
}
private void OnDrawGizmos()
{
Gizmos.color = Color.yellow;
for (float x = 0; x < 40; x += size)
{
for (float z = 0; z < 40; z += size)
{
var point = GetNearestPointOnGrid(new Vector3(x, 0f, z));
Gizmos.DrawSphere(point, 0.1f);
}
}
}
}
CornerPlacer:
using UnityEngine;
public class CornerPlacer : MonoBehaviour
{
private Grid grid;
public GameObject corner;
public bool placeCornerMode;
private void Awake()
{
grid = FindObjectOfType<Grid>();
}
void Update()
{
if (placeCornerMode)
{
GetItemsCorner();
}
}
void GetItemsCorner()
{
if (Input.GetMouseButtonDown(0))
{
RaycastHit hitInfo;
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
if (Physics.Raycast(ray, out hitInfo))
{
PlaceCornerNear(hitInfo.point);
placeCornerMode = false;
}
}
}
private void PlaceCornerNear(Vector3 clickPoint)
{
var finalPosition = grid.GetNearestPointOnGrid(clickPoint);
Instantiate(corner, finalPosition, Quaternion.identity);
}
public void CornerModeOn()
{
placeCornerMode = true;
}
}
Following this tutorial, I'm trying to use Line Renderer component to draw a sine wave from point A to B.I'm using the input mouse position. However what I did so far is not working, it just draw the sine wave along the x axis, I need to draw it from the start point to the input mouse position.
private void Update()
{
if (Input.GetMouseButtonDown(0))
{
Vector3 newPos = Camera.main.ScreenToWorldPoint(Input.mousePosition);
newPos.z = 0;
CreatePointMarker(newPos);
GenerateNewLine();
}
}
private void CreatePointMarker(Vector3 pointPosition)
{
Instantiate(linePointPrefab, pointPosition, Quaternion.identity);
}
private void GenerateNewLine()
{
GameObject[] allPoints = GameObject.FindGameObjectsWithTag("PointMarker");
Vector3[] allPointPositions = new Vector3[allPoints.Length];
var pointList = new List<Vector3>();
for (int i = 0; i < 50; i ++)
{
var dir = allPoints[0].transform.position - allPoints[1].transform.position;
float x = dir.x * i;
float y = Mathf.Sin(x * Time.time);
var sine = new Vector3(x, y, 0f);
var tangentLine = allPoints[0].transform.position + sine;
pointList.Add(tangentLine);
}
SpawnLineGenerator(pointList.ToArray());
}
}
private void SpawnLineGenerator(Vector3[] linePoints)
{
GameObject newLineGen = Instantiate(lineGeneratorPrefab);
LineRenderer lRend = newLineGen.GetComponent<LineRenderer>();
lRend.positionCount = linePoints.Length;
lRend.SetPositions(linePoints);
}
As an alternative I would suggest to instead use
lineRenderer.useWorlsSpace = false;
so the points are no longer set in worldspace but in the local space relative to the linerenderer transform.
Now you can simply rotate the linerenderer transform to point towards the latest user input position.
I couldn't use your code example since I don't know what your prefabs are and do so I created my own from the code in the given Video. I hope you can reuse/recreate the parts necessary
public class SinusWave : MonoBehaviour
{
public Vector3 initalPosition;
public int pointCount = 10;
public LineRenderer line;
private Vector3 secondPosition;
private Vector3[] points;
private float segmentWidth;
private void Awake()
{
line = GetComponent<LineRenderer>();
line.positionCount = pointCount;
// tell the linerenderer to use the local
// transform space for the point coorindates
line.useWorldSpace = false;
points = new Vector3[pointCount];
}
private void Update()
{
if (Input.GetMouseButtonDown(0))
{
// Camera.main.ScreenToWorldPoint needs a value in Z
// for the distance to camera
secondPosition = Camera.main.ScreenToWorldPoint(Input.mousePosition - Vector3.forward * Camera.main.transform.position.z);
secondPosition.z = 0;
var dir = secondPosition - initalPosition;
// get the segmentWidth from distance to end position
segmentWidth = Vector3.Distance(initalPosition, secondPosition) / pointCount;
// get the difference angle in the Z axis between the current transform.right
// and the direction
var angleDifference = Vector3.SignedAngle(transform.right, dir, Vector3.forward);
// and rotate the linerenderer transform by that angle in the Z axis
// so the result will be that it's transform.right
// points now towards the clicked position
transform.Rotate(Vector3.forward * angleDifference, Space.World);
}
for (var i = 0; i < points.Length; ++i)
{
float x = segmentWidth * i;
float y = Mathf.Sin(x * Time.time);
points[i] = new Vector3(x, y, 0);
}
line.SetPositions(points);
}
}
Btw I just assume here that GameObject.FindGameObjectsWithTag("PointMarker"); actually retuns all the so far spawned linePointPrefab objects.
It would be better to store them right away when you spawn them like
private List<Transform> allPoints = new List<Transform>();
...
allPoints.Add(Instantiate(linePointPrefab, pointPosition, Quaternion.identity).transform);
then you can skip the usage of FindObjectsWithType completely
For adopting this local space position also for the line point prefabs you instantiate simply instantiate them as child of the linerenderer transform or spawn them and the linerenderer under the same parent so the relative positions are the same. In this case you wouldn't rotate the linerenderer but the entire parent object so the spawned linepoints are rotated along with it
I try to write a script that show the sprite that follows by finger. But I need to this sprite can move only on specified distance from the anchor and only between specified angles. Now, I have the next code:
public class GunPowerController : MonoBehaviour {
public GameObject _fingerprint;
public Transform _anchor;
public Gun _gun;
public float _maxPower = 1f;
public float _maxAngle = 15f;
public float _minAngle = 0f;
private Camera _camera;
private GameObject _fingerprintInstance;
void Awake()
{
_maxPower = Mathf.Abs(_maxPower);
_camera = Camera.main;
}
// Update is called once per frame
void Update ()
{
if (Input.GetMouseButtonDown(0))
{
var touchWorldPosition = _camera.ScreenToWorldPoint(Input.mousePosition);
var hitInfo = Physics2D.Raycast(touchWorldPosition, Vector2.zero);
if (hitInfo && hitInfo.transform.gameObject.Equals(gameObject))
{
touchWorldPosition.z = transform.position.z;
_fingerprintInstance = (GameObject)Instantiate(_fingerprint, touchWorldPosition, Quaternion.identity);
}
}
else if (_fingerprintInstance != null)
{
if (Input.GetMouseButtonUp(0))
{
Destroy(_fingerprintInstance);
}
else
{
var touchWorldPosition = _camera.ScreenToWorldPoint(Input.mousePosition);
Move(touchWorldPosition);
}
}
}
private void Move(Vector3 target)
{
target.z = transform.position.z;
Vector3 distance = target - _anchor.position;
Vector3 axis = _anchor.position;
axis.x = -1f;
float angle = Vector3.Angle(axis, distance) * Mathf.Sign(distance.y - axis.y);
if (distance.sqrMagnitude > _maxPower * _maxPower)
{
distance.Normalize();
distance *= _maxPower;
target = _anchor.position + distance;
}
if(_minAngle > angle)
{
//Here I need to hold vector rotation while the user doesn't
//return to the available space between angles.
}
else if (_maxAngle < angle)
{
//Here I need to hold vector rotation while the user doesn't
//return to the available space between angles.
}
_fingerprintInstance.transform.position = Vector3.Lerp(_fingerprintInstance.transform.position, target,
10f * Time.deltaTime);
}
}
I try to rotate a vector target on difference _min|maxAngle - angle but it works wrong. How to make it?
The current problem:
P.S. I retried a lot of variants but coldn't make it. If it needs some details, pls, write me I'll post.
You should use Mathf.Clamp .
Link :https://docs.unity3d.com/ScriptReference/Mathf.Clamp.html