Search code examples
c#wpf2dgame-enginegame-physics

How do I refresh the UI smartest in a 2D WPF game?


I am creating a game engine for WPF (and later also for UWP). I am trying to move away from DispatcherTimer to StopWatch, to get a more constant speed of the game (as DispatcherTimer skips turns if there are too many objects in the game).

I can write the code for updating the game correctly using a StopWatch (that is, my game objects' speeds and positions are calculated correctly when I check for ticks or ms passed since last update), but when I replace the DispatcherTimer's tick events with a simple while-loop and a call to the screen-drawing method, the GUI will never redraw.

I know there are several ways to force a redraw of the window, but what would be my best option for a 2D game in WPF? I'm just looking for something simple that works rather efficiently without a lot of hassle. It doesn't have to be super fast, but it should be rather accurate in terms of constant speed (i.e. the game should move at a constant speed regardless of the number of dynamic objects).

This is my current code for placing objects onscreen, called many times per second from my gameloop:

for (int i = 0; i < Bodies.NumberOfBodies(); 
{
    Body body = Bodies.Bodylist[i];
    var shape = body.GetShape();
    game.Children.Add(shape); //each movable body's shape is added again. 

    Canvas.SetLeft(shape, body.PosX - offsetX);
    Canvas.SetTop(shape, body.PosY - offsetY);
}

So far, I have read up on the subject, but there are many solutions and I don't know what would be the most suitable.

On stackoverflow, I have looked at many answers, including

moving physics body at a constant speed

and

WPF forcing redraw of canvas

and

Confusion about Refreshing the UI in WPF

and

How to control frame rate in WPF by using dispatcher timer accurately?

and

How can I get better timed events for my games?

There is a similiar question here: How to make a render loop in WPF? with the same reply, but the circumstances are a bit different. For example, I don't need a constant frame rate per se, I just want timing to be reasonably accurate.

There seems to be a lot of confusion about these things, and I feel that whenever I start exploring one of the ideas, I end up trying to work with very complex matters. Once again, all I want to do is to get the redraw method to actually redraw the GUI at, say, 20 times per second.

Thanks!

Petter

Edit:

My DispatcherTimer code was requested, so here it is:

    public void StartTimer()
    {
        //A note on the dispatcherTimer: http://www.wpf-tutorial.com/misc/dispatchertimer/
        //The code below fires as often as possible (depending on the computer).
        //When moves are calulated, the real elapsed time is taken in consideration.

        /*Test: 300 added movable objects; a setting of 100ms
         * --> 99 calls to CalculateMoves in 30 seconds; should have been 300.
         * In other words, the game moves at a third of the actual speed.
         * With only 30 added objects, the same settings gives 270 calls per 30 seconds.
         * With only 3 added objects, the same settings gives 273 calls per 30 seconds.
         * 
         */

        var timer = new DispatcherTimer();
        timer.Interval = new TimeSpan(0, 0, 0, 0, 10); // Each every n milliseconds (set low to avoid flicker)
        timer.Tick += EachTick;
        dtStart = DateTime.Now; //start time is set for performing various calculations

        //Add a bunch of objects for speed testing purposes
        for (int i = 0; i < 30; i++)
        {
           AddBody();
        }

        stopWatch.Start();
         timer.Start();
         // Gameloop();
    }


 private void EachTick(object sender, object e)
    {
        //while (true)
        //{

           // System.Threading.Thread.Sleep(50); //gives the computer a chance to draw the gameboard (50 = 50 millieconds. 
                                               //Increase to give less strain on the computer, but a too high value (above 10) will give a "strobo" effect.


            //This variable is used to get to the controls (labels etc) of the MainWindow (WPF)
            //  MainWindow mainWin = System.Windows.Application.Current.Windows.Cast<System.Windows.Window>().FirstOrDefault(window => window is MainWindow) as MainWindow;

            if (runGame == false) return; //only go on if not in pause mode

            //  mainWin.txtInfo.Text = "";  time.ToString("mm\\:ss");//Shows the timer in a textbox, only showing minutes and seconds.

            //if (counter % 100 == 0)
            //{
            //    //Add a flower
            //    AddBody();
            //}

            //float rndBalloon = rand.Next(75, 250); //so that the balloons come at irregular intervals

            //if (counter % rndBalloon == 0)
            //{
            //    //Add a balloon
            //    AddBalloon();
            //}

            //change direction of cloud
            int cloudIndex = (Utilities.FindBodyIndex("cloud"));
            Body cloud = Bodies.Bodylist[cloudIndex];
            if (cloud.PosX > 600) cloud.SpeedX = -0.3f;
            if (cloud.PosX < 100) cloud.SpeedX = 0.3f;

            TrackKeyboard(); //Used to see what keys are pressed by the user(s)
            EnemyMoves(); //Can be used to move enemies closer to the player
            CalculateMoves(); //move the bodies
            CollisionDetection(); //check for colliding bodies
            Bodies.DeleteBodiesMarkedForDeletion(); //remove bodies marked for deletion
            BoardSetup.DrawGame(game, Bodies.Bodylist);

            MainWindow mainWin = System.Windows.Application.Current.Windows.Cast<System.Windows.Window>().FirstOrDefault(window => window is MainWindow) as MainWindow;

            ////The score is updated onscreen
            mainWin.txtScore.Text = msg; // timesPassed.ToString(); //counter.ToString();

            //If the infotext is displayed, this code will update it (but not toggle in on/off)
            if (updateDisplayInfo == true) BoardSetup.PrintInfo(InfoMessage().ToString());

            //Each fireball becomes a little bit paler (they fade out and eventually disappear)
            FireballPaler();
            counter++;
        //}
    }

Solution

  • Have you tried setting Priority for DispatcherTimer, you can use Higher Priority to get accurate results https://msdn.microsoft.com/en-us/library/system.windows.threading.dispatcherpriority(v=vs.110).aspx

    If you need such exact frame rate this question might be a help : WPF real-time rendering

    But you still might face some issues which this question can help you

    Constant framerate in WPF for game