[UNET] Syncing the position of a non-player object such as a trap (no player authority whatsoever)

I have a game where players are dropped into an arena from a Lobby. Everything is working well, except the traps. There are animated sawblades that slide back and forth in the Z direction. On the host, everything is okay, as is expected. On the clients, their position is shifted (so that they dont even reach the same place as the host’s). However, because I am properly using commands, the player still dies appropriately only when they are hit by the sawblades actual position (server/host position).

The issue is, when using a custom script, their position is not synced. When using a Network Transform component, their transform is constantly flickering, however one of the two flickering positions is the actual correct position.

Here is my custom sync position script:
using UnityEngine;
using System.Collections;
using UnityEngine.Networking;

public class CTFSawMove : NetworkBehaviour {

	[SyncVar (hook="SyncPositionValues")] private Vector3 syncPos;
	private Vector3 lastPos;
	private float threshold = 0.2f;
	private float lerpRate = 16.0f;
	[SerializeField] private Transform myTransform;

	public int speed;
	public int length;

	private float offset;
	public int bladeDamage;
	public Animator sawAnimation;

	public override void OnStartLocalPlayer() {
		sawAnimation = myTransform.GetComponent<Animator> ();
	}

	public override void OnStartClient() {
		sawAnimation = myTransform.GetComponent<Animator> ();
	}

	void Start () {
        offset = myTransform.position.z;
		syncPos = new Vector3(myTransform.position.x, myTransform.position.y, myTransform.position.z);
	}

    void Update() {
		MoveSaw ();
		LerpPosition ();
	}

	void MoveSaw() {
		myTransform.position = new Vector3(transform.position.x, transform.position.y, offset + (Mathf.PingPong(Time.time * speed, length)));	
	}

	void LerpPosition() {
		myTransform.position = Vector3.Lerp (myTransform.position, syncPos, Time.deltaTime * lerpRate);
	}

	void FixedUpdate () {
		TransmitPosition ();
	}

	[Command]
	void CmdProvidePositionToServer(Vector3 pos) {
		syncPos = pos;
	}

	[ClientCallback]
	void TransmitPosition() {
		if( isLocalPlayer && Vector3.Distance(myTransform.position, lastPos) > threshold ) {
			CmdProvidePositionToServer(myTransform.position);
			lastPos = myTransform.position;
		}
	}

	[Client]
	void SyncPositionValues(Vector3 latestPos) {
		syncPos = latestPos;
	}

	void OnTriggerEnter(Collider o){
		if (o.transform.tag == "Player" || o.transform.tag == "You") {
			CmdTellServerWhoWasHitByBlades (o.transform.name, bladeDamage);
		}
	}

	[Command]
	void CmdTellServerWhoWasHitByBlades(string hitName, int dmg) {
		GameObject go = GameObject.Find (hitName);
		go.GetComponent<CTFPlayerHealth> ().DeductHealth (dmg);
	}
}

I’ve been googling this for quite some time, but it seems there isn’t a single person who has ever had trouble with syncing non-player objects. Everyone just has issues with non-player objects that need authority.

I’ve worked on some multiplayer projects with UNET, and faced the same problem with sync NPC objects states. My solution was (It worked for me already) :

  • The NPC on server will take the authority (both transform + physics collision)
  • Server NPC will transmit it’s position to clients (include Local clients).
  • Client replicate the positions they received from Server.
  • So, the lerping task will execute only on clients, not on Server.
  • The same with physics collisions, I would disable NPC’s collider on clients, so the OnTriggerEnter() will only be called on Server.

Take a look into your code, I see some problems:

  • The local take the authority on Transform, it transmit it’s position to server, but also do the lerping task, I think It cause position flicking on clients.
  • It look like you let the Local take the physics collision authority too. But, if you not disable the Collider on other places (Server, remote clients), the OnTriggerEnter() will be call in all places, may cause some warning messages. I think it not good :wink:

Some more suggestions :

  • With the simple moving (in your case, the trap only moving in one coordinate, and just slide back and forth), we dont need to sync all of it positions, just need to sync when and where it start sliding, that enought infomation for clients to replicating NPC’s state, instead of sending too much positions, in case you have a lot of network bandwidth.

@ThinhHB I actually solved this issue some time back. I refactored all of my code to use a more structured Server Authoritative system. Client only ever sends inputs and just runs a local simulation of the server’s scene. Along with that I had an issue where if I had movement turned on from the beginning (in the prefab), it would cause desync’s in the movement.

So I solved that by turning off the movement script in the prefab, spawning the prefab, and then using an RPC to turn on the movement script for all clients.

hello @LutzKellen I am facing same problem. Host can easily drag a non player object which is seen from client side also. but when client try to drag that same object then client not able to do it.

   public class OnTouchTest :  NetworkBehaviour
    {
private Transform myTransform;
[SerializeField] float lerpRate = 15;
[SyncVar] private Vector3 syncPos;
[SyncVar]
private GameObject objectID;
private NetworkIdentity objNetId;

private Vector3 lastPos;
private float threshold = 0.5f;

void Start () {
	if (isLocalPlayer) {
		objectID = GameObject.FindGameObjectWithTag ("cubeObject"); 
		myTransform = objectID.GetComponent<Transform> ();
	}
}

void FixedUpdate () {
	TransmitPosition ();
	LerpPosition ();
}

void LerpPosition () {
	if (!isLocalPlayer) {
		objectID.transform.position = Vector3.Lerp (objectID.transform.position, syncPos, Time.deltaTime * lerpRate);
	}
}

[Command]
void Cmd_ProvidePositionToServer (Vector3 pos) {
	syncPos = pos;
}

[ClientCallback]
void TransmitPosition () {
		Cmd_ProvidePositionToServer (objectID.transform.position);
		lastPos = objectID.transform.position;
}

}