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];
}
}
}
Related
So I'm trying to implement shooting in a multiplayer game. I want to spawn a bullet out of a weapon. On the server side it works, but as I shoot as a client it says [Netcode] Behaviour index was out of bounds. Did you mess up the order of your NetworkBehaviours?.
The bullet is a prefab which I included in NetworPrefabs. The prefab also has a Network Object component.
This is where I call shoot()
if(shot == false && transform.GetChild(0).GetComponent<Weapon>().bulletInMag > 0 && reloading == false)
{
shot = true;
StartCoroutine(shoot());
}
This is what shoot() does
IEnumerator shoot()
{
firePoint = transform.GetChild(0).GetChild(0).transform;
weapon = transform.GetChild(0).gameObject.GetComponent<Weapon>();
if (weapon.threeShot)
{
for(int i=0; i<3; i++)
{
if(NetworkManager.Singleton.IsServer)
{
spawnBullet();
} else
{
spawnBulletServerRpc();
}
weapon.bulletInMag -= 1;
SharedFunctions.Instance.updateAmmoCount();
yield return new WaitForSeconds(0.2f);
}
yield return new WaitForSeconds(weapon.shootingIntervall);
shot = false;
}
else
{
if (NetworkManager.Singleton.IsServer)
{
spawnBullet();
}
else
{
spawnBulletServerRpc();
}
weapon.bulletInMag -= 1;
SharedFunctions.Instance.updateAmmoCount();
yield return new WaitForSeconds(weapon.shootingIntervall);
shot = false;
}
}
void spawnBullet()
{
GameObject bullet = Instantiate(prefabBullet, firePoint.position, transform.rotation);
bullet.GetComponent<Rigidbody2D>().AddForce(firePoint.up * weapon.bulletSpeed, ForceMode2D.Impulse);
bullet.GetComponent<NetworkObject>().Spawn();
}
[ServerRpc]
void spawnBulletServerRpc()
{
spawnBullet();
}
I'm thankful for any help.
I finally fixed the issue. The error [Netcode] Behaviour index was out of bounds. Did you mess up the order of your NetworkBehaviours? basically means that there is a mismatch between server and client representation of a network object.
At the beginning of my Player script I wrote:
public override void OnNetworkSpawn()
{
if (!IsOwner) Destroy(this);
}
which deletes the component if your not the owner.
So when I joined I had the script on client side but it wasn't there on server side. So when I tried to make an ServerRpc it didn't know where to send it. The same goes for ClientRpc.
A very helpful source was the Unity Mulitplayer Networking Discord: https://discord.gg/buMxnnPvTb
I am trying to build a multiplayer tile-based strategy game. From my understanding, I should create a "Player" GameObject, and then the map should be stored as a collection of GameObjects with a NetworkObject component to sync from client to server.
However, I am having issues syncing GameObjects between the host and the client. When a server is created, only the server should generate the map (map generation code marked). When a player connects, they should get their own game component.
namespace HelloWorld
{
public class HelloWorldManager : MonoBehaviour
{
public GameObject networkedPrefab;
void OnGUI()
{
GUILayout.BeginArea(new Rect(10, 10, 300, 300));
if (!NetworkManager.Singleton.IsClient && !NetworkManager.Singleton.IsServer)
{
if (GUILayout.Button("Client")) NetworkManager.Singleton.StartClient();
if (GUILayout.Button("Server")) {
// Generate 'map' here of 10 items
for (int i = 0; i < 10; i++) {
var go = Instantiate(networkedPrefab);
go.transform.position = new Vector2((i - 5), 0);
go.gameObject.name = "Circle-" + i;
}
NetworkManager.Singleton.StartServer();
}
}
else
{
StatusLabels();
SubmitNewPosition(); // Adds button for player to randomly move their position
}
GUILayout.EndArea();
}
static void SubmitNewPosition()
{
if (GUILayout.Button(NetworkManager.Singleton.IsServer ? "Move" : "Request Position Change"))
{
if (NetworkManager.Singleton.IsServer && !NetworkManager.Singleton.IsClient )
{
foreach (ulong uid in NetworkManager.Singleton.ConnectedClientsIds)
NetworkManager.Singleton.SpawnManager.GetPlayerNetworkObject(uid).GetComponent<HelloWorldPlayer>().Move();
}
else
{
var playerObject = NetworkManager.Singleton.SpawnManager.GetLocalPlayerObject();
var player = playerObject.GetComponent<HelloWorldPlayer>();
player.Move();
}
}
}
}
}
Here is what I am getting with 0 clients (The map is represented by circles):
After adding a client, here is what I get on the server side:
... And on the client side I get nothing:
Here are the prefabs I used:
Any help to get things to sync across the server and client would be greatly appreciated.
As your objects are generated at runtime (rather than saved in the scene), you need to call Spawn() on the network object. Adding a NetworkObject is enough if your objects are saved in the scene but dynamically instantiating them at runtime requires a spawn call.
For your map:
for (int i = 0; i < 10; i++) {
var go = Instantiate(networkedPrefab);
go.transform.position = new Vector2((i - 5), 0);
go.gameObject.name = "Circle-" + i;
go.GetComponent<NetworkObject>().Spawn();
}
As an aside, I think you need to start the server before doing the spawn.
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 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;
}