如何强制重绘窗口? [英] How do I force the window to be redrawn?

查看:123
本文介绍了如何强制重绘窗口?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述





一个复杂的问题:我正在开发一个2D游戏引擎。到目前为止,我已经使用Dispatcher Timer / EachTick来调用我的游戏循环代码。这已经运行良好,但调度程序计时器的问题在于,如果游戏循环跳过一步(因为游戏中的对象太多以适应一次性事件),代码将等到下一个调度时间。例如:如果它设置为100毫秒并且游戏循环执行需要110毫秒,游戏将暂停直到200毫秒过去。这是有问题的,因为它会导致游戏快速或慢速,具体取决于游戏中的对象数量。例如,射击的敌人越多,游戏运行得越快。



此外,我真的希望能够让游戏运行起来如果游戏引擎的用户想要创建基于时间的游戏(比如足球游戏),其中秒数很重要并且必须是恒定的,这是一个恒定的速度。



因此我尝试将其替换为简单的while循环,然后重新计算对象的移动:SpeedX = SpeedX * timePassed,表示speedX值是每秒的X速度,而timePassed是自之后传递的ms数量游戏循环中的最后一次旅行。



令我惊讶的是,这个解决方案没有用。首先,游戏被绞死,因为根本没有画出来。我想是由于线程问题,它从来没有花时间来绘制游戏。因此,我插入了一个Thread.Sleep(50)来减慢速度并允许绘制游戏。查看断点,代码按原样运行,对象就是它们应该在的位置,但屏幕(WPF中的XAML画布)不会重绘。我试过来回切换,当gameloop方法设置为EachTick时,它像以前一样工作但不是我的更改。



有了这些背景,如何修复我的游戏循环,或者说,如何强制屏幕更新? (下面的代码,屏幕通常是重新绘制的,调用BoardSetup.DrawGame(游戏,Bodies.Bodylist);.)



另外我真的不明白为什么这个发生了,所以解释会很棒。 (我怀疑使用Dispatcher Timer,一个线程解决方案负责单独绘制窗口,或者沿着那些线绘制。)



小更新:I发现更多人有同样的问题,但我不确定解决问题的最佳方法。我只想要一些运作良好且速度快的东西。



非常感谢!



Petter



 private void Gameloop()
// private void EachTick(对象发送者,对象e)
{
while(true)
{

System.Threading.Thread.Sleep(50); //让计算机有机会绘制游戏板(50 = 50毫秒)。
//增加以减轻计算机的压力,但过高的值(大于10)会产生strobo效果。


if(runGame == false)return; //仅在暂停模式下才继续使用


// if(counter%100 == 0)
// {
// //添加flower
// AddBody();
//}

TrackKeyboard(); //用于查看用户按下了哪些键
EnemyMoves(); //可用于将敌人移近玩家
CalculateMoves(); //移动物体
CollisionDetection(); //检查碰撞体
Bodies.DeleteBodiesMarkedForDeletion(); //删除标记为删除的主体
BoardSetup.DrawGame(game,Bodies.Bodylist);

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

////测试msg
mainWin.txtScore.Text = msg; // timesPassed.ToString(); //counter.ToString();

//如果显示信息文本,此代码将更新它(但不会打开/关闭)
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); //每隔n毫秒(设置为低以避免闪烁)
timer.Tick + = EachTick;

...然后打电话给EachTick。







最后,这是我画游戏的地方。代码有点长而且笨拙,因为它允许正常游戏和滚动游戏。另外,我看到这里的逻辑不太好(这是一项正在进行中的工作),但它不会影响我手边的问题。





 ///< summary> 
///此方法在屏幕上绘制主体的所有形状(圆形/矩形)。
///此外,它设置每个可移动对象的最终X和Y位置。
///< / summary>
static public void DrawGame(Canvas游戏,IReadOnlyList< Body> bodylist)
{
//首先,所有可移动的物体都从游戏布局中删除。
//这可以确保从bodylist中删除的实体也从游戏中删除。
//替代方案是game.Children.Clear();,但删除所有
//(包括静态对象,分数​​标签等)。
float offsetX = 0;
float offsetY = 0;

if(Variables.scrollingGame)
{
//滚动模式:玩家始终处于中间位置且棋盘移动
//首先计算游戏的偏移量
//浮动中心X =(Canvas.GetRight。 - Canvas.GetLeft)/ 2; //不工作
// float centerY =(Canvas.GetBottom - Canvas.GetTop)/ 2;
float centerX = 300; //临时解决方案:将此设置为适合您的设置的内容。
浮动中心Y = 250;

int index = Utilities.FindBodyIndex(Player); //在列表中找到播放器
if(index< 0)返回的索引; //如果没有命中,游戏将在此处崩溃
Body player = bodylist [index];
offsetX = player.PosX - centerX;
offsetY = player.PosY - centerY;
}
foreach(游戏中的Rectangle rect.Children.OfType< Rectangle>()。ToArray())
{
if(!rect.Name.StartsWith(static_) ))
{//删除除以关键字
game.Children.Remove(rect)开头的所有矩形;
}
}

foreach(游戏中的椭圆圈。孩子.OfType< Ellipse>()。ToArray())
{
if(! circle.Name.StartsWith(static_))
{//删除除以关键字
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); //再次添加每个可移动体的形状。
if(body.Movable)
{
//注意这是一个实体移动的地方。
body.PosX = body.PosX + body.SpeedX;
body.PosY = body.PosY + body.SpeedY;
}
Canvas.SetLeft(shape,body.PosX - offsetX);
Canvas.SetTop(shape,body.PosY - offsetY);
}
}





我尝试了什么:



我尝试了不同的代码变体,但不知道到底要找什么。

解决方案

< blockquote>我找到了一个与我同事合作的答案。点击这里查看答案:



在WPF中添加eventhandler(compositiontarget.rendering) [ ^ ]


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.

解决方案

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

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


这篇关于如何强制重绘窗口?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