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
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++;
//}
}