Unity Netcode: Change Ownership doesn't work for me - unity3d

I have been trying to spawn gameObject(Specifically Player gameObject) in Server using this player.GetComponent().Spawn() and then tried to change ownership. It didn't works for me. So frustrated. Can anyone help on this.
I want to spawn two different player in server and client. So I tried to spawn all the players in server only and tried to change the ownership using ownerclientId, LocalClientId. Nothing worked. What happens is that it spawns two players in both server and client screen but only server has the ownership.
Code snippet:
private void Start()
{
var playerSelected = GameObject.Find("PlayerSelected");
int selectedPlayer = playerSelected.GetComponent<PlayerSelected>().selectPlayer;
if (NetworkManager.Singleton.IsServer)
{
PlayerSpawner(selectedPlayer, OwnerClientId);
}
else
{
PlayerSpawnerOnServerRpc(selectedPlayer, OwnerClientId);
}
}
[ServerRpc(RequireOwnership = false)]
void PlayerSpawnerOnServerRpc(int selectedPlayer, ulong clientId)
{
PlayerSpawner(selectedPlayer, clientId);
}
void PlayerSpawner(int selectedPlayer, ulong clientId)
{
GameObject player;
switch(selectedPlayer)
{
case 0:
player = Instantiate(player1Prefab, spawnPositionPlayer1);
player.GetComponent<NetworkObject>().Spawn();
player.GetComponent<NetworkObject>().ChangeOwnership(clientId);
break;
case 1:
player = Instantiate(player2Prefab, spawnPositionPlayer2);
player.GetComponent<NetworkObject>().Spawn();
player.GetComponent<NetworkObject>().ChangeOwnership(clientId);
break;
}
}

The reason this doesn't work is that the server is sending two messages in quick succession.
player.GetComponent<NetworkObject>().Spawn();
player.GetComponent<NetworkObject>().ChangeOwnership(clientId);
The first tells the clients to spawn an object. The second gives ownership to the object. You cannot guarantee (even running locally) that the Spawn() has finished when the ChangeOwnership() is called. So when ChangeOwnership() is called, the client may not have finished initialising the object that's spawned.
Unity were aware of this, and provided you with SpawnWithOwnership (docs). Replace your two lines with one:
player.GetComponent<NetworkObject>().SpawnWithOwnership(clientId);

Related

I want to make a P2P networked (thinking about using mirror) game with unity, with no dedicated server, similar to Terraria and Valheim

I have managed to basically connect with my friend over internet, by forwarding my IP address from my router settings... this is not viable because there are few people willing to do what I did to play games with their friends. So how to actually do UDP hole punching (basically what I did manually to my router) in unity using the mirror networking solution...
A common solution to this problem is WebRTC, which takes care of the hole punching under the hood. Unity maintains this package which implements WebRTC. They also provide a great tutorial on how to use it. The nuts and bolts of it are:
using UnityEngine;
using Unity.WebRTC;
public class MyPlayerScript : MonoBehaviour
{
RTCPeerConnection localConnection, remoteConnection;
RTCDataChannel sendChannel, receiveChannel;
private void Awake()
{
// Initialize WebRTC
WebRTC.Initialize();
// Create local peer
localConnection = new RTCPeerConnection();
sendChannel = localConnection.CreateDataChannel("sendChannel");
channel.OnOpen = handleSendChannelStatusChange;
channel.OnClose = handleSendChannelStatusChange;
// Create remote peer
remoteConnection = new RTCPeerConnection();
remoteConnection.OnDataChannel = ReceiveChannelCallback;
// register comms paths
localConnection.OnIceCandidate = e => {
!string.IsNullOrEmpty(e.candidate)
|| remoteConnection.AddIceCandidate(ref e);
}
remoteConnection.OnIceCandidate = e => {
!string.IsNullOrEmpty(e.candidate)
|| localConnection.AddIceCandidate(ref e);
}
localConnection.OnIceConnectionChange = state => {
Debug.Log(state);
}
}
//handle begin
IEnumerator Call(){
var op1 = localConnection.CreateOffer();
yield return op1;
var op2 = localConnection.SetLocalDescription(ref op1.desc);
yield return op2;
var op3 = remoteConnection.SetRemoteDescription(ref op1.desc);
yield return op3;
var op4 = remoteConnection.CreateAnswer();
yield return op4;
var op5 = remoteConnection.setLocalDescription(op4.desc);
yield return op5;
var op6 = localConnection.setRemoteDescription(op4.desc);
yield return op6;
}
//handle send messages
void SendMessage(string message)
{
sendChannel.Send(message);
}
void SendBinary(byte[] bytes)
{
sendChannel.Send(bytes);
}
//handle receive messages
void ReceiveChannelCallback(RTCDataChannel channel)
{
receiveChannel = channel;
receiveChannel.OnMessage = HandleReceiveMessage;
}
void HandleReceiveMessage(byte[] bytes)
{
var message = System.Text.Encoding.UTF8.GetString(bytes);
Debug.Log(message);
}
//handle end
private void OnDestroy()
{
sendChannel.Close();
receiveChannel.Close();
localConnection.Close();
remoteConnection.Close();
WebRTC.Finalize();
}
}
I have also found a useful way to do this using mirror and the epic free relay for this. Thanks so much for the other answers, it really helped understand better what I needed to search and use!
You can use Photon Bolt or Photon Fusion to let players host a game on their local machine like minecraft, etc. Photon provides relay as well as tries to use STUN to establish direct peer to peer connection to the host via UDP. PUN2 is also a good choice, although I like Photon Bolt/Fusion better - it's less of a simple RPC framework and more programmer oriented. Also, PUN does not do any STUN direct peer connection, it will always be relayed. Photon Bolt and Fusion will first attempt a STUN direct peer connection and then fallback to relay if necessary. It's been around for years and is the best choice.
Sure you can develop a Unity game with Mirror (although without a relay built in) but it's not nearly as easy to setup and use as Photon Bolt/Fusion and they don't provide a relay. As someone mentioned, you might be able to hack something together in some way but yeah - not recommended.
Ugh, yeah don't use WebRTC for a Unity game (or probably anything other than streaming music/video like it was made for to be honest).
Unity's MLAPI is "under development" and their last API was suddenly dropped "deprecated", so I wouldn't use that.

