(Solved)Networking: Spawn an item on client only

Hello,
I got some trouble with an ability that allow you to spawn an item when warp to it’s location by pressing again:
It works on host side, i can warp to the item with a “Findwithtag”, i trrying to set a client only spawnable item because i don’t want to other player to warp on wrong item cause of tag.

Actually with this script the item spawn on host only:

Spell script:

void Update()
{
if (Input.GetButtonDown("Ability4"))
                {
                    if (!isLocalPlayer)
                        return;

                    if (teleport == false)
                    {
                        CmdBaliseDrop();
                    }
                    if (teleport == true)
                    {
                        teleport = false;
                    }
                    else
                    {
                        teleport = true;
                    }
                                   }
}
[Command]
    void CmdBaliseDrop()
    {
        GameObject instance = Instantiate(Balise, magicspawn1.position, cameratest.GetComponent<Transform>().rotation) as GameObject;
    }

Player script:

if (Input.GetButtonDown("Ability4"))
                {
                    if (teleport == false)
                    {
                        teleport = true;
                    }
                    if (teleport == true)
                    {

                        GameObject balise = GameObject.FindWithTag("balise");

                        playerminato.transform.position = balise.transform.position;
                        WarpSound.Play();
                    }
                    isCoolDown4 = true;
                    nextFireTime10 = Time.time + cooldowntime10;
                }

Can you help me please ?

Up ~

You’re instantiating an object on the server only.

If you want to create a tracker for the client to return to, do something like this:
(Tracker creation):

  1. Send a Cmd to the server.
  2. Make server spawn / create object to track the position.
  3. Keep a track for the created tracker - something like Dictionary<clientId, Tracker>;
  4. Add the tracker based on the clientId which has sent this command;

clientId - is just an (u)int that can be fetched from the network manager / player object
(I think. Haven’t used UNet / Mirror in a while. Just an identifier to determine which player controls which object).
Tracker can be a struct which contains current player’s position / rotation (Vector3, Quaternion);

  1. Send a targeted RPC to the client, to notify it that tracker has been spawned.

When client X requests a teleport:
(Teleport request)

  1. Send a Cmd to the server.
  2. Check on the server if the tracker for the specific player exists (Dictionary.TryGetValue(clientId, out var Tracker)).
  3. Move position of the player on the server or do nothing if the tracker doesn’t exist.
  4. (Optional) Send targeted rpc to that client to update position / rotation.

Also, Don’t use .FindWithTag, as that may be inconsistent. Especially over network.
Also, forget about “host” architecture. There’s always only client(s), and a server.
Host is just server and a client running on the same instance.
By doing so, you’ll get less confused on how things should communicate.

I never used dictionary until now, can u explain a bit more the part about Dictionary<clientId, Tracker> please ?

Any lookup will do, I just find it simplest.

https://docs.microsoft.com/en-us/dotnet/api/system.collections.generic.dictionary-2?view=netframework-4.7.2

I didn’t understand with that link, so many information, i don’t know what to do o_o’
How do i lock the balise on the dictionary ? Please

“3. Keep a track for the created tracker - something like Dictionary<clientId, Tracker>;”

// Server context / Pseudo-code

private Dictionary<int, Tracker> _lookup = new Dictionary<int, Tracker>();

private void OnCmdSetTracker(){
    // get a client id first
    // int clientId = ...;
    // Get client position and rotation
    Tracker tracker = new Tracker();
     // Set position and rotation of the player to the tracker
    _lookup[clientId] = tracker;
}

// On teleport request

private void OnCmdTeleportRequest() {
    // Fetch the client id
    int clientId = ...;
    // Grab the previous tracker:
    Tracker tracker;
    if (!_lookup.TryGetValue(clientId, out tracker)) {
          // No tracker available. Do nothing, or send no tracker rpc to the client
          return;     
    }

    // Grab the player and set the position / rotation based on the tracker object
}

