MonoGame 绘制功能未重绘

时间:2021-06-04 17:26:55

标签: c# 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);
}

2 个答案:

答案 0 :(得分:1)

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

在屏幕上显示像素的操作是由 Monogame 在调用 Update() 内部完成的。在您的代码中,您正在用新的排序状态覆盖 “绘制缓冲区”。当需要在屏幕上显示像素时,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();
}

sort-gif

您的排序效果很好。

答案 1 :(得分:0)

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

您应该永远不要自己在 Game1 中调用 Update() 或 Draw()。由于 Draw() 实际上不会在屏幕上呈现任何内容,因此您正在浪费 CPU 周期。

与其他编程范式不同,游戏循环(更新和绘制)就是一切,无需等待。虽然有可能违反此规则,但在大多数情况下不应该这样做。 >


要将时间方面应用于传统循环,必须通过 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);
}
相关问题