我最近开始玩单机游戏并了解它的工作原理,我想对排序算法进行可视化。
当我启动程序时,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);
}
答案 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();
}
您的排序效果很好。
答案 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 的循环在之前终止了一次迭代达到了门槛。我已经解决了这个问题。
以下代码使用这些输入约定:
此代码还提供了一个提前退出条件,要求每次通过都进行更改(交换),否则退出。此代码使冒泡排序的复杂性更接近 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);
}