How Do I Sync a NavMeshAgent?

The whole answer can be found below.

Please note: The Photon forum is closed permanently. After many dedicated years of service we have made the decision to retire our forum and switch to read-only: we've saved the best to last! And we offer you support through these channels:

Try Our
Documentation

Please check if you can find an answer in our extensive documentation on PUN.

Join Us
on Discord

Meet and talk to our staff and the entire Photon-Community via Discord.

Read More on
Stack Overflow

Find more information on Stack Overflow (for Circle members only).

Write Us
an E-Mail

Feel free to send your question directly to our developers.

How Do I Sync a NavMeshAgent?

PVTarwater
2018-02-03 00:59:01

Hello. I am new to these forums and Photon Unity Networking. I am trying to create a multiplayer game where several players can shoot and kill a bunch of spawning mobs all in the same room. I have the players spawning into the room correctly and I even have firing and other components working. What i am struggling with is enemies spawning and using a navmesh to locate and attack players.

The behavior that is happening is that the Master client's enemies look fine, move smoothly and spawn in the correct location. The behavior on the other clients however is very jittery movement, sometimes not even appearing at all, or its jittering so much that the object looks transparent. Movement does not follow calculated paths at all even though the Master Client is telling them what path they should be taking. They also do not spawn where the spawner is located despite the fact that I have the master client specifically telling the objects where to spawn at which is at the spawn object). the spawning itself is actually based on a pooling class that uses PhotonNetwork.Instantiate. I have tried manually syncing the scripts with OnPhotonSerializeView, tried using PhotonViews, rearranged where RPCs are being called and so on and so far I am totally stumped because nothing is working. The only thing that works correctly are the players and their weapons when those spawn from their triggers.

I have yet to find any tutorials on this specific topic online and Google searches have yielded nothing. Is it even possible to sync a navmeshagent? any assistance would be highly appreciated.

The code I am using: (in next post since there is a character limit apparently)

Comments

PVTarwater
2018-02-03 18:21:19

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

public class EnemyTargeting_MP : Photon.PunBehaviour {

    private List playerList;

    private Transform target;  
    private NavMeshAgent agent;  
    private Vector3 destination;

    private GameObject PlayerObject  
    {  
        get  
        {  
            return playerList[Random.Range(0, GameObject.FindGameObjectsWithTag("Player").Length)];  
        }  
    }

	// Use this for initialization  
	void Start () {  
        if (PhotonNetwork.connected)  
        {  
            if (PhotonNetwork.isMasterClient)  
            {  
                Initialize();  
            }  
            else  
            {  
                GetComponent().enabled = false;  
                GetComponent().enabled = false;  
            }  
        }          
	}

    private void OnEnable()  
    {  
        if (PhotonNetwork.isMasterClient)  
        {  
            InvokeRepeating("Refresh", 0.1f, 0.1f);  
        }          
    }

    private void OnDisable()  
    {  
        CancelInvoke("Refresh");  
    }

    private void Initialize()  
    {  
        playerList = new List();  
        for (int i = 0; i < GameObject.FindGameObjectsWithTag("Player").Length; i++)  
        {  
            playerList.Add(GameObject.FindGameObjectsWithTag("Player")[i]);  
        }

        target = PlayerObject.transform;  
        agent = GetComponent();

        if (gameObject.activeSelf)  
        {  
            InvokeRepeating("Refresh", 0.1f, 0.1f);  
        }  
    }

    private void Refresh()  
    {  
        FindPlayer();          
    }

    private void FindPlayer()  
    {  
        if (PlayerObject != null)  
        {  
            if (PlayerObject.activeSelf)  
            {  
                MoveToTarget();  
            }  
            else  
            {  
                if (GameObject.FindGameObjectWithTag("Player"))  
                {  
                    target = PlayerObject.transform;  
                }  
            }  
        }  
        else  
        {  
            if (GameObject.FindGameObjectWithTag("Player"))  
            {  
                target = PlayerObject.transform;  
            }  
        }  
    }

