I am currently trying to make a little drawing game where two players can draw at the same time over the network.
I am using a GameObject with a TrailRenderer to draw the lines.
Right now only the drawings of the host player are shown on both machines.
If a client player clicks and tries to draw I can see that a new object is spawned but the transform doesn't get updated. The spawned prefab has a NetworkIdentity (with Local Player Authority checked) and NetworkTransform attached to it. The following script is spawned by both players and also has a NetworkIdentity (with Local Player Authority checked).
I think I am actually doing something wrong with the CmdTrailUpdate and how to handle it but I can't really figure out what.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Networking;
public class TrailDrawer : NetworkBehaviour {
private Plane objPlane;
private GameObject currentTrail;
private Vector3 startPos;
public GameObject trail;
public void Start()
{
objPlane = new Plane(Camera.main.transform.forward * -1, this.transform.position);
}
// Update is called once per frame
void FixedUpdate() {
if (isLocalPlayer) {
if (Input.GetMouseButtonDown(0)) {
CmdSpawn();
} else if (Input.GetMouseButton(0)) {
CmdTrailUpdate();
}
}
}
[Command]
private void CmdTrailUpdate() {
Ray mRay = Camera.main.ScreenPointToRay(Input.mousePosition);
float rayDistance;
if (objPlane.Raycast(mRay, out rayDistance)) {
currentTrail.transform.position = mRay.GetPoint(rayDistance);
}
}
[Command]
private void CmdSpawn(){
Ray mRay = Camera.main.ScreenPointToRay(Input.mousePosition);
float rayDistance;
if (objPlane.Raycast(mRay, out rayDistance)) {
startPos = mRay.GetPoint(rayDistance);
currentTrail = (GameObject)Instantiate(trail, startPos, Quaternion.identity);
NetworkServer.Spawn(currentTrail);
}
}
}
I think your problem is:
[Command] means: Invoke the method from a client but only execute it on the server.
=> You are executing both methods CmdSpawn and CmdTrailUpdate only on the server.
But:
how shoud the server know your client's Input.mousePosition?
You don't want to raycast from the server's camera.main, but rather from the client's.
Solution:
Do both things local on the client and pass the position as parameter on the [Cmd] methods to the server.
Since you say the object already has a NetworkTransform, you wouldn't need to transmit the updated position to the server because NetworkTransform already does it for you. So calling CmdTrailUpdate from the client would not be neccesarry.
But: After spawning the object you have to tell the client who is calling CmdSpawn, which is his local currentTrail which's position he has to update. I would do this by simply passing the calling clients gameObject also to the CmdSpawn method and on the server call a [ClientRpc] method to set this client's currentTrail object.
(I'm assuming here, that the script you posted is attached diectly to the Player objects. If that is not the case, instead of the lines with this.gameObject you have to get the player's gameObject in a nother way.)
void FixedUpdate() {
if (!isLocalPlayer) return;
if (Input.GetMouseButtonDown(0)) {
// Do the raycast and calculation on the client
Ray mRay = Camera.main.ScreenPointToRay(Input.mousePosition);
float rayDistance;
if (objPlane.Raycast(mRay, out rayDistance)) {
startPos = mRay.GetPoint(rayDistance);
// pass the calling Players gameObject and the
// position as parameter to the server
CmdSpawn(this.gameObject, startPos);
}
} else if (Input.GetMouseButton(0)) {
// Do the raycast and calculation on the client
Ray mRay = Camera.main.ScreenPointToRay(Input.mousePosition);
float rayDistance;
if (objPlane.Raycast(mRay, out rayDistance)) {
// only update your local object on the client
// since they have NetworkTransform attached
// it will be updated on the server (and other clients) anyway
currentTrail.transform.position = mRay.GetPoint(rayDistance);
}
}
}
[Command]
private void CmdSpawn(GameObject callingClient, Vector3 spawnPosition){
// Note that this only sets currentTrail on the server
currentTrail = (GameObject)Instantiate(trail, spawnPosition, Quaternion.identity);
NetworkServer.Spawn(currentTrail);
// set currentTrail in the calling Client
RpcSetCurrentTrail(callingClient, currentTrail);
}
// ClientRpc is somehow the opposite of Command
// It is invoked from the server but only executed on ALL clients
// so we have to make sure that it is only executed on the client
// who originally called the CmdSpawn method
[ClientRpc]
private void RpcSetCurrentTrail(GameObject client, GameObject trail){
// do nothing if this client is not the one who called the spawn method
if(this.gameObject != client) return;
// also do nothing if the calling client himself is the server
// -> he is the host
if(isServer) return;
// set currentTrail on the client
currentTrail = trail;
}
Related
I have got a problem with Gun not sticking to a Player. It only happens for a client. As you can see on the screen, the gun position is fine for the host(Player on the right). Gun Prefab has Network Identity with Local Player Authority checked, and Network Transform, same for Player.
This is my code for Player:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Networking;
public class Player : NetworkBehaviour
{
[SerializeField] float speed = 10f;
[HideInInspector] public GameObject playerGun;
public GameObject gunPrefab;
void Update()
{
if (!isLocalPlayer)
{
return;
}
Movement();
if (Input.GetKeyDown(KeyCode.I))
{
CmdGetGun();
}
if (playerGun)
CarryGun();
}
private void Movement()
{
Vector3 position = new Vector3(Input.GetAxisRaw("Horizontal"), Input.GetAxisRaw("Vertical")).normalized * Time.deltaTime * speed;
transform.position += position;
MouseMovement();
}
private void MouseMovement()
{
Vector3 mousePosition = Camera.main.ScreenToWorldPoint(Input.mousePosition) - transform.position;
mousePosition.Normalize();
float rotation_z = Mathf.Atan2(mousePosition.y, mousePosition.x) * Mathf.Rad2Deg;
transform.rotation = Quaternion.Euler(0f, 0f, rotation_z);
}
[Command]
public void CmdGetGun()
{
Debug.Log("SPAWNING A GUN");
playerGun = (GameObject)Instantiate(gunPrefab, transform.position, transform.rotation);
NetworkServer.SpawnWithClientAuthority(playerGun, connectionToClient);
}
public void CarryGun()
{
Debug.Log("carring A GUN");
playerGun.transform.position = new Vector3(transform.position.x, transform.position.y, transform.position.z - 1);
playerGun.transform.rotation = transform.rotation;
}
}
I spent days trying to figure it out. I am new to Unity, especially Unet and maybe i do not understand something.
I know the position of a gun is wrong but i will change it after i deal with this problem. For now i just want it to stick to a Player both on Client and Host side.
Problem
This Command method is always executed on the server .. so also with the properties on the server.
Meaning:
You never set playerGun on the client side only on the server in
playerGun = Instantiate ...
So, since your client never gets its playerGun value set CarryGun is never executed on the client.
Solution 1
To avoid that you should use a ClientRpc method to set the value also on all clients.
[Command]
public void CmdGetGun()
{
Debug.Log("SPAWNING A GUN");
playerGun = (GameObject)Instantiate(gunPrefab, transform.position, transform.rotation);
NetworkServer.SpawnWithClientAuthority(playerGun, connectionToClient);
// Tell the clients to set the playerGun reference.
// You can pass it as GameObject since it has a NetworkIdentity
RpcSetGunOnClients(playerGun);
}
// Executed on ALL clients
// (which does not mean on all components but just the one of
// this synched GameObject, just to be clear)
[ClientRpc]
private void RpcSetGunOnClients (GameObject gun)
{
playerGun = gun;
}
Solution 2
This is an alternative solution which does basically the same steps from above but it would not longer need the CarryGun method and a NetworkTransform, making your code more efficient by saving both, method calls and network bandwidth:
Instead of spawning the gun on top level in hierarchy to a certain global position and rotation
playerGun = (GameObject)Instantiate(gunPrefab, transform.position, transform.rotation);
and than updating it's position and rotation all the time to the player's tranfroms and transfere them separately via the NetworkTransforms you could simply make it a child of the Player object itself using e.g. Instantiate(Object original, Vector3 position, Quaternion rotation, Transform parent); on the server:
playerGun = (GameObject)Instantiate(gunPrefab, gunLocalPositionOffset, gunLocalRotationOffset, transform);
This should allways keep it in the correct position without having to Update and synchronize it's position and rotation all the time and without any further transfere methods and values.
All you have to do is again have a ClientRpc in order to tell the clients as well to make this gun a a child of your Player using e.g. SetParent(Trasnform parent, bool worldPositionStays):
playerGun.transform.SetParent(transform, false);
and if necessary apply the local position and rotation offsets. Again you could use a value, everyone has acces to or pass it to the ClientRpc from the server - your choice ;)
so your methods could now look like
// In order to spawn the gun with an offset later
// It is up to you where those values should come from / be passed arround
// If you crate your Prefab "correctly" you don't need them at all
//
// correctly means: the most top GameObject of the prefab has the
// default values position(0,0,0) and rotation (0,0,0) and the gun fits perfect
// when the prefab is a child of the Player => You don't need any offsets at all
Vector3 gunLocalPositionOffset= Vector3.zero;
Quaternion gunLocalRotationOffset= Quaternion.identity;
[Command]
public void CmdGetGun()
{
Debug.Log("SPAWNING A GUN");
// instantiates the prefab as child of this gameObject
// you still can spawn it with a local offset position
// This will make its position be already synched in the Players own
// NetworkTransform -> no need for a second one
playerGun = (GameObject)Instantiate(gunPrefab, gunLocalPositionOffset, gunLocalRotationOffset, gameobject);
// you wouldn't need this anymore since you won't change the spoition manually
// NetworkServer.SpawnWithClientAuthority(playerGun, connectionToClient);
NetworkServer.Spawn(playerGun);
// Tell the clients to set the playerGun reference.
// You can pass it as GameObject since it has a NetworkIdentity
RpcSetGunOnClients(playerGun);
}
// Executed on all clients
[ClientRpc]
private void RpcSetGunOnClients (GameObject gun)
{
// set the reference
playerGun = gun;
// NetworkServer.Spawn or NetworkServer.SpawnWithClientAuthority doesn't apply
// the hierachy so on the Client we also have to make the gun a child of the player manually
// use the flag worldPositionStays to avoid a localPosition offset
playerGun.transform.SetParent(transform, false);
// just to be very sure you also could (re)set the local position and rotation offset later
playerGun.transform.localPosition = gunLocalPositionOffset;
playerGun.transform.localrotation = gunLocalRotationOffset;
}
Update1
For overcoming the problem with later connected clients refer to this answer.
It suggests using a [SyncVar] and override OnStartClient. An adopted version could look like
// Will be synched to the clients
// In our case we know the parent but need the reference to the GunObject
[SyncVar] public NetworkInstanceId playerGunNetId;
[Command]
public void CmdGetGun()
{
Debug.Log("SPAWNING A GUN");
playerGun = (GameObject)Instantiate(gunPrefab, gunLocalPositionOffset, gunLocalRotationOffset, transform);
// Set the playerGunNetId on the Server
// SyncVar will set on all clients including
// newly connected clients
playerGunNetId = playerGun.GetComponent<NetworkIdentity>().netId;
NetworkServer.Spawn(playerGun);
RpcSetGunOnClients(playerGun);
}
public override void OnStartClient()
{
// When we are spawned on the client,
// find the gun object using its ID,
// and set it to be our child
GameObject gunObject = ClientScene.FindLocalObject(playerGunNetId);
gunObject.transform.SetParent(transform, false);
gunObject.transform.localPosition = gunLocalPositionOffset;
gunObject.transform.localrotation = gunLocalRotationOffset;
}
(Note you still need the ClientRpc for the already connected clients.)
Update2
The method from Update1 might not work because playetGunNetId might not be set yet when OnStartPlayer is executed.
To overcome this you can use a hook method for our SyncVar. Something like
// The null reference might somehow still come from
// ClientScene.FindLocalObject
[SyncVar(hook = "OnGunIdChanged"]
private NetworkInstanceId playerGunNetId;
// This method is executed on all clients when the playerGunNetId value changes.
// Works when clients are already connected and also for new connections
private void OnGunIdChanged (NetworkInstanceId newValue)
{
// Honestly I'm never sure if this is needed or not...
// but in worst case it's just redundant
playerGunNetId = newValue;
GameObject gunObject = ClientScene.FindLocalObject(playerGunNetId);
gunObject.transform.SetParent(transform, false);
gunObject.transform.localPosition = gunLocalPositionOffset;
gunObject.transform.localrotation = gunLocalRotationOffset;
}
Now you shouldn't even need the ClientRpc anymore since everything is handled by the hook for both, already connected and newly connected clients.
I want to animation of a static object which is in environment of unity multiplayer, are need to happen when player click on it. and also the animation would show every player who are connected to the server
public class AnimationCtr : NetworkBehaviour
{
Animator anim;
bool canSpawn;
int count;
[SyncVar]
bool flag;
// Use this for initialization
void Start ()
{
anim = GetComponent<Animator>();
flag = false;
}
private void Update()
{
if (Input.GetMouseButtonDown(0))
{
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
RaycastHit hit;
if (Physics.Raycast(ray, out hit, 100.0f))
{
if (hit.transform.gameObject.name.Equals("door") && (!flag))
{
// anim.SetInteger("btnCount", 2);
Debug.Log(hit.transform.gameObject.name);
anim.SetInteger("btnCount", 2);
flag = true;
}
else if (hit.transform.gameObject.name.Equals("door")&& (flag))
{
anim.SetInteger("btnCount", 3);
flag = false;
}
}
}
}
}
One problem is that you cannot set a [Synvar] on the client side.
From the [SyncVar] docu:
These variables will have their values sychronized from the server to clients
I would also only let the server decide whether the current value of flag is true or false.
public class AnimationCtr : NetworkBehaviour
{
Animator anim;
bool canSpawn;
int count;
// no need for sync ar since flag is only needed on the server
bool flag;
// Use this for initialization
void Start ()
{
anim = GetComponent<Animator>();
flag = false;
}
private void Update()
{
if (Input.GetMouseButtonDown(0))
{
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
RaycastHit hit;
if (!Physics.Raycast(ray, out hit, 100.0f)) return
if (!hit.transform.gameObject.name.Equals("door")) return;
// only check if it is a door
// Let the server handel the rest
CmdTriggerDoor();
}
}
// Command is called by clients but only executed on the server
[Command]
private void CmdTriggerDoor()
{
// this will only happen on the Server
// set the values on the server itself
// get countValue depending on flag on the server
var countValue = flag? 3 : 2;
// set animation on the server
anim.SetInteger("btnCount", countValue);
// invert the flag on the server
flag = !flag;
//Now send the value to all clients
// no need to sync the flag as the decicion is made only by server
RpcSetBtnCount(countValue);
}
// ClientRpc is called by the server but executed on all clients
[ClientRpc]
private void RpcSetBtnCount(int countValue)
{
// This will happen on ALL clients
// Skip the server since we already did it for him it the Command
// For the special case if you are host -> server + client at the same time
if (isServer) return;
// set the value on the client
anim.SetInteger("btnCount", countValue);
}
}
Though, if you only want to animate if the door is opened or closed, I'ld rather use a bool parameter e.g. isOpen in the animator and simply change that instead (animator.SetBool()). And have two transitions between the two states:
Default -> State_Closed -- if "isOpen"==true --> State_Open
<-- if "isOpen"==false --
In your anmations you just save 1 single keyframe with the target position. Than on the transition you can set the transition duration -> how long it takes the door to open/close (they could even be different).
I was learning multiplayer game implementation through Unity Multiplayer system.
So I come across this really good tutorials written for beginners:
Introduction to a Simple Multiplayer Example
From this tutorial, I can't able to understand this page content:
Death and Respawning
Through this code, in tutorial they are talking about our player will respawn (player will transform at 0 position and health will be 100) and player can start fighting again.
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.Networking;
using System.Collections;
public class Health : NetworkBehaviour {
public const int maxHealth = 100;
[SyncVar(hook = "OnChangeHealth")]
public int currentHealth = maxHealth;
public RectTransform healthBar;
public void TakeDamage(int amount)
{
if (!isServer)
return;
currentHealth -= amount;
if (currentHealth <= 0)
{
currentHealth = maxHealth;
// called on the Server, but invoked on the Clients
RpcRespawn();
}
}
void OnChangeHealth (int currentHealth )
{
healthBar.sizeDelta = new Vector2(currentHealth , healthBar.sizeDelta.y);
}
[ClientRpc]
void RpcRespawn()
{
if (isLocalPlayer)
{
// move back to zero location
transform.position = Vector3.zero;
}
}
}
As per my thinking -> All clients are executing ClientRPC so all devices local players will move at the spawn position and health get full.
As per this tutorial -> only own player's spawn position and health get updated.
So why this thing is happening that I can't able to understand?
Actually RPC get called on all clients then all clients should require to move at start position and get full health.
Please give me some explanation in this.
As you can see on this image, you need to think that network systems aren't a "shared" room, they actually works as copying everything about the others on your own room.
Knowing that, now you can understand that if you send the Rpc from your Player 1, that Rpc will be executed on your Player1, and the Player 1- Copy on Player's 2 room.
Cause as you can read on the docs, the [ClientRpc] attribute:
is an attribute that can be put on methods of NetworkBehaviour
classes to allow them to be invoked on clients from a server.
So as Player 1 room is the host (server+client) an Player 2 is client, it will be executed on 2 room's, but evaluated on the first one.
Edit: That means that when, for example, Player 1 will die, it will call the RPC function, and (cause it's an RPC) his copy (Player1-Copy) on Room 2, will do the same.
As per my understanding, respawnprefab has to be instantiated on ServerRpc and the actual respawning logic (changing the transform of the player) should be given in ClientRpc.
private void Die()
{
isDead = true;
//Disable Components
for (int i = 0; i < disableOnDeath.Length; i++)
{
disableOnDeath[i].enabled = false;
}
Collider _col = GetComponent<Collider>();
if (_col != null)
{
_col.enabled = false;
}
Debug.Log(transform.name + " is Dead");
//call respawn method
StartCoroutine(Respawn(transform.name));
}
private IEnumerator Respawn(string _playerID)
{
yield return new WaitForSeconds(GameManager.instance.matchSettings.respawnDelay);
SpawningServerRpc(_playerID);
}
[ServerRpc(RequireOwnership = false)]
void SpawningServerRpc(string _playerID)
{
Transform spawnedPointTransform = Instantiate(spawnPoint);
spawnedPointTransform.GetComponent<NetworkObject>().Spawn(true);
SpawnClientRpc(_playerID);
}
[ClientRpc]
void SpawnClientRpc(string _playerID)
{
Player _player = GameManager.GetPlayer(_playerID);
Debug.Log("The player who dies is:" + _player.transform.name);
_player.transform.position = spawnPoint.position;
Debug.Log("I am Respawned" + _player.name + "Position:" + _player.transform.position);
SetDefaults();
}
Hope this helps you. Cheers👍
I am very new to Unity. I am working on a simple multiplayer game.
Problem I am facing is I am not able to sync the sprite renderer's flip state when we press left and right arrow keys.
Below is the code I tried.
[SerializeField]
private SpriteRenderer spriteRenderer;
[Command]
void CmdProvideFlipStateToServer(bool state)
{
spriteRenderer.flipX = state;
}
[ClientRpc]
void RpcSendFlipState(bool state)
{
CmdProvideFlipStateToServer(state);
}
private void Flip()
{
facingRight = !facingRight;
if(isClient){
spriteRenderer.flipX = !facingRight;
}
if(isLocalPlayer){
RpcSendFlipState(spriteRenderer.flipX);
}
}
I'm assuming what you want is:
In any moment the function Flip() is called on a Client.
=> his local Sprite is changed and you want to synchronize this over a server to the other clients.
If this is the case you re using Command and ClientRpc the wrong way:
Command: is invoked on the Client but only executed on the Server
ClientRpc: is invoked on the Server but only executed on (ALL) clients
=> your script should rather look somehow like
[SerializeField]
private SpriteRenderer spriteRenderer;
// invoked by clients but executed on the server only
[Command]
void CmdProvideFlipStateToServer(bool state)
{
// make the change local on the server
spriteRenderer.flipX = state;
// forward the change also to all clients
RpcSendFlipState(state)
}
// invoked by the server only but executed on ALL clients
[ClientRpc]
void RpcSendFlipState(bool state)
{
// skip this function on the LocalPlayer
// because he is the one who originally invoked this
if(isLocalPlayer) return;
//make the change local on all clients
spriteRenderer.flipX = state;
}
// Client makes sure this function is only executed on clients
// If called on the server it will throw an warning
// https://docs.unity3d.com/ScriptReference/Networking.ClientAttribute.html
[Client]
private void Flip()
{
//Only go on for the LocalPlayer
if(!isLocalPlayer) return;
// make the change local on this client
facingRight = !facingRight;
spriteRenderer.flipX = !facingRight;
// invoke the change on the Server as you already named the function
CmdProvideFlipStateToServer(spriteRenderer.flipX);
}
I am making a simple LAN game to just get my bearings in Unity Networking. All it is supposed to do is when a player clicks on a square in a grid, it changes it blue. My issue is when the LAN Host clicks on a square, it only updates locally and doesn't update the clients. When the client clicks on a square, it updates locally and the LAN host gets updated, but other clients do not get updated. All of my grid pieces have a network identity attached to them
Any ideas?
Heres the code:
using UnityEngine;
using System.Collections;
using UnityEngine.Networking;
public class Player_Paint : NetworkBehaviour {
[SyncVar]GameObject syncGridPiece;
GameObject gridPiece;
void Update () {
Paint();
TransmitGridColours();
}
void Paint(){
if(isLocalPlayer && Input.GetMouseButtonDown(0)){
RaycastHit2D hit = Physics2D.Raycast(Camera.main.ScreenToWorldPoint(Input.mousePosition), Vector2.zero);
if(hit.collider != null){
print(GameObject.Find (hit.transform.name));
gridPiece = hit.collider.transform.gameObject;
gridPiece.GetComponent<SpriteRenderer>().color = Color.blue;
}
}
}
[Command]
void CmdProvideGridColourToServer(GameObject gridPiece){
if(gridPiece){
syncGridPiece = gridPiece;
syncGridPiece.GetComponent<SpriteRenderer>().color = Color.blue;
}
}
[Client]
void TransmitGridColours(){
if(isLocalPlayer){
CmdProvideGridColourToServer(gridPiece);
}
}
}
You need to add a hook to your syncGridPiece syncvar.
In the hook function set the color to blue, like you did in the Command.
The hook will be called on remote players.
Also I don't think you can use a GameOject as a syncvar.
You just need to send it's ID.
[SyncVar(hook=UpdateGridPiece)] int syncGridPiece
Maybe an RPC would be more adapted to what you want to do.
From what I understand syncGridPiece could be any square so you shouldn't pass all updates in the same message.
Just send that X players clicked X square with an RPC.
Look at the damage example : https://docs.unity3d.com/Manual/UNetActions.html
Figured it out. Had to use ClientRpc. Code below.
using UnityEngine;
using System.Collections;
using UnityEngine.Networking;
public class Player_Paint : NetworkBehaviour {
GameObject gridPiece;
void Start () {
}
// Update is called once per frame
void Update () {
Paint();
}
void Paint(){
if(isLocalPlayer && Input.GetMouseButtonDown(0)){
RaycastHit2D hit = Physics2D.Raycast(Camera.main.ScreenToWorldPoint(Input.mousePosition), Vector2.zero);
if(hit.collider != null){
print(GameObject.Find (hit.transform.name));
gridPiece = hit.collider.transform.gameObject;
print("SettingColor");
CmdProvideGridColourToServer(gridPiece,Color.blue);
}
}
}
[Command]
void CmdProvideGridColourToServer(GameObject gridPiece,Color color){
RpcTransmitGridColours(gridPiece,color);
}
[ClientRpc]
void RpcTransmitGridColours(GameObject gridPiece, Color color){
gridPiece.GetComponent<SpriteRenderer>().color = color;
}
}