Multiplayer in Unity - c#

Below is the code for movement of player object and it works perfectly fine. But in the TellClientsToMoveClientRpc why are we doing transform.position as it refers to the the current gameobject but not every object moves, only the one which called the above Rpc moves which is desirable. But why all other gameobject do not move ?
using UnityEngine;
namespace HelloWorld
{
public class HelloWorldPlayer : NetworkBehaviour
{
private const float speed = 20f;
private void Update()
{
if (!IsOwner)
return;
PlayerMove();
}
void PlayerMove()
{
Vector3 velocity = new(Input.GetAxis("Horizontal"), 0f, Input.GetAxis("Vertical"));
if(velocity.sqrMagnitude >= 1)
{
TellServerToMoveClientServerRpc(velocity, NetworkManager.Singleton.LocalClientId);
}
}
[ServerRpc]
void TellServerToMoveClientServerRpc(Vector3 velocity, ulong clientId)
{
TellClientsToMoveClientRpc(velocity, clientId);
}
[ClientRpc]
void TellClientsToMoveClientRpc(Vector3 velocity, ulong clientId)
{
transform.position += speed * Time.deltaTime * velocity;
}
}
}```

A multiplayer game like this means that there are multiple clients running the same program (the game), meaning that there is a copy of all of the same game objects on each client's instance of the game.
This means that the game object you attached your HelloWorldPlayer script on is present on each client.
When you call TellServerToMoveClientServerRpc from a client, it will execute that function on the same game object the client called the function from, but on the server. This is because you added a [ServerRPC].
Now that the server has control over what's going to happen next, it calls TellClientsToMoveClientRpc. Now this time, since you added a [ClientRpc], that function will be called on the same game object the original client called the function from, but now on every single client that's connected to your server.
Since it's always the same game object calling the function (just on different instances of the game, running on different computers), not all game objects move: only the one that initially called the TellServerToMoveClientServerRpc function.

Related

Why is my player's movement speed changing? (using Fishnet with Unity)

I'm fairly new to Unity, and I've begun learning how to use Fishnet networking. I've created a basic player movement script that syncs player position far faster than a Network Transform would. But I'm running into a bizarre problem I don't know how to solve.
In my scene, I have a Network Manager which, upon connection, spawns my Player prefab--a simple sprite with a player script and a network object. I haven't added a network transform, since I'll be syncing each player's position manually to reduce delay between clients. Here's the player script:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using FishNet.Object;
public class Player : NetworkBehaviour
{
private void Update()
{
if (IsOwner) //only the client that owns this object will run this code
{
//get input, send it to server
float horizontalInput = Input.GetAxisRaw("Horizontal");
float verticalInput = Input.GetAxisRaw("Vertical");
RpcMoveCharacter(horizontalInput, verticalInput);
}
//since this is an observers rpc, only the server will call it
RpcSendCharacterPosition(transform.position.x, transform.position.y);
}
[ServerRpc]
public void RpcMoveCharacter(float x, float y)
{
//change the position of the server's instance of the player
transform.Translate(x * 10f * Time.deltaTime * Vector3.right);
transform.Translate(y * 10f * Time.deltaTime * Vector3.up);
}
[ObserversRpc]
public void RpcSendCharacterPosition(float x, float y)
{
if (IsClientOnly)
{
//ensure clients' instance of the player match the server's' position
transform.position = new Vector2(x, y);
}
}
}
The script works perfectly...except for one problem: the movement speed of the player isn't consistent for both players. The issues only occur when I build and run my game, then have the two versions of the game connect.
When either player is a host (server + client) their player object moves at medium speed on both screens. This is the intended speed.
When the version of my game running from my unity editor window is only a client, the player moves at fast speed on both screen--many times faster than intended.
When the version of my game I created using 'build and run' is only a client, the player moves at slow speed on both screens--many times slower than intended.
I've tested everything I can think of. One test I did was to prevent the network manager from spawning the player prefab, place the player object in scene ahead of time, and convert this:
private void Update()
{
if (IsOwner)
{
float horizontalInput = Input.GetAxisRaw("Horizontal");
float verticalInput = Input.GetAxisRaw("Vertical");
RpcMoveCharacter(horizontalInput, verticalInput);
}
RpcSendCharacterPosition(transform.position.x, transform.position.y);
}
[ServerRpc]
to this:
private void Update()
{
//now anyone can control the player object
float horizontalInput = Input.GetAxisRaw("Horizontal");
float verticalInput = Input.GetAxisRaw("Vertical");
RpcMoveCharacter(horizontalInput, verticalInput);
RpcSendCharacterPosition(transform.position.x, transform.position.y);
}
//same effect as note above
[ServerRpc (RequireOwnership = false)]
in order to see if there was something about the player spawning feature was bugged. My changes had zero effect whatsoever--nothing changed at all. If my editor was a client only it still moved the player too quickly, and if my build was a client only it still moved the player too slowly.
Another thing I tried was to make a brand new project in case I had toggled a setting weirdly or something in the last one. Once I had created a new project, all I did was import fishnet, add fishnet's default NetworkManager object to my scene, create a simple prefab called player, add a network object and the original player script to the player prefab, set the network manager to spawn the player prefab, and tried again. No luck--everything was exactly the same.
Any ideas? I'm super stuck here--I don't know what else to try, since everything in the code/scene seems to be working perfectly. I can't figure out why my build would be behaving differently than my editor's play mode, regardless of which is the server (or host) and which is the client only.
Thanks!
Creator of FishNet here. The only possible way you can update a transforms data quicker than the built-in NetworkTransform is by sending a RPC and snapping the transform every time data arrives.
Otherwise setting the interpolation and interval to 1 on NetworkTransform will send every tick, and smooth over only 1 tick. It's literally the fastest possible way to update transforms without snapping them.
If you want the entire network to move faster then add a TimeManager component to your network manager and increase the tick rate.
So I don't really know how exactly this Fishnet works.
But as said in general in any networking you can never rely on
All your devices running at the same FPS (frames per second)
Your networked messages arriving at the server/other clients immediately
Your networked messages arriving at the server/other clients in the exact same intervals as you ended them
So what I would rather do as mentioned is
First of all do not send network messages every frame but rather some fixed time intervals (like e.g. often used every 0.2 seconds)
Rather handle all local movement locally and immediately
It would be very bad for the UX if you send your user inputs to the server and have to wait until it is applied by receiving back the resulting position. This causes a 2-times network delay which would be extremely uncanny for the local user.
Instead of the delta rather synchronize the resulting position value.
This way you can be sure all players are in sync with the actual resulting positions and it works immediately also for players who joined the session later or potentially missed a few input messages due to network lag.
So I would do something like
public class Player : NetworkBehaviour
{
// Interval in seconds how often to send your position to the server/clients
[SerializeField] private float sendInterval = 0.2f;
// How fast you can move in units per second
[SerializeField] private float moveSpeed = 10f;
// Use this to adjust your input sensitivities
[SerializeField] [Min(0)] private float inputSensitivityX = 1f;
[SerializeField] [Min(0)] private float inputSensitivityY = 1f;
// Might have to play a bit with this value to make smooth interpolation faster or slower
// 5 is an arbitrary value but works quite good from experience
// depends on your sendInterval and movespeed as well
[SerializeField] privte float interpolation = 5f;
// keeps track of passed time
private float sendTimer;
private Vector2 receivedTargetPosition;
private void Start()
{
if(!IsOwner)
{
receivedTargetPosition = transform.position;
}
}
private void Update()
{
//only the client that owns this object will run this code
if (IsOwner)
{
//get input
var horizontalInput = Input.GetAxisRaw("Horizontal");
var verticalInput = Input.GetAxisRaw("Vertical");
var input = new Vector2(horizontalInput * inputSensitivityX, verticalInput * inputSensitivityY);
// Makes sure that you always have maximum 1 magnitude for the input
input = Vector2.ClampMagnitude(input, 1f);
// use the rotation to already rotate this vector from local into world space
input = trasform.rotation * input;
// Here you want the deltaTime of THIS DEVICE
var movement = moveSpeed * Time.deltaTime * input;
// Move your player LOCALLY
transform.position += (Vector3)movement;
}
// If you are not the owner you rather apply the received position
else
{
// I would e.g. smoothly interpolate somewhat like
transform.position = Vector3.Lerp(transform.position, receivedTargetPosition, interpolation * Time.deltaTime);
}
// Check if next send time interval has passed
sendTimer += Time.deltaTime;
if(sendTimer >= sendInterval)
{
sendTimer = 0;
if(IsServer)
{
RpcSendPositionToClients(transform.position.x, transform.position.y);
}
else
{
RpcSendPositionToServer(transform.position.x, transform.position.y);
}
}
}
[ServerRpc]
public void RpcSendPositionToServer(float x, float y)
{
// just in case
// the owner already gets its position in Update so nothing to do
if(IsOwner) return;
//change the position of the server's instance of the player
receivedTargetPosition = new Vector2(x, y);
}
[ClientRpc]
public void RpcSendPositionToClients(float x, float y)
{
// Owner and server already know the positions
if(IsOwner || IsServer) return;
receivedTargetPosition = new Vector2(x, y);
}
}

Unity: Smooth camera movement on UI button click

I'm trying to make the camera move to a certain position after clicking a UI button.
For the positioning, I use empty game objects' (CameraPositionStart and CameraPositionFinish) coordinates.
For some reason, the camera just teleports to the position needed instead of smoothly moving to it.
Here's my code:
using UnityEngine;
using UnityEngine.UI;
public class CameraMover : MonoBehaviour
{
public float speed;
public Button ButtonStartNew;
public GameObject CameraPositionStart;
public GameObject CameraPositionFinish;
private void Update()
{
ButtonStartNew.onClick.AddListener(moveCamera);
}
public void moveCamera()
{
transform.position = Vector3.Lerp(transform.position, CameraPositionFinish.transform.position, Time.deltaTime * speed);
}
}
This behavior is likely caused because of two reasons.
The method Update is executed once every frame. Here every frame you are adding an additional call that should be made when you click the button on the UI. The result of this, I suspect, is that when the several hundred calls to moveCamera all happen at the same time, and despite moveCamera only moving the transform by Lerp(Time.DeltaTime * Speed) -- the object moves closer(since you're lerping) instantly because you're calling it a bunch.
private void Update()
{
ButtonStartNew.onClick.AddListener(moveCamera);
}
Consider only adding one call to moveCamera to the on click event. We can do this in Start or Awake since they're only called once.
private void Start()
{
ButtonStartNew.onClick.AddListener(moveCamera);
}
However this along does not solve the problem. Because if we do that moveCamera will only be called once and the result would be the camera would move very slightly toward the target and stop.
Consider implementing a Coroutine that will move the transform over time(every frame) until it has reached the target.
private void moveCamera()
{
// A coroutine runs every frame until it stops returning values
StartCoroutine(MoveCamera);
}
private IEnumerator MoveCamera()
{
// check the distance and see if we still need to move towards the destination ​
while(Vector3.Distance(transform.position, CameraPositionFinish.transform.position) > 1.0f)
​{
transform.position = Vector3.Lerp(transform.position, CameraPositionFinish.transform.position, Time.deltaTime * speed);
// Return nothing meaningful and wait until next frame​
yield return null;
}
}

How can I get an instance of a gameObject from the Server to the client using Unity's Unet?

I am making a game using UNET and making it for the HoloLens. Basically, my game is pretty simple, players join the session and then they can spawn a ship to control with an Xbox controller. I have the move mechanics and even a basic shoot function working. What I really want to add next is something like a homing missile. I have the code for the homing missile and it works fine on the host but, I can't figure out how to get my missile object information back to the player if that makes sense. I will show you what I mean.
My Command:
[Command]
public void CmdFireMissle()
{
Vector3 bulletDir = planeToSpawn.transform.forward;
Vector3 bulletPos = planeToSpawn.transform.position + (bulletDir * (0.01f + 3 * planeToSpawn.transform.localScale.x));
// The bullet needs to be transformed relative to the shared anchor.
missleToSpawn = Instantiate(missle, sharedWorldAnchorTransform.InverseTransformPoint(bulletPos), Quaternion.LookRotation(bulletDir));
missleToSpawn.transform.localScale = planeToSpawn.transform.localScale * 0.1f;
missleToSpawn.GetComponentInChildren<Rigidbody>().velocity = bulletDir * 1.0f;
NetworkServer.Spawn(missleToSpawn);
RpcPlayBulletAudio(planeToSpawn);
// Clean up the bullet in 15 seconds.
Destroy(missleToSpawn, 15.0f);
}
Update Method:
void Update()
{
if (controllerInput.GetButtonDown(ControllerButton.A) && planeSpawned )
{
if (isLocalPlayer)
{
bool raycastHit = Physics.Raycast(transform.position, direction: transform.forward, hitInfo: out hit, maxDistance: range);
if (raycastHit && hit.transform.gameObject.CompareTag("Plane"))
{
enemyShip = hit.transform.gameObject;
CmdFireMissle();
// I need to get a reference to my missleToSpawn object on my client
// So I can use it here in this coroutine
StartCoroutine(MoveTo(missleToSpawn, misslePos, enemyShip, 1));
}
}
}
}
It works fine on the host since it has the reference when it calls the Command I just can't figure out how to get the reference for the client.
#Dtb49 you can attach a script to the "missle" object, while this script is used to get the spawn object's NetworkInstanceId once it's spawned.
public class YourScript: NetworkBehaviour {
public static NetworkInstanceId nid;
void Start(){
nid = this.netId;
}
}
In other script, you can get the object reference by using NetworkServer.objects[NetworkInstanceId].gameObject like:
if (NetworkServer.objects.ContainsKey (YourScript.nid)) {
Debug.Log (NetworkServer.objects[YourScript.nid].gameObject);
}

ClientRpc Function not being carried out on all clients in Unity3d c#

Currently I am working on a networked 2d platformer game. I have a script that is supposed to instantiate the players jetpack called JetpackManager. However when the player is spawned into the scene the code only spawns a jetpack into the hosts scene and not into the clients' scenes. This results in only the hosts scene working properly while all the players in the clients' scenes have no jetpacks. This is my code for the JetpackManager:
using UnityEngine;
using UnityEngine.Networking;
public class JetpackManager : NetworkBehaviour {
[SerializeField]
private Jetpack[] jetpacks;
[SerializeField]
private Transform jetPackHold;
private PlayerController playerController;
private Jetpack currentJetpack;
void Start(){
playerController = GetComponent<PlayerController> ();
if (isLocalPlayer) {
CmdEquipJetpack (0);
}
}
[Command]
void CmdEquipJetpack(int jetpackNumber){
RpcEquipJetpack (jetpackNumber);
}
[ClientRpc]
void RpcEquipJetpack(int jetpackNumber){
if (currentJetpack != null) {
Destroy (currentJetpack.gameObject);
}
currentJetpack = Instantiate (jetpacks[jetpackNumber], jetPackHold.position, jetPackHold.rotation);
currentJetpack.transform.SetParent (jetPackHold);
playerController.InitialiseJetpackVariables (currentJetpack);
}
}
So essentially my problem is that the code within the RpcEquipJetpack Function is for some reason only being called on the host and not on any of the clients.
You should instantiate Network object with network class
Network.Instantiate
Network instantiate a prefab.
The given prefab will be instanted on all clients in the game.
Synchronization is automatically set up so there is no extra work
involved. The position, rotation and network group number are given as
parameters. Note that in the example below there must be something set
to the playerPrefab in the Editor. You can read more about
instantiations in the object reference Object.Instantiate.

How to attach the camera to a player object instantiated by HLAPI Network Manager?

In short, I have a very simple multiplayer game. It's the Roll A Ball game (Unity3D tutorial). So right now I have the players etc spawning perfectly and everyone is able to control their own balls perfectly fine.
But here's the problem: I've got a default Main Camera. Since it's only the local player itself that needs to see it, I figured there's no point in trying to spawn a seperate camera for each player on the server.
However, to make the camera follow the player, I need to attach it the player gameobject. Obviously I can't attach it to the player prefab as it's a clone the camera needs to follow. But since the player is being spawned by the Network Manager component, I have no idea on how to refer to this clone.
What I've tried myself:
public class CameraController : NetworkManager
{
public GameObject playerPrefab;
public Transform target;
private Vector3 offset;
public override void OnServerAddPlayer(NetworkConnection conn, short playerControllerId)
{
GameObject player = (GameObject)Instantiate(playerPrefab, new Vector3(0, 0.5f, 0), Quaternion.identity);
target = player.transform;
NetworkServer.AddPlayerForConnection(conn, player, playerControllerId);
}
void Start()
{
offset = transform.position - target.position;
}
void LateUpdate()
{
transform.position = transform.position + offset;
}
}
But this resulted in:
Which I find extremely odd since as you can clearly see, there's no NetworkIdentity component on the NetworkManager object. I've been trying A LOT of things for the past 4 hours now and I just can't do it. So now I'm hoping you guys can help me out.
Edit: This is how the Network Manager normally spawns a player. As you can see, there's no code for it:
I had the same issue and figured out the followig solution. Seems like you already got a solution, but maybe it is interesting to share some possible ways for other people in the same situation.
This is a way to do it without a camera attached to the prefabs.
I'm using a NetworkManager to instantiate Player-prefabs. (Same as you)
I solved the problem of finding references to the clone objects by letting the clones tell the camera, who they are (or which transform belongs to them).
The Player has the following script:
using UnityEngine;
using UnityEngine.Networking;
using System.Collections;
public class PlayerController : NetworkBehaviour {
public override void OnStartLocalPlayer()
{
GetComponent<MeshRenderer>().material.color = Color.blue;
Camera.main.GetComponent<CameraFollow>().target=transform; //Fix camera on "me"
}
void Update ()
{
if (!isLocalPlayer)
{
return;
}
var x = Input.GetAxis("Horizontal") * Time.deltaTime * 150.0f;
var z = Input.GetAxis ("Vertical") * Time.deltaTime * 3.0f;
transform.Rotate (0, x, 0);
transform.Translate (0,0,z);
}
}
On my default main Camera (there is no camera attached to the player prefab, just the default camera) I put the following script on. It takes a target which I initialised with the prefab using the inspector.
using UnityEngine;
using System.Collections;
public class CameraFollow : MonoBehaviour {
public Transform target; //what to follow
public float smoothing = 5f; //camera speed
Vector3 offset;
void Start()
{
offset = transform.position - target.position;
}
void FixedUpdate()
{
Vector3 targetCamPos = target.position + offset;
transform.position = Vector3.Lerp (transform.position, targetCamPos,smoothing * Time.deltaTime);
}
}
After starting the game, each clone tells the camera who he is, so the target changes to the individual clients clone with this line from the Player's Script:
Camera.main.GetComponent<CameraFollow>().target=transform; //Fix camera on "me"
This way you don't need to create one camera per instance of player-prefabs (I'm not sure if this makes big differences in performance) and you don't have to deactivate all cameras which don't belong to your client.
If you host the game in the editor you can see that there is just 1 camera instead of one camera per connected client (like when you attach it to the prefab).
I think this is a good use of this method, you can use it to put things in it, which should be applied to the Local Player only.
public override void OnStartLocalPlayer()
{
}
I tried by starting the game in the editor and in a build and it seems to work well.
I would add a camera to the prefab and then write a player script like this:
using UnityEngine.Networking;
public class Player : NetworkBehaviour
{
public Camera camera;
void Awake()
{
if(!isLocalPlayer)
{
camera.enabled = false;
}
}
}
I've not really worked with networking but what if you do this after you spawn the local player
Camera.main.transfor.SetParent(the transform of the local player here);
As I understand the problem each separate instance of the game has a main camera.
Thanks to Rafiwui's point into the right direction, I've finally managed to get it working. All I had to do was adept his code a bit. The end result was:
public Camera camera;
void Awake()
{
camera.enabled = false;
}
public override void OnStartLocalPlayer()
{
camera.enabled = true;
}
Thanks A LOT to you all for helping me out! This has been quite a day for me.

Categories

Resources