    private void MoveToTarget()  
    {  
        if (target.GetComponent())  
        {  
            agent.SetDestination(target.GetComponent().GetNavMeshPosition());  
            agent.isStopped = false;  
        }

        RaycastHit hit;  
        Ray ray = new Ray(transform.position, -transform.up);  
        if (Physics.Raycast(ray, out hit))  
        {  
            Vector3 incomingVector = hit.point - transform.position;  
            transform.rotation = Quaternion.FromToRotation(transform.up, hit.normal) * transform.rotation;  
        }

        Quaternion rotation = (agent.desiredVelocity).normalized != Vector3.zero ? Quaternion.LookRotation((agent.desiredVelocity).normalized) : transform.rotation;  
        transform.rotation = rotation;  
    }  
}

The code for enemy spawning:

  
using System.Collections.Generic;  
using UnityEngine;

public class Spawner_MP : Photon.PunBehaviour {

    [SerializeField] private float initialSpawnTime;                                                // The amount of time required before the first spawn is activated.  
    [SerializeField] private float repeatSpawnTime;                                                 // The amount of time before the next spawn is activated.  
    [SerializeField] private List enemies;                                              // The list of objects that can be spawned.

    private GameObject obj;

	// Use this for initialization  
	void Start () {  
        if (PhotonNetwork.isMasterClient)  
        {  
            InvokeRepeating("InitiateSpawner", initialSpawnTime, repeatSpawnTime);  
        }          
    }

    private void InitiateSpawner()  
    {  
        SpawnEnemy();  
    }

    // Spawns enemies at the spawner's location.      
    private void SpawnEnemy()  
    {  
        int i = Random.Range(0, enemies.Count);                                                     // Randomly selects an enemy to spawn from the enemies list.  
        obj = ObjectPooling_MP.currentPooling.GetPooledObject(enemies[i].tag);                      // Retrieves prefab from pool.  
        obj.transform.position = transform.position;                                                // The location the enemy is to spawn at.  
        obj.transform.rotation = transform.rotation;                                                // The direction the enemy is to be facing when spawned.  
        obj.SetActive(true);                                                                        // Activates the prefab and make it visible in the level.  
    }  
}

The class that the spawner inherits from.



using System.Collections.Generic;  
using UnityEngine;

// This is the object class that determines a poolable object's base properties.  
[System.Serializable]  
public class PoolItem  
{  
    public string name;  
    public GameObject pooledObject;  
    public int pooledAmount;  
    public bool canGrow;  
}

// The actual class that pools objects.  
public class ObjectPooling_MP : Photon.PunBehaviour  
{  
    [SerializeField] private List itemsToPool;  
    public static ObjectPooling_MP currentPooling;  // Class reference is declared here.  
    private List pooledObjects;

    private void Awake()  
    {  
        currentPooling = this;  // Creates a public reference to itself.  
    }

    // Initializes the pooled object list.  
    private void Start()  
    {  
        pooledObjects = new List();

        foreach (PoolItem item in itemsToPool)  
        {  
            for (int i = 0; i < item.pooledAmount; i++)  
            {  
                GameObject obj = PhotonNetwork.Instantiate(item.pooledObject.name, new Vector3(0, 0, 0), Quaternion.identity, 0) as GameObject;  
                obj.SetActive(false);  
                pooledObjects.Add(obj);  
            }  
        }  
    }

    // Returns a pooled object from the pooled objects list.  
    public GameObject GetPooledObject(string tag)  
    {  
        // This returns the object to be pooled.  
        for (int i = 0; i < pooledObjects.Count; i++)  
        {  
            if (!pooledObjects[i].activeInHierarchy && pooledObjects[i].tag == tag)  
            {  
                return pooledObjects[i];  
            }  
        }

        // This adds more objects to the pool if Can Grow is checked on.  
        foreach (PoolItem item in itemsToPool)  
        {  
            if (item.pooledObject.tag == tag)  
            {  
                if (item.canGrow)  
                {  
                    GameObject obj = PhotonNetwork.Instantiate(item.pooledObject.name, new Vector3(0, 0, 0), Quaternion.identity, 0) as GameObject;  
                    pooledObjects.Add(obj);  
                    return obj;  
                }  
            }  
        }

        return null;  
    }  
}

