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);
}
Related
I am trying to play and stop a particle system which is a child of a player prefab. So far I have gotten the host client's thrust particle effect to update across all clients but I haven't been able to figure out a non-host client. I would like it if every time a player presses the thruster button the game would display the thruster effect across all clients and when they stop the particle system stops and other clients would see it as well.
So far my code is as follows:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Mirror;
public class PlayerMovement : NetworkBehaviour
{
public float thrustPower = 300f;
Rigidbody2D m_playerRb;
bool m_thrustOn = false;
public ParticleSystem m_playerPs;
[SyncVar(hook = nameof(SetThrusters))]
bool m_thrusterState = false;
private void Start()
{
m_playerRb = GetComponent<Rigidbody2D>();
m_playerPs.Stop();
}
private void Update()
{
if (isLocalPlayer)
{
MovePlayer();
m_thrusterState = m_thrustOn;
}
}
private void FixedUpdate()
{
if (m_thrustOn)
{
m_playerRb.AddForce(gameObject.transform.up * thrustPower * Time.fixedDeltaTime, ForceMode2D.Force);
}
}
private void SetThrusters(bool oldVal, bool newVal)
{
if (newVal) m_playerPs.Play();
else if (newVal == false) m_playerPs.Stop();
}
private void MovePlayer()
{
m_thrustOn = Input.GetKey(KeyCode.W) ? true : false;
float horizontalInput = Input.GetAxisRaw("Horizontal");
transform.Rotate(new Vector3(0, 0, horizontalInput * -180 * Time.deltaTime));
}
}
I cant really find anything online about Mirror in terms of particle systems. Let me know if you need any more info.
Thanks!
SyncVar always only go in the direction HOST -> CLIENT !
For the other way round you would need to use a Command
like e.g.
[Command]
void CmdSetSetThrusters(bool newVal)
{
// the hook would not be executed on the host => do it manually now
SetThrusters(m_thrusterState, newVal);
// set it on the host -> now via synVar automatically forwarded to others
m_thrusterState = newVal;
}
And then you will have to add a check whether you are the host or not like e.g.
if (isLocalPlayer)
{
MovePlayer();
if(isServer)
{
// afaik this you still have to call manually for yourself
// not sure here how SyncVar Hooks are handled on the host itself
SetThrusters(m_thrusterState, m_thrustOn);
// this is now synced to others
m_thrusterState = m_thrustOn;
}
else
{
// if your not Host send the command
CmdSetSetThrusters(m_thrustOn);
}
}
Personally for this reason for me the SyncVar with Hooks was always a little bit confusing and suspect.
You could as well simply implement both directions yourself using e.g.
[Command]
void CmdSetSetThrusters(bool newVal)
{
// set the value on yourself
SetThrusters(newVal);
// Then directly forward it to all clients
RpcSetThrusters(newVal);
}
[ClientRpc]
private void RpcSetThrusters(bool newValue)
{
// set the value on all clients
SetThrusters(newVal);
}
private void SetThrusters(bool newVal)
{
if (newVal)
{
m_playerPs.Play();
}
else
{
m_playerPs.Stop();
}
// if you are the HOST forward to all clients
if(isServer)
{
RpcSetThrusters(newVal);
}
}
and then
if (isLocalPlayer)
{
MovePlayer();
if(isServer)
{
SetThrusters(m_thrusterState);
}
else
{
// if your not Host send the command
CmdSetSetThrusters(m_thrustOn);
}
}
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 have a 2 player game. One user becomes server the other client.I am calling the method "buttonclicked" from both the sides when they are connected to add a tower.
When the method "buttonclicked" is called from the side which becomes the server, code executes well and an enemy is generated on both the side which is the server and the one which is the client.
However when the same code is run from the client the enemy is not generated on the side which becomes the server.
Below is the code
public void buttonclicked(int enemyID){
CmdgenerateEnemies(enemyID);
}
[Command]
public void CmdgenerateEnemies(int enemyID)
{
RpcgenerateEnemywithID(enemyID);
}
[ClientRpc]
public void RpcgenerateEnemywithID(int enemyID)
{
enemyID = 1; // hardcoded for testing
for(int i = 0; i < enemiesArray.Length; i++)
{
GameObject enemy = enemiesArray [i];
if(enemy.GetComponent<EnemyScript>().enemyId == enemyID)
{
GameObject einstance = Instantiate(enemy, spawnPoint1);
einstance.transform.parent = canvas.transform;
einstance.GetComponent<splineMove>().pathContainer = topPath[1];
}
}
}
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;
}
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;
}
}