petter2012 Ответов: 1

Как заставить окно быть перерисованным?


Привет,

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.

Кроме того, я действительно хотел бы иметь возможность заставить игру работать с постоянной скоростью в случае, если пользователь игрового движка хочет создать игру, основанную на времени (скажем, футбольный матч), где количество секунд имеет значение и должно быть постоянным.

Поэтому я попытался заменить это простым циклом while-loop , а затем пересчитать перемещения объектов: SpeedX = SpeedX*timePassed, что означает, что значение speedX-это скорость X в секунду, а timePassed-это количество МС, прошедших с момента последней поездки в игровом цикле.

К моему удивлению, это решение не сработало. Во-первых, игра повисла, так как не была нарисована вообще. Я предполагаю, что это никогда не занимало времени, чтобы нарисовать игру, из-за проблем с потоками. Поэтому я вставил нитку.Сон(50) , чтобы замедлить ход событий и позволить игре быть нарисованной. Глядя на точки останова, код работает так, как должен, и объекты находятся там, где они должны быть, но экран (XAML canvas в WPF) не перерисовывается. Я пробовал переключаться туда и обратно, и когда метод gameloop вместо этого установлен на EachTick, он работает, как и раньше, но не с моими изменениями.

Со всем этим фоном, как я могу исправить свой gameloop, или, скорее, как я могу заставить экран обновиться? (Код ниже, и экран обычно перерисовывается вызовом BoardSetup.DrawGame(game, Bodies.Бодилист);.)

Кроме того, я действительно не могу понять, почему это происходит, поэтому объяснение было бы потрясающим. (Я подозреваю, что с помощью таймера диспетчера потоковое решение заботится о рисовании окна отдельно или что-то в этом роде.)

Небольшое обновление: я обнаружил, что больше людей имеют ту же проблему, но я не уверен в лучшем способе ее решения. Я просто хочу что-то, что работает хорошо и быстро.

Большое спасибо!

Петтер

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


...а вот мой оригинальный код DispatcherTimer (который работает и вызывается из 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.



Наконец, здесь я рисую игру. Код немного длинный и неуклюжий, так как он позволяет использовать как "нормальные" игры, так и игры с прокруткой. Кроме того, я вижу, что логика здесь не так хороша (это незавершенная работа), но это не влияет на мой вопрос.


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


Что я уже пробовал:

Я пробовал разные варианты своего кода, но на самом деле не знаю, что искать.

1 Ответов

Рейтинг:
2

petter2012

Я нашел ответ, работая с моим коллегой. Проверьте здесь ответ:

Добавление обработчика событий (compositiontarget.rendering) в WPF[^]