PVTarwater
2018-02-06 10:26:29

The Enemy spawner.

/*==========================================================================================================  
* Spawner_MP script  
==========================================================================================================*/

using System.Collections.Generic;  
using UnityEngine;

public class Spawner_MP : Photon.PunBehaviour {

    [SerializeField] private float initialSpawnTime;            
// The amount of time required before the first spawn is activated.

    [SerializeField] private float repeatSpawnTime;          
// The amount of time before the next spawn is activated.

    [SerializeField] private List enemies;   
// The list of objects that can be spawned.

    private GameObject obj;

	// Use this for initialization  
	void Start () {  
        if (PhotonNetwork.isMasterClient)  
        {  
            InvokeRepeating("InitiateSpawner", initialSpawnTime, repeatSpawnTime);  
        }          
    }

    private void InitiateSpawner()  
    {  
        SpawnEnemy();  
    }

    // Spawns enemies at the spawner's location.      
    private void SpawnEnemy()  
    {  
        int i = Random.Range(0, enemies.Count);           
// Randomly selects an enemy to spawn from the enemies list.

        obj = ObjectPooling_MP.currentPooling.GetPooledObject(enemies[i].tag);        
// Retrieves prefab from pool.

        obj.transform.position = transform.position;              
// The location the enemy is to spawn at.

        obj.transform.rotation = transform.rotation;              
// The direction the enemy is to be facing when spawned.

        obj.SetActive(true);           
// Activates the prefab and make it visible in the level.  
    }  
}  

The pooling class:

/*  
 * OBJECT POOLING MP  
 * This is a generic pooling class for online multiplayer that can pool any object. Attach this to an empty game object in the scene. In the inspector,  
 * plug in the object to be pooled, how many are to be pooled at one time, and determine if the pool can be expanded at runtime.  
*/

using System.Collections.Generic;  
using UnityEngine;

// This is the object class that determines a poolable object's base properties.  
[System.Serializable]  
public class PoolItem  
{  
    public string name;  
    public GameObject pooledObject;  
    public int pooledAmount;  
    public bool canGrow;  
}

// The actual class that pools objects.  
public class ObjectPooling_MP : Photon.PunBehaviour  
{  
    [SerializeField] private List itemsToPool;  
    public static ObjectPooling_MP currentPooling;  // Class reference is declared here.  
    private List pooledObjects;

    private void Awake()  
    {  
        currentPooling = this;  // Creates a public reference to itself.  
    }

    // Initializes the pooled object list.  
    private void Start()  
    {  
        pooledObjects = new List();

        foreach (PoolItem item in itemsToPool)  
        {  
            for (int i = 0; i < item.pooledAmount; i++)  
            {  
                GameObject obj = PhotonNetwork.Instantiate(item.pooledObject.name, new Vector3(0, 0, 0), Quaternion.identity, 0) as GameObject;  
                obj.SetActive(false);  
                pooledObjects.Add(obj);  
            }  
        }  
    }

    // Returns a pooled object from the pooled objects list.  
    public GameObject GetPooledObject(string tag)  
    {  
        // This returns the object to be pooled.  
        for (int i = 0; i < pooledObjects.Count; i++)  
        {  
            if (!pooledObjects[i].activeInHierarchy && pooledObjects[i].tag == tag)  
            {  
                return pooledObjects[i];  
            }  
        }

        // This adds more objects to the pool if Can Grow is checked on.  
        foreach (PoolItem item in itemsToPool)  
        {  
            if (item.pooledObject.tag == tag)  
            {  
                if (item.canGrow)  
                {  
                    GameObject obj = PhotonNetwork.Instantiate(item.pooledObject.name, new Vector3(0, 0, 0), Quaternion.identity, 0) as GameObject;  
                    pooledObjects.Add(obj);  
                    return obj;  
                }  
            }  
        }

        return null;  
    }  
}

