How to repeatedly execute a method until said otherwise - c#

I have an EventSystem for managing my turn-based game in Unity.
public class EventSystem : MonoBehaviour
{
private static List<Action> _commandsQueue = new List<Action>();
private bool _canExecuteCommand = true;
public void AddToQueue(Action command)
{
_commandsQueue.Add(command);
}
private void StartCommandExecution()
{
_commandsQueue[0]();
_canExecuteCommand = false;
}
public void CommandExecutionComplete()
{
_canExecuteCommand = true;
}
public void PlayFirstCommandFromQueue()
{
if (_commandsQueue.Any() && _canExecuteCommand)
{
StartCommandExecution();
}
else
{
Debug.LogError("No Command In Queue");
}
}
}
How do I put a method in Update() until _canExecuteCommand is true again but only for some methods?

It is quite broad what you are trying to do but in general you would use an endless loop within a Coroutine.
You can create a generic routine which invokes any Action you pass in as parameter once a frame like e.g.
private IEnumerator InvokeEveryFrame(Action action)
{
// This looks strange but is okey in a Coroutine as long as you yield somewhere within
while(true)
{
action?.Invoke();
// This tells Unity to "pause" this routine here
// render the current frame and continue from here in the next frame
yield return null;
}
}
So all that's left is starting the routine using MonoBehaviour.StartCoroutine like e.g.
Coroutine routine = StartCoroutine(SomeParameterlessMethod);
or if you need parameters
Coroutine routine = StartCoroutine(() => SomeMethod(x, y, z));
and then at some point later stop it using MonoBehaviour.StopCoroutine and the stored Coroutine reference like e.g.
StopCoroutine(routine);
how exactly you store that reference is up to you of course up to you.

Related

Unity Networked Ready check

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

C# Custom Invoke method to invoke various other methods

I have various methods that work fine, but I want to call them only after a delay. To avoid writing a different method for all of them I figured it's more beneficial to Invoke them somehow. I made the methods so that they exclude Unity's timeScale, so they always wait for Real seconds, using a custom built short function.
The WaitForRealSeconds:
public class WaitForRealSecondsClass
{
#region Wait for real seconds
public Coroutine WaitForRealSeconds(float aTime, MonoBehaviour mono)
{
return mono.StartCoroutine(_WaitForRealSeconds(aTime));
}
private IEnumerator _WaitForRealSeconds(float aTime)
{
while (aTime > 0.0f)
{
aTime -= Mathf.Clamp(Time.unscaledDeltaTime, 0, 0.2f);
yield return null;
}
}
#endregion
}
The way I wish to Invoke a Move function of mine:
public void InvokeAnim(float timeBeforeStart, Action<MonoBehaviour> MoveFunction, MonoBehaviour mono)
{
if (moveRoutine != null)
{
mono.StopCoroutine(moveRoutine);
}
moveRoutine = _InvokeAnim(timeBeforeStart, MoveFunction, mono);
}
IEnumerator _InvokeAnim(float timeBeforeStart, Action<MonoBehaviour> MoveFunction, MonoBehaviour mono)
{
yield return new WaitForRealSecondsClass().WaitForRealSeconds(timeBeforeStart, mono);
MoveFunction(mono);
}
And the Move(MonoBehaviour mono) itself:
public void Move(MonoBehaviour mono)
{
if (moveRoutine != null)
{
mono.StopCoroutine(moveRoutine);
}
moveRoutine = _Move(from, to, overTime, mono);
mono.StartCoroutine(moveRoutine);
}
What I tested and worked is the Move itself, the WaitForRealSeconds I used in another project for UI waiting when the game was stopped, it was fine then.
As I said I have many methods to invoke, all of them return void and have a parameter MonoBehaviour. Currently it doesn't do anything and I have no idea why.
Sod it, I was dump enough to forget actually Starting that coroutine.
In my Invoke:
public void InvokeAnim(float timeBeforeStart, Action<MonoBehaviour> MoveFunction,
MonoBehaviour mono)
{
if (moveRoutine != null)
{
mono.StopCoroutine(moveRoutine);
}
moveRoutine = _InvokeAnim(timeBeforeStart, MoveFunction, mono);
mono.StartCoroutine(moveRoutine); //one line was missing
}
IEnumerator _InvokeAnim(float timeBeforeStart, Action<MonoBehaviour> MoveFunction,
MonoBehaviour mono)
{
yield return new WaitForRealSecondsClass().WaitForRealSeconds(timeBeforeStart, mono);
MoveFunction(mono);
}

Restarting a Coroutine ignore the yield WaitForSeconds