It’s not that difficult. Just read the manual.

    private void OnCmdSetTracker()
    {
        m_Identity = GetComponent<NetworkIdentity>();
        int clientID = m_Identity.connectionToClient;
        // get a client id first
        // int clientId = ...;
        // Get client position and rotation
        Vector3 BalisePosition = gameObject.transform.position;
        GameObject tracker = new GameObject();
        // Set position and rotation of the player to the tracker
        _lookup[clientId] = tracker;
    }

I got an error with client id int: cannot convert to int

lookup[clientId] = tracker; : clientID doesn’t exist in this context

private Dictionary<int, GameObject> _lookup = new Dictionary<int, GameObject>();
i replaced Traker by GameObject because it said that doesn’t exist too
I think that won’t work with gameobject…

It is a pseudo-code after all. :stuck_out_tongue:

Tracker is just an example.
Note that you don’t need gameobjects for this. Something like struct with position / rotation will do.

Also, NetworkIdentity.connectionToClient is not an int. It’s a NetworkIdentity.connectionToClient.connectionId that is int.

What should i write in that

struct Tracker { } ?

    private void OnCmdSetTracker()
    {
       NetworkIdentity m_Identity = GetComponent<NetworkIdentity>();
        int clientID = m_Identity.connectionToClient.connectionId;
        // get a client id first
        // int clientId = ...;
        // Get client position and rotation
       Vector3 BalisePosition = gameObject.transform.position;
        GameObject tracker = new GameObject();
        // Set position and rotation of the player to the tracker
        _lookup[clientId] = tracker;
    }

Thank you for your help, but i still need information please

Something like this:

public struct Tracker {
     public Vector3 Position;
     public Quaternion Rotation;
}

Then assign the position / rotation of the player to it:

Tracker tracker = new Tracker {
                  Position = // player position,
                  Rotation = // player rotation
};

I had major problem on other script sorry for late answer,
On Spell script:

if (Input.GetButtonDown("Ability4"))
                {
                    if (!isLocalPlayer)
                        return;

                    if (teleport == false)
                    {
                        OnCmdSetTracker();
                       // CmdBaliseDrop();
                    }
                    if (teleport == true)
                    {
                        teleport = false;
                    }
                    else
                    {
                        teleport = true;
                    }
                    nextFireTime2 = Time.time + cooldowntime2;
                }
public void OnCmdSetTracker()
    {

        // get a client id first
        NetworkIdentity m_Identity = GetComponent<NetworkIdentity>();
        // int clientId = ...;
        int clientID = m_Identity.connectionToClient.connectionId;
        // Get client position and rotation
        Tracker tracker = new Tracker
        {
            Position = gameObject.transform.position,
            Rotation = gameObject.transform.rotation
        };
        // Set position and rotation of the player to the tracker
        _lookup[clientID] = tracker;
    }
    public void OnCmdTeleportRequest()
    {
        NetworkIdentity m_Identity = GetComponent<NetworkIdentity>();
        // int clientId = ...;
        int clientID = m_Identity.connectionToClient.connectionId;
        // Fetch the client id
        // Grab the previous tracker:
        Tracker tracker;
        if (!_lookup.TryGetValue(clientID, out tracker))
        {
            // No tracker available. Do nothing, or send no tracker rpc to the client
            return;
        }
        gameObject.transform.position = tracker.Position;
        // Grab the player and set the position / rotation based on the tracker object
    }

On PlayerController script(they are both on same gameobject):

if (Input.GetButtonDown("Ability4"))
                {
                    if (teleport == false)
                    {
                        teleport = true;
                    }
                    if (teleport == true)
                    {
                        gameObject.GetComponent<ShotSpellMinato>().OnCmdTeleportRequest();
                        WarpSound.Play();
                    }
                    isCoolDown4 = true;
                    nextFireTime10 = Time.time + cooldowntime10;
                }

It’s working perfectly for the “host”(client who create the room, note that it’s a matchmaking system), but for client who join i got this error: (look screenshot)

It’s the same error as my other thread here: ReplacePlayerForConnection and Object reference not set to an instance of an object - Unity Engine - Unity Discussions

Have you any idea?
Thank you for your help until now by the way

It’s seems like null is either NetworkIdentity, or the lookup itself.
Hard to tell without seeing actual script, as all line numbers are incorrect.