Getting warning Trying to send command for object without authority

I'm new to unity multiplayer so just learning it's concepts. I have this script on my player
private void Start()
{
if (isServer)
{
//run when if it's server
} else
{
//run if its client
this.InstantiateBullet();
}
}
[Command]
void InstantiateBullet()
{
Debug.Log("Test Command");
}
I'm getting a warning "Trying to send command for object without authority. ShootHooks.InstantiateBullet" My question is do I have to assign my host player prefab to client an authority so it can call InstantiateBullet() without getting warning? Or can I just ignore the warning
As the error/warning tells you you may only call commands from an object you have the authority over.
Currently it is running on all instances of your player prefab, also the ones that belong to the other connected players and you don't have the authority over those but they do!
So simply make an additional check
private void Start()
{
if(hasAuthority)
{
if(isServer)
{
...
}
else
{
InstantiateBullet();
}
}
}

Problem in authority shifting of non-player object

I am making a multiplayer game and I want to have player to interact with a non-player object (whose transform can be changed by any player). When I interact them with the player who joined first (or the guy who is hosting) its working but if I try to interact it with the another player (the one who joined second) the objects goes back to the location that the first player left him at.
So what I tried is to shift the authority of non-player object but I am having the following errors.
Anyone is having the same issue or knows any other way to do the above task? I am using the following code to change the authority:
[Command]
void Cmd_AssignLocalAuthority(GameObject obj)
{
print("shifting authority successfully");
NetworkInstanceId nIns = obj.GetComponent<NetworkIdentity>().netId;
GameObject client = NetworkServer.FindLocalObject(nIns);
NetworkIdentity ni = client.GetComponent<NetworkIdentity>();
ni.AssignClientAuthority(connectionToClient);
}
[Command]
void Cmd_RemoveLocalAuthority(GameObject obj)
{
print("reverting authority successfully");
NetworkInstanceId nIns = obj.GetComponent<NetworkIdentity>().netId;
GameObject client = NetworkServer.FindLocalObject(nIns);
NetworkIdentity ni = client.GetComponent<NetworkIdentity>();
ni.RemoveClientAuthority(ni.clientAuthorityOwner);
}
And the error I am getting is this
You need to know that the changes SHOULD be called from a player object, not the object itself, as it do not have authority.
For setting authority you should do something like this:
[Command]
public void CmdSetAuth(NetworkInstanceId objectId, NetworkIdentity player)
{
GameObject iObject = NetworkServer.FindLocalObject(objectId);
NetworkIdentity networkIdentity = iObject.GetComponent<NetworkIdentity>();
//Checks if anyone else has authority and removes it and lastly gives the authority to the player who interacts with object
NetworkConnection otherOwner = networkIdentity.clientAuthorityOwner;
if (otherOwner == player.connectionToClient)
{
return;
}
else
{
if (otherOwner != null)
{
networkIdentity.RemoveClientAuthority(otherOwner);
}
networkIdentity.AssignClientAuthority(player.connectionToClient);
}
networkIdentity.AssignClientAuthority(player.connectionToClient);
}

Photon matchmaking - Join or create a room using in Unity with an SQL lobby

