Advertisement

[UNITY] Enemy Movement

Started by April 17, 2016 06:58 PM
7 comments, last by Craigis 8 years, 7 months ago

Hey Guys,

So I started coding the movement for my first enemy character.

The enemy is a square....and I want him to roll on his side every 1 seconds or so as he move towards the player.

But currently, he just moves straight toward the player.

I tried putting in some move intervals but navigation seems to just ignore that and go straight toward the player.

I have an animation set up to rotate 45 degrees as long as he is moving.

How I can make it look like the enemy is just rolling towards the enemy every other second? start, stop, start, stop.


using UnityEngine;
using System.Collections;

public class AI_CubeEnemy : MonoBehaviour {

    public Animator anim;
    public float moveTimer = 0;
    public float rollTimer = 0;
    public float moveInterval = 2;
    public bool isWalkingLeft = false;
    public bool isWalkingRight = false;

    private Rigidbody cubeEnemy;

    Transform player;
    NavMeshAgent nav;

    void Awake ()
    {
        player = GameObject.FindGameObjectWithTag("Player").transform;
        nav = GetComponent<NavMeshAgent>();
        anim = gameObject.GetComponent<Animator>();
        cubeEnemy = gameObject.GetComponent<Rigidbody>();
    }

    void Update()
    {

        anim.SetBool("isWalkingRight", isWalkingRight);
        anim.SetBool("isWalkingLeft", isWalkingLeft);

        moveTimer += Time.deltaTime;

        if (moveTimer >= moveInterval)
        {
            if (rollTimer == 0)
            {
                while (rollTimer <= 2)
                {             
                        if (player.transform.position.x > transform.position.x)
                        {
                            isWalkingRight = true;
                            isWalkingLeft = false;
                        }

                        if (player.transform.position.x < transform.position.x)
                        {
                            isWalkingLeft = true;
                            isWalkingRight = false;

                        }

                    nav.SetDestination(player.position);
                    rollTimer += Time.deltaTime;
                }

                moveTimer = 0;
                nav.speed = 0;
                rollTimer = 0;

            }
        }
            
    }

}

with the nav.speed = 0; the enemy doesn't move at all, when I comment it out, it moves towards the player.

It's like it ignores all my conditional statements.

Try either Debug.Log to output that loop, or step through it a few times under the debugger.

That while loop is kind of suspicious.

Advertisement
You'll need to set nav.speed back to its original value at some point, if you ever want it to start moving again.

Right now it's going to stay zero after that code is hit for the first time.

Try either Debug.Log to output that loop, or step through it a few times under the debugger.

That while loop is kind of suspicious.

You'll need to set nav.speed back to its original value at some point, if you ever want it to start moving again.

Right now it's going to stay zero after that code is hit for the first time.

So I fixed it up a bit, and it works a little better now but it still doesn't STOP once it starts rolling.

I put in breaks point to watch the loops and it works as I though it would.

The moveTimer is for how often it can move.

The rollTimer is for how long it can move once it starts moving.

But when I actually the run the game, The enemy waits 2seconds intially then just rolls towards the player infinitely even though I can see the moveTimer resetting to 0 and stuff....hmmmm....


    void Update()
    {

        anim.SetBool("isWalkingRight", isWalkingRight);
        anim.SetBool("isWalkingLeft", isWalkingLeft);

        moveTimer += Time.deltaTime;

        if (moveTimer >= moveInterval)
        {
            if (rollTimer == 0)
            {
                while (rollTimer <= 1)
                {             
                        if (player.transform.position.x > transform.position.x)
                        {
                            isWalkingRight = true;
                            isWalkingLeft = false;
                        }

                        if (player.transform.position.x < transform.position.x)
                        {
                            isWalkingLeft = true;
                            isWalkingRight = false;

                        }

                    nav.SetDestination(player.position);
                    rollTimer += Time.deltaTime;
                }

                moveTimer = 0;
                nav.velocity = new Vector3(0, 0);
                cubeEnemy.velocity = new Vector3(0, 0);
                rollTimer = 0;
            }
       
        }
            
    }

}

Can you design the algorithm without the use of the while loop (and just with the branching ifs)? I haven't tried your code out on a project of my own, but with what I know of Unity I'm willing to bet it's causing issues within Update (since it's called every frame).

Can you design the algorithm without the use of the while loop (and just with the branching ifs)? I haven't tried your code out on a project of my own, but with what I know of Unity I'm willing to bet it's causing issues within Update (since it's called every frame).

Yeah, Ill try that, or putting it into a separate function, I was thinking the same thing regarding update.

Advertisement

So basically got this working now, but with one small caveat.

