Unet Syncing animator layer weight Unity 3D - unity3d

I'm currently working on a multiplayer fps to learn UNET and Unity. in this project I'm using Network Animator Component to sync my animations.
I have one layer that runs the normal states like walking and jumping and another that just controls the upper body for scoping. This layer gets activated when scoping.
The parameters a syncing fine but not the layer weight.
The script that controls the layer weight gets disabled on the remote player as it is part of the same script that takes care of shooting and we don't want to shoot from the remote player.
So I placed that code on a different script and made Layerweight a Syncvar, still no results.
this is the snippet that sets the weight and fades it back out.
if (isScoped)
{
animator.SetLayerWeight(1, 1);
layerWeight = 1;
}
else
{
layerWeight = Mathf.Lerp(layerWeight, 0, 0.1f);
animator.SetLayerWeight(1, layerWeight);
}
animator.SetBool("Scoped", isScoped);
how can I sync a variable that is updated on a disabled script?
And what are the different custom attributes like [Command] and [ClientRpc] I should use to get this working?
I have read the documentation and manuals of UNET, but struggle at this relatively easy task as I'm having a hard time understanding the difference of these calls.

In general the main problem with [SyncVar] is that they are
synchronized from the server to clients
so not the other way round!
Then
[Command] is a method that is called by any client but only executed on the server
[ClientRpc] is kind of the opposite, it is called by the server and then executed on all clients
so what I would do is have a setter method for the layerweight like
private void SetLayerWeight(float value)
{
// only player with authority may do so
if(!isLocalPlayer) return;
// Tell the server to update the value
CmdSetLayerWeight(value);
}
[Command]
private void CmdSetLayerWeight(float value)
{
// Server may now tell it to all clients
RpcSetLayerWeight(value);
}
[ClientRpc]
private void RpcSetLayerWeight(float value)
{
// called on all clients including server
layerweight = value;
animator.SetLayerWeight(1, layerweight);
}
and call it like
if (isScoped)
{
SetLayerWeight(1);
}
else
{
SetLayerWeight(Mathf.Lerp(layerWeight, 0, 0.1f));
}
Note though that passing this forth and back between server and client will cause some uncanny lag!
You would probably be better syncing the isScoped bool instead and assign the value directly on the calling client:
private void SetScoped(bool value)
{
// only player with authority may do so
if(!isLocalPlayer) return;
// for yourself already apply the value directly so you don't have
// to wait until it comes back from the server
scoped = value;
// Tell the server to update the value and who you are
CmdSetLayerWeight(value);
}
// Executed only on the server
[Command]
private void CmdSetLayerWeight(bool value)
{
// Server may now tell it to all clients
RpcSetLayerWeight(value);
}
// Executed on all clients
[ClientRpc]
private void RpcSetLayerWeight(bool value)
{
// called on all clients including server
// since setting a bool is not expensive it doesn't matter
// if this is done again also on the client who originally initialized this
scoped = value;
}

Related

Unity - Mirror Networking : Player client disconnect after scene changed

I'm currently using Mirror Networking to make a multiplayer game. I have a scene when players are all connected, they can choose their characters and set the ready. If all players are ready, I change current scene to arena scene using MyNetworkManager.ServerChangeScene(arenaSceneName). This method sets all player clients as not ready. But After the scene was loaded, my player client is no longer connected to my host and I don't know why.
Can you help me please ?
Thanks a lot for answers.
Clients that connect to this server will automatically switch to this scene. This is called automatically if onlineScene or offlineScene are set, but it can be called from user code to switch scenes again while the game is in progress. This automatically sets clients to be not-ready. The clients must call NetworkClient.Ready again to participate in the new scene."
if its not solve problem can you give more information about your project
Are you using MatchInterestManager ? This can be lead some problems like yours
Edit 1 -
Maybe the problem was host
can you run your code on server not host
Edit 2 -
i belive your command function runs on client not server because you miss "Cmd" prefix on your command function change it CmdOnAllPlayersReady()
public class ReadyPlayerChecker : NetworkBehaviour
{
public List<PlayerBehaviour> activePlayers;
public List<PlayerBehaviour> GetActivePlayers()
{
return activePlayers;
}
// Start is called before the first frame update
void Start()
{
activePlayers = new List<PlayerBehaviour>();
}
// Update is called once per frame
void Update()
{
foreach (PlayerBehaviour player in FindObjectsOfType<PlayerBehaviour>
(true))
{
if(!activePlayers.Contains(player))
{
activePlayers.Add(player);
}
}
bool allPlayersReady = true;
foreach (PlayerBehaviour player in activePlayers)
{
if (!player.IsReady())
{
allPlayersReady = false;
}
}
if (allPlayersReady && activePlayers.Count > 0)
{
OnAllPLayersReady();
}
}
[Command]
public void OnAllPLayersReady()
{
GameObject.Find("SceneManager").GetComponent<SceneChanger>
().LoadScene("SimpleArena");
}
}
This is my Network Manager settings :
I succeed to make the playerclient connected when the scene change. Now, the player prefab for my client is in host view but not in the corresponding client view. Do someone can tell me why ?

