I have a system where a player can create a room and then other players can join that room. At any point the master client can change scenes for everyone in the room and they can start playing but other players can still join the room and will instantly get their scenes synced.
However I would like to show the players that are choosing a room to join whether or not the game in that room has already begun. I decided to do that by using the customproperties of a room to save an integer which can be either 0 (players are still waiting) or 1 (players are in game). I set the custom properties after getting a callback on the master client that the room was created, however the custom properties don't change after I use SetCustomProperties.
public override void OnCreatedRoom()
{
base.OnCreatedRoom();
ExitGames.Client.Photon.Hashtable roomProps = new ExitGames.Client.Photon.Hashtable();
roomProps["inGame"] = 0;
PhotonNetwork.CurrentRoom.SetCustomProperties(roomProps);
}
public void StartLobbyGame() {
ExitGames.Client.Photon.Hashtable roomProps = new ExitGames.Client.Photon.Hashtable();
roomProps["inGame"] = 1;
PhotonNetwork.CurrentRoom.SetCustomProperties(roomProps);
PhotonNetwork.LoadLevel("InGame");
}
Any idea why that is?
You are creating an empty hashtable, so it doesn't contains the key "inGame".
You need to add it first before accessing it using roomProps["inGame"] = 0;
You can add keys like that :
ExitGames.Client.Photon.Hashtable roomProps = new ExitGames.Client.Photon.Hashtable() { {"inGame", 0} };
or
roomProps.Add("inGame", 0);
I fixed my issue by adding the custom property to the room options before creating the room with those options. After that in my start game method i change the value of the property from 0 to 1.
public void CreateRoom(string name)
{
RoomOptions ropts = new RoomOptions() { IsOpen = true, IsVisible = true, MaxPlayers = 8 };
ExitGames.Client.Photon.Hashtable roomProps = new ExitGames.Client.Photon.Hashtable();
roomProps.Add("inGame", 0);
ropts.CustomRoomProperties = roomProps;
PhotonNetwork.CreateRoom(name, ropts);
}
public void StartLobbyGame() {
PhotonNetwork.CurrentRoom.CustomProperties["inGame"] = 1;
PhotonNetwork.LoadLevel("InGame");
}
Related
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 using Screenshot code to take a screenshot of the screen which is working fine but its also taking the arcore models with it. Is there a way to take a screenshot before models are rendered?
I tried to SetActive(false) then take a screenshot then SetActive(true), it does work but there's a noticeable difference i.e. model disappears than reappears.
Update: This is a script applied on ScreenShotCamera and it is updated after removing all the bugs (thanks to #Shingo), feel free to use it it's working properly
using GoogleARCore;
using OpenCVForUnitySample;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.XR;
[RequireComponent(typeof(Camera))]
public class SnapshotCamera : MonoBehaviour
{
Camera snapCam;
public UnityEngine.UI.Text text;
public RenderTexture mRenderTexture;
int resWidth=480;
int resHeight=800;
// Start is called before the first frame update
public void initialize(ARBackgroundRenderer background, Material material)
{
background = new ARBackgroundRenderer();
snapCam = GetComponent<Camera>();
background.backgroundMaterial = material;
background.camera = snapCam;
background.mode = ARRenderMode.MaterialAsBackground;
if (snapCam.targetTexture == null)
{
snapCam.targetTexture = new RenderTexture(resWidth, resHeight, 24);
}
else
{
snapCam.targetTexture.height = resHeight;
snapCam.targetTexture.width = resWidth;
//resHeight = snapCam.targetTexture.height;
//resWidth = snapCam.targetTexture.width;
}
background.camera.cullingMask = LayerMask.NameToLayer("Default");
//snapCam.CopyFrom(background.camera);
snapCam.gameObject.SetActive(false);
}
public void TakeSnapShot()
{
snapCam.gameObject.SetActive(true);
}
void LateUpdate()
{
if (snapCam.gameObject.activeInHierarchy)
{
snapCam.cullingMask = LayerMask.NameToLayer("Default");
if (ARCoreBackgroundRenderer.screenShot == null)
ARCoreBackgroundRenderer.screenShot = new Texture2D(resWidth, resHeight, TextureFormat.RGB24, false);
snapCam.Render();
RenderTexture.active = snapCam.targetTexture;
ARCoreBackgroundRenderer.screenShot.ReadPixels(new Rect(0, 0, resWidth, resHeight), 0, 0);
ARCoreBackgroundRenderer.screenShot.Apply();
snapCam.gameObject.SetActive(false);
HandPoseRecognition.captureTexture = false;
//string name = string.Format("{0}_Capture{1}_{2}.png", Application.productName, "{0}", System.DateTime.Now.ToString("yyyy-MM-dd_HH-mm-ss"));
//UnityEngine.Debug.Log("Permission result: " + NativeGallery.SaveImageToGallery(ARCoreBackgroundRenderer.screenShot, Application.productName + " Captures", name));
}
}
}
Perhaps I was a little ambiguous, what u mentioned in the comment has already been resolved thanks to you but the problem now is.
I'll show you the images:
These are the 2 cameras I have:
This is what my Main (ARCore Camera) shows
And this is what the (ScreenShot Camera) Shows
You can use layer, put every arcore models in one layer (eg. ARLAYER), then set camera's culling mask to avoid these models.
Pseudo code:
// Set models' layer
foreach (GameObject arcoreModel in arcoreModels)
arcoreModel.layer = ARLAYER;
// Set camera's culling mask
camera.cullingMask = ~(1 << ARLAYER);
camera.Render();
Create screenshot camera from another camera
var go = new GameObject("screenshotcamera");
// Copy transform
go.transform.position = mainCamera.transform.position.
...
// Copy camera
var screenshotcamera= go.AddComopnent<Camera>();
screenshotcamera.CopyFrom(mainCamera);
Update with your script
snapCam = GetComponent<Camera>();
I used to have the following particle system that worked.
// In the inspector I drag in the leaf particle system.
public ParticleSystem LeafStormParticleSystem;
private IEnumerator activate(float ActivateFor) {
//Change number of particles to 150
LeafStormParticleSystem.maxParticles = 150;
// LeafStormParticleSystem.
var newEmission = LeafStormParticleSystem.emission;
var rate = newEmission.rate;
rate.constantMin = 20;
rate.constantMax = 21;
newEmission.rate = rate;
Now as you can probably tell, this simple increases the number of particles. Now, this used to work and probably doesn't because of the new particle system I keep reading about.
However on this new particle system I have a question and issues getting it to work.
Issue
Correct me if i'm wrong but the particle system is defined as follows
void Start()
{
ParticleSystem ps = GetComponent<ParticleSystem>();
var main = ps.main;
main.startDelay = 5.0f;
main.startLifetime = 2.0f;
}
Now if I have 3 particle systems, how do I specify which one i'm referring to? Since I cant define it as public and drag the particle system to it in the editor anymore?
Issue B
Now i tried following what unity said in their forums and did the following
ParticleSystem ps = GetComponent<ParticleSystem>();
var main = ps.main;
main.maxParticles = 150;
// LeafStormParticleSystem.maxParticles = 150;
do not create your own module instances, get them from a ParticleSystem instance UnityEngine.ParticleSystem+MainModule.set_maxParticle
Any help with Issues A and B will be appreciated.
Thank you
Issue A
Only one ParticleSystem component can be attached to any one GameObject at a time. Sub-ParticleSystems must therefore be attached to separate GameObjects too (generally children of the GameObject holding the first ParticleSystem), which can be dropped directly onto public fields.
public ParticleSystem LeafStormParticleSystem;
void Start ()
{
if (LeafStormParticleSystem != null)
{
var main = LeafStormParticleSystem.main;
main.maxParticles = 150;
}
}
Issue B
Your code looks fine, however a critical part of the error message was missing; NullReferenceException which is telling you that a reference in your code is equal to NULL. In your case, that reference would be the ps variable used to store the ParticleSystem reference, which is either a consequence of not attaching this script to the GameObject holding your ParticleSystem or simply that you have no ParticleSystem attached at all. In either case, make sure both script and ParticleSystem are attached to the same GameObject and check your references like so;
void Start ()
{
ParticleSystem ps = GetComponent<ParticleSystem>();
if (ps != null)
{
var main = ps.main;
main.maxParticles = 150;
}
}
I am trying to create a simple 2D Turn based multiplayer game using Photon Unity Networking.
It is just a simple turn based game where a player 1 (host) presses his button and it adds his score and changes its turn to player 2 (client) who presses his button to add score and change turn to player 1. It continues to infinite.
I was able to connect two players in the game using the basic Photon documentation. Now I need to add the networking logic of taking turns and changing them.
I searched the internet but I can't understand the RPC and SerializeView of Photon. I am really confused with that. Please Help me. Thank you in future. Here is my GameManager Script
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.UI;
public class GameManager : Photon.PunBehaviour
{
public Text roomName;
public Text player1Name,player2Name;
public List<string> playersConnected = new List<string>();
int scoreP1 = 0, scoreP2 = 0;
public Text scoreTextP1, scoreTextP2;
public Button p1Btn, p2Btn;
int playerTurn = 1;
void Start()
{
roomName.text = SceneManager.GetActiveScene().name;
p2Btn.gameObject.SetActive(false);
p1Btn.gameObject.SetActive(true);
}
public void AddScoreP1()
{
scoreP1++;
scoreTextP1.text = scoreP1.ToString();
ChangePlayerTurn();
}
public void AddScoreP2()
{
scoreP2++;
scoreTextP2.text = scoreP2.ToString();
ChangePlayerTurn();
}
void ChangePlayerTurn()
{
if (playerTurn == 1)
{
playerTurn = 2;
p2Btn.gameObject.SetActive(true);
p1Btn.gameObject.SetActive(false);
}
else
{
playerTurn = 1;
p1Btn.gameObject.SetActive(true);
p2Btn.gameObject.SetActive(false);
}
print("Player Turn: P" + playerTurn);
}
void LoadArena()
{
if (!PhotonNetwork.isMasterClient)
{
Debug.LogError("PhotonNetwork : Trying to Load a level but we are not the master Client");
}
Debug.Log("PhotonNetwork : Loading Level : " + PhotonNetwork.room.PlayerCount);
PhotonNetwork.LoadLevel("Room for " + PhotonNetwork.room.PlayerCount);
}
public override void OnLeftRoom()
{
SceneManager.LoadScene(0);
}
public void LeaveRoom()
{
PhotonNetwork.LeaveRoom();
}
public override void OnPhotonPlayerConnected(PhotonPlayer other)
{
Debug.Log("OnPhotonPlayerConnected() " + other.NickName); // not seen if you're the player connecting
foreach (PhotonPlayer _player in PhotonNetwork.playerList)
{
playersConnected.Add(other.NickName);
}
if (PhotonNetwork.isMasterClient)
{
Debug.Log("OnPhotonPlayerConnected isMasterClient " + PhotonNetwork.isMasterClient); // called before OnPhotonPlayerDisconnected
LoadArena();
}
}
public override void OnPhotonPlayerDisconnected(PhotonPlayer other)
{
Debug.Log("OnPhotonPlayerDisconnected() " + other.NickName); // seen when other disconnects
foreach (PhotonPlayer _player in PhotonNetwork.playerList)
{
playersConnected.Remove(other.NickName);
}
if (PhotonNetwork.isMasterClient)
{
Debug.Log("OnPhotonPlayerDisonnected isMasterClient " + PhotonNetwork.isMasterClient); // called before OnPhotonPlayerDisconnected
LoadArena();
}
}
}
RPC is basically a way of invoking a function on a remote client.
Usually in a multiplayer setup you'd want your MasterClient to control the flow of the game. So, when two players join a room, what you need to do is from your MasterClient, decide which player goes first and then call a RPC function from MasterClient telling both client whose the first turn is. Then whichever player's turn it is, just activate its button and let them add score (Send RPC to MasterClient as well for that, so that everyone can stay in sync.) and the update turn and tell everyone via another RPC and so on.
though you can also use Events for such cases, they need less preparation to be used.
Both in a nutshell:
RPC, Remote Procedural calls, is used to call a certain method to all or certain clients/users in the same room/level. Example like this, you might want to use RCP to update the teams score if a team scored a goal.
SerializeView is used by PUN to synchronize and read data a few times per second, depending on the serialization rate. Example like this, you will use this to get and read to see each other's data like how much points another player have in realtime.
both of these functions are practically similar, but their use is entirely different.
There is more information about RCP and SerializeView on the Photon Website Link
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.