0

I'm trying to create a countdown where the text displays, "GAME STARTS IN: " and using a for loop and Thread.Sleep a variable counts down from three. I started by using the designer to create the "game starts in:" part, but after the variable wouldn't show up I moved it to code. Now nothing shows up. This is what I have now in my timer method:
if (!countedDown)
DoCountdown();

Countdown.Hide();

And then in a DoCountdown method:

this.Countdown.BackColor = System.Drawing.Color.Transparent;                                                               
this.Countdown.ForeColor = System.Drawing.Color.White;
this.Countdown.Location = new System.Drawing.Point(360, 17);
this.Countdown.Name = "Countdown";
this.Countdown.Font = new System.Drawing.Font("Segoe UI", 12F, 
System.Drawing.FontStyle.Regular, 
System.Drawing.GraphicsUnit.Point);
this.Countdown.Size = new System.Drawing.Size(185, 24);
this.Countdown.TabIndex = 6;
                       
 countedDown = true;
 for (int i = 3; i > 0; i--)
 {
    Countdown.Text = "GAME STARTS IN: " + i;
    System.Threading.Thread.Sleep(1000);
 }

I put a breakpoint at System.Threading.Thread.Sleep(100) and everything seemed normal. Countdown.Text was equal to "GAME STARTS IN: 3". After trying to integrate the solutions the text doesn't show up. Here is some more context in my code:

This is from my start screen form

   private void QuitGame(object sender, EventArgs e)
   {
      Application.Exit();
   }

    private void StartMultiplayerGame(object sender, EventArgs e)
    {
        GameScreen startGame = new GameScreen();
        startGame.Show();
        Hide();
    }
ineedhelp
  • 11
  • 3
  • `Countdown.Text = "GAME STARTS IN: " + i;` is not going to get updated until the `for` loop is finished. – JohnG Aug 04 '21 at 08:06
  • 1
    You could add a Countdown.Refresh() in the loop... But locking up the main thread like this is bad practice... Try using a Timer with events instead. – Dan Byström Aug 04 '21 at 08:10
  • 1
    I suggest starting `Timer` that fires every 1000ms and simply keep a global count... when it hits 3, stop the timer and proceed. – JohnG Aug 04 '21 at 08:13
  • I'll try this but I might have some trouble, not being very familiar with c# and winForms. – ineedhelp Aug 04 '21 at 08:22
  • Perhaps consider Microsoft's Reactive Framework and you can do this: `Observable.Timer(TimeSpan.Zero, TimeSpan.FromSeconds(1.0)).Take(3).ObserveOn(this).Subscribe(x => Countdown.Text = $"GAME STARTS IN: {3 - x}", () => Countdown.Text = $"Started");` – Enigmativity Aug 04 '21 at 09:03

3 Answers3

1

Try something like below. A button is used to start the timer and set the initial values.

int count = 3;
private void button2_Click(object sender, EventArgs e) {
  timer1.Interval = 1000;
  count = 3;
  label1.Text = "GAME STARTS IN: " + count;
  timer1.Start();
}

private void timer1_Tick(object sender, EventArgs e) {
  count--;
  if (count != 0) {
    label1.Text = "GAME STARTS IN: " + count;
  }
  else {
    timer1.Stop();
    label1.Text = "GAME STARTED";
    MessageBox.Show(" -> GO");
  }
}

Edit per OP comments.

Try the code like this in the start screen form...

private void StartMultiplayerGame(object sender, EventArgs e) {
  count = 3;
  label1.Text = "GAME STARTS IN: " + count;
  timer1.Start();
}

Then change the timer code to...

private void timer1_Tick(object sender, EventArgs e) {
  count--;
  if (count != 0) {
     label1.Text = "GAME STARTS IN: " + count;;
  }
  else {
    timer1.Stop();
    label1.Text = "Game Started";
    GameScreen startGame = new GameScreen();
    startGame.Show();
    this.Hide();
  }
}
JohnG
  • 9,259
  • 2
  • 20
  • 29
  • Would it be possible to have a method to create the timer because I want the countdown to occur multiple times? I am making a pong game so when the ball goes off the screen it will reset, the timer counts down from three and then the timer end the game proceeds. – ineedhelp Aug 04 '21 at 08:33
  • You can use the same `Timer`... just call its `timer1.Start()` method to use it again. – JohnG Aug 04 '21 at 08:35
  • Show the code that is giving you problems. I tested the code and it works as expected so I can only guess you are doing something wrong. Also you are contradicting yourself… first you say… _“I was hoping for there to be a way for this to work without having to click anything.”_ … Then the very next sentence…. _”I wanted there to be a delay so that when you click start game in the main menu…”_ … ? You can start the timer anywhere you want. Also, when you start the timer…. You need to set `count = 3` BEFORE you start the timer. Please show your updated code. [edit] your question to add the code. – JohnG Aug 04 '21 at 13:18
  • If you want the game to start after the countdown... then you will need to START the game IN the TIMER when the count hits zero (0). In other words... in my code where you see `MessageBox.Show(" -> GO");` ... this is where you would START the game. – JohnG Aug 04 '21 at 13:20
  • I have a startScreen form that contains a start game button. How would I implement your code there without CS0103 errors since the Countdown and timer are in a separate form? – ineedhelp Aug 04 '21 at 13:35
  • I am not following... where is the code that starts the timer? I would think that in the start screen form, when the user clicks start, your code would start the timer, then IN the timer code, when it reaches zero... THEN open the other form. If you want another `Timer` in the other form, then you could create a new one in THAT form. – JohnG Aug 04 '21 at 13:48
  • I apologize if I was unclear. The timer is in the gameScreen form. I know this isn't ideal but I am not a very experienced coder and I followed a tutorial so this is how they dd it. – ineedhelp Aug 04 '21 at 13:53
0

loop blocking the main thread to refresh UI so the required scenario can be archived by moving the loop to a separate method

void doCountDown()
        {
            for (int i = 10; i > 0; i--)
            {
                setCountDownText( "GAME STARTS IN: " + i);
                System.Threading.Thread.Sleep(1000);
            }
        }

creating anew thread that start this method

 new System.Threading.Thread(new System.Threading.ThreadStart(doCountDown)).Start();

and because of the need to update UI in another thread and to make it safe separate the setText in a separate method that update based on checking required to invoke property this will make it work in all cases

 void setCountDownText(string txtValue)
        {
            if (Countdown.InvokeRequired)
            {
                Action safeWrite = delegate { setCountDownText(txtValue); };
                Countdown.Invoke(safeWrite);
            }
            else
                Countdown.Text = txtValue;
        } 
MHassan
  • 415
  • 4
  • 9
  • I tried this solution and although the game did wait for three seconds, unfortunately, no text was displayed. – ineedhelp Aug 04 '21 at 12:52
  • @ineedhelp kindly check the running sample attached on my repository https://github.com/mhassanshaker/GameCountDown – MHassan Aug 04 '21 at 14:14
0

The "modern" way to do this is using async/await.

For example, launching the DoCountdown() from a button handler could look like this:

async void testBtn_Click(object sender, EventArgs e)
{
    await DoCountdown();
}

async Task DoCountdown()
{
    // <Initialisation of Countdown elided for brevity>

    for (int i = 3; i > 0; i--)
    {
        Countdown.Text = "GAME STARTS IN: " + i;
        await Task.Delay(1000);
    }
}

However, whatever calls DoCountdown() will need to be declared as async, and so on up the call tree.

Note that the only acceptable place to have async void rather than async Task as a return type for an async method is where the method is an event handler such as the button handler in the example above.

Matthew Watson
  • 104,400
  • 10
  • 158
  • 276