Click here to Skip to main content
15,901,035 members
Please Sign up or sign in to vote.
1.00/5 (1 vote)
See more:
Hi,

A complicated question: I am developing a 2D game engine. Up until now, I have used a Dispatcher Timer/EachTick to call my gameloop code. This has worked fine, but the problem with a dispatcher timer is that if the gameloop skips a step (because of too many objects in the game to fit in one time event), the code will wait until the next dispatched time. So for example: if it's set to 100 ms and the gameloop takes 110 ms to execute, the game will pause until 200 ms have passed. This is problematic, as it causes the games to be quick or slow depending on the number of objects in the game. For example, the more enemies have been shot off, the faster the game runs.

In addition, I'd really like to be able to make a game run at a constant speed in case a user of the game engine wants to create a time-based game (say a soccer game), where the number of seconds matter and must be constant.

Therefore I have tried substituting this for a simple while-loop and then recalculating the moves of objects: SpeedX = SpeedX*timePassed , meaning that the speedX value is the X speed per second, and timePassed is the amount of ms passed since last trip in the game loop.

To my surprise, this solution didn't work. First, the game hanged as wasn't drawn at all. I guess is that it never took the time to draw the game, due to threading problems. Therefore, I inserted a Thread.Sleep(50) to slow things down and allow for the game to be drawn. Looking at breakpoints, the code runs as it should, and the objects are where they are supposed to be, but the screen (XAML canvas in WPF) is not redrawn. I have tried switching back and forth, and when the gameloop method is instead set to EachTick, it works as before but not with my changes.

With all this background, how can I fix my gameloop, or rather, how can I force the screen to update? (Code below, and the screen is usually redrawn calling BoardSetup.DrawGame(game, Bodies.Bodylist);.)

In addition I can't really understand why this happens, so an explanation would be awesome. (I suspect that using a Dispatcher Timer, a threading solution takes care of drawing the window separately, or something along those lines.)

Small update: I found that more people have the same issue, but I am not sure of the best way to solve it. I just want something that works well and is fast.

Thanks a lot!

Petter

  private void 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.


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


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

           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;

           ////Testing msg
           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());

           counter++;
       }
   }


...and here's my original DispatcherTimer code (which works, and is called from MainWindow.xaml.cs):

 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;

...and then a call to EachTick.



Finally, this is where I draw the game. The code is a bit long and clumsy, since it allows for both "normal" games and scrolling games. Also, I see that the logic is not so good here (it's a work in progress), but it doesn't impact my question at hand.


/// <summary>
/// This method draws all the shapes of the bodies (circles/rectangles) onscreen.
/// In addition, it sets the final X and Y positions of each movable object.
/// </summary>
static public void DrawGame(Canvas game, IReadOnlyList<Body> bodylist)
{
    //First, all movable bodies are deleted from the Game layout.
    //This ensures that bodies deleted from the bodylist are actually removed also from the Game.
    //An alternative would be game.Children.Clear();, but that deletes everything
    //(including static objects, score labels, etc).
    float offsetX = 0;
    float offsetY = 0;

    if (Variables.scrollingGame)
    {
        //Scrolling mode: the player stays in the middle at all times and the board is moving
        //First the offset for the game is calculated
        //float centerX = (Canvas.GetRight. - Canvas.GetLeft) / 2; //not working
        //float centerY = (Canvas.GetBottom - Canvas.GetTop) / 2;
        float centerX = 300; //temporary solution: set this to something that works for your setup.
        float centerY = 250;

        int index = Utilities.FindBodyIndex("Player"); //finds the index in the list for the player
        if (index < 0) return; //if there is no hit, the game will otherwise crash here
        Body player = bodylist[index];
        offsetX = player.PosX - centerX;
        offsetY = player.PosY - centerY;
    }
    foreach (Rectangle rect in game.Children.OfType<Rectangle>().ToArray())
    {
        if (!rect.Name.StartsWith("static_"))
        {//delete all rectangles except those that start with the keyword
            game.Children.Remove(rect);
        }
    }

    foreach (Ellipse circle in game.Children.OfType<Ellipse>().ToArray())
    {
        if (!circle.Name.StartsWith("static_"))
        {//delete all circles except those that start with the keyword
            game.Children.Remove(circle);
        }
    }

    for (int i = 0; i < Bodies.NumberOfBodies(); i++)
    {
        Body body = Bodies.Bodylist[i];
        var shape = body.GetShape();
        game.Children.Add(shape); //each movable body's shape is added again.
        if (body.Movable)
        {
            //Note that this is where a body is actually "moved".
            body.PosX = body.PosX + body.SpeedX;
            body.PosY = body.PosY + body.SpeedY;
        }
        Canvas.SetLeft(shape, body.PosX - offsetX);
        Canvas.SetTop(shape, body.PosY - offsetY);
    }
}


What I have tried:

I have tried different variations of my code, but don't know really what to look for.
Posted
Updated 20-Oct-17 3:28am
v2

1 solution

I found an answer working with a colleague of mine. Check here for answer:

Adding an eventhandler (compositiontarget.rendering) in WPF[^]
 
Share this answer
 

This content, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)



CodeProject, 20 Bay Street, 11th Floor Toronto, Ontario, Canada M5J 2N8 +1 (416) 849-8900