I have the following simple class extending Networkbehaviour. I need to wait for a connection to server in order to call a [Command] funtcion.
Any UNET or multiplayer experts able to tell me what I am doing wrong?
using System.Collections;
using UnityEngine;
using UnityEngine.Networking;
public class KeepClientConnected : NetworkBehaviour {
public int pingInterval = 30;
// Use this for initialization
void Awake () {
DontDestroyOnLoad(transform.gameObject);
}
void Start(){
}
void OnDisconnectedFromServer(NetworkDisconnection info)
{
if (Network.isServer)
Debug.Log("Local server connection disconnected");
else
if (info == NetworkDisconnection.LostConnection)
Debug.Log("Lost connection to the server");
else
Debug.Log("Successfully diconnected from the server");
}
private void OnConnectedToServer()
{
Debug.Log("0 ASSERT OnConnectedToServer");
StartCoroutine(myCoRoutine());
}
[Command]
void Cmd_KeepConnection()
{
Debug.Log("2 ASSERT calling KeepConnection on Server");
}
IEnumerator myCoRoutine()
{
while (true)
{
Cmd_KeepConnection();
Debug.Log("1 ASSERT calling KeepConnection on Server");
yield return new WaitForSeconds(2);
}
}
}
I haven't used OnConnectedToServer but I can give you a couple of alternatives.
Make a Player. Have this network behavior exist on that player. The player will only get created once the client is connected.
Extend NetworkManager and override OnStartClient while making sure to still call base.OnStartClient. In this method you can be sure that the client is started and ready as it passes a reference to the client to you.
As a side note I used the following code in my player's Update method:
if (KeepAlive)
{
LastPing += Time.deltaTime;
if (LastPing > 10)
{
LastPing = 0;
CmdPing();
}
}
Related
I set the timer of the existence of the room as the properties of the room. As soon as the master player leaves the room, an exception is triggered: Operation setProperties (252) not called because client is not connected or not ready yet, client state: Leaving. This error does not affect the operation, there is no such error when another player exits. How can I handle this exception? It just bothers the eyes.
private void Update()
{
_currentPlayersCountText.text = PhotonNetwork.PlayerList.Length.ToString();
if (PhotonNetwork.IsMasterClient)
{
if (PhotonNetwork.PlayerList.Length >= 5)
{
_startGameButton.GetComponent<Button>().enabled = true;
}
else
{
_startGameButton.GetComponent<Button>().enabled = false;
}
Hashtable ht = PhotonNetwork.CurrentRoom.CustomProperties;
ht.Remove("timer");
ht.Add("timer", _roomTimer.timeRemaining);
PhotonNetwork.CurrentRoom.SetCustomProperties(ht);
}
if (_roomTimer.timeRemaining <= 110)
{
LeaveRoom();
}
}
Here is the code that most likely causes this error.
You could try
public void LeaveRoom()
{
PhotonNetwork.LeaveRoom();
}
public override void OnLeftRoom()
{
StartCoroutine(WaitToLeave());
}
IEnumerator WaitToLeave()
{
while(PhotonNetwork.InRoom)
yield return null;
SceneManager.LoadScene(0);
}
Trying to create a ready check using PUN2 so that all players will load into the game scene at the same time, but I do not understand how to check another players custom property and keep a count of how many players are currently ready and if all are ready then start the game. I think I have a custom property set up for every player that should be but I am unsure if it working at all.
public class HeroSelectController : MonoBehaviour
{
[HideInInspector]
public string selectedHero;
private PhotonView PV;
private bool PlayerReady = false;
private ExitGames.Client.Photon.Hashtable _playerCustomProperties = new ExitGames.Client.Photon.Hashtable();
private void Update()
{
Debug.Log("Player Ready = " + _playerCustomProperties["PlayerReady"]);
}
private void HeroSelect()
{
PlayerReady = true;
selectedHero = "PlayerTest";
PhotonNetwork.SetPlayerCustomProperties(_playerCustomProperties);
_playerCustomProperties["PlayerReady"] = PlayerReady;
}
public void OnClickHeroButton()
{
HeroSelect();
if (PhotonNetwork.IsMasterClient)
{
foreach (var photonPlayer in PhotonNetwork.PlayerList)
{
photonPlayer.CustomProperties["PlayerReady"] = true;
PhotonNetwork.LoadLevel(3);
}
}
}
}
What is currently happening is that the master client can start the game regardless of everyone else's state. Feel like I might be overthinking all of this and there is a much similar solution as I would expect a function like this to be common place as I would expect something similar to be used in many online games so if I am going about completely the wrong way please point me a more suitable direction
Don't know exactly how Photon works but I assume these properties are already synchronized and as far as I understand you simply want some kind of a check like e.g.
private bool AllPlayersReady
{
get
{
foreach (var photonPlayer in PhotonNetwork.PlayerList)
{
if(photonPlayer.CustomProperties["PlayerReady"] == false) return false;
}
return true;
}
}
Which you could probably also shorten using Linq like
using System.Linq;
...
private bool AllPlayersReady => PhotonNetwork.PlayerList.All(player => player.CustomProperties["PlayerReady"] == true);
And then use it like
public void OnClickHeroButton()
{
HeroSelect();
if (!PhotonNetwork.IsMasterClient) return;
if(!AllPlayersReady)
{
return;
}
PhotonNetwork.LoadLevel(3);
}
This can still be enhanced since now the master has to press repeatedly until maybe everyone is ready. I would additionally use a Coroutine like
public void OnClickHeroButton()
{
HeroSelect();
if (!PhotonNetwork.IsMasterClient) return;
StartCoroutine (WaitAllReady ());
}
private IEnumerator WaitAllReady()
{
yield return new WaitUntil (() => AllPlayersReady);
PhotonNetwork.LoadLevel(3);
}
Thanks to Iggy:
Instead of a routine which checks every frame you can use OnPlayerPropertiesUpdate in order to check for the ready state
void OnPlayerPropertiesUpdate(Player targetPlayer, Hashtable changedProps)
{
if (!PhotonNetwork.IsMasterClient) return;
// you can even limit the check to make it
// only if "PlayerReady" is among the changed properties
if(!changedProps.Contains("PlayerReady")) return;
if(!AllPlayersReady) return;
PhotonNetwork.LoadLevel(3);
}
Note: Typed on smartphone but I hope the idea gets clear
I'm new in testing for Unity3D, and I wanted to write some tests for our game. I managed to write this tests which work fine:
using System.Collections;
using NUnit.Framework;
using UnityEngine;
using UnityEngine.TestTools;
using Game.Characters;
using Game.Inventory;
namespace Tests
{
public class ItemTests
{
[UnityTest]
public IEnumerator ItemIsOnHand()
{
// Setup
Player player = MonoBehaviour.Instantiate(Resources.Load<GameObject>("Tests/Player"));
yield return null; // Wait for prefab to load
Character character = player.GetComponent<Character>();
WorldItem worldItem = WorldItem.Create(Resources.Load<Item>("Items/item"), 1);
yield return null; // Wait for prefab to load
character.Inventory.Give(worldItem);
ItemScript itemScript = character.HandItemContainer.gameObject.GetComponentInChildren<ItemScript>();
// Test
Assert.IsNotNull(itemScript);
// Teardown
Object.Destroy(player);
Object.Destroy(character);
Object.Destroy(worldItem);
Object.Destroy(itemScript);
}
[UnityTest]
public IEnumerator ItemIsDisabledAtStart()
{
// Setup
Player player = MonoBehaviour.Instantiate(Resources.Load<GameObject>("Tests/Player"));
yield return null; // Wait for prefab to load
Character character = player.GetComponent<Character>();
WorldItem worldItem = WorldItem.Create(Resources.Load<Item>("Items/item"), 1);
yield return null; // Wait for prefab to load
character.Inventory.Give(worldItem);
ItemScript itemScript = character.HandItemContainer.gameObject.GetComponentInChildren<ItemScript>();
// Test
Assert.IsFalse(itemScript.IsActive);
// Teardown
Object.Destroy(player);
Object.Destroy(character);
Object.Destroy(worldItem);
Object.Destroy(itemScript);
}
}
}
As you can see, both tests (and other tests too) use the same setup and teardown. I declare the variables outside the methods (to be available for every test). Teardown method is working fine, as it doesn't need to wait for anything to load.
private Player player;
private Character character;
private WorldItem worldItem;
private ItemScript itemScript;
[TearDown]
public void Teardown()
{
Debug.Log("Teardown called");
Object.Destroy(player);
Object.Destroy(character);
Object.Destroy(ectomodulatorWorldItem);
}
But I can't manage to make the SetUp method work. If I make it a coroutine ([SetUp] public IEnumerator SetUp(){...}) it never runs (I checked with the debugger). And if I make it a simple void method and delete the yield return nulls, it fails at character.Inventory.Give(worldItem); because worldItem is still null.
Is there a way I can make a SetUp method work?
Thanks
I found what I needed. Just like [Test] attribute only works on void methods and you need to use [UnityTest] to run a coroutine, there is also a [UnitySetUp] attribute that allows to use coroutine setup instead of a void one.
Link to the docs for more info: https://docs.unity3d.com/Packages/com.unity.test-framework#1.1/manual/reference-actions-outside-tests.html#unitysetup-and-unityteardown
In case you're interested, my tests file is like this:
using System.Collections;
using NUnit.Framework;
using UnityEngine;
using UnityEngine.TestTools;
using Game.Characters;
using Game.Inventory;
namespace Tests
{
public class ItemTests
{
Player player;
Character character;
WorldItem worldItem;
ItemScript itemScript;
[UnitySetUp]
public IEnumerator SetUp()
{
player = MonoBehaviour.Instantiate(Resources.Load<GameObject>("Tests/Player"));
yield return null; // Wait for prefab to load
character = player.GetComponent<Character>();
worldItem = WorldItem.Create(Resources.Load<Item>("Items/item"), 1);
yield return null; // Wait for prefab to load
character.Inventory.Give(worldItem);
itemScript = character.HandItemContainer.gameObject.GetComponentInChildren<ItemScript>();
}
[TearDown]
public void Teardown()
{
Object.Destroy(player);
Object.Destroy(character);
Object.Destroy(ectomodulatorWorldItem);
}
[Test]
public void ItemIsOnHand()
{
// Test
Assert.IsNotNull(itemScript);
}
[Test]
public void ItemIsDisabledAtStart()
{
// Test
Assert.IsFalse(itemScript.IsActive);
}
}
}
Is there a way to do this? I want to prevent the local client running on the server (host) from sending some commands while allowing all the remote clients to do so. Is this possible? Currently the code below runs on ALL clients. Is there a way to modify the script to make it function in this way? Thank you!
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.Networking;
public class Player_Data : NetworkBehaviour
{
private void Start()
{
if (!isLocalPlayer)
{
return;
}
CmdSendServerData();
}
[Command]
void CmdSendServerData()
{
DisplayDataOnScreen("CmdSendServerData Called");
}
[Server]
void DisplayDataOnScreen(string data)
{
GameObject infoDisplayText = GameObject.Find("InfoDisplay");
infoDisplayText.GetComponent<Text>().text += data + "\n";
}
}
Use isServer property. You can check it before cammand call.
public class Player_Data : NetworkBehaviour
{
private void Start()
{
if (isLocalPlayer && !isServer)
{
CmdSendServerData();
}
}
[Command]
void CmdSendServerData()
{
DisplayDataOnScreen("CmdSendServerData Called");
}
[Server]
void DisplayDataOnScreen(string data)
{
GameObject infoDisplayText = GameObject.Find("InfoDisplay");
infoDisplayText.GetComponent<Text>().text += data + "\n";
}
}
I'm making an event that does the failLevel stuff when it fires off. For that I have made a delegate
public delegate Coroutine FailGame(IEnumerator function);
public static event FailGame gameFailedEvent;
like so and I subscribed the appropriate function to it
void Start ()
{
gameFailedEvent += StartCoroutine;
}
It works when it is called from the same script like so:
gameFailedEvent(WaitThenFailLevel());
when this WaitThenFailLevel() looks like this:
IEnumerator WaitThenFailLevel()
{
CharacterController2D.playerDied = true;
if (CharacterController2D.animState != CharacterController2D.CharAnimStates.fall)
{
CharacterController2D.currentImageIndex = 0;
CharacterController2D.animState = CharacterController2D.CharAnimStates.fall;
}
CharacterController2D.movementDisabled = true;
yield return new WaitForSeconds(0.2f);
StartCoroutine(ScaleTime(1.0f, 0.0f, 1.2f));
}
It works fine here. Now, I have another object that can kill the player (dangerous times I know) and I don't want to copy paste everything again, I just want it to fire off the static event made in the script above.
I DID try making the WaitThenFailGame function
public static
and make static all my other ienumerators but I got an error named "An object reference is required for non-static field..."
Hence I tried the event stuff.
All well and fine, but I can't call the event from the other script because I can't pass it the function from the script mentioned.
What to do now?
Here is the example code:
EventContainor.cs
public class EventContainer : MonoBehaviour
{
public static event Action<string> OnGameFailedEvent;
void Update()
{
if (Input.GetKey(KeyCode.R))
{
// fire the game failed event when user press R.
if(OnGameFailedEvent = null)
OnGameFailedEvent("some value");
}
}
}
Listener.cs
public class Listener : MonoBehaviour
{
void Awake()
{
EventContainer.OnGameFailedEvent += EventContainer_OnGameFailedEvent;
}
void EventContainer_OnGameFailedEvent (string value)
{
StartCoroutine(MyCoroutine(value));
}
IEnumerator MyCoroutine(string someParam)
{
yield return new WaitForSeconds(5);
Debug.Log(someParam);
}
}
using UnityEngine;
public class ScriptA : MonoBehaviour
{
public ScriptB anyName;
void Update()
{
anyName.DoSomething();
}
}
using UnityEngine;
public class ScriptB : MonoBehaviour
{
public void DoSomething()
{
Debug.Log("Hi there");
}
}
This is linking functions between scripts , Copied from Here, maybe coroutines are the same, Then you need to start the coroutine in void Start() {} , You may find this useful as well.