How to generate a random number and make sure it is the same for everyone using Photon in Unity?

Hi Guys I am converting a single player Sudoko game into a multiplayer game (right now 2 players) using Photon in Unity.
The basic logic of the Sudoku game is that there are 300 puzzle data already loaded in it. A random number between 1 to 300 is picked up and the corresponding puzzle is loaded.
But the problem I am facing is even though I have made sure that the same number is getting picked up for both the client and the master server, different puzzles are getting loaded.
So basically I script called MultiManager attached to the MultiManager GameObject in the Sudoku screen.The script looks something like this.
void Start()
{
PV = GetComponent<PhotonView>();
if (PhotonNetwork.IsMasterClient)
{
puzzleIndex = Random.Range(0, 300);
PV.RPC("RPC_PuzzleIndex", RpcTarget.Others, puzzleIndex);
}
gameManager = FindObjectOfType(typeof(GameManager)) as GameManager;
gameManager.PlayNewGame("easy");
}
[PunRPC]
void RPC_PuzzleIndex(int puzzleIndexNUmber)
{
puzzleIndex = puzzleIndexNUmber;
}
So in the GameManager script you have these functions:
public void PlayNewGame(string groupId)
{
// Get the PuzzleGroupData for the given groupId
for (int i = 0; i < puzzleGroups.Count; i++)
{
PuzzleGroupData puzzleGroupData = puzzleGroups[i];
if (groupId == puzzleGroupData.groupId)
{
PlayNewGame(puzzleGroupData);
return;
}
}
}
private void PlayNewGame(PuzzleGroupData puzzleGroupData)
{
// Get a puzzle that has not yet been played by the user
PuzzleData puzzleData = puzzleGroupData.GetPuzzle();
// Play the game using the new puzzle data
PlayGame(puzzleData);
}
And in the PuzzleGroupData class you have this function :
public PuzzleData GetPuzzle()
{
return new PuzzleData(puzzleFiles[MultiManager.puzzleIndex], shiftAmount, groupId);
}
I don't quite get as to whats wrong which is happening. I tried to use other variations like keeping that random number outside of the condition inside of PhotonNetwork.isMasterClient and all, but doesn't work.
If anyone can help it would be great. By the way this Sudoku game was purchased and I am trying to convert it to a mutliPlayer game
Since the master is usually the first one in a room so also the first one getting Start called I think what happens is that your other clients are simply not connected yet when the RPC is called.
Further it might also happen (actually pretty likely) that Start is called before the RPC has the chance to be received.
I would rather actually wait until you have the value and do
void Start()
{
PV = GetComponent<PhotonView>();
if (PhotonNetwork.IsMasterClient)
{
PV.RPC(name of(RPC_PuzzleIndex), RpcTarget.AllBuffered, Random.Range(0, 300));
}
}
[PunRPC]
void RPC_PuzzleIndex(int puzzleIndexNUmber)
{
puzzleIndex = puzzleIndexNUmber;
gameManager = FindObjectOfType(typeof(GameManager)) as GameManager;
gameManager.PlayNewGame("easy");
}
This way
the RpcTarget.AllBuffered makes sure that also clients joining later will receive the call
there is no way you start a game without receiving the random value first

Unity3D: Custom UnityEvent AddListener not firing

