Player ReSpawn Logic Unity Multiplayer - unity3d

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👍

Related

Setting tags on a Unity GameObject with Photon

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).

Destroying Prefabs Object After Spawn Using collision

I currently have some objects spawning from a prefab and am attempting to destroy only one of the spawned items of that prefab. I was searching online and I found tons of different examples but I have been unable to get any of them to work. I tried setting up an instance of the Instantiate and destroying that instance but I am unable to get it to work. The spawn/collision script is attached to the main camera if that matters. Collision with other items in my game work and the prefab does have a box collider set to isTrigger. Again, I know there are plenty of examples explaining this etc, but I can't get it to work and maybe I am not understanding what I actually should be doing.
Spawner code:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class bloodVialSpawner : MonoBehaviour
{
public GameObject vialOfBlood;
private GameObject vialss;
private int hunger =10;
public int numOfVials;
public int minSpawnRange, maxSpawnRange;
public int minSpawnRange2, maxSpawnRange2;
// Start is called before the first frame update
float timeSpawns = 2;
List<GameObject> vialsInstantiated = new List<GameObject>();
void Start()
{
StartCoroutine(becomeHungry());
InvokeRepeating("SpawnVials", timeSpawns, timeSpawns);
}
private void Update()
{
if (hunger == -1)
{
Debug.Log("sigh");
}
}
void SpawnVials()
{
for (int i = 0; i < numOfVials; i++)
{
vialsInstantiated.Add(Instantiate(vialOfBlood, SpawnPosition(), Quaternion.identity) as GameObject);
}
}
Vector3 SpawnPosition()
{
int x, y, z;
y = 59;
x= UnityEngine.Random.Range(minSpawnRange, maxSpawnRange);
z = UnityEngine.Random.Range(minSpawnRange2, maxSpawnRange2);
return new Vector3(x, y, z);
}
IEnumerator becomeHungry()
{
while (true)
{
hunger -= 1;
yield return new WaitForSeconds(1);
Debug.Log(hunger);
}
}
}
Spawner Script is on the Main Camera. Player used is the First Person Player Unity provides.
Code for destroying spawned object:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class destroyVial : MonoBehaviour
{
void OnTriggerEnter(Collider col)
{
if (col.gameObject.tag == "vials")
{
Destroy(col.gameObject);
Debug.Log("yell");
}
}
}
Destroy code is on prefab. Note prefab is not in hierarchy as it should not be.
Firstly,
I see that you're spawning things in a for-loop and overwriting the vialss variable every time:
for (int i = 0; i < numOfVials; i++)
{
vialss = Instantiate(vialOfBlood,
SpawnPosition(),
Quaternion.identity) as GameObject;
}
And then, on collision, you're Destroying vialss, which in this case will be the latest spawned object. And if you collide with anything after 1 collision, vialss will already be deleted and probably throw an exception. Maybe that's fine in your game, but the logic looks a bit flawed.
Also, I'm assuming you want to destroy the object you're colliding with? Does something like this not work?
void OnTriggerEnter(Collider col)
{
if (col.gameObject.tag == "vials")
{
Destroy(col.gameObject); // <== Remove colliding object
Debug.Log("yell");
}
}
If you, for some unrelated reason, need a list of all your spawned vials, maybe you'd like to convert that to a list instead:
List<GameObject> spawnedVials = new List<GameObject>();
void SpawnVials()
{
for (int i = 0; i < numOfVials; i++)
{
var vial = Instantiate<GameObject>(vialOfBlood,
SpawnPosition(),
Quaternion.identity)
spawnedVials.Add(vial);
}
}
Finally,
make sure that the collision detection is working. You're saying that the script is attached to your camera. please make sure you have a Collider on the camera. But you're saying that other colliders are working, so I'm guessing you have this under control.
I'd guess your issue lies in the flawed logic I initially described.
OnTriggerEnter needs to be on a script attached to the object it's colliding with, or the vial itself. It can't be on the main camera as OnTriggerEnter will never get called.
I would recommend you to keep scripts to one job, you should split that script into a Spawn script and a Collider script. And create a empty GameObject with the sole purpose of spawning prefabs.
Also there's some errors in your code:
void OnTriggerEnter(Collider col)
{
if (col.gameObject.tag == "vials")
{
Destroy(col.gameObject); // Destroy the gameObject you're colliding with
Debug.Log("yell");
}
}
Also the variable vialss isn't doing what you're expecting, vialss is only referencing to the last instantiated vial, so better save all vials in a List:
List<GameObject> vialsInstantiated = new List<GameObject>();
And then:
void SpawnVials()
{
for (int i = 0; i < numOfVials; i++)
{
vialsInstantiated.Add(Instantiate(vialOfBlood, SpawnPosition(), Quaternion.identity) as GameObject);
}
}

Unity Unet Updating transform while clicked

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;
}

OnCollisionEnter in unity won't call function

I'm a complete unity novice. I want to make a simple scene where you have three lives and you lose a live if you collide with a cube. This is my script:
using UnityEngine;
using UnityEngine.UI;
using System.Collections;
public class Lives : MonoBehaviour {
public Transform player;
public static int lives;
public Image live1;
public Image live2;
public Image live3;
// Use this for initialization
void Start () {
lives = 3;
live1.enabled = true;
live2.enabled = true;
live3.enabled = true;
}
void Update () {
DisplayOfHearts();
}
public static void Damage() {
lives -= 1;
}
public void OnCollisionEnter(Collision col) {
if (col.gameObject.tag == "cube") {
Lives.Damage();
}
}
public void DisplayOfHearts() {
if (lives == 2) {
live3.enabled = false;
}
else if (lives == 1) {
live2.enabled = false;
}
else if (lives == 0) {
live1.enabled = false;
}
}
}
What happens is the player can't move through the cube but the amount of lives stays three. Is there something I'm missing?
The problem is you have attached the script to a wrong game object. The script and the collider must be attached to the same game object.
Unity methods inside a MonoBehaviour script (such as OnEnable, Update, FixedUpdate, Awake, Start, OnTriggerEnter, OnCollisionStay, etc..) only work for the game object which the script is attached to.
If you attach the script to another game object don't expect any of those to work. Update only works while that game object is active. OnCollisionEnter only works when a collision occurs on a collider which is attached directly to that game object. (it doesn't even work when a child has the collider instead of the actual game object where script is attached to)

Simple Unity5 LAN game, server updating clients are not

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;
}
}