The targeting/movement.

/*==========================================================================================================  
* EnemyTargeting_MP script  
==========================================================================================================*/

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

public class EnemyTargeting_MP : Photon.PunBehaviour {

    private List playerList;

    private Transform target;  
    private NavMeshAgent agent;  
    private Vector3 destination;

    private GameObject PlayerObject  
    {  
        get  
        {  
            return playerList[Random.Range(0, GameObject.FindGameObjectsWithTag("Player").Length)];  
        }  
    }

	// Use this for initialization  
	void Start () {  
        if (PhotonNetwork.connected)  
        {  
            if (PhotonNetwork.isMasterClient)  
            {  
                Initialize();  
            }  
            else  
            {  
                GetComponent().enabled = false;  
                GetComponent().enabled = false;  
            }  
        }          
	}

    private void OnEnable()  
    {  
        if (PhotonNetwork.isMasterClient)  
        {  
            InvokeRepeating("Refresh", 0.1f, 0.1f);  
        }          
    }

    private void OnDisable()  
    {  
        CancelInvoke("Refresh");  
    }

    private void Initialize()  
    {  
        playerList = new List();  
        for (int i = 0; i < GameObject.FindGameObjectsWithTag("Player").Length; i++)  
        {  
            playerList.Add(GameObject.FindGameObjectsWithTag("Player")[i]);  
        }

        target = PlayerObject.transform;  
        agent = GetComponent();

        if (gameObject.activeSelf)  
        {  
            InvokeRepeating("Refresh", 0.1f, 0.1f);  
        }  
    }

    private void Refresh()  
    {  
        FindPlayer();          
    }

    private void FindPlayer()  
    {  
        if (PlayerObject != null)  
        {  
            if (PlayerObject.activeSelf)  
            {  
                MoveToTarget();  
            }  
            else  
            {  
                if (GameObject.FindGameObjectWithTag("Player"))  
                {  
                    target = PlayerObject.transform;  
                }  
            }  
        }  
        else  
        {  
            if (GameObject.FindGameObjectWithTag("Player"))  
            {  
                target = PlayerObject.transform;  
            }  
        }  
    }

    private void MoveToTarget()  
    {  
        if (target.GetComponent())  
        {  
            agent.SetDestination(target.GetComponent().GetNavMeshPosition());  
            agent.isStopped = false;  
        }

        RaycastHit hit;  
        Ray ray = new Ray(transform.position, -transform.up);  
        if (Physics.Raycast(ray, out hit))  
        {  
            Vector3 incomingVector = hit.point - transform.position;  
            transform.rotation = Quaternion.FromToRotation(transform.up, hit.normal) * transform.rotation;  
        }

        Quaternion rotation = (agent.desiredVelocity).normalized != Vector3.zero ? Quaternion.LookRotation((agent.desiredVelocity).normalized) : transform.rotation;  
        transform.rotation = rotation;  
    }  
}

PVTarwater
2018-02-06 10:26:30

The Enemy spawner.

/*==========================================================================================================  
* Spawner_MP script  
==========================================================================================================*/

using System.Collections.Generic;  
using UnityEngine;

public class Spawner_MP : Photon.PunBehaviour {

    [SerializeField] private float initialSpawnTime;            
// The amount of time required before the first spawn is activated.

    [SerializeField] private float repeatSpawnTime;          
// The amount of time before the next spawn is activated.

    [SerializeField] private List enemies;   
// The list of objects that can be spawned.

    private GameObject obj;

	// Use this for initialization  
	void Start () {  
        if (PhotonNetwork.isMasterClient)  
        {  
            InvokeRepeating("InitiateSpawner", initialSpawnTime, repeatSpawnTime);  
        }          
    }

    private void InitiateSpawner()  
    {  
        SpawnEnemy();  
    }