I have my own custom UnityEvent and am trying to add a listener.
I have used AddListener on numerous other UI objects, such as buttons, dropdowns, toggles, etc. so I understand the process. However, when I Invoke my UnityEvent, it simply doesn't fire.
I'm receiving no error messages, and after doing reading and research, everything looks correct. So, not sure what to do further.
This is an object that emits when it's rotated.
This is the basics of my code:
using UnityEngine.Events;
public class Rotator: MonoBehaviour
{
public UnityEvent OnRotate;
int angle = 0;
int newAngle = 0;
void Start()
{
OnRotate = new UnityEvent();
}
void Update()
{
newAngle = (int)transform.rotation.eulersAngles.z;
if (newAngle != angle)
{
print ("Actual Instance ID: " + GetInstanceID());
print ("Invoking!");
OnRotate.Invoke();
angle = newAngle;
}
}
}
and
public class Owner: MonoBehaviour
{
public Rotator rotator;
void Start()
{
print ("Rotator Instance ID: " + rotator.GetInstanceID());
rotator.OnRotate.AddListener(
() => UpdateRotation()
);
}
void UpdateRotation()
{
print ("Invoked!");
}
}
When the Rotator has it's angle changed, I get this in the console:
Actual Instance ID: 11234
Rotator Instance ID: 11234
Invoking!
The instance ID is to make sure I'm working with the same objects and not going in circles for nothing. They match, so I'm listening to the object that's firing.
However, the listener isn't firing. I've tried different combinations with delegates, etc. but it's all the same. No errors. It just doesn't invoke.
Obviously, I'm doing something wrong, but what is it?
Thanks for any help.
Somehow your answered your new edited version of the question with exactly the code you previously provided in the First Version of your Question!
As I tried to tell you ... if you anywhere in your code do OnRotate = new UnityEvent() of course you thereby erase any persistent callbacks and any runtime callbacks added before that moment!
In short
Simply leave it as
public UnityEvent OnRotate;
and you don't even have to think about it anymore.
For understanding why it also works if you put it in Awake please simply have a look at the Order of Execution for Event Functions
&rightarrow; First Awake and OnEnabled is called for every GameObject/Component. Then all Start methods are called as soon as the GameObject/Component is active.
Within each of these blocks (Awake + OnEnable) and (Start) the order of execution between different component types is not guaranteed unless you explicitly configure it via the Script Execution Order Settings where you could define that Owner is simply run before Rotator .. then having both in Start would also work again.
Why does it also work if you do it on the public field?
&rightarrow; Because this field is serialized. That means it is initialized automatically in the Inspector and then stored together with the Scene or prefab asset including any persistent callbacks.
And then Later Unity re-uses the serialized Version of the field so actually you can completely remove the new UnityEvent(); since it doesn't have any effect on a serialized field! It will always be initialized automatically anyway!
Ok, I found out what the issue was.
My question now is "why?".
I changed my code from:
public UnityEvent OnRotate;
void Start() {
OnRotate = new UnityEvent();
}
to
public UnityEvent OnRotate = new UnityEvent();
void Start() {
}
And now it works.
Although, now that I think about it, Awake() is the method where they all fire before initialization, whereas Start() is when the object is created. So the Start() of the Rotator is probably getting called after the Owner is adding a listener.

Unity NetworkTransform, force update every Frame?

