I'm making a multiplayer game and I'm trying to instantiate a new object on the client. The object should be controlled by that player alone.
I tried it by simply instantiating it on the client, and then spawning it:
public class Player : NetworkBehaviour
{
[SerializeField]
private Card _testCard;
void Update()
{
if (!isLocalPlayer) return;
if (Input.GetKeyDown(KeyCode.Space))
{
RaycastHit hit;
if (Physics.Raycast(Camera.main.ScreenPointToRay(Input.mousePosition), out hit))
{
var card = Instantiate(_testCard);
card.transform.position = hit.point;
card.Name = "Test";
NetworkServer.Spawn(card.gameObject);
//or call this from a command, shown below
}
}
}
/*[Command]
void CmdPlayTestCard(string name, Vector3 position)
{
var card = Instantiate(_testCard);
card.transform.position = position;
card.Name = name;
NetworkServer.Spawn(card.gameObject);
}*/
}
This spawns the object on the client, and it can be controlled by it, but doesn't spawn on the server.
I also did the same in a Command and then it is instantiated everywhere but the client can't controll it. The server can however control it.
What is the proper way of doing this? Creating an object that should be controlled by one of the players, rather than the server?
I tried googling it but couldn't find anything.
Thanks!
I found that this is comming in Unity 5.2, the current beta release notes list this as a feature: "Networking: Added support for client-side authority for non-player objects."
So this will be coming in September for everyone.
Oddler, I don't know the answer, but there is a community at irc.freenode.net in the channel #unity3d-unet that might!
In addition, some resources and code snippets are being gathered here:
https://goo.gl/UmBBpM
Related
I use Playfab and Photon for my Unity project. I added some weapons, shields and body types to my player prefab and disabled all of them (except the head). I store which weapon, shield and body type the user have, and pull them from playfab when the player logs in. But the problem is, I can see my own weapon, shield and body but cannot see other player's. I can only see their heads! I assume that it is because when other user joins the scene, their prefab instantiates with PhotonNetwork but SetActive method is working for individuals, not for the server. Here is my code:
public override void OnJoinedRoom()
{
s = "PlayerArmature(Clone)/";
if(PFLogin.prefabName=="Female"){
gameObject = PhotonNetwork.Instantiate(playerPrefabFemale.name, new Vector3(73, 22, 34), Quaternion.identity, 0,null);
s = "PlayerArmatureF(Clone)/";
s += "FemaleCharacterPolyart/";
view = playerPrefabFemale.GetComponent<PhotonView>();
}else{
gameObject = PhotonNetwork.Instantiate(playerPrefab.name, new Vector3(73, 22, 34), Quaternion.identity, 0,null);
s += "MaleCharacterPolyart/";
view = playerPrefab.GetComponent<PhotonView>();
}
GameObject body = GameObject.Find(s+PFLogin.body);
GameObject cloak = GameObject.Find(s+PFLogin.cloak);
GameObject shield = GameObject.Find(s+"root/pelvis/spine_01/spine_02/spine_03/clavicle_l/upperarm_l/lowerarm_l/hand_l/weapon_l/"+PFLogin.shield);
GameObject weapon = GameObject.Find(s+"root/pelvis/spine_01/spine_02/spine_03/clavicle_r/upperarm_r/lowerarm_r/hand_r/weapon_r/"+PFLogin.weapon);
body.SetActive(true);
cloak.SetActive(true);
shield.SetActive(true);
weapon.SetActive(true);
if (view.IsMine)
{
view.RPC("ShowMesh", RpcTarget.AllBuffered, s);
}
}
void ShowMesh(string s)
{
GameObject body = GameObject.Find(s+PFLogin.body);
GameObject cloak = GameObject.Find(s+PFLogin.cloak);
GameObject shield = GameObject.Find(s+"root/pelvis/spine_01/spine_02/spine_03/clavicle_l/upperarm_l/lowerarm_l/hand_l/weapon_l/"+PFLogin.shield);
GameObject weapon = GameObject.Find(s+"root/pelvis/spine_01/spine_02/spine_03/clavicle_r/upperarm_r/lowerarm_r/hand_r/weapon_r/"+PFLogin.weapon);
body.SetActive(true);
cloak.SetActive(true);
shield.SetActive(true);
weapon.SetActive(true);
}
PFLogin is a static class which stores static variables(body&weapon etc) that are assigned with playfab responses.
So my question is how can I call SetActive(true) such that everyone see others bodys(or other things).
At first I've tried without RPC and all I see was heads without bodies in the screen, now I've tried with RPC but nothing changed. It could be my lack because I am very new to all of this.
I would try to use the isMine to make every prefab active:
//at the beginning public PhotonView PV;
public void Awake()
{
PV = Gameobject.GetComponent<PhotonView>();
}
if (!PV.isMine)
{ Prefab.gameobject.SetActive(true) }
I hope it helps, I have never done it like you did, so I am
As the title says i'm getting the UnassignedReferenceException error for a variable already set. Using ScriptableObjects im working on an inventory system(partially from scratch) and im trying to access the EquipmentUi in another class using a GameObject to hold the prefab containing the Character script. There is no issue with this working to access the Character script as shown in the picture because i can access the name. However, when i try and access the EquipmentUI of that character it gives the error. This isnt the last part i need access to but i have figured out that the UI is the part i cant access, i need the script held in it(which has worked before in another class).
The variable is already assigned, there is no other object in my scene with the same script attached, and the code can access other parts of the script i want access to which is why i made a new post after seeing the other posts and not seeing one that had quite the same issue.
using System.Collections;
using System.Collections.Generic;
using UnitEngine;
[CreateAssetMenu(fileName = "New Character Equipment", menuName = "Inventory/Character/CharacterEquipment)]
public class CharacterEquipmentObject : ScriptableObject
{
[SerializeField]
protected Player user;
[SerializeField]
private GameObject equipmentUser; // Only used to get the user because SOs cant getObject<>
[SerializeField]
public EquipmentObject[] equipment = new EquipmentObject[8];
[SerializeField]
EquipmentDisplay equipmentUI;
//private string[] SlotList = new string[8]{"Helmet", "Shoulders", "Chest", "MainHand", "OffHand", "Ring", "Legs", "Feet"}; may need later
public void start() // must be called in display
{
user = equipmentUser.GetComponent<Player>(); // set the user to be used in other classes\
Debug.Log("user in CEO: " + user.characterName); // this displays fine
Debug.Log("user " + user.characterName + "'s equipment: " + user.EquipmentUI.name); // This is what the editor says is empty when the slot has it assigned
equipmentUI = user.EquipmentUI.GetComponentInChildren<EquipmentDisplay>();
Debug.Log("equipmentUI: " + equipmentUI.name);
}
Thanks for any help in advance.
Player class:
public class Player : Character
{
[SerializeField]
protected Character[] companions = new Character[3];
[SerializeField]
GameObject InventoryUI;
public GameObject EquipmentUI;
bool isSelling = false;
public string characterName;
protected override void Start()
{
InventoryUI.SetActive(false);
EquipmentUI.SetActive(false);
Debug.Log("Player Equipment UI: " + EquipmentUI.GetComponentInChildren<EquipmentDisplay>().name);
base.Start();
}
private void OnTriggerEnter(Collider other)
{
var item = other.GetComponent<InteractItem>();
if(item)
{
characterInventory.addItem(item._item, 1);
useItem(item._item);
}
Destroy(other.gameObject);
}
private void Update()
{
if(Input.GetKeyDown(KeyCode.I))
{
InventoryUI.SetActive(!InventoryUI.activeSelf);
}
if(Input.GetKeyDown(KeyCode.C))
{
EquipmentUI.SetActive(!EquipmentUI.activeSelf);
if(EquipmentUI.activeSelf == true)
{
EquipmentUI.GetComponentInChildren<EquipmentDisplay>().updateEquipmentSlots();
}
}
}
Thanks for all the help on here but i figured out it is a ScriptableObjects issue as i went in and changed the way it is run, even putting the function to edit the users InventoryObject into the player itself it still gave the same issue.
In the player function Update() that does not have the InventoryObject(ScriptableObject) the update runs fine but as soon as the player function EmptySlot() is called from the InventoryObject it sends the same error.
Not entirely sure why this is but SOs are unable to access the GameObject that holds the script for the inventory display in them whatsoever. If anyone has a reason as to why this is I would love to know, but i am going to move on from this particular part and change how it works.
You might be experiencing a bug I have been facing where the element variable info does not update to show variable changes.
To force it to update, cause an error in your code. Switch to unity, when it shows you the error go back and fix it, and see if the variable value changes then.
Hi Guys I am converting a single player Sudoko game into a multiplayer game (right now 2 players) using Photon in Unity.
The basic logic of the Sudoku game is that there are 300 puzzle data already loaded in it. A random number between 1 to 300 is picked up and the corresponding puzzle is loaded.
But the problem I am facing is even though I have made sure that the same number is getting picked up for both the client and the master server, different puzzles are getting loaded.
So basically I script called MultiManager attached to the MultiManager GameObject in the Sudoku screen.The script looks something like this.
void Start()
{
PV = GetComponent<PhotonView>();
if (PhotonNetwork.IsMasterClient)
{
puzzleIndex = Random.Range(0, 300);
PV.RPC("RPC_PuzzleIndex", RpcTarget.Others, puzzleIndex);
}
gameManager = FindObjectOfType(typeof(GameManager)) as GameManager;
gameManager.PlayNewGame("easy");
}
[PunRPC]
void RPC_PuzzleIndex(int puzzleIndexNUmber)
{
puzzleIndex = puzzleIndexNUmber;
}
So in the GameManager script you have these functions:
public void PlayNewGame(string groupId)
{
// Get the PuzzleGroupData for the given groupId
for (int i = 0; i < puzzleGroups.Count; i++)
{
PuzzleGroupData puzzleGroupData = puzzleGroups[i];
if (groupId == puzzleGroupData.groupId)
{
PlayNewGame(puzzleGroupData);
return;
}
}
}
private void PlayNewGame(PuzzleGroupData puzzleGroupData)
{
// Get a puzzle that has not yet been played by the user
PuzzleData puzzleData = puzzleGroupData.GetPuzzle();
// Play the game using the new puzzle data
PlayGame(puzzleData);
}
And in the PuzzleGroupData class you have this function :
public PuzzleData GetPuzzle()
{
return new PuzzleData(puzzleFiles[MultiManager.puzzleIndex], shiftAmount, groupId);
}
I don't quite get as to whats wrong which is happening. I tried to use other variations like keeping that random number outside of the condition inside of PhotonNetwork.isMasterClient and all, but doesn't work.
If anyone can help it would be great. By the way this Sudoku game was purchased and I am trying to convert it to a mutliPlayer game
Since the master is usually the first one in a room so also the first one getting Start called I think what happens is that your other clients are simply not connected yet when the RPC is called.
Further it might also happen (actually pretty likely) that Start is called before the RPC has the chance to be received.
I would rather actually wait until you have the value and do
void Start()
{
PV = GetComponent<PhotonView>();
if (PhotonNetwork.IsMasterClient)
{
PV.RPC(name of(RPC_PuzzleIndex), RpcTarget.AllBuffered, Random.Range(0, 300));
}
}
[PunRPC]
void RPC_PuzzleIndex(int puzzleIndexNUmber)
{
puzzleIndex = puzzleIndexNUmber;
gameManager = FindObjectOfType(typeof(GameManager)) as GameManager;
gameManager.PlayNewGame("easy");
}
This way
the RpcTarget.AllBuffered makes sure that also clients joining later will receive the call
there is no way you start a game without receiving the random value first
I'm trying to add a healthbar which is affected by the choices made and link the two but am unable to do so. Any help would be appreciated.
I will recommend you that before you go any further with your game development process, you research a bit more about general OOP programming (no offense or anything, it will just make things waaay easier, trust me).
That being said, here's an example to steer you to the right direction:
You can create a script with a global variable called health and subtract it every time the player makes a decision that would "punish" them, here's an example of how that could work:
PlayerManager.cs (put this script on your player)
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PlayerManager : MonoBehaviour {
public float health = 100f;
private void Update() {
if (health < 0)
Die();
}
private void Die () {
//Fancy animations and text can be added on this method
Destroy(gameObject);
}
}
And on your dialogue script, once they choose a wrong answer, then you can just decrease health on the proper player manager instance, like this:
DialogueSystem.cs
public PlayerManager playerManager;
public float damage = 10f;
private void Start() {
//You can initialize other stuff here as well
//This line is assuming you have the dialogue system script attach to your player as well
playerManager = GetComponent<PlayerManager>();
}
private void Update () {
//This is obviously going to change depending on how your system is built
if (wrongChoice) {
playerManager.health -= damage;
wrongChoice = false;
}
}
I have a player object who can equip multiple weapons. When a weapon is equipped, its transform's parent is set to its hand. I have messed around with this for some time and cannot get this to work for both the host and the client. Right now I am trying to equip the weapon on the server, and tell all the clients to set their parents transforms.
public NetworkInstanceId weaponNetId;
[Command]
void Cmd_EquipWeapon()
{
var weaponObject = Instantiate (Resources.Load ("Gun"),
hand.position,
Quaternion.Euler (0f, 0f, 0f)) as GameObject;
weaponObject.transform.parent = hand;
NetworkServer.Spawn (weaponObject);
//set equipped weapon
var weapon = weaponObject.GetComponent<Weapon> () as Weapon;
weaponNetId = weaponObject.GetComponent<NetworkIdentity> ().netId;
Rpc_SetParentGameobject (weaponNetId);
}
[ClientRpc]
public void Rpc_SetParentGameobject(NetworkInstanceId netID)
{
weaponNetId = netId;
}
And in the update I am updating the weapons transform
void Update () {
// set child weapon tranform on clients
if (!isServer) {
if (weaponNetId.Value != 0 && !armed) {
GameObject child = NetworkServer.FindLocalObject (weaponNetId);
if (child != null) {
child.transform.parent = hand;
}
}
}
I know this isn't the most optimized way to do this..but right now I am just trying to get this to work any way possible and then work on tweaking it. Seems like it should be a simple task.
We do a similar thing in our multiplayer game. There are a few things you need to do to get this working. Firstly, the concept:
Setting the weapon's parent on the server is trivial, as you have found. Simply set the transform's parent as you would normally in Unity. However, after spawning this object on the server with NetworkServer.Spawn, it will later be spawned on clients in the root of the scene (hierarchy outside of the spawned prefab is not synchronised).
So in order to adjust the hierarchy on the client I would suggest that you:
Use a SyncVar to synchronise the netID of the parent object between the server and client.
When the object is spawned on the client, find the parent using the synchronised netID and set it as your transform's parent.
Therefore, I would adjust your code to look something like this. Firstly, set the weapon's parent netId before you spawn it. This will ensure that when it is spawned on clients, the netId will be set.
[Command]
void Cmd_EquipWeapon()
{
var weaponObject = Instantiate (Resources.Load ("Gun"),
hand.position,
Quaternion.Euler (0f, 0f, 0f)) as GameObject;
weaponObject.parentNetId = hand.netId; // Set the parent network ID
weaponObject.transform.parent = hand; // Set the parent transform on the server
NetworkServer.Spawn (weaponObject); // Spawn the object
}
And then in your weapon class:
Add a parentNetId property.
Mark it as [SyncVar] so that it synchronises between server and client copies.
When spawned on a client, find the parent object using the netId and set it to our transform's parent.
Perhaps something like:
[SyncVar]
public NetworkInstanceId parentNetId;
public override void OnStartClient()
{
// When we are spawned on the client,
// find the parent object using its ID,
// and set it to be our transform's parent.
GameObject parentObject = ClientScene.FindLocalObject(parentNetId);
transform.SetParent(parentObject.transform);
}
I found this post really useful but I have a small addendum.
The netId will be on the root of the hierarchy so its useful to know that you can traverse down the hierarchy using transform.Find.
Something like this ..
GameObject parentObject = ClientScene.FindLocalObject(parentNetId);
string pathToWeaponHolder = "Obj/targetObj";
transform.SetParent(parentObject.transform.Find(pathToWeaponHolder));
After reading Andy Barnard's solution, I came up with this slightly modified solution. Instead of a SyncVar, there is a Client RPC for the server to call any time a NetworkIdentity needs to change parents. This does require the parent to also have a NetworkIdentity (though it need not be a registered prefab).
public void Server_SetParent (NetworkIdentity parentNetworkIdentity) {
if (parentNetworkIdentity != null) {
// Set locally on server
transform.SetParent (parentNetworkIdentity.transform);
// Set remotely on clients
RpcClient_SetParent (parentNetworkIdentity.netId, resetTransform);
}
else {
// Set locally on server
transform.SetParent (null);
// Set remotely on clients
RpcClient_SetParent (NetworkInstanceId.Invalid, resetTransform);
}
}
[ClientRpc]
void RpcClient_SetParent (NetworkInstanceId newParentNetId) {
Transform parentTransform = null;
if (newParentNetId != NetworkInstanceId.Invalid) {
// Find the parent by netid and set self as child
var parentGobj = ClientScene.FindLocalObject (newParentNetId);
if (parentGobj != null) {
parentTransform = parentGobj.transform;
}
else {
Debug.LogWarningFormat ("{0} Could not find NetworkIdentity '{1}'.", gameObject.name, newParentNetId.Value);
}
}
transform.SetParent (parentTransform);
}
These two are part of a NetworkBehavior, which obviously RequiresComponent(typeof(NetworkIdentity)). If you really need this behavior on a client to server, I'd suggest creating a command that passed the NetworkIdentity to the server, which would just call the server's public method. It's one set of network messages more than optimal, but meh.