- Home /
Moving from one position to another
Hello I'm making a little AI behaviour with not much success, problem is I need my NPC to choose from several positions, move to one of them, then choose again and move to that one, and so on...
Problem is I haven't managed a way to make it move from the first position to the second one until the first one is reached and son on...
I decided the best way to do this was to put a line in update with Vector2.MoveTowards and feed it with the desired endPosition
if (shouldLerp)
{
npc.transform.position = Vector2.MoveTowards(transform.position, endPosition, moveSpeed * Time.deltaTime);
}
IEnumerator StopWandering()
{
yield return new WaitForSeconds(Random.Range(5f,10f));
wandering = false;
StopCoroutine(Wander());
StartCoroutine(InitialMove());
yield return null;
}
public IEnumerator InitialMove()
{
MoveToLadder();
yield return new WaitUntil(() => Vector2.Distance(this.transform.position, endPosition) == 0);
MoveToLevel();
yield return new WaitUntil(() => Vector2.Distance(this.transform.position, endPosition) == 0);
MoveToValve();
yield return new WaitUntil(() => Vector2.Distance(this.transform.position, endPosition) == 0);
yield return null;
}
public Vector2 MoveToLadder()
{
int numeroRandom = Random.Range(0, 100);
if (numeroRandom % 2 == 0)
{
endPosition = ladderLeft.transform.position;
}
else endPosition = ladderRight.transform.position;
return endPosition;
}
public Vector2 MoveToLevel()
{
int numeroRandom = Random.Range(0, 100);
if (numeroRandom % 2 == 0 && this.transform.position.x < 0)
{
endPosition = level1Left.transform.position;
}
else if (numeroRandom % 2 != 0 && this.transform.position.x > 0)
{
endPosition = level1Right.transform.position;
}
else if (numeroRandom % 2 == 0 && this.transform.position.x < 0)
{
endPosition = level2Left.transform.position;
}
else if (numeroRandom % 2 != 0 && this.transform.position.x > 0)
{
endPosition = level2Right.transform.position;
}
return endPosition;
}
….
Sometimes it works some others it does not and I need to be foolproof, the game I'm trying to emulate is Cock In or Chicken Chase for the commodore, that's the movement I need for a similar scene. https://www.lemon64.com/?mainurl=https%3A//www.lemon64.com/games/details.php%3FID%3D3309
Could you elaborate on what is not working? Does it stop at a position, does it not choose the next?
Two side notes: You don't want to compare Vector2.Distance == 0, as distance calculates a float subtraction. Due to the inherent inaccurateness of floats, this might never be exactly 0. You should check for a threshold ins$$anonymous$$d, something like Vector2.Distance < 0.001.
Also, why do you return your endPosition in your $$anonymous$$oveToXXX functions? You don't use the returned result. It gets updated as it is a class scope variable. They should be void functions ins$$anonymous$$d.
Sorry the problem was that sometimes it reached the first position (1/3) some times it skipped over the last position (3/3)
as the code is designed, the npc will ever do the route ladder->level->valve, even if have a decision to stay and don't do the route, will go there to check... I think is better to do a random chooser between this options inside the main coroutine, but, before refactorize it, I think you need this code working, so, lets point some errors...
1) on the piece of code that you show, you never manage bool shouldLerp, if you dont assign it in other place, do it on Initial$$anonymous$$ove()
public IEnumerator Initial$$anonymous$$ove()
{
$$anonymous$$oveToLadder();
shouldLerp = true;
yield return new WaitUntil(() => Vector2.Distance(this.transform.position, endPosition) == 0);
shouldLerp = false;
$$anonymous$$oveToLevel();
shouldLerp = true;
yield return new WaitUntil(() => Vector2.Distance(this.transform.position, endPosition) == 0);
shouldLerp = false;
$$anonymous$$oveToValve();
shouldLerp = true;
yield return new WaitUntil(() => Vector2.Distance(this.transform.position, endPosition) == 0);
shouldLerp = false;
yield return null;
}
2) on $$anonymous$$oveToLevel()
you are considering the height of each level, but, to go to the ladder you aren't. Your NPC will go to the same point ever, the top or the bottom of the ladder, or anywhere its centered... Because the ladder is vertical u can consider just the x axis to displacement...
public Vector2 $$anonymous$$oveToLadder()
{
int numeroRandom = Random.Range(0, 100);
if (numeroRandom % 2 == 0)
{
endPosition = ladderLeft.transform.position;
}
else endPosition = ladderRight.transform.position;
//same level that the npc currently is
endPosition.y = npc.transform.position.y;
return endPosition;
}
3) the if condition of line 42(and line 46) is the same as line 50(with line 54)... so line 50(and 54) never will be called, to evade this kind of errors, I suggest use nested ifs sometimes, just when is necessary ofc
public Vector2 $$anonymous$$oveToLevel()
{
int numeroRandom = Random.Range(0, 100);
if (numeroRandom % 2 == 0 ){
if (this.transform.position.x < 0)
{
endPosition = level1Left.transform.position;
}
else
{
endPosition = level1Right.transform.position;
}
}
else
{
if (this.transform.position.x < 0)
{
endPosition = level2Left.transform.position;
}
else
{
endPosition = level2Right.transform.position;
}
}
return endPosition;
}
4) and last but not less important, float numbers arent precise so, get the exact 0.0f when u get the distance between two close points, are.. unusual
public bool Reached(){
//squared $$anonymous$$imiun distance to be considerer as "at the waypoint" (set is as public global?)
float sqrt$$anonymous$$inDiff = 0.03f
//use squared for faster calculation
return (this.transform.position-endPosition).sqr$$anonymous$$agnitude < sqrt$$anonymous$$inDiff;
}
and use it in the main coroutine
public IEnumerator Initial$$anonymous$$ove()
{
$$anonymous$$oveToLadder();
yield return new WaitUntil( Reached());
$$anonymous$$oveToLevel();
yield return new WaitUntil( Reached());
$$anonymous$$oveToValve();
yield return new WaitUntil( Reached());
yield return null;
}
Thank you very much I'll try your advice to optimize the code a little bit indeed the problem was the distance between the 2 positions (current-target) the sprite not always reached a distance of 0 also changed some other details like defining the positions as vector2, I wrote them as "coordinates x, y" and worked very well there's the code as an answer, and altough it's working properly now any enhancement is very welcome.
Answer by FallingRocketGames · Jan 16, 2019 at 11:32 AM
I'm sure I over did it but this worked flawlessly:
void Update () {
if (shouldLerp)
{
npc.transform.position = Vector2.MoveTowards(transform.position, endPosition, moveSpeed * Time.deltaTime);
//Flip Sprite
if (npc.transform.position.x > startPosition)
{
npc.transform.rotation = Quaternion.Euler(0, 0, 0);
}
if (npc.transform.position.x < startPosition)
{
npc.transform.rotation = Quaternion.Euler(0, 180, 0);
}
}
currentPosition = npc.transform.position;
startPosition = npc.transform.position.x;
}
public IEnumerator Wander()
{
moveSpeed = 1;
shouldLerp = true;
wandering = true;
while (wandering)
{
endPosition = new Vector2(Random.Range(-6f, 6f), Random.Range(-3.5f, -2.5f));
yield return new WaitForSeconds(Random.Range(5f, 8f));
yield return null;
}
}
IEnumerator StopWandering()
{
yield return new WaitForSeconds(Random.Range(5f,10f));
wandering = false;
StopCoroutine(Wander());
shouldLerp = false;
StartCoroutine(MoveToLadder());
yield return null;
}
public IEnumerator MoveToLadder()
{
moveSpeed = 1;
shouldLerp = true;
print(ladderLeft.transform.position.x + ladderLeft.transform.position.y);
ladderReached = false;
shouldLerp = false;
int numeroRandom = Random.Range(0, 100);
if (numeroRandom % 2 == 0)
{
endPosition = new Vector2(ladderLeft.transform.position.x, ladderLeft.transform.position.y);
}
else endPosition = new Vector2(ladderRight.transform.position.x, ladderRight.transform.position.y);
while (!ladderReached)
{
shouldLerp = true;
if (Mathf.Approximately(currentPosition.x, endPosition.x) && Mathf.Approximately(currentPosition.y, endPosition.y))
{
ladderReached = true;
shouldLerp = false;
print("LLego a escalera");
StartCoroutine(MoveToLevel());
}
yield return null;
}
yield return null;
}
public IEnumerator MoveToLevel()
{
//ladderReached = false;
levelReached = false;
shouldLerp = true;
int numeroRandom = Random.Range(0, 100);
if (numeroRandom % 2 == 0 && this.transform.position.x < 0)
{
endPosition = new Vector2(level1Left.transform.position.x, level1Left.transform.position.y);
}
else if (numeroRandom % 2 != 0 && this.transform.position.x < 0)
{
endPosition = new Vector2(level2Left.transform.position.x, level2Left.transform.position.y);
}
else if (numeroRandom % 2 == 0 && this.transform.position.x > 0)
{
endPosition = new Vector2(level1Right.transform.position.x, level1Right.transform.position.y);
}
else if (numeroRandom % 2 != 0 && this.transform.position.x > 0)
{
endPosition = new Vector2(level2Right.transform.position.x, level2Right.transform.position.y);
}
….
glad to hear that, also spriterenderer has a property called flip for doing the flip easier.
I'll check it out didn't know about that one, thanks!
Answer by xxmariofer · Jan 16, 2019 at 10:49 AM
I find your code a bit confusing and i would suggest a little refactor, but i am pretty sure your problem is with MoveToLevel mehod, else if 3 and 4 statement never enters. that means that some times the endposition will not be modified. one example when the endposition wont be modified is when numeroRandom % 2 == 0 && this.transform.position.x > 0
Try changing the on both 3 and 4 if, cant check the movetovalve method so cant make sure if there is something else wrong.
@DCordoba solution points the same but with code and better explained, i will leave this answer in case someone doesnt checks his comment.
No, numeroRandom is just a way for me to say Random.Range[0,3] you know like when a number from 0 to 100 is even or odd but already found the problem, the sprites were scaled and never reached the absolute position so I couldn't compare the current position and the destination position so I used a loop with $$anonymous$$athf.Approximately also and haven't check if it worked in the previous way but changed: endPosition = level1Left.transform.position; to endPosition = new Vector2(level1Left.transform.position.x, level1Left.transform.position.y); as I said below I think I over didi it perhaps can be simplified but so far this worked without errors, thanks for time mate.
You didnt understand my topic, i know that there is only posible to be even or odd but in the code you first saw you confused and else if 3 and 4 was never reachable. You changed it in your last anwser but you should check your first attempt.
Answer by badadam · Jan 16, 2019 at 12:49 PM
At first add this script below to your player
public class Player2D : MonoBehaviour {
public int indexOfPoints = 0;
public List<Transform> points;
public Transform newPoint;
public float speed = 10;
// Update is called once per frame
void Update () {
transform.position = Vector3.Lerp(transform.position, newPoint.position,Time.deltaTime*speed);
}
}
Create a empty objects to your destination points, add them collider(don't fortget to make them trigger) add the script below to all empty object at your destination points
public class SpawnScript : MonoBehaviour {
public Player2D player;
private void OnTriggerEnter2D(Collider2D collision)
{
if (collision.gameObject.tag=="yourplayertag")
{
player.indexOfPoints++;
player.newPoint = player.points[player.indexOfPoints];
}
}
}
Drag all empty objects with trigger at the destnation points to "List points;" in the Player2D script you add to your player.
This sounds promising and also tried an approach like this well not quite the same but had problems adding items to the list, my script considered adding (choosing) an item after being at a certain position in x or in y so but I can try this one later on, thank you very much