0

I have a very strange problem.

I created an AI using the navmesh tools and a custom script which keeps assigning a new destination to the navmesh agent once the old destination is reached.

I had to replace the model for this AI (was a capsule before), so I did that, copied almost all components, adjusted a few parameters like agent height etc., and ran it.

The capsule AI does its job properly, but the AI with the correct model clearly doesn't.
After debugging, I discovered that the List of destinations for the modelled AI consists if Vector3.zero's, while the capsule AI has the correct vector3s in its destination list.

Here's my AI destination script:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AI;

public class AIMovement : MonoBehaviour
{

private NavMeshAgent _navMeshAgent;                         //reference to the navmesh agent
private MapController _mapControllerScript;                 //reference to the map controller script to access waypoints
private List<Vector3> _wayPoints = new List<Vector3>();
private Vector3 _destination;

// Use this for initialization
void Start()
{
    _mapControllerScript = GameObject.Find("Map_01").GetComponent<MapController>();
    SetWayPointLocations();
    _navMeshAgent = this.GetComponent<NavMeshAgent>();

    Debug.Log(_mapControllerScript.ToString());
    Debug.Log(_mapControllerScript.HealthPickUpPositions.ToString());
    Debug.Log(_navMeshAgent);
    Debug.Log(_wayPoints);
    foreach (Vector3 waypoint in _wayPoints)
    {
        Debug.Log(waypoint);
    }
}

// Update is called once per frame
void Update()
{
    MoveAroundMap();
}

void MoveAroundMap()
{
    if (_navMeshAgent.transform.position.x == _destination.x &&
        _navMeshAgent.transform.position.z == _destination.z
        || _destination == new Vector3(0, 0, 0)
        )
    {
        Debug.Log("Acquiring new position");
        _destination = _wayPoints[Random.Range(0, _wayPoints.Count-1)];
    }

    _navMeshAgent.SetDestination(_destination);
}

void SetWayPointLocations()
{
    foreach (Vector3 waypoint in _mapControllerScript.HealthPickUpPositions)
    {
        _wayPoints.Add(waypoint);
    }
    foreach (Vector3 waypoint in _mapControllerScript.AmmoPickUpPositions)
    {
        _wayPoints.Add(waypoint);
    }
}
}

Here's the console: you can clearly see the coordinates of the capsule AI being correct, while the coordinates of the broken AI being (0, 0, 0).

Console

Here's the hierarchy window: Capsule is the working AI, Character_Piccolo the non-working.

Hierarchy

Here's the inspector of the Capsule, the working AI.

Inspector_Capsule

Here's the inspector of the model, the broken AI.

Inspector_Broken_Model

Sorry for making this long, but I wanted to make sure you guys had all the needed information.

Thanks in advance!

Thrindil
  • 143
  • 3
  • 17
  • 1
    What field has zeroes, _destination or _wayPoints? Also, in the MapController component, are the pickUpPosition set on Start or Awake? Lastly, I think the problem may be in the if statement inside the MoveArroundMap method. Can you put a log inside that to see if you ever reach there? – Rodrigo Rodrigues Jun 10 '18 at 16:52
  • The _wayPoints array has 5 Vector3's being (0,0,0). Since this is the case, _destination is also (0,0,0), because _destination will hold the value of one of the _wayPoint Vector3's. The PickUpPositions are set in the Start() of MapController.cs I have a Debug.Log in the MoveAroundMap method, saying "Acquiring new position". This keeps getting called, since the _destination is (0,0,0). – Thrindil Jun 10 '18 at 18:15
  • 1
    Can you try setting the PickUpPositions on the Awake method of MapController? – Rodrigo Rodrigues Jun 10 '18 at 21:24
  • You sir, are a lifesaver. That's exactly what made it work, and it's amazing. I've spent the whole evening on this goddamn bug! Could you elaborate on what exactly went wrong with my code? If I may guess: it's the fact that I tried to fill the PickUpPositions in one script, before it existed in the other? Anyway, thank you very much! – Thrindil Jun 10 '18 at 21:52
  • Im gonna write a proper answer – Rodrigo Rodrigues Jun 10 '18 at 22:23

1 Answers1

2

The problem - and also the solution - are not in your AIMovement script, or in any edit you did while changing from the capsule to you new model, not even in any of the scripts and components you provided in the question. The problem is a subtlety in you MapController class that was already there all the time (even before your changes), and the fact that you did not experience this error before is a matter of casuality.

As you said in the comments, you're setting up HealthPickUpPositions and AmmoPickUpPositions properties of you MapController class in its Start() method. And you're reading the values of those properties on the Start() method of your component class. The thing is: the Start message runs once a gameObject is set on a loaded scene, but the order in that objects get started does not depend on your game logic (it depends on the scene hierarchy, prefabs, object instance number and other stuff, whatever...) and should be considered random.

In your specific case, the Start() of your AIMovement is being called before the Start() of MapController, i.e. before the proper values of the properties being set - hence, all you read are zero-vectors (default values).

Because of that, unity has a similar message named Awake() on gameObjects. You also don't know the order in which the objects will be awoken, but you can assure that any Start() message will be sent only after all the Awake() messages were already been sent. More on this here, here, here and here.

The solution: Put all internal initialization logic of your classes in the Awake method; and all the connections to other gameobjects (that assume they are initialized) on the Start method.

Rodrigo Rodrigues
  • 7,545
  • 1
  • 24
  • 36
  • Thanks, I get it now. I filled the array in the start function of one class, while I put a reference to it in the start function of the other class, without knowing which start function gets called first. Very well explained, thanks a lot. One last question though: are there any drawbacks to using the Awake method in ALL of my classes ALL the time to initialize ALL variables of those classes? I want to prevent this from happening in the future, but not at the cost of bad coding practice. – Thrindil Jun 11 '18 at 04:05
  • Yes, there is one gotcha: you should not try to inquire or get state from other classes from inside Awake - you don't know if they are initialized already (you can at most reference other gameObjects, like getComponent and stuff, but not their fields or methods). General rule is: Set yourself up on Awake and relate to other classes on Start. – Rodrigo Rodrigues Jun 11 '18 at 05:15