I've got this problem: I need that enemies move towards a target position, but my problem is that i wrote this inside the Update function, so the "foreach" statement is done in just one frame. Also, the "waypoint is reached" sentence is never shown. How can I solve this?
Here is the code: `public class WayPointMovement : MonoBehaviour {
public EnemyDataSpawn enemy;
private Vector3 lastPos;
void Start ()
{
lastPos = gameObject.transform.position;
}
void Update ()
{
gameObject.transform.LookAt (LevelManager.current.playerObject.transform);
//WaypointData is a scriptable object that contains the definition of "x" and "z" coordinates.
foreach (WaypointData waypoints in enemy.enemy.waypoint) {
Debug.Log (waypoints.x);
gameObject.transform.position = Vector3.MoveTowards (gameObject.transform.position, new Vector3 (lastPos.x + waypoints.x, 0f, lastPos.z + waypoints.z), Time.deltaTime * enemy.enemy.easySpeedMovement);
if (gameObject.transform.position.x == lastPos.x + waypoints.x && gameObject.transform.position.z == lastPos.z + waypoints.z) {
Debug.Log("waypoint reached");
lastPos = gameObject.transform.position;
}
}
}
}`
You need to rework Update so that it only does one frame's worth of work. That means that you have to remember a) which waypoint you're going toward, and b) how far you were through going to it. You only move on to the next waypoint when you actually get to it.
There are two ways to track the progress through waypoints:
1. You can simply delete waypoints once they are reached. Then, the waypoint you're trying to get to is always the first one in the list.
2. If you want to keep the waypoints, then you need an extra variable to track which waypoint you're currently moving toward. This could be something like an index into an array or list, or it could be an enumerator (IEnumerator<WaypointData>) on which you call MoveNext each time you finish a waypoint.
You've indicated (in comments on this answer) that you don't want to delete waypoints as you reach them. The most flexible arrangement, allowing you to add new waypoints as you go, is to use an index variable:
int waypointIndex;
void Start()
{
waypointIndex = 0;
}
void Update()
{
if (waypointIndex < waypoints.Count)
{
update position;
if (reached waypoint)
{
waypointIndex++;
}
}
}
Then, in your Update method, instead of writing a loop, you update your game object's position exactly once, check if it has reached the waypoint just once, and if it has, move on to the next waypoint before returning. If multiple moves are needed to get to a waypoint, you allow those to be multiple calls to Update; each Update is only one step of the movement.
You will need better logic for determining when you have reached a waypoint. As other commenters have indicated, with floating-point values, simply checking if they are exactly equal is not likely to work. Instead, you can check whether the distance to the waypoint is less than Time.deltaTime * enemy.enemy.easySpeedMovement, and if it is, then instead of using Vector3.MoveTowards, just set the position to the waypoint exactly, and treat that step as having reached the waypoint.
Here's the rough logic (in pseudocode) (but not as pseudocode as when I first wrote it):
Vector3 lastPos;
int waypointIndex;
void Start()
{
lastPos = gameObject.transform.position;
waypointIndex = 0;
}
void Update()
{
if (waypointIndex < waypoints.Count)
{
waypointPosition = new Vector3 (lastPos.x + waypoints.x, 0f, lastPos.z + waypoints.z);
if (Vector3.Distance(gameObject.transform.position, waypointPosition) > length of one step)
{
gameObject.transform.position = Vector3.MoveToward(gameObject.transform.position, waypointPosition);
}
else
{
gameObject.transform.position = waypointPosition;
log("Waypoint reached");
waypointIndex++;
}
}
}
Use Vector3.sqrMagnitude for comparing closeness. You can define how "close" is close enough. I'm going to use a waypoint as a Vector3 in this example. You can determine the waypoint Vector3 however you like.
// define this globally
public float closeEnough = 5f;
// Within your compare method
if((gameObject.transform.position - waypoint).sqrMagnitude < closeEnough)
{
Debug.Log("waypoint reached");
}
Note: The result of the Vector3 - Vector3, is another Vector3.
Related
I am using the Unity Engine with C#.
I have a 1x1 cube which moves forward on a grid of 49, 1x1 cubes (screenshot below) - when I press the start button on the controller.
The movement code for the cube is below.
void MovePlayerCube()
{
transform.Translate(direction * moveSpeed * Time.deltaTime);
}
When this cube passes over a cube with an arrow on it, the cube will change direction to where the arrow is pointing (staying on the same Y axis).
I need to detect the exact point at which the cube is directly over the cube with the arrow on it, and run the 'change direction' code at that point.
I'm currently using Vector3.Distance to check if the X and Z coordinates of the 2 cubes are close enough together (if they are less than 0.03f in distance), I can't check if they are equal due to floating point imprecision.
However this is really ineffective as half the time this code doesn't register for probably the same reason, and if I increase the 0.03f to a point where it never misses it becomes really noticeable that the cube isn't aligned with the grid anymore.
There has to be a proper solution to this and hopefully I've clarified the situation enough?
Any advice is appreciated.
You are moving your cube via
transform.Translate(direction * moveSpeed * Time.deltaTime);
which will never be exact an might overshoot your positions.
=> I would rather implement a coroutine for moving the cube exactly one field at a time, ensuring that after each iteration it fully aligns with the grid and run your checks once in that moment.
It doesn't even have to match exactly then, you only need to check if you are somewhere hitting a cube below you.
So something like e.g.
private Vector3Int direction = Vector3Int.left;
private IEnumerator MoveRoutine()
{
// depends on your needs if this runs just forever or certain steps
// or has some exit condition
while(true)
{
// calculate the next position
// optional round it to int => 1x1 grid ensured on arrival
// again depends a bit on your needs
var nextPosition = Vector3Int.RoundToInt(transform.position) + direction;
// move until reaching the target position
// Vector3 == Vector3 uses a precision of 1e-5
while(transform.position != nextPosition)
{
transform.position = Vector3.MoveTowards(transform.position, nextPosition, moveSpeed * Time.deltaTime);
yield return null;
}
// set target position in one frame just to be sure
transform.position = nextPosition;
// run your check here ONCE and adjust direction
}
}
start this routine only ONCE via
StartCoroutine(MoveRoutine());
or if you have certain exit conditions at least only run one routine at a time.
A Corouine is basically just a temporary Update routine with a little bit different writing => of course you could implement the same in Update as well if you prefer that
private Vector3Int direction = Vector3Int.left;
private Vector3 nextPosition;
private void Start()
{
nextPosition = transform.position;
}
private void Update()
{
if(transform.position != nextPosition)
{
transform.position = Vector3.MoveTowards(transform.position, nextPosition, moveSpeed * Time.deltaTime);
}
else
{
transform.position = nextPosition;
// run your check here ONCE and adjust direction
// then set next position
nextPosition = Vector3Int.RoundToInt(transform.position) + direction;
}
}
Then regarding the check you can have a simple raycast since you only run it in a specific moment:
if(Physics.Raycast(transform.position, Vector3.down, out var hit))
{
direction = Vector3Int.RountToInt(hit.transform.forward);
}
assuming of course your targets have colliders attached, your moved cube position (pivot) is above those colliders (assumed it from your image) and your targets forward actually points int the desired new diretcion
I would do it this way. First I would split the ability of certain objects to be "moving with certain speed" and "moving in a certain direction", this can be done with C# interfaces. Why? Because then your "arrow" cube could affect not only your current moving cube, but anything that implements the interfaces (maybe in the future you'll have some enemy cube, and it will also be affected by the arrow modifier).
IMovingSpeed.cs
public interface IMovementSpeed
{
float MovementSpeed{ get; set; }
}
IMovementDirection3D.cs
public interface IMovementDirection3D
{
Vector3 MovementDirection { get; set; }
}
Then you implement the logic of your cube that moves automatically in a certain direction. Put this component on your player cube.
public class MovingStraight: MonoBehaviour, IMovementSpeed, IMovementDirection3D
{
private float _movementSpeed;
Vector3 MovementSpeed
{
get { return _movementSpeed; }
set { _movementSpeed = value; }
}
private Vector3 _movementDirection;
Vector3 MovementDirection
{
get { return _movementDirection; }
set { _movementDirection= value; }
}
void Update()
{
// use MovementSpeed and MovementDirection to advance the object position
}
}
Now to implement how the arrow cube modifies other objects, I would attach a collision (trigger) component for both moving cube and the arrow cube.
In the component of the arrow cube, you can implement an action when something enters this trigger zone, in our case we check if this object "has direction that we can change", and if so, we change the direction, and make sure that the object will be aligned, by forcing the arrow cube's position and the other object's position to be the same on the grid.
public class DirectionModifier: MonoBehaviour
{
private Vector3 _newDirection;
private void OnTriggerEnter(Collider collider)
{
IMovementDirection3D objectWithDirection = collider as IMovementDirection3D ;
if (objectWithDirection !=null)
{
objectWithDirection.MovementDirection = _newDirection;
// to make sure the object will continue moving exactly
// from where the arrow cube is
collider.transform.position.x = transform.position.x;
collider.transform.position.y = transform.position.y;
}
}
}
If you made your trigger zones too large, however, then the moving cube will "jump" abruptly when it enters the arrow cube's trigger zone. You can fix it by either starting a coroutine as other answers suggested, or you could make the trigger zones pretty small, so that the jump is not noticeable (just make sure not to make them too small, or they may not intersect each other)
You could then similarly create many other modifying blocks, that would change speed or something
I think that it is enough for you to check if the X and Z coordinates are equal, since the movement occurs only along them
Example
if(_player.transfom.position.x == _gameSquare.transfom.position.x && _player.transfom.position.z == _gameSquare.transfom.position.z)
{
Debag.Log("Rotate!")
}
So if I wanted multiple different NPCs to each go to their own set of waypoints, what would be a good way of achieving this?
I have a setup where I have 3 sets of tags, Waypoint1-1, Waypoint2-1, and Waypoint3-1, as well as a script that auto populates all three sets of waypoints.
But how would I specify what waypoint for each of possible three NPC to choose, and then get it to target the next waypoint automatically? I.E. NPC 1 sets Waypoint1-1 as a target, NPC 2 sets Waypoint2-1 as a target, and then I have them move there. Once arrived, they set Waypoint1-2 and Waypoint2-2 as a target respectively. I don't need them to pathfind per se, and I figured transform.lookat should work; I'm more concerned with them moving between waypoints as specified.
From the way you have asked the question, I am assuming that NPC 1 is going to walk to waypoint 1, then move automatically to waypoint 2 and finally to waypoint 3. The NPC will then repeat this, effectively walking around on a triangular path.
You could do this buy creating a script, let's call it NPCMovementScript and attach it to each NPC.
Inside the script, you could have a list of waypoints and then invoke MoveTowards on the NPC object to move towards each waypoint, I have included an example in the following script:
public class NPCMovementScript : MonoBehaviour
{
public float speed = 1.0f;
public List<Transform> listOfWaypoints;
private int currentIndexOfWaypoint;
void Start()
{
currentIndexOfWaypoint = 0;
}
void Update()
{
if (Vector3.Distance(transform.position, listOfWaypoints[currentIndexOfWaypoint].position) < 0.001f)
{
currentIndexOfWaypoint++;
if(currentIndexOfWaypoint == listOfWaypoints.Count - 1)
{
currentIndexOfWaypoint = 0;
}
}
var step = speed * Time.deltaTime;
transform.position = Vector3.MoveTowards(transform.position, listOfWaypoints[currentIndexOfWaypoint].position, step);
}
}
You would have to populate the waypoint List<Transform> by dragging and dropping the waypoint objects in to the list, in the right order, inside the editor.
You may also want to incorporate your suggestion of using the LookAt method too.
Hope this helps.
I'm trying to make a camera track two targets in a 2D game on unity but I can't quite get it to work.
This is the code I currently have, but it's changing the rotation in transform instead of position. the ortho size is changing but it is not properly tracking the center point between the characters. is there anyway I can fix this?
using System.Collections.Generic;
using UnityEngine;
public class CameraZoom : MonoBehaviour
{
private Camera cameraRef;
private GameObject[] playerPos;
void Start()
{
cameraRef = GetComponent<Camera>();
playerPos = GameObject.FindGameObjectsWithTag("Player");
Debug.Log(playerPos[0].transform.position);
Debug.Log(playerPos[1].transform.position);
Debug.Log(cameraRef.tag);
StartCoroutine(ZoomInOut());
}
// Update is called once per frame
void Update()
{
}
IEnumerator ZoomInOut()
{
while (true)
{
if (playerPos[0] != null && playerPos[1] != null)
{
Vector3 lookPoint = Vector3.Lerp(playerPos[0].transform.position, playerPos[1].transform.position, 0.5f);
cameraRef.transform.LookAt(lookPoint);
float distance = Vector3.Distance(playerPos[0].transform.position, playerPos[1].transform.position);
if (distance > (cameraRef.orthographicSize * 2))
{
cameraRef.orthographicSize += 0.05f;
// if (distance < (cameraRef.orthographicSize * 2))
//{
// cameraRef.orthographicSize -= 0.1f;
//}
}
else
if (distance < (cameraRef.orthographicSize))
{
cameraRef.orthographicSize -= 0.05f;
}
yield return new WaitForSeconds(0.02f);
}
}
}
}
Well cameraRef.transform.LookAt does exactly what you described
Rotates the transform so the forward vector points at worldPosition.
Since your game is 2D anyway what you rather want to do is set the camera to exactly the same XY position
var lookPoint = (playerPos[0].transform.position, playerPos[1].transform.position) * 0.5f;
//Maintain the Z depth position
lookPoint.z = cameraRef.transform.position.z;
cameraRef.transform.position = lookPoint;
Also instead of
yield return new WaitForSeconds(0.02f);
rather use
yield return null;
The latter makes it run every frame. But anyway assuming 60FPS a frame takes about 0.017seconds so waiting0.02` will most probably mean you wait two frames since this value is not the exact time but rather the minimum time to wait/skip to the next frame.
And move this OUT of the if block!!
If one of your objects is missing => endless loop -> Freeze/Crash of Unity and your app! You definitely want to wait one frame for every iteration of your while loop!
Actually you could as well do the thing in Update without the loop at all :)
And then instead of
cameraRef.orthographicSize -= 0.05f;
I would rather either use
// Take differences in the frame rate into account (jitter)
cameraRef.orthographicSize -= 3 * Time.deltaTime;
or directly interpolate
// Smooth interpolate -> if the target and current value are far apart
// this zooms faster and then becomes slower when already very close
// Adjust that "5" according to your needs
cameraRef.orthographicSize = Mathf.Lerp(cameraRef.orthographicSize, distance, 5 * Time.deltaTime);
without using your conditions at all.
Typed on the phone but I hope the idea gets clear
i want to make a simple script that when u clicked the screen the ball in the game will move (20000 * Time.deltaTime) to the right, and then if i'll click again, it will move to the left side and then right and so on.
I managed to get the ball to move to the right, but i need it to wait after the animation is finish because i need to check if the player clicked again (if he did i need to check to what direction to move the ball).
I tried many methods i found online like checking if Rigidbody.velocity.magnitude == 0.0f that means the ball is not moving..
public Rigidbody rb;
public Transform PlayerPosition;
// Start is called before the first frame update
void Start()
{
}
// Update is called once per frame
void Update()
{
if (Input.GetMouseButtonDown(0))
{
rb.AddForce(20000 * Time.deltaTime, 0, 0); // moving ball to the right
while (rb.velocity.magnitude != 0.0f) // I tried to check until the ball is not moving
{
}
Debug.Log(PlayerPosition.position.x);
}
}
And here is my latest try:
void Update()
{
if (Input.GetMouseButtonDown(0))
{
rb.AddForce(20000 * Time.deltaTime, 0, 0); // moving ball to the right
if(rb.velocity.magnitude < 0.05f) // if i click the ball it just prints it and not wating for the ball to not move
{
Debug.Log(PlayerPosition.position.x);
}
}
}
I expected the output to wait until the animation is finished but instead, its printing the vaule(x) the moment i click the mouse.
Edit
You need to check if your animation is still playing. You are checking only if your velocity is greater than 0.05f, which is correctly printing out the statement.
Use Animation.IsPlaying(string name). One caveat is that this method will return false for the same frame of Update that it was invoked, since the animation hasn't technically started until afterward.
void Update()
{
if (!rb.velocity.magnitude <= 0.01f && !Animation.IsPlaying(nameOfAnimation))
{
Debug.Log("We're not moving and the animation is not playing");
}
}
Original
You should not need to use while in your Update method.
Use an if statement inside of your Update
void Update()
{
if (rb.velocity.magnitude > 0.01f) Debug.Log("We're moving!");
}
First
rb.velocity.magnitude != 0.0f
will almost allways be true due to single precision floatong point : Two float values even if they seem to be equal logical are most likely not.
So you can either use a threshold how you tried already
if(rb.velocity.magnitude <= 0.5f)
or use Mathf.Approximately which uses a very small Epsilon or threshold for the comparing
if(Mathf.Approximately(rb.velocity.magintude, 0))
Than it sounds like you want to wait until the ball has stopped moving and than output the position - like e.g. for a billard game. So actually there seems to be no Animation involved.
In most cases where you think/speek of of an "animation" you actually mean "doing something over time" not to confuse with using an Animator or Animation component with AnimationClips in Unity.
You can/should use a Coroutine for that:
public Rigidbody rb;
public Transform PlayerPosition;
// a flag to make sure there is only one animation at a time
private bool isMoving;
// a flag for altering between left and right movement
private bool isMovingRight;
// Update is called once per frame
void Update()
{
// only allow clicks while not moving already
if (!isMoving && Input.GetMouseButtonDown(0))
{
// stop further input until not moving anymore
isMoving = true;
// add the force
// (you might btw want to skip that Time.deltaTime here it makes no sense)
rb.AddForce(isMovingRight ? 20000 : -20000 * Time.deltaTime, 0, 0);
// alter the direction for the next call
isMovingRight = !isMovingRight;
// if you rather want to be able to interrupt the current animation by clicking again
// remove the isMoving flag and instead use this
//StopCoroutine(WaitForMoveStops());
// Start the routine
StartCoroutine(WaitForMoveStops());
}
}
private IEnumerator WaitForMoveStops()
{
// Inside a Coroutine while is okey now
// as long as you yield somwhere
// check if velocity is below threshold
while (!Mathf.Approximately(rb.velocity.magnitude, 0)
{
// yield in simple words means "leave" this method here, render the frame
// and than continue from here in the next frame
yield return null;
}
// I would now hard reset the velocity just to be sure
rb.velocity = Vector3.zero;
Debug.Log(PlayerPosition.position.x);
// whatever you want to do now
// reset the flag to allow input again
isMoving = false;
}
I think you want to move it if it's stopped, then call AddForce only when it's idle:
var wasMovingLastTime = false;
void Update()
{
var isMoving = rb.velocity.magnitude > 0f;
if (wasMovingLastTime && !isMoving)
{
/// Has just finished moving
Debug.Log(PlayerPosition.position.x);
}
if (Input.GetMouseButtonDown(0))
{
if (!isMoving)
{
rb.AddForce(20000 * Time.deltaTime, 0, 0);
}
}
wasMovingLastTime = isMoving;
}
I wanted to use my coroutine to smoothly interpolate the position and rotation of multiple GameObjects with the script attached to them. When I start the coroutine, the smooth part works fine, but all of my Objects get moved to the same position, which was not what I intended. I want to understand why that is the case, and if there is a smart way to deal with it.
This is what my coroutine looks like:
IEnumerator interpolate_Plate(Transform targ)
{
float passedTime = 0;
while (!transform.Equals(targ))
{
passedTime += Time.deltaTime;
transform.position = Vector3.Lerp(transform.position, targ.position, passedTime);
transform.rotation = Quaternion.Lerp(transform.rotation, targ.rotation,passedTime);
yield return null;
}
yield break;
}
I was thinking about creating a mastercoroutine with a list in it and then call the smoothing part.
Is the problem just, that the Reference of targ always gets reset for all coroutinecalls on the stack?
As requested by you the function that calls the coroutine:
public void move_Plate(Transform newPosition)
{
StartCoroutine(interpolate_Plate(newPosition));
}
Okay so I found the solution, since Unity or C# works with pointers and so on the problem was. that I continously changed the transforms of all Objects, since I used the pointer to the transform of the next Object. But I moved that Object to so it all ended up on the last Object I moved.
How to prevent this:
I created a new class which stores my values so position and rotation of the old one. I store an Instance of that in my Class where I move the plates.
I now changed from coroutine to the update method as suggested in the comments. With a flag I check if I should move the Object or not. Then I move it smoothly to the new Position.
Code:
private Trans MoveTo;
private bool move;
void Update()
{
if (move)
{
float passedTime = 0;
passedTime += Time.deltaTime;
transform.position = Vector3.Lerp(transform.position, MoveTo.position, passedTime);
transform.rotation = Quaternion.Lerp(transform.rotation, MoveTo.rotation, passedTime);
}
}
Seems to work.