Are you sure you’re not calling the method directly on the client? It supposed to be called only on server.
(Does it have [Command] on it?)

[Command]
    public void CmdSetTracker()
    {

        // get a client id first
        NetworkIdentity m_Identity = GetComponent<NetworkIdentity>();
        // int clientId = ...;
        int clientID = m_Identity.connectionToClient.connectionId;
        // Get client position and rotation
        Tracker tracker = new Tracker
        {
            Position = gameObject.transform.position,
            Rotation = gameObject.transform.rotation
        };
        // Set position and rotation of the player to the tracker
        _lookup[clientID] = tracker;
    }
    [Command]
    public void CmdTeleportRequest()
    {
        NetworkIdentity m_Identity = GetComponent<NetworkIdentity>();
        // int clientId = ...;
        int clientID = m_Identity.connectionToClient.connectionId;
        // Fetch the client id
        // Grab the previous tracker:
        Tracker tracker;
        if (!_lookup.TryGetValue(clientID, out tracker))
        {
            // No tracker available. Do nothing, or send no tracker rpc to the client
            return;
        }
        gameObject.transform.position = tracker.Position;
        // Grab the player and set the position / rotation based on the tracker object
    }

With that i got no error on console but the client doesn’t warp :confused:

Since some days i got this error
A connection has already been set as ready. There can only be one.
UnityEngine.Networking.NetworkIdentity:UNetStaticUpdate()
Check screenshot, i don’t know why I have that, can it make some problem for the client?
I heard that can be a problem with the networkmanager script, but i didn’t find out the solution…
Here NetworkManager script:

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Networking;
using UnityEngine.Networking.Match;

public class MyLobby : NetworkManager {
    public GameObject LobbyOption;
    public GameObject TitleScreen;
    private bool TitleBool;
    public enum Selection { none, minato, saitama, goku, war };
    public Selection currentSelect;
    private float nextRefreshTime;
    //  public GameObject MinatoButton;
    // public bool minato;
    GameObject playerselected;
    public GameObject MinatoPlayer;
//   public GameObject SaitamaButton;
   // public bool Saitama;
    public GameObject SaitamaPlayer;
//   public GameObject GokuButton;
  //  public bool Goku;
    public GameObject GokuPlayer;
//    public GameObject WarButton;
   // public bool War;
    public GameObject WarPlayer;


   
    public void StartHosting()
    {
        StartMatchMaker();
        matchMaker.CreateMatch("Jasons Match", 4, true, "", "", "", 0, 0, OnMatchCreated);
       
    }

    private void OnMatchCreated(bool success, string extendedInfo, MatchInfo responseData)
    {
        base.StartHost(responseData);

        RefreshMatches();
    }
    private void Update()
    {
        if (Time.time >= nextRefreshTime)
        {
            RefreshMatches();
        }
       
        if(currentSelect == Selection.war)
        {
            playerselected = WarPlayer;
        }
        if (currentSelect == Selection.minato)
        {
            playerselected = MinatoPlayer;
        }
        if (currentSelect == Selection.saitama)
        {
            playerselected = SaitamaPlayer;
        }
        if (currentSelect == Selection.goku)
        {
            playerselected = GokuPlayer;
        }
        // currentSelect = MinatoButton.GetComponent<CharacterSelectionScreen>().currentSelect;
        //  Goku = GokuButton.GetComponent<CharacterSelectionScreen>().GokuSelected;
        // Saitama = SaitamaButton.GetComponent<CharacterSelectionScreen>().SaitamaSelected;
        // War = WarButton.GetComponent<CharacterSelectionScreen>().WarSelected;
    }

    private void RefreshMatches()
    {
        nextRefreshTime = Time.time + 5f;
        if (matchMaker == null);
        StartMatchMaker();

        matchMaker.ListMatches(0, 10, "", true, 0, 0, HandleListMatchesComplete);

    }

    public void JoinMatch(MatchInfoSnapshot match)
    {
        if (matchMaker == null)
            StartMatchMaker();

        matchMaker.JoinMatch(match.networkId, "", "", "", 0, 0, HandleJoinedMatch);
    }