My enemy starts moving towards my player, then he resets his position once and then starts moving as intended....can't figure out why for the life of me.

See code below.


using UnityEngine;
using System.Collections;

public class AI_CubeEnemy : MonoBehaviour {

    public Animator anim;
    public float moveInterval = 2;
    public bool moving;
    public bool isWalkingLeft = false;
    public bool isWalkingRight = false;



    private Rigidbody cubeEnemy;

    Transform player;
    NavMeshAgent nav;

    void Start ()
    {
        
        anim = gameObject.GetComponent<Animator>();

        anim.SetBool("isWalkingRight", isWalkingRight);
        anim.SetBool("isWalkingLeft", isWalkingLeft);
        anim.SetBool("moving", moving);

        cubeEnemy = gameObject.GetComponent<Rigidbody>();

        nav = GetComponent<NavMeshAgent>();
        player = GameObject.FindGameObjectWithTag("Player").transform;
        StartCoroutine(moveEnemy());
    }

    void Update()
    {
    

        if (nav.velocity.x != 0)
        {
            moving = true;
        }
        else
        {
            moving = false;
        }
        Debug.Log("Moving: " + moving);
       

    }

    
    IEnumerator moveEnemy ()
    {

       nav.SetDestination(player.position);

       yield return new WaitForSeconds(moveInterval);

       nav.Stop();
       
      
       yield return new WaitForSeconds(moveInterval);
       nav.Resume();

       StartCoroutine(moveEnemy());

    }


}

It seems me that you have a wrong understanding of how updating is intended to work. The MonoBehavior.Update() method is invoked periodically, i.e. once per cycle of the game loop. Due to technical reasons, the time from one invocation to the next will not ever be exactly the same. Therefore the Time class and especially Time.deltaTime can be used. The MonoBehavior.Update() should update the respective object so that its state matches with the current moment in time. I.e. a snapshot is to be computed therein. Since all active objects are updated, each update should happen as fast as possible.

That has at least 2 consequences:

(a) The while-loop inside MonoBehavior.Update() is problematic as several posters have already mentioned. If your game runs with approximately 60 fps, i.e. there should be 60 updates per second, and your routine defers that by 59/60 seconds per run, the game will all but not responsive.

(b) Since MonoBehavior.Update() is already invoked periodically, it should not be necessary to run a co-routine in parallel, at least not for the task you're trying to solve.

The intended way of doing an update is more or less like so: When entering the method, determine the behavior that is currently set and hence was active during the last Time.deltaTime seconds. Manipulate the object accordingly in all belongings that are not updated automatically. Then determine based on input and game situation which behavior is to be set for the pending time delta, and store that for the next update.

It seems me that you have a wrong understanding of how updating is intended to work. The MonoBehavior.Update() method is invoked periodically, i.e. once per cycle of the game loop. Due to technical reasons, the time from one invocation to the next will not ever be exactly the same. Therefore the Time class and especially Time.deltaTime can be used. The MonoBehavior.Update() should update the respective object so that its state matches with the current moment in time. I.e. a snapshot is to be computed therein. Since all active objects are updated, each update should happen as fast as possible.

That has at least 2 consequences:

(a) The while-loop inside MonoBehavior.Update() is problematic as several posters have already mentioned. If your game runs with approximately 60 fps, i.e. there should be 60 updates per second, and your routine defers that by 59/60 seconds per run, the game will all but not responsive.

(b) Since MonoBehavior.Update() is already invoked periodically, it should not be necessary to run a co-routine in parallel, at least not for the task you're trying to solve.

The intended way of doing an update is more or less like so: When entering the method, determine the behavior that is currently set and hence was active during the last Time.deltaTime seconds. Manipulate the object accordingly in all belongings that are not updated automatically. Then determine based on input and game situation which behavior is to be set for the pending time delta, and store that for the next update.

Hi Haegarr,

Thanks for taking the time to explain that, helped a lot in my understanding of unity.

If you look at my latest code snippet, I actually removed all those loops from update and am using a simple function now to do the required action.

As to my original issue with my enemy resetting, I found the issue by reading the documentation. (Which I should proli do more often)

"If you want a NavMesh Agent to push around physics objects or use physics triggers:

  • Add a Collider component (if not present)

  • Add Rigidbody component

Turn on kinematic (Is Kinematic) - this is important!


Kinematic means that the rigid body is controlled by something else than the physics simulation
If both NavMesh Agent and Rigidbody (non-kinematic) are active at the same time, you have race condition
Both components may try to move the agent at the same which leads to undefined behavior"

So I turned on Kinematic and that fixed the resetting issue.

This topic is closed to new replies.

Advertisement