如何在 2D WPF 游戏中刷新 UI 最聪明?
How do I refresh the UI smartest in a 2D WPF game?
我正在为 WPF(后来也为 UWP)创建一个游戏引擎。我正在尝试从 DispatcherTimer 转移到 StopWatch,以获得更稳定的游戏速度(因为如果游戏中的对象太多,DispatcherTimer 会跳过转弯)。
我可以使用秒表编写正确更新游戏的代码(也就是说,当我检查自上次更新以来经过的刻度或毫秒数时,我的游戏对象的速度和位置计算正确),但是当我替换DispatcherTimer 的 tick 事件带有简单的 while 循环和对屏幕绘制方法的调用,GUI 永远不会重绘。
我知道有几种方法可以强制重绘 window,但是对于 WPF 中的 2D 游戏,我最好的选择是什么?我只是在寻找一些简单的东西,它可以非常有效地工作而没有很多麻烦。它不必非常快,但在恒定速度方面应该相当准确(即无论动态对象的数量如何,游戏都应该以恒定速度移动)。
这是我当前用于在屏幕上放置对象的代码,每秒从我的游戏循环中调用多次:
for (int i = 0; i < Bodies.NumberOfBodies();
{
Body body = Bodies.Bodylist[i];
var shape = body.GetShape();
game.Children.Add(shape); //each movable body's shape is added again.
Canvas.SetLeft(shape, body.PosX - offsetX);
Canvas.SetTop(shape, body.PosY - offsetY);
}
到目前为止,我已经阅读了有关该主题的内容,但是有很多解决方案,但我不知道最合适的是什么。
在Whosebug上,看了很多答案,包括
和
WPF forcing redraw of canvas
和
Confusion about Refreshing the UI in WPF
和
和
How can I get better timed events for my games?
这里有一个类似的问题:How to make a render loop in WPF? 回复相同,但情况有点不同。例如,我本身不需要恒定的帧速率,我只希望计时相当准确。
这些事情似乎有很多困惑,我觉得每当我开始探索其中一个想法时,我最终都会尝试处理非常复杂的事情。再一次,我想要做的就是让重绘方法以每秒 20 次的速度实际重绘 GUI。
谢谢!
彼得
编辑:
我的 DispatcherTimer 代码被请求,所以这里是:
public void StartTimer()
{
//A note on the dispatcherTimer: http://www.wpf-tutorial.com/misc/dispatchertimer/
//The code below fires as often as possible (depending on the computer).
//When moves are calulated, the real elapsed time is taken in consideration.
/*Test: 300 added movable objects; a setting of 100ms
* --> 99 calls to CalculateMoves in 30 seconds; should have been 300.
* In other words, the game moves at a third of the actual speed.
* With only 30 added objects, the same settings gives 270 calls per 30 seconds.
* With only 3 added objects, the same settings gives 273 calls per 30 seconds.
*
*/
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;
dtStart = DateTime.Now; //start time is set for performing various calculations
//Add a bunch of objects for speed testing purposes
for (int i = 0; i < 30; i++)
{
AddBody();
}
stopWatch.Start();
timer.Start();
// 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.
//This variable is used to get to the controls (labels etc) of the MainWindow (WPF)
// MainWindow mainWin = System.Windows.Application.Current.Windows.Cast<System.Windows.Window>().FirstOrDefault(window => window is MainWindow) as MainWindow;
if (runGame == false) return; //only go on if not in pause mode
// mainWin.txtInfo.Text = ""; time.ToString("mm\:ss");//Shows the timer in a textbox, only showing minutes and seconds.
//if (counter % 100 == 0)
//{
// //Add a flower
// AddBody();
//}
//float rndBalloon = rand.Next(75, 250); //so that the balloons come at irregular intervals
//if (counter % rndBalloon == 0)
//{
// //Add a balloon
// AddBalloon();
//}
//change direction of cloud
int cloudIndex = (Utilities.FindBodyIndex("cloud"));
Body cloud = Bodies.Bodylist[cloudIndex];
if (cloud.PosX > 600) cloud.SpeedX = -0.3f;
if (cloud.PosX < 100) cloud.SpeedX = 0.3f;
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;
////The score is updated onscreen
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());
//Each fireball becomes a little bit paler (they fade out and eventually disappear)
FireballPaler();
counter++;
//}
}
您是否尝试过为 DispatcherTimer 设置优先级,您可以使用更高的优先级来获得准确的结果https://msdn.microsoft.com/en-us/library/system.windows.threading.dispatcherpriority(v=vs.110).aspx
如果您需要如此精确的帧速率,这个问题可能会有帮助:
WPF real-time rendering
但是您仍然可能会遇到一些问题,这个问题可以帮助您
Constant framerate in WPF for game
不要将 WPF 用于游戏引擎。根本没有可靠的方法来控制帧率,而且渲染效率极低。最好的选择是 CompositionTarget.Rendering https://msdn.microsoft.com/en-us/library/system.windows.media.compositiontarget.rendering(v=vs.110).aspx,但即使那样有时也不可靠。
我正在为 WPF(后来也为 UWP)创建一个游戏引擎。我正在尝试从 DispatcherTimer 转移到 StopWatch,以获得更稳定的游戏速度(因为如果游戏中的对象太多,DispatcherTimer 会跳过转弯)。
我可以使用秒表编写正确更新游戏的代码(也就是说,当我检查自上次更新以来经过的刻度或毫秒数时,我的游戏对象的速度和位置计算正确),但是当我替换DispatcherTimer 的 tick 事件带有简单的 while 循环和对屏幕绘制方法的调用,GUI 永远不会重绘。
我知道有几种方法可以强制重绘 window,但是对于 WPF 中的 2D 游戏,我最好的选择是什么?我只是在寻找一些简单的东西,它可以非常有效地工作而没有很多麻烦。它不必非常快,但在恒定速度方面应该相当准确(即无论动态对象的数量如何,游戏都应该以恒定速度移动)。
这是我当前用于在屏幕上放置对象的代码,每秒从我的游戏循环中调用多次:
for (int i = 0; i < Bodies.NumberOfBodies();
{
Body body = Bodies.Bodylist[i];
var shape = body.GetShape();
game.Children.Add(shape); //each movable body's shape is added again.
Canvas.SetLeft(shape, body.PosX - offsetX);
Canvas.SetTop(shape, body.PosY - offsetY);
}
到目前为止,我已经阅读了有关该主题的内容,但是有很多解决方案,但我不知道最合适的是什么。
在Whosebug上,看了很多答案,包括
和
WPF forcing redraw of canvas
和
Confusion about Refreshing the UI in WPF
和
和
How can I get better timed events for my games?
这里有一个类似的问题:How to make a render loop in WPF? 回复相同,但情况有点不同。例如,我本身不需要恒定的帧速率,我只希望计时相当准确。
这些事情似乎有很多困惑,我觉得每当我开始探索其中一个想法时,我最终都会尝试处理非常复杂的事情。再一次,我想要做的就是让重绘方法以每秒 20 次的速度实际重绘 GUI。
谢谢!
彼得
编辑:
我的 DispatcherTimer 代码被请求,所以这里是:
public void StartTimer()
{
//A note on the dispatcherTimer: http://www.wpf-tutorial.com/misc/dispatchertimer/
//The code below fires as often as possible (depending on the computer).
//When moves are calulated, the real elapsed time is taken in consideration.
/*Test: 300 added movable objects; a setting of 100ms
* --> 99 calls to CalculateMoves in 30 seconds; should have been 300.
* In other words, the game moves at a third of the actual speed.
* With only 30 added objects, the same settings gives 270 calls per 30 seconds.
* With only 3 added objects, the same settings gives 273 calls per 30 seconds.
*
*/
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;
dtStart = DateTime.Now; //start time is set for performing various calculations
//Add a bunch of objects for speed testing purposes
for (int i = 0; i < 30; i++)
{
AddBody();
}
stopWatch.Start();
timer.Start();
// 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.
//This variable is used to get to the controls (labels etc) of the MainWindow (WPF)
// MainWindow mainWin = System.Windows.Application.Current.Windows.Cast<System.Windows.Window>().FirstOrDefault(window => window is MainWindow) as MainWindow;
if (runGame == false) return; //only go on if not in pause mode
// mainWin.txtInfo.Text = ""; time.ToString("mm\:ss");//Shows the timer in a textbox, only showing minutes and seconds.
//if (counter % 100 == 0)
//{
// //Add a flower
// AddBody();
//}
//float rndBalloon = rand.Next(75, 250); //so that the balloons come at irregular intervals
//if (counter % rndBalloon == 0)
//{
// //Add a balloon
// AddBalloon();
//}
//change direction of cloud
int cloudIndex = (Utilities.FindBodyIndex("cloud"));
Body cloud = Bodies.Bodylist[cloudIndex];
if (cloud.PosX > 600) cloud.SpeedX = -0.3f;
if (cloud.PosX < 100) cloud.SpeedX = 0.3f;
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;
////The score is updated onscreen
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());
//Each fireball becomes a little bit paler (they fade out and eventually disappear)
FireballPaler();
counter++;
//}
}
您是否尝试过为 DispatcherTimer 设置优先级,您可以使用更高的优先级来获得准确的结果https://msdn.microsoft.com/en-us/library/system.windows.threading.dispatcherpriority(v=vs.110).aspx
如果您需要如此精确的帧速率,这个问题可能会有帮助: WPF real-time rendering
但是您仍然可能会遇到一些问题,这个问题可以帮助您
Constant framerate in WPF for game
不要将 WPF 用于游戏引擎。根本没有可靠的方法来控制帧率,而且渲染效率极低。最好的选择是 CompositionTarget.Rendering https://msdn.microsoft.com/en-us/library/system.windows.media.compositiontarget.rendering(v=vs.110).aspx,但即使那样有时也不可靠。