    private void HandleJoinedMatch(bool success, string extendedInfo, MatchInfo responseData)
    {
        StartClient(responseData);
    }

    private void HandleListMatchesComplete(bool success, string extendedInfo, List<MatchInfoSnapshot> responseData)
    {
        AvaibleMatchesList.HandleNewMatchList(responseData);
    }
    public override void OnServerAddPlayer(NetworkConnection conn, short playerControllerId)
    {
        TitleScreen.SetActive(false);
       LobbyOption.SetActive(true);
            GameObject player = (GameObject)Instantiate(playerPrefab, Vector3.zero, Quaternion.identity);
         
            NetworkServer.AddPlayerForConnection(conn, player, playerControllerId);

    }
    // if (team == 1)
    //    { player.GetComponent<MinatoController_Net>().team = 1; }
    //int team = TeamHandler.GetComponent<TeamHandler>().team;
 
   
    public void Wargreymon()
    {
        //  LobbySelect.GetComponent<MyLobby>().currentSelect = war;
        currentSelect = Selection.war; // changes currentSel to Up.

    }

    //  MinatoButton.GetComponent<CharacterSelectionScreen>().MinatoSelected = false;
    //    SaitamaButton.GetComponent<CharacterSelectionScreen>().SaitamaSelected = false;
    //   GokuButton.GetComponent<CharacterSelectionScreen>().GokuSelected = false;
    //   WarSelected = true;

    public void Minato()
    {
        currentSelect = Selection.minato; // changes currentSel to Up.
                                          //  WarButton.GetComponent<CharacterSelectionScreen>().WarSelected = false;
                                          //  SaitamaButton.GetComponent<CharacterSelectionScreen>().SaitamaSelected = false;
                                          //   GokuButton.GetComponent<CharacterSelectionScreen>().GokuSelected = false;
                                          //   MinatoSelected = true;
    }
    public void Goku()
    {
        currentSelect = Selection.goku; // changes currentSel to Up.
                                        //  WarButton.GetComponent<CharacterSelectionScreen>().WarSelected = false;       
                                        //   MinatoButton.GetComponent<CharacterSelectionScreen>().MinatoSelected = false;
                                        //   SaitamaButton.GetComponent<CharacterSelectionScreen>().SaitamaSelected = false;
                                        //   GokuSelected = true;
    }
    public void Saitama()
    {
        currentSelect = Selection.saitama; // changes currentSel to Up.
                                           //  WarButton.GetComponent<CharacterSelectionScreen>().WarSelected = false;
                                           //  MinatoButton.GetComponent<CharacterSelectionScreen>().MinatoSelected = false;
                                           ////  GokuButton.GetComponent<CharacterSelectionScreen>().GokuSelected = false;
                                           //SaitamaSelected = true;
    }
        public void PlayerChoosed(NetworkIdentity player)
        {

            var conn = player.connectionToClient;

            var newPlayer = Instantiate<GameObject>(playerselected);

        LobbyOption.SetActive(false);
            Destroy(player.gameObject);
            NetworkServer.ReplacePlayerForConnection(conn, newPlayer, 0);
        }
   
}

Try outputing a debug inside this block, it might be that you’re fetching invalid client it:

if (!_lookup.TryGetValue(clientID, out tracker))
        {
            Debug.LogWarning("No tracker found for "+clientId);
            // No tracker available. Do nothing, or send no tracker rpc to the client
            return;
        }

It should print it out on the server / host. Otherwise see if the command is called on the server. Debug until it works :slight_smile:

I have no Debug either server or client
I just noticed from scene server view, that when i use teleport request, i see the player warping, but in client view he stays on same position and walk from that position when he moves (in scene view he warp back to normal position with move input)
I also notied that when a client join, the server spawn 2 playerPrefabs, one is only on the server view and doesn’t move but got collider.

Up~

Make sure you’ve enabled warnings in the console (previous screenshot has them disabled).

That piece of code is supposed to work on the server, so I suggest looking at the console on the host / server side after client has performed an action. Moreover, I suggest asking specific questions about synchronization on the multiplayer subforum.