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.
Related
I am trying to tag two PUN instantiated game objects with "Player1" and "Player2" tags by looking at their PhotonView ViewIDs through an RPC call. I am able to successfully tag the player 1 game object with the player 1 tag, however, no matter what I try, I am unable to set the player2 tag to the player2 object. The code is networked and running on two Oculus Quest headsets. I can start the application on one Quest and it will assign the Player1 tag properly. However, when I start the application on the second Quest, it spawns a player gameobject, but does not tag the object with the Player2 tag even though the player 2 object's PhotonView matches the "2001" value. Below is the code that I am using to spawn in an XROrigin and a networked representation for each player.
using System;
using UnityEngine;
using Photon.Pun;
using Photon.Realtime;
using UnityEngine.XR.Interaction.Toolkit;
public class NetworkPlayerSpawner : MonoBehaviourPunCallbacks
{
public GameObject XROriginPrefab;
[HideInInspector]
public GameObject spawnedPlayerPrefab;
private PhotonView pv;
private void Start()
{
pv = GetComponent<PhotonView>();
}
private void Update()
{
// Debug.Log(PhotonNetwork.CurrentRoom.PlayerCount);
}
public override void OnJoinedRoom()
{
base.OnJoinedRoom();
var playerCount = PhotonNetwork.CurrentRoom.PlayerCount;
Debug.Log("The player count is: " + playerCount);
var teleportAreas = GameObject.FindGameObjectsWithTag("Floor");
//playerCount = 2;
if (playerCount == 1)
{
XROriginPrefab = Instantiate(XROriginPrefab, new Vector3(0, 2.36199999f, 3.78999996f),
new Quaternion(0, 0, 0, 1));
spawnedPlayerPrefab = PhotonNetwork.Instantiate("Network Player", transform.position, transform.rotation);
//spawnedPlayerPrefab.tag = "Player1";
foreach (GameObject go in teleportAreas)
{
go.AddComponent<TeleportationArea>();
}
}
else
{
XROriginPrefab = Instantiate(XROriginPrefab, new Vector3(-10.3859997f,1.60699999f,10.6400003f),
new Quaternion(0,0,0,1));
spawnedPlayerPrefab = PhotonNetwork.Instantiate("Network Player", transform.position, transform.rotation);
//spawnedPlayerPrefab.tag = "Player2";
//If teleport breaks again, I uncommented this line, so it should be commented out again. Should allow for teleport in User 2's room.
foreach (GameObject go in teleportAreas)
{
go.AddComponent<TeleportationArea>();
}
}
rpcCallTagAssign();
}
public override void OnPlayerEnteredRoom(Player newPlayer)
{
base.OnPlayerEnteredRoom(newPlayer);
Debug.Log("Remote Player Joined!");
rpcCallTagAssign();
}
public override void OnLeftRoom()
{
base.OnLeftRoom();
PhotonNetwork.Destroy(spawnedPlayerPrefab);
}
[PunRPC]
private void tagAssign()
{
if (spawnedPlayerPrefab.GetComponent<PhotonView>().ViewID==1001)
{
spawnedPlayerPrefab.tag = "Player1";
}
if (spawnedPlayerPrefab.GetComponent<PhotonView>().ViewID==2001)
{
spawnedPlayerPrefab.tag = "Player2";
}
}
private void rpcCallTagAssign()
{
pv.RPC("tagAssign", RpcTarget.AllViaServer);
}
}
I am new to networking with Photon, so any help with resolving this issue would be greatly appreciated. Thank you!
The code needs to run on each player (including the copies). The current code can only change the object you have a reference for (spawnedPlayerPrefab). The easiest way is to add an RPC function on the player. That RPC would get called for each instance of that player across the connected clients.
Script On Player.
[PunRPC]
private void AssignTag()
{
if (photonView.ViewID == 1001)
{
gameObject.tag = "Player1";
}
else if (photonView.ViewID == 2001)
{
gameObject.tag = "Player2";
}
}
In NetworkPlayerSpawner
spawnedPlayerPrefab = PhotonNetwork.Instantiate(...);
spawnedPlayerPrefab.GetComponent<PhotonView>().RPC("AssignTag", RpcTarget.AllBufferedViaServer);
The RPC is buffered so future clients entering after you will set your player copy to the correct tag as well (or vice versa).
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'm trying to develop a 3D multiplayer game with Unity. I don't know much about Photon, there are similar questions to the one I'm going to ask, but I still haven't found a solution. I will be glad if you help.
I have two scenes named "menu" and "game". In the menu scene, users make character selection after authenticating with playfab. After completing the selection, they connect to the lobby and set up a room and load the game scene. So far everything is successful. However, when the game scene is loaded, I have difficulty loading the characters selected by the users into the scene.
Here is the code file where I make the users choose their characters:
public class choose : MonoBehaviour {
private GameObject[] characterList;
private int index;
PhotonView PV;
private void Awake()
{
PV = GetComponent<PhotonView>();
}
private void Start()
{
index = PlayerPrefs.GetInt("CharacterSelected");
characterList = new GameObject[transform.childCount];
for (int i=0; i< transform.childCount; i++)
characterList[i] = transform.GetChild(i).gameObject;
foreach (GameObject go in characterList)
go.SetActive (false);
if (characterList [index])
characterList [index].SetActive (true);
}
public void ToggleLeft(){
characterList [index].SetActive (false);
index--;
if (index < 0)
index = characterList.Length - 1;
characterList [index].SetActive (true);
}
public void ToggleRight(){
characterList [index].SetActive (false);
index++;
if (index == characterList.Length)
index = 0;
characterList [index].SetActive (true);
}
public void kaydetbuton() {
PlayerPrefs.SetInt ("CharacterSelected", index);
} }
Here is the code file where I make the characters in the game move:
public class control : MonoBehaviour {
public FixedJoystick LeftJoystick;
private GameObject leftjoystick;
public FixedButton Button;
private GameObject button;
public FixedTouchField TouchField;
private GameObject touchField;
protected ThirdPersonUserControl Control;
protected float CameraAngle;
protected float CameraAngleSpeed = 0.2f;
PhotonView PV;
void Awake()
{
PV = GetComponent<PhotonView>();
}
void Start()
{
if (!PV.IsMine)
return;
Control = GetComponent<ThirdPersonUserControl>();
leftjoystick = GameObject.Find("Fixed Joystick");
if (leftjoystick != null)
{
LeftJoystick = leftjoystick.GetComponent<FixedJoystick>();
}
button = GameObject.Find("Handle (1)");
if (button != null)
{
Button = button.GetComponent<FixedButton>();
}
touchField = GameObject.Find("tfield");
if (touchField != null)
{
TouchField = touchField.GetComponent<FixedTouchField>();
}
}
void FixedUpdate() {
if (PV.IsMine)
{
Control.m_Jump = Button.Pressed;
Control.Hinput = LeftJoystick.Direction.x;
Control.Vinput = LeftJoystick.Direction.y;
CameraAngle += TouchField.TouchDist.x * CameraAngleSpeed;
Camera.main.transform.position = transform.position + Quaternion.AngleAxis(CameraAngle, Vector3.up) * new Vector3(1, 2, 3);
Camera.main.transform.rotation = Quaternion.LookRotation(transform.position + Vector3.up * 2f - Camera.main.transform.position, Vector3.up);
}
} }
There is a game object named "karakteryükle" in the menu scene. The code file named "choose" is in this object. There are 4 characters in this game object. Each character has a code file named "control", photon view, photon transform view, animator view component. And the game object named "karakteryükle" is also available as a prefab in the Resources folder.
I am sharing the picture of the components loaded on each character
I shared a picture of the game object named "karakter yükle"
I'm trying to load "karakter yükle" when the scene named game is loaded
PhotonNetwork.Instantiate("karakteryükle", new Vector3((float)-0.43, (float)1.1, (float)-25.84), Quaternion.identity, 0, null);
Result: The "karakteryükle" is loaded onto the stage, but the same character is loaded for each player, the character chosen by each player is not loaded.
I need your opinion on this.
Each player only knows their own setting for the index, because they use the value set in PlayerPrefs.
private void Start()
{
index = PlayerPrefs.GetInt("CharacterSelected");
}
This works for our local player, no problem there. But what happens when a different player enters the scene.
The playerObject is spawned on each client.
Each client handles that playerObject locally (this is the reason IsMine exist).
Player1 executes index = PlayerPrefs.GetInt(..) on their copy of Player2.
What you can do is send a buffered RPC to set the selected character on those remote copies. We want to buffer the rpc so new players change their remote copies of everyone to the appropriate character.
myPhotonView.RPC("SetCharacterIndex", RpcTarget.OthersBuffered, index);
and the corresponding RPC method
[PunRPC]
private void SetCharacterIndex(int index)
{
// Disable other characters and enable the one at this index
}
In the end you end up with something like
void Start()
{
characterList = new GameObject[transform.childCount];
for (int i=0; i< transform.childCount; i++)
{
characterList[i] = transform.GetChild(i).gameObject;
characterList[i].SetActive(false);
}
if (isMine)
{
index = PlayerPrefs.GetInt("CharacterSelected");
// Notify all remote copies of us to change their index
//
photonView.RPC("SetCharacterIndex", RpcTarget.OthersBuffered, index);
// Set the index locally
//
SetCharacterIndex(index);
}
}
[PunRPC]
private void SetCharacterIndex(int index)
{
if (characterList [index])
characterList [index].SetActive (true);
}
Hopefully that helps clear up the reason this happens (networking can be confusing at times).
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 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];
}
}
}