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
}
Related
I am using this code along with other methods after it, but cannot get the form I've been using in designer to pop up. The only thing that pops up successfully is the MessageBox asking the player if they would like to play as X. I've tried commenting that out to see if that's the issue for why it won't load, but I'm completely lost as to why my form will not load at all.
namespace mmelichar_Topic6_Activity13
{
public partial class mmelichar_TicTacToe : Form
{
int player = 0;
int position;
int turn = 0;
int playerMove;
int firstMove;
int secondMove;
string[,] location = new string[3, 3] { { "", "", "" }, { "", "", "" }, { "", "", "" } };
public mmelichar_TicTacToe()
{
InitializeComponent();
}
private void mmelichar_TicTacToe_Load(object sender, EventArgs e)
{
//There's only two moves that have to be hard-coded for AI to be able to
//play tic-tac-toe near perfectly, each game /should/ result in a tie or
//a win for the CPU. Other than that, the tryWin and tryBlock methods
//should be able to win the game if there is an availability for that,
//or block the opponent from winning if they cannot win quite yet.
Random rnd = new Random();
DialogResult dialogResult = MessageBox.Show("Do you want to play as X?", "Player Choice", MessageBoxButtons.YesNo, MessageBoxIcon.Information);
if (dialogResult == DialogResult.Yes)
{
player = 1;
}
else if (dialogResult == DialogResult.No)
{
player = 0;
}
//player is O
if (player == 0 && turn == 0)
{
firstTurnCPU();
//player turn
mre.WaitOne();
}
//player is X
if (player == 1 && turn == 0)
{
//player turn 1
mre.WaitOne();
//cpu turn 1
firstTurnCPU();
//player turn 2
mre.WaitOne();
//cpu turn 2
secondTurnCPU();
//player turn 3
mre.WaitOne();
//cpu turn 3
tryWin();
tryBlock();
//player turn 4
mre.WaitOne();
//cpu turn 4
tryWin();
tryBlock();
//player turn
}
}
private readonly ManualResetEvent mre = new ManualResetEvent(false);
private void playerTurn_EventHandler(object sender, EventArgs e)
{
mre.Set();
}
edit: updated code to remove while loop and include my ManualResetEvent
You have an infinite loop:
while (playing)
{
// perform a bunch of logic
// but probably don't do anything async or properly interact with the UI
}
An infinite loop on the UI threat would certainly prevent the UI from ever drawing to the screen. (It may also be running away with the CPU a lot more than you want it to.)
If you really do want a "game loop" style of game construction, there are approaches you can take in Windows Forms. But now would be a good time to really think through the design of the game before going that route.
Windows Forms is highly event-driven. It's idle most of the time, and responds when users interact with the UI. (A click here, a mouse-over there, etc.) If your game fits that structure (turn based, etc.) then use that structure since it's more native to Windows Forms.
You can also combine the two, using Windows Forms events to process user interactions, but when the program launches you can create a separate thread for your game loop to process ongoing events/logic in the background. Just make sure that loop isn't killing the CPU by constantly running. It's reasonable to sleep the thread for a moment on each iteration of the loop.
I had two problems within my code:
First, an infinite while loop that I forgot to remove, increasing CPU usage needlessly.
Second, use of ManualResetEvent instead of just using normal flow within the program to hand control back and forth from player to CPU and vice-versa.
I am trying to create a timer, which, for example, every 3 seconds during eg 15 seconds will perform an action.
I tried to use gameTime.ElapsedGameTime.TotalSeconds and loop, but unfortunately it doesn't work.
I have an Attack () function that reduces player statistics when an enemy attacks it. I would like that in case of one particular enemy, this function for a specified period of time would subtract player's HP, eg for every 3 seconds. I guess it should be done in the Update function to access gameTime, unfortunately, I have no idea how to do it.
public override Stats Attack()
{
attack = true;
return new Stats(0, -stats.Damage, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
}
public override void Update(GameTime gameTime)
{
spriteDirection = Vector2.Zero; // reset input
Move(Direction); // gets the state of my keyborad
float deltaTime = (float)gameTime.ElapsedGameTime.TotalSeconds; // make movement framerate independant
spriteDirection *= Speed; // add hero's speed to movement
position += (spriteDirection * deltaTime); // adding deltaTime to stabilize movement
totalPosition = new Vector2((int)((BottomBoundingBox.Center.X) / 32.0f), (int)((BottomBoundingBox.Center.Y) / 32.0f));
base.Update(gameTime);
}
I will make it simple, so you need to modify my code to achieve your desire result.
My best guess is that you want to have a special effect when your monsters hit your player.
First, you need to check if the monster actually hits the player (if collision is detected):
if (collision)//if it's true
{
// Apply your special effect if it is better than
// the one currently affecting the target :
if (player.PoisonModifier <= poisonModifier) {
player.PoisonModifier = poisonModifier;
player.ModifierDuration = modifierDuration;
}
//player.setColor(Color.Blue);//change color to blue
player.hitPoints -= Poision.Damage;//or enemy.PoisonDamage or whatever you define here
hit.Expire();//this can be for the arrow or bullet from your enemy or simply just a normal hit
}
In your Player class, you need:
public float ModifierDuration {
get {
return modifierDuration;
}
set {
modifierDuration = value;
modiferCurrentTime = 0;
}
}
Then in Update method of Player class:
// If the modifier has finished,
if (modiferCurrentTime > modifierDuration) {
// reset the modifier.
//stop losing HP code is here
modiferCurrentTime = 0;//set the time to zero
setColor(Color.White);//set back the color of your player
}
count += gameTime.ElapsedGameTime.TotalSeconds;//timer for actions every 3s
if (posionModifier != 0 && modiferCurrentTime <= modifierDuration) {
// Modify the hp of the enemy.
player.setHP(player.getCurrentHP() - posionDamage);
//Or change it to every 3s
//if (count > 3) {
// count = 0;
//DoSubtractHP(player);
//}
// Update the modifier timer.
modiferCurrentTime += (float) gameTime.ElapsedGameTime.TotalSeconds;
setColor(Color.Blue);//change the color to match the special effect
}
Hope this helps!
You need to store the start time, or the last time that the action was carried out. Then during each update compare the elapsed time to the stored time. If 3 seconds have passed then perform the action, store the current time and repeat the process.
I do not know monogame, but if I were doing this in one of my C# applications, I would use a timer, and pass in anything that the timer would need to modify.
There is good info here https://learn.microsoft.com/en-us/dotnet/api/system.timers.timer?view=netframework-4.8 and I stole a bit of code from here and modified it as an example to demonstrate my idea. I extended the System.Timer to allow it to run for a duration and stop itself. You can set the frequency and duration and forget about it. Assuming that you are able to update this information from a timer.
class Program
{
private static FixedDurationTimer aTimer;
static void Main(string[] args)
{
// Create a timer and set a two second interval.
aTimer = new FixedDurationTimer();
aTimer.Interval = 2000;
// Hook up the Elapsed event for the timer.
aTimer.Elapsed += OnTimedEvent;
// Start the timer
aTimer.StartWithDuration(TimeSpan.FromSeconds(15));
Console.WriteLine("Press the Enter key to exit the program at any time... ");
Console.ReadLine();
}
private static void OnTimedEvent(Object source, System.Timers.ElapsedEventArgs e)
{
FixedDurationTimer timer = source as FixedDurationTimer;
if (timer.Enabled)
{
Console.WriteLine("The Elapsed event was raised at {0}", e.SignalTime);
}
}
public class FixedDurationTimer : System.Timers.Timer
{
public TimeSpan Duration { get; set; }
private Stopwatch _stopwatch;
public void StartWithDuration(TimeSpan duration)
{
Duration = duration;
_stopwatch = new Stopwatch();
Start();
_stopwatch.Start();
}
public FixedDurationTimer()
{
Elapsed += StopWhenDurationIsReached;
}
private void StopWhenDurationIsReached(object sender, ElapsedEventArgs e)
{
if (_stopwatch != null && Duration != null)
{
if (_stopwatch.Elapsed > Duration)
{
Console.WriteLine("Duration has been met, stopping");
Stop();
}
}
}
}
}
You could see examples of how to pass objects into the timer here (#JaredPar's example) How do I pass an object into a timer event?
string theString = ...;
timer.Elapsed += (sender, e) => MyElapsedMethod(sender, e, theString);
static void MyElapsedMethod(object sender, ElapsedEventArgs e, string theString) {
...
}
One way to do this would be to use coroutines. MonoGame does not have built-in support for them like other game engines, but they are not too complicated to implement yourself. You need some knowledge of the yield keyword and enumerators to understand them, but once abstracted away they make your game code way easier to write and understand.
Here's an example of what your gameplay logic would look using a Coroutine system like the one described below:
public void Attack(Enemy enemyAttacking)
{
if (enemyAttacking.Type == "OneParticularEnemy")
{
StartCoroutine(RunDamageOverTimeAttack());
}
}
// This coroutine starts a second coroutine that applies damage over time, it
// then waits 15 seconds before terminating the second coroutine.
public IEnumerator RunDamageOverTimeAttack()
{
var cr = StartCoroutine(ApplyDamageOverTime());
yield return 15000; // in milleseconds (ms), i.e. 15000 ms is 15 seconds
cr.IsFinished = true;
}
// This coroutine applies the damage every 3 seconds until the coroutine is finished
public IEnumerator ApplyDamageOverTime()
{
while (true)
{
ApplyDamageToPlayer();
yield return 3000;
}
}
The code reads very close to the way you described the actual problem you're trying to solve. Now for the coroutine system...
The StartCouroutine method creates a Coroutine class instance and stores it. During the Update step of the game loop you iterate through the coroutines and update them, providing gameTime to calculate when the next step of the method should run. Each step executes the code in the routine until a yield is found OR until the method ends naturally. Once the coroutine is finished you clear them out. This logic looks something like this:
private List<Coroutine> coroutines = new List<Coroutine>();
public Coroutine StartCoroutine(IEnumerator routine)
{
var cr = new Coroutine(routine);
couroutines.Add(cr);
return cr;
}
public void UpdateCoroutines(GameTime gameTime)
{
// copied in case list is modified during coroutine updates
var coroutinesToUpdate = coroutines.ToArray();
foreach (coroutine in coroutinesToUpdate)
coroutine.Update(gameTime);
coroutines.RemoveAll(c => c.IsFinished);
}
public void Update(GameTime gameTime)
{
// normal update logic that would invoke Attack(), then...
UpdateCoroutines(gameTime);
}
A Coroutine class is responsible for tracking the time remaining between steps of the routine, and tracking when the routine is finished. It looks something like this:
public class Coroutine
{
private IEnumerator routine;
private double? wait;
public Coroutine(IEnumerator routine)
{
this.routine = routine;
}
public bool IsFinished { get; set; }
public void Update(GameTime gameTime)
{
if (IsFinished) return;
if (wait.HasValue)
{
var timeRemaining = wait.Value - gameTime.ElapsedGameTime.TotalMilliseconds;
wait = timeRemaining < 0 ? null : timeRemaining;
// If wait has a value we still have time to burn before the
// the next increment, so we return here.
if (wait.HasValue) return;
}
if (!routine.MoveNext())
{
IsFinished= true;
}
else
{
wait = routine.Current as double?;
}
}
}
This may seem considerably more complex than other solutions provided here, and it may be overkill, but Coroutines allow you to forgo tracking a bunch of state in tracking variables, making complex scenarios easier to follow and cleaner to read. For example, here's a arrow spawning strategy I used Coroutines for in Ludum Dare 37. It spawns 3 arrows 600 milleseconds apart with a 3 second wait between them: https://github.com/srakowski/LD37/blob/477cf515d599eba7c4b55c3f57952865d894f741/src/LD37/GameObjects/BurstArrowSpawnBehavior.cs
If you'd like more social proof of the value of Coroutines take a look at Unity. Unity is one of the more popular game engines, and it has Coroutine support. They describe a scenario where it is useful in their documentation: https://docs.unity3d.com/Manual/Coroutines.html.
I use this for my game :
Public Async Function DelayTask(Time As Double) As Threading.Tasks.Task
Await Threading.Tasks.Task.Delay(TimeSpan.FromSeconds(Time))
End Function
Converted to C# :
public async System.Threading.Tasks.Task DelayTask(double Time)
{
await System.Threading.Tasks.Task.Delay(TimeSpan.FromSeconds(Time));
}
You would use it like this in an Async Function :
Await DelayTask(1.5);
The number is in seconds, you can change this by changing the TimeSpan.whateverformat.
Considering that you'll have various things that affect your stats maybe you're better off at having an update subroutine in your Stats class that will check a list of effects that are scheduled to update after one point in time.
This would be better for performance than having each effect relying on its own thread.
I would like my app to show the message on the first back button press as "Please touch back button again to quit the app" and when it is pressed again the app should quit. I think I have added appropriate code but it doesn't work.
The script is attached as a component to the canvas element.
The script contains the public variable which I assigned the panel(Child of canvas) UI element.
Scene hierarchy
Observed:
When I pressed the back button the text appears but only a fraction of a second and then disappear all of a sudden and the next back button press did not resulted in app quit.
Desired
On first back button press it should show the message and with in say 3 seconds if the second back button pressed the app should quit.
Relevant information:
Unity 2017.1.0f3
Here is the Code link :
https://gist.github.com/bmohanrajbit27/431221fc80e0b247649289fd136f9cfb
public class ChangeSceneScript : MonoBehaviour
{
private bool iQuit = false;
public GameObject quitobject;
void Update()
{
if (iQuit == true)
{
if (Input.GetKeyDown(KeyCode.Escape))
{
Application.Quit();
}
}
if (Input.GetKeyDown(KeyCode.Escape))
{
quitobject.SetActive(true);
iQuit = true;
StartCoroutine(QuitingTimer());
}
}
IEnumerator QuitingTimer()
{
yield return new WaitForSeconds(3);
iQuit = false;
quitobject.SetActive(false);
}
}
I've seen few instances where Application.Quit(); did not work on Android. When this happens, use System.Diagnostics.Process.GetCurrentProcess().Kill(); to exit out of the program.
Now, for your timer issue, start a coroutine in the Update function when input is pressed for the first time. Use a flag to make sure that this coroutine function is not started again until the last one has finished. A boolean variable is fine.
Inside, that coroutine function, don't use yield return new WaitForSeconds(3); to wait for the timer. Use a while loop with the combination of yield return null; to wait until the timer is done. Increment the timer with Time.deltaTime each frame. Now, you can easily check for the second press in that coroutine function and exit if pressed.
If you also want this to work in the Editor, you have to use UnityEditor.EditorApplication.isPlaying = false; to exit. The example below should also work in the Editor. See the comments in the code if you have any question.
public GameObject quitobject;
private bool clickedBefore = false;
void Update()
{
//Check input for the first time
if (Input.GetKeyDown(KeyCode.Escape) && !clickedBefore)
{
Debug.Log("Back Button pressed for the first time");
//Set to false so that this input is not checked again. It will be checked in the coroutine function instead
clickedBefore = true;
//Activate Quit Object
quitobject.SetActive(true);
//Start quit timer
StartCoroutine(quitingTimer());
}
}
IEnumerator quitingTimer()
{
//Wait for a frame so that Input.GetKeyDown is no longer true
yield return null;
//3 seconds timer
const float timerTime = 3f;
float counter = 0;
while (counter < timerTime)
{
//Increment counter while it is < timer time(3)
counter += Time.deltaTime;
//Check if Input is pressed again while timer is running then quit/exit if is
if (Input.GetKeyDown(KeyCode.Escape))
{
Debug.Log("Back Button pressed for the second time. EXITING.....");
Quit();
}
//Wait for a frame so that Unity does not freeze
yield return null;
}
Debug.Log("Timer finished...Back Button was NOT pressed for the second time within: '" + timerTime + "' seconds");
//Timer has finished and NO QUIT(NO second press) so deactivate
quitobject.SetActive(false);
//Reset clickedBefore so that Input can be checked again in the Update function
clickedBefore = false;
}
void Quit()
{
#if UNITY_EDITOR
UnityEditor.EditorApplication.isPlaying = false;
#else
//Application.Quit();
System.Diagnostics.Process.GetCurrentProcess().Kill();
#endif
}
Essentially I have a door trigger event where if the player presses the switch then the door open. I wish however to do it so that if the door associated needs two switches to open well... then it only opens if two switches are pressed. Here is my code for my InteractSwitch
public class InteractSwitch : MonoBehaviour
{
Animator animator;
public DoorEventTrigger[] doorTriggers;
public bool pressed;
private bool down;
// Use this for initialization
void Start ()
{
animator = GetComponent <Animator> ();
}
// Update is called once per frame
void Update ()
{
}
void OnCollisionEnter2D (Collision2D target)
{
if (target.gameObject.tag == "Player") {
down = true;
animator.SetInteger ("SwitchTrig", 1);
foreach (DoorEventTrigger trigger in doorTriggers)
if (trigger != null)
trigger.Toggle (true);
}
}
When triggered this event checks for player, shows switch has been pressed then sends a bool to a function called Toggle which handles the operation of the door.
Next I have my DoorEventTrigger event which checks if the bool sent = true. If it is then the door will open. Here is where I am stuck. As seen in the code I have created a array of InteractSwitch which stores the amount of switches I want the player to have pressed before the door will open. I then state that if the length > 1 then this if condition is true and some code will be added here which will then open the door only if the player has selected the InteractSwitch[].length switches. My question is how would I check that all instances of InteractSwitch for this DoorEventTrigger equals true?
Here is my DoorEventTrigger code
public void Toggle (bool val)
{
if (switchTriggers.Length > 1) {
Debug.Log ("HAS THIS ACTUALLY DONE ANYTHING");
door.Open ();
}
else
{
if (val)
door.Open ();
else
door.Close ();
}
}
}
The solution I came up with is nowhere near I believe the most efficient answer as it requires code each time u add a new switch but this is what I have
firstly in the InteractSwitch script the bool down is public so it can be accessed by the switchtriggers
public bool down;
Next is the added code to the Toggle() method
public void Toggle (bool val)
{
if (switchTriggers.Length > 1) {
if((switchTriggers[0].down == true) && (switchTriggers[1].down == true)){
Debug.Log ("THIS ACTUALLY DONE SOMETHING!");
door.Open ();
}
}
else
{
if (val)
door.Open ();
else
door.Close ();
}
}
Now from the public SwitchInteract[]; I know the exact amount of elements in the array however this could be checked through switchTriggers.length; Knowing there is two elements in the array my condition switchTriggers.Length > 1 is correct so next I take the two elements in the array and check if they are down; if BOTH are down then the condition continues and the Debug.Log() validates this. Finally Open() is called which opens the door.
You can have the switches increment or decrement a required switch variable in the door object.
Give the door object a requiredSwitch variable.
Give each switch a door variable and set it to the door it controls.
When a switch is activated, have it decrement its door's requiredSwitch variable.
When the door's requiredSwitch variable is 0, then the door opens.
You can also have the switches increment the variable if they get deactivated.
This solution allows you to add switches by just setting the door variable of the switch and incrementing the door's initial requiredSwitch variable.
I have the a script called Timer.cs. This script is connected to some GUI Text, which displays the amount of time remaining in the game.
Also attached to this script is an Audio Source with my desired sound selected. When the clock reaches zero, the text changes to say "GAME OVER!" and the character controls lock up; however, the sound does not play.
All other instances of audio.Play() in my scene are working fine, and when I set the Audio Source to "Play On Awake", it plays without a problem. What could be the problem?
Using UnityEngine;
using System.Collections;
public class Timer : MonoBehaviour {
public float timer = 300; // set duration time in seconds in the Inspector
public static int sound = 1;
public static int go = 1;
bool isFinishedLevel = false; // while this is false, timer counts down
void Start(){
PlayerController.speed = 8;
PlayerController.jumpHeight = 12;
}
void Update (){
if (!isFinishedLevel) // has the level been completed
{
timer -= Time.deltaTime; // I need timer which from a particular time goes to zero
}
if (timer > 0)
{
guiText.text = timer.ToString();
}
else
{
guiText.text = "GAME OVER!"; // when it goes to the end-0,game ends (shows time text over...)
audio.Play();
int getspeed = PlayerController.speed;
PlayerController.speed = 0;
int getjumpHeight = PlayerController.jumpHeight;
PlayerController.jumpHeight = 0;
}
if (Input.GetKeyDown("r")) // And then i can restart game: pressing restart.
{
Application.LoadLevel(Application.loadedLevel); // reload the same level
}
}
}
Given that you are calling it as part of your Update routine, I'd have to guess that the problem is you calling it repeatedly. I.e. you're calling it every frame as long as timer <= 0.
You shouldn't call Play() more than once. Or at least not again while it is playing. A simple fix would be something along the lines of
if(!audio.isPlaying)
{
audio.Play();
}
See if that solves your problem, and then you can take it from there.
I had error using audio.Play(); and used following it fixed the error for me
GetComponent<AudioSource>().Play();