I am trying to teach myself C# and Unity knowledge via this course over on Udemy.com.
In one of the first examples a very basic text adventure was created.
Now starting from scratch, I have used the same game structure used there, trying to recreate my own version of this, I've run into a problem:
Input.GetKeyDown(KeyCode.R)
gets activated multiple times in a row and I haven't found the solution in hours.
Here's the relevant parts of the code:
using UnityEngine;
using UnityEngine.UI;
using System.Collections;
public class TextEngine : MonoBehaviour {
public Text myText;
private int oxygen;
private enum States
{
wakeup, window_0, room_1, corridor_1, corridor_left,
corridor_right, corridor_right_left, locked_supply_door,
damaged_supply_door, supply_door_keypad
}
private States myState;
// Use this for initialization
void Start() {
myState = States.wakeup;
}
// Update is called once per frame
void Update()
{
// Basic gameplay loop:
//if myState equals scenario a, then call method a
if (myState == States.wakeup) wakeup();
if (myState == States.room_1) room1();
if (myState == States.corridor_1) corridor_1();
if (myState == States.corridor_right) corridor_right();
if (myState == States.locked_supply_door) locked_supply_door();
}
void wakeup()
{
oxygen = 100;
myState = States.room_1;
}
void room1()
{
myText.text = "Oxygen Level: " + oxygen + "\n\nYou wake up. Press W to see Window. "
+"Press C to go down the Corridor.";
if (Input.GetKeyDown(KeyCode.W))
{
myState = States.window_0;
}
else if (Input.GetKeyDown(KeyCode.C))
{
myState = States.corridor_1;
}
}
void corridor_1()
{
myText.text = "Oxygen Level: " + oxygen + "\n\nYou walk the corridor to the end."
+ "You can turn Left or Right.\nFrom the Left you hear two gun shots!"
+"\nFrom the Right side comes an eerie silence."
+ "\n\nPress L to turn Left, R to turn Right. Press B to go Back.";
if (Input.GetKeyDown(KeyCode.L))
{
myState = States.corridor_left;
}
else if (Input.GetKeyDown(KeyCode.R))
{
print("R1");
myState = States.corridor_right;
}
else if (Input.GetKeyDown(KeyCode.B))
{
print("B");
myState = States.room_1;
}
}
void corridor_right()
{
myText.text = "Oxygen Level: " + oxygen + "\n\nAnother T-junction!\n"
+ "You can turn Left or Right.\nFrom the left you still hear nothing."
+ "\nFrom the Right side you hear a deep and frightening buzz!"
+ "\n\nPress L to turn Left, R to turn Right. Press B to go Back.";
if (Input.GetKeyDown(KeyCode.L))
{
myState = States.corridor_right_left;
}
else if (Input.GetKeyDown(KeyCode.R))
{
print("R2");
myState = States.locked_supply_door;
}
else if (Input.GetKeyDown(KeyCode.B))
{
print("B");
myState = States.corridor_1;
}
}
void locked_supply_door()
{
myText.text = "Oxygen Level: " + oxygen + "\n\nYou stand in front of a rusty metal door. You step closer. At eye-level there is a dusty sign, "
+ "barely readable. You wipe off the dust. It reads: 'Supply Room'. Maybe you can find some Oxygen tanks and "
+ "some communication devices in there?"
+ "\n\nPress S to Search for a handle."
+ "\n\nPress R to Ram the door, it looks weak!\n\n"
+ "Press B to go Back.";
if (Input.GetKeyDown(KeyCode.S))
{
myState = States.supply_door_keypad;
}
else if (Input.GetKeyDown(KeyCode.R))
{
print("R3");
myState = States.damaged_supply_door;
}
else if (Input.GetKeyDown(KeyCode.B))
{
print("B2");
myState = States.corridor_1;
}
}
}
What I expect to happen:
The game starts.
myState = States.wakeup; gets initalized.
wakeup() is called, myState = States.room_1; calls room1() via Update() and the text displayed on screen.
When pressing the C key, room(1) calls myState = States.corridor_1; which via Update() calls corridor_1() and the text is display again.
Everything working as expected so far. But now everthing goes wrong.
I expect this behavior:
4.A I press the R key, "R1" gets printed and via myState = States.corridor_right; the corridor_right() is called and waits for another key prompt.
But then this happens:
4.B I press the R key, "R1" gets printed and via myState = States.corridor_right; the corridor_right() is called and then without anymore input immediately "R2" gets printed, myState = States.locked_supply_door; is executed, which calls locked_supply_door() via Update() and then immediately "R3" gets printed to the console and then it changes myState = States.damaged_supply_door; where it would continue to call a method via Update(), but I've not yet implemented that method.
The steps seem to get executed correctly, but that GetKeyDown behaves more like `GetKey' and not like itself!
Why does GetKeyDown seemingly get activated multiple times, even when it's supposed to do so only once?
Most problems the people have seem to call GetKeyDowntwice, but not what seems several times.
A common problem seemed to be that the script is attached multiple times somewhere in Unity, which is not the case here: Canvas with Text Field + attaced Script.
If you have made it this far, I want to thank you for the time you already took to help me.
If only I could solve this problem, I cannot find the solution! My rubber duck also doesn't help me here.
The problem is that you are not doing the "update" with ELSE, so when you change from one state to another, it will not wait 1 frame to pass, and will directly go other if, because you changed and is correct, you enter the other if, and because a frame didn't pass till the last R was down, it will still say true to that...
So, in your "Update" function, make them with ELSE IF, not all IF. This way, when you enter one if, when the function inside finished, it will pass 1 frame to re enter.
So change Update to this:
void Update()
{
// Basic gameplay loop:
//if myState equals scenario a, then call method a
if (myState == States.wakeup) wakeup();
else if (myState == States.room_1) room1();
else if (myState == States.corridor_1) corridor_1();
else if (myState == States.corridor_right) corridor_right();
else if (myState == States.locked_supply_door) locked_supply_door();
}
Related
Ok, so this is something that never happened to me.
I'm working on a audio-based game, and need things to happen whenever a given midi note is played. No problem getting the note event and details, but once I call the actual function that's supposed to do something with it, something weird happens. (I'm using DryWetMidi to listen for midi NoteOn events, if that is relevant to the issue)
I'm trying to find a specific gameobject and change something within it. As soon as I search for it, the function simply stops execution without any error, exception or feedback of any kind. If I debug the code, it reaches that line fine, stops if I set a breakpoint on it, but if I click "step over" to go through the code, it simply continues execution and "steps out of the debug break" (not sure how to call this, hope it makes sense).
Here's the code
public void PlayNote(int note, int velocity)
{
Debug.Log("Playing note " + note + " at velocity " + velocity); // this logs the message fine
GameObject key = GameObject.Find(note.toString()); // execution reaches this line
// nothing is executed from this point on
if (key != null)
key.GetComponent<ObjectSetup>().SetPressed(true);
Debug.Log("Done"); // never logs anything
}
The gameobject in question is already created, as it is visible in-game, and the function works if I call it from somewhere else (let's say: from Start function of the current script).
I'm stumped, as I can't even understand why it's "jumping ship" mid-execution... If I try to search for another gameobject, or simply create a new one, it does exactly the same thing.
The first thing I see with your code is toString() instead of ‘ToString()`.
I also hazard a guess that you’re not seeing any error message because you might have unselected the error category in the console. That’s the red icon in the top right of the Console window. If that’s not selected, you won’t see any errors in the console.
You can read up about the switches at the Unity documentation.
Since you are listening to events from some external library: Are you sure these are involved on the Unity main thread?
Most of the Unity API can only be used on the main thread! I suspect this to be the issue here as it reaches the line containing GameObject.Find but nothing beyond.
You would basically need to dispatch the calls back into the Unity main thread. A pattern often used for this could look like
public class MainThreadDispatcher : MonoBehaviour
{
private static MainThreadDispatcher _instance;
public static MainThreadDispatcher Instance => _instance;
private readonly ConcurrentQueue<Action> _actions = new ();
private void Awake ()
{
if(_instance && _instance != this)
{
Destroy(gameObject);
return;
}
_instance = this;
// optionally
DontDestroyOnLoad(gameObject);
}
private void Update()
{
while(_actions.TryDequeue(out var action))
{
action?.Invoke();
}
}
public void DoInNextUpdate(Action action)
{
_actions.Enqueue(action);
}
}
public void PlayNote(int note, int velocity)
{
Debug.Log("Playing note " + note + " at velocity " + velocity); // this logs the message fine
GameObject key = GameObject.Find(note.toString()); // execution reaches this line
// nothing is executed from this point on
if (key != null)
key.GetComponent<ObjectSetup>().SetPressed(true);
Debug.Log("Done"); // never logs anything
}
And then you do
public void PlayNote(int note, int velocity)
{
MainThreadDispatcher.Instance.DoInNextUpdate(() =>
{
Debug.Log("Playing note " + note + " at velocity " + velocity); // this logs the message fine
GameObject key = GameObject.Find(note.ToString());
if (key && key.TryGetComponent<ObjectSetup>(out var setup))
setup.SetPressed(true);
Debug.Log("Done");
});
}
I am making an ability system in my game and am running into a few issues. I have set up a script that tells me which "ability" is closest to my cursor and changes the animation state if it is. With one of my abilities, the debug screen says "The closest ability is A" (which works) if selected, but if my cursor is closer to another, it says "The closest ability is A" followed by "The closest ability is B". This is screwing up my animations by triggering selected on both objects when only one should be selected. Where i my code is making this problem occur and how can I fix it so that only 1 ability can be selected at a time.
Thanks in advance!
Closest to cursor script
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class AtMouseCursor : MonoBehaviour
{
// Start is called before the first frame update
void Start()
{
}
// Update is called once per frame
void Update()
{
ClosestAbility();
Vector2 cursorPos = Camera.main.ScreenToWorldPoint(Input.mousePosition);
transform.position = cursorPos;
}
void ClosestAbility()
{
float distanceToClosestAbility = Mathf.Infinity;
Ability closestAbility = null;
Ability[] allAbilities = GameObject.FindObjectsOfType<Ability>();
foreach (Ability currentAbility in allAbilities)
{
float distanceToAbility = (currentAbility.transform.position - this.transform.position).sqrMagnitude;
if (distanceToAbility < distanceToClosestAbility)
{
distanceToClosestAbility = distanceToAbility;
closestAbility = currentAbility;
UnityEngine.Debug.Log("The Closest Ability is " + closestAbility.name);
}
}
foreach (Ability currentAbility in allAbilities)
{
if (currentAbility != closestAbility)
{
currentAbility.GetComponent<Ability>().AbilityNotSelected();
}
else if (currentAbility == closestAbility)
{
currentAbility.GetComponent<Ability>().AbilitySelected();
}
}
}
}
Ability Script (Goes on ability prefabs)
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Ability : MonoBehaviour
{
public Animator anim;
void Start()
{
anim = GetComponent<Animator>();
}
public void AbilitySelected()
{
UnityEngine.Debug.Log("Bruh im selscted");
anim.SetBool("isSelected", true);
}
public void AbilityNotSelected()
{
anim.SetBool("isSelected", false);
}
}
You just need to move your Log statement outside [after] the first loop. You won't know that it's actually the closest ability until you've checked all of them.
(And maybe add a check that allAbilities isn't null or empty.)
Update 1:
ClosestAbility() uses this.transform.position but Update() calls ClosestAbility() before updating said transform position. This looks like it would probably cause you to animate the ability closest to the old mouse position rather than the current mouse position. But it may not be noticeable if it's called every frame.
Update 2:
Code I had in mind:
void ClosestAbility()
{
float distanceToClosestAbility = Mathf.Infinity;
Ability closestAbility = null;
Ability[] allAbilities = GameObject.FindObjectsOfType<Ability>();
if ((allAbilities == null) || (allAbilities.Length == 0))
{
return;
}
foreach (Ability currentAbility in allAbilities)
{
float distanceToAbility = (currentAbility.transform.position - this.transform.position).sqrMagnitude;
if (distanceToAbility < distanceToClosestAbility)
{
distanceToClosestAbility = distanceToAbility;
closestAbility = currentAbility;
UnityEngine.Debug.Log(closestAbility.name + " is close, but there might be an ability that's closer still");
}
}
UnityEngine.Debug.Log("I checked all the abilities and " + closestAbility.name + " is the closest");
foreach (Ability currentAbility in allAbilities)
{
if (currentAbility != closestAbility)
{
currentAbility.GetComponent<Ability>().AbilityNotSelected();
}
else if (currentAbility == closestAbility)
{
currentAbility.GetComponent<Ability>().AbilitySelected();
}
}
UnityEngine.Debug.Log("Finished updating all the animations without any exceptions");
}
Update 3: I'm completely unfamiliar with Unity, but since currentAbility is an Ability, I wonder if the call to currentAbility.GetComponent<Ability>() is unnecessary at best and you could/should just have currentAbility.AbilityNotSelected()? Though if your animations ever turn on it must work at least some of the time, maybe. Still, I'd check if the GetComponent call is returning what you think it's returning.
UnityEngine.Debug.Log("The Closest Ability is " + closestAbility.name);
You are calling this for each ability in the list consecutively. So,
if (distanceToAbility < distanceToClosestAbility)
can be true two or more times in your initial foreach loop. The loop itself is fine. Just use
UnityEngine.Debug.Log("I checked all the abilities and " + closestAbility.name + " is the closest");
this code from IceGlasses code after your first foreach loop to log the closest ability.
The first main problem seems to be the calling
ClosestAbility();
before updating cursorPos and transform.position. This causes ClosestAbility function to use the transform.position value of the previous frame.
The second problem seems to be calling ClosestAbility function at every frame. So if one of the ability animations take longer than a frame and you move your cursor closer to another ability the second ability will also be triggered.
Unless you stop the first animation when the second animation starts playing OR unless you call ClosestAbility function if none of the animations are playing 2 or more animations can play at the same time.
I was working on an interactable button script for a 3D game in unity, when I noticed that if I pressed the interact button(e) for a bit longer the script ran twice despite having used the "GetKeyDown" command which should only run for one frame. I have two instances of those buttons, so I created a "buttonId" variable to differentiate each of them. However, when I looked at the console I saw that both messages came from the same button at the exact same time. If I release the button fast enough(meaning I barely tap it), the script only runs once and everything works just fine. This said, I have no idea what's wrong.
public float radius = 3;
public int buttonId = 0;
//changed in editor for every instance of the script
void Update()
{
Collider[] hitColliders = Physics.OverlapSphere(transform.position, radius);
//look for colliders in a radius
for (int i = 0; i < hitColliders.Length; i++)
{
if (hitColliders[i].CompareTag("Player"))
//checks if the collider it's looking at is the player's collider
{
// *other stuff*
if (Input.GetKeyDown("e"))
//checks whether it's the frame where e was pressed or not
{
Debug.Log("E pressed\nButton ID: " + buttonId);
// *do stuff*
}
}
}
}
Console feed:
"E pressed Button ID: 1 UnityEngine.Debug:Log(Object)
ButtonScript:Update() (at Assets/Scripts/ButtonScript.cs:90)"
this, but two times
unity version: "Unity 2019.3.14f1 Personal <DX11>"
Your logic is a bit backwards; you're doing:
Every frame, scan for close Colliders and if the Player clicks E, do something for each of them
but should be doing:
Every frame, check if Player clicks E and if someone is close, do something
Given the code supplied, it could probably be refactored to something like:
Collider[] hitColliders = Physics.OverlapSphere(transform.position, radius);
var playerCollided = hitColliders.FirstOrDefault(x => x.CompareTag("Player")) != null;
if (playerCollided)
{
// *other stuff*
}
if (playerCollided && Input.GetKeyDown("e"))
{
Debug.Log("E pressed\nButton ID: " + buttonId);
}
My bad, the player had two objects on it with the "player" tag so the script ran for both of them. I already fixed it
I am having an issue where Input.GetKeyDown is not working when I try to proceed in my text adventure game.
Once I get to the part of the game where the story talks about picking up a needle off the ground, I prompt the user to press one of three keys.
I have tried to change the keys the the player presses and that did not fix the issue.
As well I have added a Debug.Log statement to make sure that the key is being pressed.
public class ComicTextController : MonoBehaviour {
//publically exposing the gameText varible
public Text gameText;
//setting the Enum for states
private enum States { comic1, comic2, comic3, comic4, comic5, tryagain }; //Place new states here in the {} brackets
//States from enum varible
private States myState;
// Use this for initialization
void Start () {
myState = States.comic1;
}
// Update is called once per frame
void Update () {
if (myState == States.comic1)
{
state_comic1();
} else if(myState == States.comic2)
{
state_comic2();
} else if (myState == States.tryagain)
{
state_tryagain();
}
else if (myState == States.comic3)
{
state_comic3();
} else if(myState == States.comic4)
{
state_comic4();
}
}
void state_comic1()
{
gameText.text = "Rocket: Make sure to look both ways before crossing the street!I love going to Phoenix Comics." +
"They’re always so nice to me there.Did you know they do game events nearly every night of the… uh oh!" +
"Is that a needle on the ground? What do we do?\n\n"+
"Player: Pick it up. (Press the P key to pick it up)\n\n" +
"Player: Ignore it, go inside the store, and forget about it. (Press I to ignore)\n\n" +
"Player: Notice where it is, go inside the store, and tell an adult. (Press the S key to go into the Store)";
if (Input.GetKeyDown(KeyCode.P))
{
state_comic2();
Debug.Log ("Pressed the P Key");
} else if (Input.GetKeyDown(KeyCode.I))
{
state_comic3();
} else if (Input.GetKeyDown(KeyCode.S))
{
state_comic4();
}
}
void state_comic2()
{
gameText.text = "Rocket: No way! Sharp needles (sharps) are hazardous. They may be bright and colorful, but they’re not for children." +
"Maybe we should tell an adult?\n\n" +
"Press the T Key to try again";
if (Input.GetKeyDown(KeyCode.T))
{
state_tryagain();
}
}
void state_tryagain()
{
gameText.text = "Rocket: Make sure to look both ways before crossing the street!I love going to Phoenix Comics." +
"They’re always so nice to me there.Did you know they do game events nearly every night of the… uh oh!" +
"Is that a needle on the ground? What do we do?\n\n" +
"Player: Ignore it, go inside the store, and forget about it. (Press I to ignore)\n\n" +
"Player: Notice where it is, go inside the store, and tell an adult. (Press the S key to go into the Store)";
if (Input.GetKeyDown(KeyCode.I))
{
state_comic3();
} else if (Input.GetKeyDown(KeyCode.S))
{
state_comic4();
}
}
void state_comic3()
{
gameText.text = "Rocket: Although they seem like trash because they’re abandoned on the ground," +
"sharp needles (sharps) are hazardous, especially where there are lots of people walking around." +
"Maybe we should tell an adult?\n\n" +
"Press the T key to try again";
if (Input.GetKeyDown(KeyCode.T))
{
state_tryagain();
}
}
void state_comic4()
{
gameText.text = "Rocket: They were so grateful we let them know about the sharp needles (sharps) out in front." +
"Now they can carefully clean them up or let the city authorities know. Good work!\n\n" +
"Press the Right Arrow key to advance";
if (Input.GetKeyDown(KeyCode.RightArrow))
{
SceneManager.LoadScene("UrbanJungleStreet");
}
}
}
The issue is in the state_comic1() method.
I'm developing a card game but I need to have a function that stops the program until the player hasn't clicked in the PictureBox of his card to discard it.
The algorithm of my game is this:
int nextDrawer = 0; // the players which will discard a card are determinated in counterclockwise starting from the human player
for (int i = 0; i < players; i++) // untill all the players hasn't drawed a card
{
if (i == 0) .... // the human player has to click on a picture box to discard a card
else .... // an AI player will discard a card which is selected randomly from the 3 cards which AI has got in its hand
}
The problem is that when a mance ends, the first who will discard a card could change. If the players are numerated with 0 (human player), 1 (first AI player), 2 (second AI player) and 3 (third AI player), at the first mance the first to discard a card is the human player, but at the second mance the first to discard could be the 2 AI player and the human player has to wait until all the AI players before him discard a card (in this case, the round would be 2-3-0-1).
How can I cancel the click event if the AI players hasn't discarded a card yet?
UPDATE
I don't always need to wait that all AI players had drawed a card: if the winner of the mance is the number 2, the round would be 2-3-0-1: that means the player has to wait the AI players 2 and 3 drawed, then the player has to click one PictureBox, and the loop will return back to the AI players and then the AI player 1 is allowed to discard its card.
UPDATE 2
I've thought something like that:
int leader = 0; // who is going to discard first
int nextDiscarder = leader; // next player who's going to discard
for (int i = 0; i < nPlayers; i++) // until all the players hasn't discarded
{
if (nextDiscarder == 0) // the human has to discard
{
enablePictureBoxClickEvent;
// now before the loop continue the program has to wait the event click on a picture box
}
else
{
AI[nextDiscarder].discard(); // the ai player will discard
}
if (nextDiscarder == players - 1) // if nextDiscarder has reached the end of the table
nextDiscarder = 0; // return to the begin until all player has discarded a card
else
++nextDiscarder; // continue to discard with the next player
}
and in my event click I'd do something like this:
private myEventClick(object sender, EventArgs e)
{
.... // do the instructions needed to discard a card
disableMyEventClick;
returnToLoop;
}
but the main problem is that I don't know how to write in code my instruction returnToLoop.
I know most of the people will argue that you should use event-driven approach, but async/await feature can be used for easily implementing things like this w/o the need of implementing manually state machines.
I already posted similar approach in Force loop to wait for an event and A Better Way to Implement a WaitForMouseUp() Function?, so basically this is the same helper as in the former with Button replaced with Control:
public static class Utils
{
public static Task WhenClicked(this Control target)
{
var tcs = new TaskCompletionSource<object>();
EventHandler onClick = null;
onClick = (sender, e) =>
{
target.Click -= onClick;
tcs.TrySetResult(null);
};
target.Click += onClick;
return tcs.Task;
}
}
Now all you need is to mark your method as async and use await:
// ...
if (nextDiscarder == 0) // the human has to discard
{
// now before the loop continue the program has to wait the event click on a picture box
await pictureBox.WhenClicked();
// you get here after the picture box has been clicked
}
// ...
I like Ivan solution, because it looks good, and is reusable easily anywhere else you need to wait for a control.
However, I wanted to provide another solution, because I feel like the way are doing this is far more complicated that it could be.
So let's resume this :
At some point in the game, you need players to select a card they don't want to throw it away
There is one human player, which is number 0 in your array
The human player is not always the first to decide which card to throw away.
To decide which card to throw away, you display a picturebox to the player and you wait for him to click on it.
I believe a simple solution could be :
You start by removing the card for the AI players before the human (if human is first to discard, this will do nothing, if human is last, all AI will discard here)
You enable the PictureBox and you end your function
In the click event of the PictureBox, you remove the user card, then you remove the card for the remaining AI players that are after the human (if human is first, all AI will remove a card here, if human is last, you do nothing)
Done...
So this would look like this :
//We need an instance variable, to keep track of the first player
int _firstPlayerToDiscard = 0;
private void StartDiscardingProcess(int FirstToDiscard)
{
_firstPlayerToDiscard = FirstToDiscard;
if (FirstToDiscard != 0) //If the player is the first, we do nothing
{
//We discard for every other AI player after the human player
for (int i = FirstToDiscard; i < nPlayers; i++)
{
AI[i].Discard();
}
}
//Now we fill the PictureBox with the cards and we display it to the player
DiscardPictureBox.Enabled = true;
//or DiscardPictureBox.Visible = true;
//and we are done here, we know basically wait for the player to click on the PictureBox.
}
private void pictureBox_click(Object sender, EventArgs e)
{
//Now we remove the card selected by the player
// ...
//And we remove the cards from the other AI players
//Note that if the player was first to discard, we need to change the instance variable
if (_firstPlayerToDiscard == 0) { _firstPlayerToDiscard = nbPlayers; }
for (int i = 1; i < _firstPlayerToDiscard; i++)
{
AI[i].Discard();
}
}
And you're pretty much done...
NB: Sorry if syntax is bad or unusual, I usually code in VB .Net... Feel free to edit syntax issues...
The following code demonstrates a simple timer based state machine. In this case, the machine's state is the current Player's Turn. This example lets each Play decide when to let the next player have her turn by setting the state to the next player. Add additional states for other things the program should check for. This program architecture runs relatively smoothly because the program threads are not blocked in tight loops. The "faster" each player can complete and exit the turn, the better - even if the player's turn repeats 10000 times without doing anything before letting the next player play.
In the example below, the Click event handler advances the machine state from the Human's turn to the AI's turn. This effectively pauses the game until the Human Clicks. Since the Turn is not blocked in a tight loop, you can have other buttons for the Human to click on like "Pass", "Start Over", and "Quit".
using System;
using System.Windows.Forms;
using System.Timers;
namespace WindowsFormsApplication1
{
public partial class Form1 : Form
{
private System.Timers.Timer machineTimer = new System.Timers.Timer();
// These are our Machine States
private const int BEGIN_PLAY = 0;
private const int HUMAN_PLAYER_TURN = 1;
private const int AI_PLAYER_TURN = 2;
// This is the Current Machine State
private int currentPlayer = BEGIN_PLAY;
// Flag that lets us know that the Click Event Handler is Enabled
private bool waitForClick = false;
// The AI members, for example 100 of them
private const int AIcount = 100;
private object[] AIplayer = new object[AIcount];
private int AIcurrentIndex = 0; // values will be 0 to 99
public Form1()
{
InitializeComponent();
this.Show();
// The Timer Interval sets the pace of the state machine.
// For example if you have a lot of AIs, then make it shorter
// 100 milliseconds * 100 AIs will take a minimum of 10 seconds of stepping time to process the AIs
machineTimer.Interval = 100;
machineTimer.Elapsed += MachineTimer_Elapsed;
MessageBox.Show("Start the Game!");
machineTimer.Start();
}
private void MachineTimer_Elapsed(object sender, ElapsedEventArgs e)
{
// Stop the Timer
machineTimer.Stop();
try
{
// Execute the State Machine
State_Machine();
// If no problems, then Restart the Timer
machineTimer.Start();
}
catch (Exception stateMachineException)
{
// There was an Error in the State Machine, display the message
// The Timer is Stopped, so the game will not continue
if (currentPlayer == HUMAN_PLAYER_TURN)
{
MessageBox.Show("Player Error: " + stateMachineException.Message, "HUMAN ERROR!",
MessageBoxButtons.OK, MessageBoxIcon.Error);
}
else if (currentPlayer == AI_PLAYER_TURN)
{
MessageBox.Show("Player Error: " + stateMachineException.Message, "AI ERROR!",
MessageBoxButtons.OK, MessageBoxIcon.Error);
}
else
{
MessageBox.Show("Machine Error: " + stateMachineException.Message, "Machine ERROR!",
MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
}
private void State_Machine()
{
// This routine is executing in the Timer.Elapsed Event's Thread, not the Main Form's Thread
switch (currentPlayer)
{
case HUMAN_PLAYER_TURN:
Play_Human();
break;
case AI_PLAYER_TURN:
Play_AI();
break;
default:
Play_Begin();
break;
}
}
private void Play_Human()
{
// This routine is executing in the Timer.Elapsed Event's Thread, not the Main Form's Thread
// My Turn!
if (!waitForClick)
{
// Please Wait until I take a card...
// I am using this.Invoke here because I am not in the same thread as the main form GUI
// If we do not wrap the code that accesses the GUI, we may get threading errors.
this.Invoke((MethodInvoker)delegate
{
pictureBox1.Click += PictureBox1_Click;
});
// set this flag so we do not re-enable the click event until we are ready next time
waitForClick = true;
}
}
private void PictureBox1_Click(object sender, EventArgs e)
{
// This routine is executing in the Main Form's Thread, not the Timer's Thread
// Stop the game for a little bit so we can process the Human's turn
machineTimer.Stop();
// Disable the Click Event, we don't need it until next time
pictureBox1.Click -= PictureBox1_Click;
waitForClick = false;
// To Do: Human's Turn code...
// Let the AI Play now
currentPlayer = AI_PLAYER_TURN;
machineTimer.Start();
}
private void Play_AI()
{
// This routine is executing in the Timer.Elapsed Event's Thread, not the Main Form's Thread
if (AIcurrentIndex < AIcount)
{
// If we do not wrap the code that accesses the GUI, we may get threading errors.
this.Invoke((MethodInvoker)delegate
{
// To Do: AI Player's Turn code...
});
// Advance to the next AI
AIcurrentIndex++;
}
else
{
// Reset to the beginning
AIcurrentIndex = 0;
currentPlayer = BEGIN_PLAY;
}
}
private void Play_Begin()
{
// This routine is executing in the Timer.Elapsed Event's Thread, not the Main Form's Thread
// If we do not wrap the code that accesses the GUI, we may get threading errors.
this.Invoke((MethodInvoker)delegate
{
// ... do stuff to setup the game ...
});
// Now let the Human Play on the next Timer.Elapsed event
currentPlayer = HUMAN_PLAYER_TURN;
// After the Human is done, start with the first AI index
AIcurrentIndex = 0;
}
}
}
i would have design the process in a different way based on events without loop, but following your way you should use an autoreset event to notify your loop myEvent have been fired.
AutoResetEvent clickEventFired = new AutoResetEvent(false); // instanciate event with nonsignaled state
AutoResetEvent clickEventFired = new AutoResetEvent(true); // instanciate event with signaled state
clickEventFired.Reset(); // set state to nonsignaled
clickEventFired.Set(); // set state to signaled
clickEventFirect.WaitOne(); // wait state to be signaled
https://msdn.microsoft.com/en-us/library/system.threading.autoresetevent(v=vs.110).aspx
public static void yourLoop()
{
int leader = 0; // who is going to discard first
int nextDiscarder = leader; // next player who's going to discard
// instanciate auto reset event with signaled state
AutoResetEvent clickEventFired = new AutoResetEvent(true);
for (int i = 0; i < nPlayers; i++) // until all the players hasn't discarded
{
if (nextDiscarder == 0) // the human has to discard
{
enablePictureBoxClickEvent;
clickEventFired.WaitOne(); // wait for event to be signaled
}
else
{
AI[nextDiscarder].discard(); // the ai player will discard
clickEventFired.Reset(); // set event state to unsignaled
}
if (nextDiscarder == players - 1) // if nextDiscarder has reached the end of the table
nextDiscarder = 0; // return to the begin until all player has discarded a card
else
++nextDiscarder; // continue to discard with the next player
}
}
private myEventClick(object sender, EventArgs e)
{
.... // do the instructions needed to discard a card
disableMyEventClick;
clickEventFired.Set(); // signal event
}