I am trying to implement skill based matchmaking using Photon in Unity. It seems
I got most of this code from the documentation and it works but not well.
The problem is that you can't use JoinOrCreate() with the sql lobby type so my logic here is try and find a room, if it fails create one.
void init()
{
_client = Game.Context.GetComponent<SocketConnectionManager>().client;
joinRoom();
}
public void joinRoom()
{
TypedLobby sqlLobby = new TypedLobby("skillLobby", LobbyType.SqlLobby);
string sqlLobbyFilter = "C0 BETWEEN 100 AND 200";
_client.OpJoinRandomRoom(null, MatchMaker.MaxPlayers, MatchmakingMode.FillRoom, sqlLobby, sqlLobbyFilter);
}
public void createRoom()
{
RoomOptions o = new RoomOptions();
o.MaxPlayers = MatchMaker.MaxPlayers;
o.CustomRoomProperties = new Hashtable() { { "C0", Game.Me.getInt("trophies") } };
o.CustomRoomPropertiesForLobby = new string[] { "C0" }; // this makes "C0" available in the lobby
TypedLobby sqlLobby = new TypedLobby("skillLobby", LobbyType.SqlLobby);
_client.OpCreateRoom("", o, sqlLobby);
}
private void onEvent(EventData obj)
{
if (_client.CurrentRoom != null)
{
if (_client.CurrentRoom.PlayerCount >= _client.CurrentRoom.MaxPlayers)
{
// sweet I am good to go.
}
}
else
{
createRoom();
}
}
The problem is this is pretty unreliable. Say two players try to find a game at the same time they will both search fail and then both create. Now I have two players sitting in empty rooms instead of playing each other.
Any ideas on a better system?
Thanks all.
Thank you for choosing Photon!
First of all, there are few things that you should understand about Photon:
you can't use JoinOrCreate() with the sql lobby type
This is not correct.
Where did you read such thing?
Did you test this yourself? What did you test exactly?
onEvent (LoadBalancingClient.OnEventAction) callback cannot be used to be notified of a failed join random room operation. Instead, you should make use of the LoadBalancingClient.OnOpResponseAction callback, as follows:
private void OnOpResponse(OperationResponse operationResponse)
{
switch (operationResponse.Code)
{
case OperationCode.JoinRandomGame:
if (operationResponse.ReturnCode == ErrorCode.NoMatchFound)
{
createRoom();
}
break;
}
}
To detect a join event inside a room (local or remote player entered a room):
private void onEvent(EventData eventData)
{
switch (eventData.Code)
{
case EventCode.Join:
int actorNr = (int)eventData[ParameterCode.ActorNr];
PhotonPlayer originatingPlayer = this.GetPlayerWithId(actorNr);
if (originatingPlayer.IsLocal)
{
}
else
{
}
break;
}
}
To answer your question:
Say two players try to find a game at the same time they will both
search fail and then both create.
Any ideas on a better system?
No.
This issue happens only during the development phase where you use a few clients to run some tests. Once you have enough user base you won't notice this issue.

ConnectAsync blocking UI Thread

I have simple WinRT application, that will be communicating with remote server via TCP.
In order to do that I'm creating new StreamSocket object, and connecting to remote server after clicking a proper button, like this:
private async void ConnectButtonClick(object sender, RoutedEventArgs e)
{
StreamSocket socket = new StreamSocket();
HostName host = new HostName("192.168.1.15");
await socket.ConnectAsync(host, "12121");
}
The problem is that this code is blocking the UI thread. I've created simple animation, using MonoGame (couple of moving sprites) that is running on the screen continously, in order to check if UI is blocked.
The problem is, that after clicking Connect button, animation is freezing for a second, so I assume that, connecting to the server is made in the UI thread.
Should I put my whole network code into a separate thread, or is this async/await enough?
I'd like to implement a loop, that will handle incoming data (with help od DataReader), like this:
private async void ReceiveLoop()
{
bool running = true;
while (running)
{
try
{
uint numStrBytes = await _reader.LoadAsync(BufferSize);
if (numStrBytes == 0)
{
Disconnect();
return;
}
string msg = _reader.ReadString(numStrBytes);
OnLog(string.Format("Received: {0}", msg));
OnDataReceived(msg);
}
catch (Exception exception)
{
OnLog("Receive failed with error: " + exception.Message);
Disconnect();
running = false;
}
}
}
Sending data will be done using StoreAsync from DataWriter.
So should I put these functions into separate threads?
Can't you just try doing that on a background thread to see if that helps? Something like this:
Task.Run(
async () =>
{
StreamSocket socket = new StreamSocket();
HostName host = new HostName("192.168.1.15");
await socket.ConnectAsync(host, "12121");
});
For any async calls in a normal .net library, the general rule is to use ConfigureAwait(false) which helps prevent issues like you are seeing. So for instance in WinRT stuff it would be:
await socket.ConnectAsync(host, "12121").AsTask().ConfigureAwait(false);
This has some great information:
http://blogs.msdn.com/b/windowsappdev/archive/2012/04/24/diving-deep-with-winrt-and-await.aspx