【问题标题】:MonoGame draw function is not redrawingMonoGame 绘图功能不重绘
【发布时间】:2021-06-04 17:26:55
【问题描述】:

我最近开始玩monogame,为了了解它的工作原理,我想对排序算法进行可视化。

当我启动程序时,draw() 函数不会在当前状态下重绘列。

它显示第一个状态,在循环结束时只是跳转到排序状态。

我错过了什么吗?

protected override void Update(GameTime gameTime)
{
    if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed || Keyboard.GetState().IsKeyDown(Keys.Escape))
        Exit();

    if (Keyboard.GetState().IsKeyDown(Keys.Enter))
    {
        int temp;
        for (int j = 0; j <= MainArray.Length - 2; j++)
        {
            for (int i = 0; i <= MainArray.Length - 2; i++)
            {   
                if (MainArray[i] > MainArray[i + 1])
                {
                    temp = MainArray[i + 1];
                    MainArray[i + 1] = MainArray[i];
                    MainArray[i] = temp;

                    Draw(gameTime);
                    Thread.Sleep(300);
                }
            }
        }
    }
    
    // TODO: Add your update logic here

    base.Update(gameTime);
}

protected override void Draw(GameTime gameTime)
{
    GraphicsDevice.Clear(Color.Black);

    spriteBatch.Begin();
    for (int i = 0; i < MainArray.Length; i++)
    {
        sprite.Draw(new Vector2(i * 40 + 20, -10), spriteBatch, new Rectangle(i * 40 + 30, 0, 30, MainArray[i] * 8));
    }
    spriteBatch.End();

    // TODO: Add your drawing code here

    base.Draw(gameTime);
}

【问题讨论】:

  • 没有必要在Update() 方法中调用Draw(gameTime),因为Draw() 方法已经自己调用了。也不鼓励使用Thread.Sleep(),因为这会迫使程序等待并且在其间什么也不做。
  • for (int j = 0; j &lt;= MainArray.Length - 2; j++) 行中存在错误。它应该是:for (int j = 0; j &lt; MainArray.Length; j++) 以确保它涵盖所有可能性。冒泡排序需要 N*N 次比较才能解决最坏的情况。

标签: c# monogame


【解决方案1】:

调用Draw() 只会将您要求它绘制的任何内容添加到一种存储应绘制内容的“绘制缓冲区” 中。它不是直接在屏幕上显示像素。

在屏幕上显示像素的操作由 Monogame 调用Update() 内部完成。在您的代码中,您正在用新的排序状态覆盖 "draw buffer"。当需要在屏幕上显示像素时,Monogame 会获取 “绘制缓冲区” 上的所有内容并进行渲染。这就是为什么你只能看到最后一个状态。

这大致是 Monogame 游戏的内部循环的样子:

public void Tick()
{
  (...)
  DoUpdate() // Calls "Update()"
  (...)
  DoDraw()   // Calls "Draw()"
             // Nested in some other methods, it also calls "Platform.Present()",
             // which is the place where pixels are displayed on the screen
  (...)
}

这是我从the source code of Monogame's Game.cs file了解到的。

解决此问题的一种方法是在另一个线程中运行排序算法,并在需要时更新要显示的值,如下所示:

protected override void Initialize()
{
    latestArray = MainArray;

    sortingThread = new Thread(new ThreadStart(Sorting));
    sortingThread.IsBackground = true;
    sortingThread.Start();
}

private void Sorting()
{
    float temp;
    for (int j = 0; j <= MainArray.Length - 2; j++) {
        for (int i = 0; i <= MainArray.Length - 2; i++) {
            if (MainArray[i] > MainArray[i + 1]) {
                temp = MainArray[i + 1];
                MainArray[i + 1] = MainArray[i];
                MainArray[i] = temp;

                latestArray = MainArray;

                Thread.Sleep(10);
            }
        }
    }

    Console.WriteLine("Sorting done !");
}

protected override void Draw(GameTime gameTime)
{
    GraphicsDevice.Clear(Color.Black);
    spriteBatch.Begin();
    displayedArray = latestArray;
    for (int i = 0; i < displayedArray.Length; i++) {
        // Draw column
    }
    spriteBatch.End();
}

您的排序效果很好。