On a purposed LAN setup, I want Unity's NetworkTransform to simply send every single frame. (Mode is "Sync Transform", not rigidbody, etc.)
I'm wondering how to do this .. if you just set the send rate to 0.00001 will it in fact do it every frame that the object is moving?
(The Inspector interface only allows up to 29Hz, simply set it in code if you want smaller values.)
I was also thinking something like on a script......
void Update() { .. SetDirtyBit( ? ); }
would that do it?
(And, what do you use as a bitmask there?)
It really takes forever to determine this sort of thing experimentally. Obviously Unity's doco is a joke. And I couldn't google anyone who'd already figured it. Any experience in this?
Secondly...
Further information. We did some experiments and indeed if you set it to a very small value, it does seem to sync the NetworkTransform every single frame.
(Of course, it doesn't bother sending if it has not moved, as #Programmer points out below.)
H O W E V E R this does not produce smooth results for kinematic objects or camera positions. I don't know why; it has to do with the Update loop and exactly when the change takes place. But what you get is a "very fine jitter".
Thirdly...
I did an experiment, simply using Commands and Rpcs, to send some information every frame (actually from one client to another).
I specifically used a purpose solo ethernet network and the apps do little or nothing other than send the info every frame (ie "manually" just using Command/Rpc), ie on Update().
In fact, it still drops many frames, it's often not sent. (Let's say, it misses 10 or 20 in 10 seconds worth.)
Good grief!
Do not bother doing this if you need some info sent "every frame", say on a LAN system.
if you just set the send rate to 0.00001 will it in fact do it every
frame that the object is moving?
Yes, but when the Object's transform is not moving or rotating, it's not updated over the network regardless of the value of NetworkTransform.sendInterval.
I was also thinking something like on a script......
void Update() { .. SetDirtyBit( ? ); }
Yes. You need to call SetDirtyBit every frame if you need the network update to be sent for the object.
And, what do you use as a bitmask there?
The SetDirtyBit function is technically the function version of the SyncVar attribute. The argument is not really documented that much but if SyncVar marked variable is changed, its dirty mask is changed to 1. This also means that you should pass 1 to the SetDirtyBit function if you want network update to be sent for the object.
You should also use it from a function marked with the [Command] attribute instead of directly from the Update function. Something like below:
void Update()
{
if (isLocalPlayer)
{
CmdUpdateTransform();
}
}
[Command]
void CmdUpdateTransform()
{
SetDirtyBit(1u);
}
While Unity claims you can update the transform by simply using the SetDirtyBit function. I don't think this is true. The statement is missing lots of information. You will need OnSerialize to serialize and send the data and OnDeserialize to receive and de-serialize it. The SetDirtyBit function is simply used to determine which data to send. It's used to reduce bandwidth issues that comes from using uNET.
The code below shows what the use of SetDirtyBit should look like. It might need few changes to work on your side.
public class Example : NetworkBehaviour
{
private const uint SEND_POSITION = 1u;
private const uint SEND_ROTATION = 2u;
private const uint SEND_SCALE = 3u;
void Update()
{
uint dirtyBit = syncVarDirtyBits;
dirtyBit |= SEND_POSITION;
dirtyBit |= SEND_ROTATION;
dirtyBit |= SEND_SCALE;
if (isLocalPlayer)
{
CmdUpdateTransform(dirtyBit);
}
}
[Command]
void CmdUpdateTransform(uint dirtyBit)
{
SetDirtyBit(dirtyBit);
}
public override bool OnSerialize(NetworkWriter writer, bool initialState)
{
if ((this.syncVarDirtyBits & SEND_POSITION) != 0u)
{
writer.Write(this.transform.position);
}
if ((this.syncVarDirtyBits & SEND_ROTATION) != 0u)
{
writer.Write(this.transform.rotation);
}
if ((this.syncVarDirtyBits & SEND_SCALE) != 0u)
{
writer.Write(this.transform.localScale);
}
return true;
}
public override void OnDeserialize(NetworkReader reader, bool initialState)
{
if ((this.syncVarDirtyBits & SEND_POSITION) != 0u)
{
Vector3 pos = reader.ReadVector3();
}
if ((this.syncVarDirtyBits & SEND_ROTATION) != 0u)
{
Quaternion rot = reader.ReadQuaternion();
}
if ((this.syncVarDirtyBits & SEND_SCALE) != 0u)
{
Vector3 scale = reader.ReadVector3();
}
}
}
In the Unity Docs It says
If sendInterval is zero, updates will be sent at the end of the frame when dirty bits are set for that script. Note that setting the value of a SyncVar will automatically set dirty bits.
So you could use your own script using NetworkSettings
using UnityEngine.Networking;
[NetworkSettings(channel = 1, sendInterval = 0.0f)]
class MyScript : NetworkBehaviour
{
[SyncVar]
int value;
}
However SyncVar only works Server -> Client.
But you can use SetDirtyBit to manually set the behaviour dirty.
Or you probably could also use InvokeSyncEvent to synchronize some values directly.
Here I found a bit more information on Dirty bits.

Unity 5.1 Networking - Spawn an object as a child for the host and all clients