    // Spawns enemies at the spawner's location.      
    private void SpawnEnemy()  
    {  
        int i = Random.Range(0, enemies.Count);           
// Randomly selects an enemy to spawn from the enemies list.

        obj = ObjectPooling_MP.currentPooling.GetPooledObject(enemies[i].tag);        
// Retrieves prefab from pool.

        obj.transform.position = transform.position;              
// The location the enemy is to spawn at.

        obj.transform.rotation = transform.rotation;              
// The direction the enemy is to be facing when spawned.

        obj.SetActive(true);           
// Activates the prefab and make it visible in the level.  
    }  
}  

The pooling class:

/*  
 * OBJECT POOLING MP  
 * This is a generic pooling class for online multiplayer that can pool any object. Attach this to an empty game object in the scene. In the inspector,  
 * plug in the object to be pooled, how many are to be pooled at one time, and determine if the pool can be expanded at runtime.  
*/

using System.Collections.Generic;  
using UnityEngine;

// This is the object class that determines a poolable object's base properties.  
[System.Serializable]  
public class PoolItem  
{  
    public string name;  
    public GameObject pooledObject;  
    public int pooledAmount;  
    public bool canGrow;  
}

// The actual class that pools objects.  
public class ObjectPooling_MP : Photon.PunBehaviour  
{  
    [SerializeField] private List itemsToPool;  
    public static ObjectPooling_MP currentPooling;  // Class reference is declared here.  
    private List pooledObjects;

    private void Awake()  
    {  
        currentPooling = this;  // Creates a public reference to itself.  
    }

    // Initializes the pooled object list.  
    private void Start()  
    {  
        pooledObjects = new List();

        foreach (PoolItem item in itemsToPool)  
        {  
            for (int i = 0; i < item.pooledAmount; i++)  
            {  
                GameObject obj = PhotonNetwork.Instantiate(item.pooledObject.name, new Vector3(0, 0, 0), Quaternion.identity, 0) as GameObject;  
                obj.SetActive(false);  
                pooledObjects.Add(obj);  
            }  
        }  
    }

    // Returns a pooled object from the pooled objects list.  
    public GameObject GetPooledObject(string tag)  
    {  
        // This returns the object to be pooled.  
        for (int i = 0; i < pooledObjects.Count; i++)  
        {  
            if (!pooledObjects[i].activeInHierarchy && pooledObjects[i].tag == tag)  
            {  
                return pooledObjects[i];  
            }  
        }

        // This adds more objects to the pool if Can Grow is checked on.  
        foreach (PoolItem item in itemsToPool)  
        {  
            if (item.pooledObject.tag == tag)  
            {  
                if (item.canGrow)  
                {  
                    GameObject obj = PhotonNetwork.Instantiate(item.pooledObject.name, new Vector3(0, 0, 0), Quaternion.identity, 0) as GameObject;  
                    pooledObjects.Add(obj);  
                    return obj;  
                }  
            }  
        }

        return null;  
    }  
}

The targeting/movement.

/*==========================================================================================================  
* EnemyTargeting_MP script  
==========================================================================================================*/

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

public class EnemyTargeting_MP : Photon.PunBehaviour {

    private List playerList;

    private Transform target;  
    private NavMeshAgent agent;  
    private Vector3 destination;

    private GameObject PlayerObject  
    {  
        get  
        {  
            return playerList[Random.Range(0, GameObject.FindGameObjectsWithTag("Player").Length)];  
        }  
    }

	// Use this for initialization  
	void Start () {  
        if (PhotonNetwork.connected)  
        {  
            if (PhotonNetwork.isMasterClient)  
            {  
                Initialize();  
            }  
            else  
            {  
                GetComponent().enabled = false;  
                GetComponent().enabled = false;  
            }  
        }          
	}

    private void OnEnable()  
    {  
        if (PhotonNetwork.isMasterClient)  
        {  
            InvokeRepeating("Refresh", 0.1f, 0.1f);  
        }          
    }