【讨论】:

  • 对这样的任务使用线程会引入并发问题。在此示例中,如果在数组交换期间发生绘图调用,它将显示为两个具有相同高度的条。对数组使用锁来保证数据一致。
  • 这是一个很好的答案。但我不能忽视代码中的不准确之处。 “你的分类效果很好。”提供的代码算法无法正确排序排列倒排列表(从高到低)。
  • @Strom 没错,我将在我的答案中添加关于如何绘制数组的精确度。
  • @Strom 我说效果很好,因为这些列看起来是按大小排序的。从低到高排序不正确吗?
  • 问题是第二个循环计数器没有运行足够的时间来完全排序所有可能的列表排列。 j &lt;= MainArray.Length - 2 应该是 j &lt;= MainArray.Length - 1。如果预排序的数据(排序前)是从高到低的顺序,排序后的输出中会有未排序的值。
【解决方案2】:

对于 MonoGame 和一般游戏编程,您必须记住 Update() 和 Draw() 已经循环,并且会自动调用。

您应该永远不要在 Game1 中自己调用 Update() 或 Draw()。因为 Draw() 实际上并没有在屏幕上渲染任何东西,所以你在浪费 CPU 周期。

与其他编程范例不同,游戏循环(更新和绘制)就是一切,不等待任何东西。虽然可以打破这个规则,但在大多数情况下不应该这样做。 p>


要将时间方面应用于传统循环,必须通过调用 Update() 来完成。

当应用于嵌套循环时,循环的顺序会反转,因为内部循环比外部循环运行得“更快”或更频繁。

对循环实施了延迟机制(计时器),以将帧从默认的 60 fps(16.66667 毫秒)更改为合理且可见的(~2.9 fps)每帧 300 毫秒。


您的代码中有一个错误:冒泡排序在最坏的情况下需要 n * n 次迭代(从大到小排序),您的代码运行:n(n-1) 次,j 的循环在之前终止一次迭代达到阈值。我已经解决了这个问题。


以下代码使用这些输入约定:

  1. 在开始之前等待按下“Enter”。
  2. 随后的“回车”将暂停/恢复进度。
  3. 在最后停止,在下一个“Enter”处重新开始

此代码还提供了一个提前退出条件,该条件要求每次通过都进行更改(交换),否则退出。此代码使冒泡排序的复杂性更接近n log n

额外需要的 Game1 类级别变量,添加到以下行:

public class Game1:Game 
{

请注意,以下变量名称可能不符合某些命名准则。请相应调整。

private int j = 0, i = 0;  // move outside since they must carry across calls of Update()
private bool isRunning = false; //Indicate status, stop when paused or done.
private double timer = 0; //accumulator for time in ms
private int timeBetweenFrames = 300;  //in ms, when to run next loop iteration
private KeyboardState ks = new KeyboardState(), oks;  // needed for key press code

更新()

protected override void Update(GameTime gameTime)
{
    oks = ks;
    ks = Keyboard.GetState();
    if(ks.IsKeyDown(Keys.Escape) Exit(); 
    // new exit code, better due to the single Getstate() call per step/tick/iteration.

    if (ks.IsKeyDown(Keys.Enter) && oks.IsKeyUp(Keys.Enter)) // wait for an Enter press  
    //must be released then pressed before it fires again
    // without the additional check this will fire 60 times per second
    {
        timer = 0; 
        // i = 0; j = 0; //for reset instead of pause on enter
        isRunning = !isRunning;
    }
    if (isRunning)
    {
        timer += gameTime.ElapsedGameTime.TotalMilliseconds; 
               // should be 16.6(1/60) for the default 60 fps
        if (timer >= timeBetweenFrames) //wait for timer
        {
            timer=0; // reset timer and do a loop iteration
            //inner loop first, outer is incremented/checked in the else
           int temp=-1; // -1 to allow early exit
           if(++i <= MainArray.Length - 2)
           {
                // loop body
                if (MainArray[i] > MainArray[i + 1])
                {
                    temp = MainArray[i + 1];
                    MainArray[i + 1] = MainArray[i];
                    MainArray[i] = temp;
                }
            }
            else // outer increment and check
            {
                if(++j >= MainArray.Length || (i < MainArray.Length - 1 && temp == -1))
                // corrected bug and provide early exit
                // bug in worst case, reverse order source, needs n*n runs
                {
                    //Done
                    isRunning = false; // stop running
                    //reset for next run
                    i = 0;
                    j = 0;
                    timer=0;
                }
            }
        }
    }
    base.Update(gameTime);
}

【讨论】:

  • 转换“常规”循环需要了解更改的速度和所涉及的状态/变量。
猜你喜欢
  • 2021-12-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-07-13
  • 1970-01-01
  • 2012-01-13
  • 1970-01-01
相关资源
最近更新 更多