I have a player object who can equip multiple weapons. When a weapon is equipped, its transform's parent is set to its hand. I have messed around with this for some time and cannot get this to work for both the host and the client. Right now I am trying to equip the weapon on the server, and tell all the clients to set their parents transforms.
public NetworkInstanceId weaponNetId;
[Command]
void Cmd_EquipWeapon()
{
var weaponObject = Instantiate (Resources.Load ("Gun"),
hand.position,
Quaternion.Euler (0f, 0f, 0f)) as GameObject;
weaponObject.transform.parent = hand;
NetworkServer.Spawn (weaponObject);
//set equipped weapon
var weapon = weaponObject.GetComponent<Weapon> () as Weapon;
weaponNetId = weaponObject.GetComponent<NetworkIdentity> ().netId;
Rpc_SetParentGameobject (weaponNetId);
}
[ClientRpc]
public void Rpc_SetParentGameobject(NetworkInstanceId netID)
{
weaponNetId = netId;
}
And in the update I am updating the weapons transform
void Update () {
// set child weapon tranform on clients
if (!isServer) {
if (weaponNetId.Value != 0 && !armed) {
GameObject child = NetworkServer.FindLocalObject (weaponNetId);
if (child != null) {
child.transform.parent = hand;
}
}
}
I know this isn't the most optimized way to do this..but right now I am just trying to get this to work any way possible and then work on tweaking it. Seems like it should be a simple task.
We do a similar thing in our multiplayer game. There are a few things you need to do to get this working. Firstly, the concept:
Setting the weapon's parent on the server is trivial, as you have found. Simply set the transform's parent as you would normally in Unity. However, after spawning this object on the server with NetworkServer.Spawn, it will later be spawned on clients in the root of the scene (hierarchy outside of the spawned prefab is not synchronised).
So in order to adjust the hierarchy on the client I would suggest that you:
Use a SyncVar to synchronise the netID of the parent object between the server and client.
When the object is spawned on the client, find the parent using the synchronised netID and set it as your transform's parent.
Therefore, I would adjust your code to look something like this. Firstly, set the weapon's parent netId before you spawn it. This will ensure that when it is spawned on clients, the netId will be set.
[Command]
void Cmd_EquipWeapon()
{
var weaponObject = Instantiate (Resources.Load ("Gun"),
hand.position,
Quaternion.Euler (0f, 0f, 0f)) as GameObject;
weaponObject.parentNetId = hand.netId; // Set the parent network ID
weaponObject.transform.parent = hand; // Set the parent transform on the server
NetworkServer.Spawn (weaponObject); // Spawn the object
}
And then in your weapon class:
Add a parentNetId property.
Mark it as [SyncVar] so that it synchronises between server and client copies.
When spawned on a client, find the parent object using the netId and set it to our transform's parent.
Perhaps something like:
[SyncVar]
public NetworkInstanceId parentNetId;
public override void OnStartClient()
{
// When we are spawned on the client,
// find the parent object using its ID,
// and set it to be our transform's parent.
GameObject parentObject = ClientScene.FindLocalObject(parentNetId);
transform.SetParent(parentObject.transform);
}
I found this post really useful but I have a small addendum.
The netId will be on the root of the hierarchy so its useful to know that you can traverse down the hierarchy using transform.Find.
Something like this ..
GameObject parentObject = ClientScene.FindLocalObject(parentNetId);
string pathToWeaponHolder = "Obj/targetObj";
transform.SetParent(parentObject.transform.Find(pathToWeaponHolder));
After reading Andy Barnard's solution, I came up with this slightly modified solution. Instead of a SyncVar, there is a Client RPC for the server to call any time a NetworkIdentity needs to change parents. This does require the parent to also have a NetworkIdentity (though it need not be a registered prefab).
public void Server_SetParent (NetworkIdentity parentNetworkIdentity) {
if (parentNetworkIdentity != null) {
// Set locally on server
transform.SetParent (parentNetworkIdentity.transform);
// Set remotely on clients
RpcClient_SetParent (parentNetworkIdentity.netId, resetTransform);
}
else {
// Set locally on server
transform.SetParent (null);
// Set remotely on clients
RpcClient_SetParent (NetworkInstanceId.Invalid, resetTransform);
}
}
[ClientRpc]
void RpcClient_SetParent (NetworkInstanceId newParentNetId) {
Transform parentTransform = null;
if (newParentNetId != NetworkInstanceId.Invalid) {
// Find the parent by netid and set self as child
var parentGobj = ClientScene.FindLocalObject (newParentNetId);
if (parentGobj != null) {
parentTransform = parentGobj.transform;
}
else {
Debug.LogWarningFormat ("{0} Could not find NetworkIdentity '{1}'.", gameObject.name, newParentNetId.Value);
}
}
transform.SetParent (parentTransform);
}
These two are part of a NetworkBehavior, which obviously RequiresComponent(typeof(NetworkIdentity)). If you really need this behavior on a client to server, I'd suggest creating a command that passed the NetworkIdentity to the server, which would just call the server's public method. It's one set of network messages more than optimal, but meh.