    private void OnDisable()  
    {  
        CancelInvoke("Refresh");  
    }

    private void Initialize()  
    {  
        playerList = new List();  
        for (int i = 0; i < GameObject.FindGameObjectsWithTag("Player").Length; i++)  
        {  
            playerList.Add(GameObject.FindGameObjectsWithTag("Player")[i]);  
        }

        target = PlayerObject.transform;  
        agent = GetComponent();

        if (gameObject.activeSelf)  
        {  
            InvokeRepeating("Refresh", 0.1f, 0.1f);  
        }  
    }

    private void Refresh()  
    {  
        FindPlayer();          
    }

    private void FindPlayer()  
    {  
        if (PlayerObject != null)  
        {  
            if (PlayerObject.activeSelf)  
            {  
                MoveToTarget();  
            }  
            else  
            {  
                if (GameObject.FindGameObjectWithTag("Player"))  
                {  
                    target = PlayerObject.transform;  
                }  
            }  
        }  
        else  
        {  
            if (GameObject.FindGameObjectWithTag("Player"))  
            {  
                target = PlayerObject.transform;  
            }  
        }  
    }

    private void MoveToTarget()  
    {  
        if (target.GetComponent())  
        {  
            agent.SetDestination(target.GetComponent().GetNavMeshPosition());  
            agent.isStopped = false;  
        }

        RaycastHit hit;  
        Ray ray = new Ray(transform.position, -transform.up);  
        if (Physics.Raycast(ray, out hit))  
        {  
            Vector3 incomingVector = hit.point - transform.position;  
            transform.rotation = Quaternion.FromToRotation(transform.up, hit.normal) * transform.rotation;  
        }

        Quaternion rotation = (agent.desiredVelocity).normalized != Vector3.zero ? Quaternion.LookRotation((agent.desiredVelocity).normalized) : transform.rotation;  
        transform.rotation = rotation;  
    }  
}

nicmar
2018-07-20 22:53:56

Did you figure this out? I'm just starting with Photon and my game is based on a lot of navmeshagents, and I get all stuttery motion on everyone except the client issuing the navmesh commands..

S_Oliver
2018-07-22 15:47:09

take alook at this http://www.thecolonizers.com/forum/index.php?topic=15.0

Navartac
2019-01-17 14:07:22

add the photoniew, transform view and animatorview components to your enemies.
regards

Yasu
2019-02-27 20:39:57

I'm facing a similar problem right now, please keep us updated if you figure it out, @S_Oliver I see a message about "Maintenance Mode" on that link

SilentDuskStuidos
2022-05-05 10:51:05

Sorry for necroing this but are you performing the navigation on all clients separately?

I've discovered that for my project, the cause of the jittery for NavMeshAgents was because I was setting the destination for the enemy NavMeshAgent on all clients each instead of the master client only.

Because of this, the destination was constantly being calculated multiple times on each client, so that transform of each NavMeshAgent was constantly being changed (which causes the jittery in their PhotonTransformNetwork)

To solve this, I only set the destination for each NavMeshAgent on the master client by checking if the player was a master client just before I set the destination.

I then sync the NavMeshAgent's transform by using the PhotonTransformView component and made the PhotonView observe this.

I hope this helps someone.

IWillMakeNameLater
2022-06-11 19:18:38

In my case (I'm using Fusion) NavMeshAgent was setting position (default behaviour) and for interpolation (smooth movement) to work you must set position yourself so NavMeshAgent doesn't try to overwrite it

so at Start() you disable setting position by navmesh agent (I also turn off rigidbody). I share my code

NavmeshAgent.updatePosition = false;

NetworkRigidbody2d.Rigidbody.bodyType = RigidbodyType2D.Kinematic;

and at simulation's update (FixedUpdateNetwork()) you set the position (I also set destination to player's position)

NetworkRigidbody2d.Rigidbody.position = NavmeshAgent.nextPosition;

NavmeshAgent.destination = Player.transform.position;

Back to top