I have an IEnumerator for a coroutine that spawn object like this
public IEnumerator spawnCoroutine;
private float spawnObstacleTimer;
void Start () {
spawnObstacleTimer = GameObject.Find("EventSystem").GetComponent<gameManager>().spawnObstacleTimer;
spawnCoroutine = spawnObsCoroutine(spawnObstacleTimer);
startSpawnCoroutine();
}
public IEnumerator spawnObsCoroutine(float timer)
{
while (true)
{
yield return new WaitForSeconds(timer);
spawnObs();
}
}
public void startSpawnCoroutine()
{
StartCoroutine(spawnCoroutine);
}
public void stopSpawnCoroutine()
{
StopCoroutine(spawnCoroutine);
}
And I start it on the Start() which it run fine. But I've put a collision detection to stop this Coroutine when triggered. Again this is running fine but it's when I call the OnCollisionExit() that restart the Coroutine with the function startSpawnCoroutine(); a new object is spawned immediatly and is ignoring the yield return new WaitForSeconds()
So how this happen? StopCouroutine should stop it all and when I restart it, it should wait for seconds before executing the spawn.
Thanks for your help
The function StopCoroutine(spawnCoroutine) could be better named PauseCoroutine(spawnCoroutine), when you call start again it restarts from the last yield inside the routine.
Change your startSpawnCoroutine() function to start a new instance of the routine to get it to "start from scratch"
public IEnumerator spawnCoroutine;
private float spawnObstacleTimer;
void Start () {
spawnObstacleTimer = GameObject.Find("EventSystem").GetComponent<gameManager>().spawnObstacleTimer;
startSpawnCoroutine();
}
public IEnumerator spawnObsCoroutine(float timer)
{
while (true)
{
yield return new WaitForSeconds(timer);
spawnObs();
}
}
public void startSpawnCoroutine()
{
//Moved the creation of the IEnumerable in to this function.
spawnCoroutine = spawnObsCoroutine(spawnObstacleTimer);
StartCoroutine(spawnCoroutine);
}
public void stopSpawnCoroutine()
{
StopCoroutine(spawnCoroutine);
}

Unity3D Coroutine Error

I'm stuck at Unity scripting. I got 3 files : Scene.cs , Player.cs and NetworkUtil.cs . I can't compile my code, as I don't know how to pass Coroutine response back to Scene.
In Scene.cs ( MonoBehavior class ) :
void OnGUI() {
if(GUI.Button(new Rect(0, 0, 100, 100), "Register")) {
StartCoroutine(registerPlayer());
}
}
IEnumerator registerPlayer() {
return Player.NewPlayer("Raptor"); // since Player is not IEnumerator, this line causes error.
}
In Player.cs ( Object class ) :
public static Player NewPlayer(string name) {
Player p = new Player();
result = p.tryRegister();
if(result) {
return p;
} else {
return null;
}
}
private bool tryRegister() {
Dictionary<string, string> data = new Dictionary<string, string>();
data.Add("some_key", "some_value");
NetworkUtil.ExecuteAPI(data);
}
In NetworkUtil.cs ( Object class ) :
public static IEnumerator ExecuteAPI(Dictionary<string, string> data) {
WWWForm form = new WWWForm();
form.AddField("mode", request);
foreach(KeyValuePair<string, string> entry in data) {
form.AddField(entry.Key, entry.Value);
}
WWW handler = new WWW(API_URL, form);
yield return handler;
if(handler.error != null) {
Debug.Log("Error occurred: " + handler.error); // Server Error
} else {
Debug.Log("Response: " + handler.text);
}
}
How can I change the codes to make the flow complete?
Note: In Object class, StartCoroutine() does not exist.
EDIT
I have changed my codes to use Delegates & Events:
// DelegatesAndEvents.cs
public class DelegatesAndEvents : MonoBehaviour {
public delegate void RegisterEventHandler(Player player);
public static event RegisterEventHandler onPlayerRegister;
public static void NewPlayerRegistered(Player player) {
if(onPlayerRegister != null) {
onPlayerRegister(player);
}
}
}
Then in Scene.cs:
void Start() {
DelegatesAndEvents.onPlayerRegister += this.userRegistered;
}
public void userRegistered(Player player) {
Debug.Log("User registered.");
}
How should I put the trigger in NetworkUtil.cs & Player.cs ?
There are several ways of doing that, because you not only need to return an IEnumerator, but it's Unity itself that will iterate on it, so you don't have the possibility to catch the return value.
Technically is possible to do the following:
IEnumerator registerPlayer() {
yield return Player.NewPlayer("Raptor"); // this is legal, player will be the Current value of the IEnumerator
}
If you want to do do something like the code above, you need to wrap the coroutine inside another, and iterate on it by yourself (I did something similar implementing behavior trees):
IEnumerator Wrap(IEnumerator playerRegister)
{
while(playerRegister.MoveNext())
{
Player p = playerRegister.Current as Player; //this can be done if you know what you are doing
yield return null;
}
}
Another way is not return anything and pass a delegate to the coroutine, that will be called passing back to the caller the required parameter.
Something like:
IEnumerator DoSomething(Action<Response> whenDone)
{
while (doingSomething)
yield return null;
whenDone(response);
}
EDIT
The other problem with your code is that you are calling another coroutine (ExecuteAPI)from inside Player's constructor.
So, making registerPlayer a coroutine is pointless since you are not yielding anything.
In your case the simplest way to go is to pass a delegate to ExecuteAPI that will be called when you receive the response from the server.

Making Threads in Unity using frames

I have an assignment to make a thread safe logging class that writes to a file. Every ten frames I am supposed to push some information of my choice to the logging class from a separate script. I was wondering how to do that. Here is my code so far.
public class Threading
{
public bool Execute = true;
public Vector3 player;
public Vector3 WriteTime;
System.Collections.Generic.Queue<float> values;
// Use this for initialization
void Start ()
{
}
// Update is called once per frame
void Update ()
{
}
public void execute()
{
while (Execute)
{
System.Threading.Thread.Sleep(500);
values.Enqueue(player.x);
UnityEngine.Debug.Log("value");
}
System.IO.StreamWriter write = new System.IO.StreamWriter("values.txt"); // writes to file every 5 seconds
while (values.Count > 0)
{
WriteTime = write.WriteLine(values.Dequeue().ToString());
}
write.Close();
}
public void Lock() // applied Lock threading
{
while(true)
{
lock (this)
{
// dont have anything here yet. Trying to figure out locks
}
}
}
Thank you.

Categories

Resources