- Home /
How to make a 2D object move smoothly in a random direction for a set amount of time.
Hello! I am currently mucking about with a 2D, top down scene trying to get some very basic AI behaviour going.
The aim is to make this pig wait for a short time, then perform one of two behaviours (move or oink), then repeat.
The waiting and the oinking I've figured out already. The trouble I'm having is with getting it to move smoothly in a random direction. I can only make it teleport from one point to the next instantly! {
// Used to make the pig play a variety of oinking sound effects.
private AudioSource pigAudioSource;
public List<AudioClip> oinks;
// The minimum and maximum amounts of time that the pig can stand still between behaviours.
public float idleMin;
public float idleMax;
// The speed at which the pig will move. (Not yet used.)
public float moveSpeed;
// Get the necessary component(s) and start the pig's behaviour coroutine.
void Start()
{
pigAudioSource = GetComponent<AudioSource>();
StartCoroutine(IdleBehaviour());
}
IEnumerator IdleBehaviour()
{
while (true)
{
// Waits a random number of seconds within the range of idleMin and idleMax.
float seconds = Random.Range(idleMin, idleMax);
yield return new WaitForSeconds(seconds);
// Generates another random number used to decide between moving and oinking.
int behaviour = Random.Range(0, 2);
switch (behaviour)
{
case 0: // Pauses this coroutine while processing the pig's movement.
yield return IdleMovement();
break;
case 1: // Selects then plays a random oink sound effect.
Debug.Log("Oink!");
int oink = Random.Range(0, oinks.Count);
pigAudioSource.PlayOneShot(oinks[oink]);
break;
}
}
}
// To do: Make the pig move in a random direction for a short amount of time, then stop.
IEnumerator IdleMovement()
{
Debug.Log("If I could move, I'd be doing it right now!");
yield return null;
}
I tried using a for loop inside the IdleMovement coroutine to run transform.Translate until a set amount of time had been reached, but I couldn't get that to work. I also tried getting the pig's RigidBody and using AddForce on it but I didn't know how to then stop that force after an amount of time had passed.
Any suggestions about how I might do this? I also wonder if I need to be using two coroutines.
Answer by unity_ek98vnTRplGj8Q · May 12, 2021 at 07:46 PM
You can try something like this
IEnumerator IdleMovement()
{
float moveTime = 2f;
float speed = 1f;
Vector3 moveDirection = Random.insideUnitCircle.normalized;
while(moveTime > 0){
moveTime -= Time.deltaTime;
rigidbody.MovePosition(transform.position + speed * moveDirection * Time.deltaTime);
yield return null;
}
}
You can change the move time and direction however you want
Interesting! I eventually decided to change my approach and created an equivalent AI without the use of coroutines, but I just tried your approach and it also worked. I think I understand now why my prior attempts at getting the coroutine version to work were not having the intended result, so thank you!
I'll include the code for my alternate approach in another response, in case anyone is interested.
Answer by Gurn94 · May 13, 2021 at 09:57 AM
For anyone curious, I figured out an alternate way to approach this problem using several Booleans and no coroutines.
private string behaviour;
private bool needsNewBehaviour = true;
private bool currentlyMoving = false;
private bool currentlyIdling = false;
// Used to make the pig play a variety of oinking sound effects.
private AudioSource pigAudioSource;
public List<AudioClip> oinks;
// Floats used to process the pig's move behaviour.
public float moveSpeed;
private float moveLength;
public float moveLengthMin;
public float moveLengthMax;
private float moveTime;
public float moveDist;
private Vector2 moveDirection;
// Floats used to process the pig's idle behaviour.
public float idleMin;
public float idleMax;
private float idleLength;
private float idleTime;
// Sprite Renderer
public SpriteRenderer pigSpriteRenderer;
// Animator
public Animator pigAnimator;
private void Start()
{
pigAudioSource = gameObject.GetComponent<AudioSource>();
}
void Update()
{
if (needsNewBehaviour)
{
behaviour = ChooseBehaviour();
needsNewBehaviour = false;
}
switch (behaviour)
{
case "oink":
if (!pigAudioSource.isPlaying)
{
int oink = Random.Range(0, oinks.Count);
pigAudioSource.PlayOneShot(oinks[oink]);
Debug.Log("The pig oinked at " + Time.time);
}
needsNewBehaviour = true;
break;
case "move":
if (!currentlyMoving)
{
moveTime = 0;
moveLength = Random.Range(moveLengthMin, moveLengthMax);
moveDirection = new Vector2(Random.Range(-moveDist, moveDist), Random.Range(-moveDist, moveDist));
Debug.Log("The pig started moving at " + Time.time);
currentlyMoving = true;
}
if (moveTime < moveLength)
{
if (moveDirection.x < 0)
{ pigSpriteRenderer.flipX = true; }
else if (moveDirection.x > 0)
{ pigSpriteRenderer.flipX = false; }
transform.Translate(moveDirection * moveSpeed * Time.deltaTime);
moveTime += Time.deltaTime;
pigAnimator.SetFloat("Speed_f", 1);
}
else
{
Debug.Log("The pig finished moving at " + Time.time);
currentlyMoving = false;
needsNewBehaviour = true;
pigAnimator.SetFloat("Speed_f", 0);
}
break;
case "idle":
if (!currentlyIdling)
{
idleTime = 0;
idleLength = Random.Range(idleMin, idleMax);
Debug.Log($"The pig is going to start idling at {Time.time} for {idleLength} seconds.");
currentlyIdling = true;
}
if (idleTime < idleLength)
{
idleTime += Time.deltaTime;
}
else
{
Debug.Log("The pig finished idling at" + Time.time);
currentlyIdling = false;
needsNewBehaviour = true;
}
break;
}
}
// Randomly returns one of several strings describing a possible piggy behaviour.
string ChooseBehaviour()
{
int behaviour = Random.Range(0, 3);
switch (behaviour)
{
case 0:
return "oink";
case 1:
return "move";
default:
return "idle";
}
}
Your answer
Follow this Question
Related Questions
How to start at a specific coroutine? 0 Answers
MoveTowards inside Coroutine 2 Answers
How to change this using a coroutine 1 Answer
I want to move the player once while he is jumping 1 Answer
Controlling AI Movement 0 Answers