C# 在窗体上绘制二维图形似乎会延迟/减慢我的程序
我刚开始学习.NET,所以很可能这是一个n00b的大错误: 我正在尝试制作一个简单的乒乓球游戏,使用表单和 System::Drawing::Graphics类将游戏绘制到窗体 我的代码的重要部分如下所示: (主游戏循环): (表格初始化代码) 另外,这不是确切的代码,我只是根据记忆写的,所以这就是任何打字错误或错误的地方 可能会出现错误的名称,或与初始化表单有关的一些重要代码行 这就是代码。 我的问题是,游戏肯定不是每15毫秒更新一次(大约60帧/秒),它的速度要慢得多,所以我要做的是每次将球拍/球移动较大的距离,以补偿它没有很快更新,这看起来很糟糕 简言之,在绘制图形时,某些因素会大大降低游戏的速度。我有一种感觉,它与我的双缓冲有关,但我无法摆脱它,因为它会产生一些令人恶心的闪烁。我的问题是,,C# 在窗体上绘制二维图形似乎会延迟/减慢我的程序,c#,.net,graphics,2d,lag,C#,.net,Graphics,2d,Lag,我刚开始学习.NET,所以很可能这是一个n00b的大错误: 我正在尝试制作一个简单的乒乓球游戏,使用表单和 System::Drawing::Graphics类将游戏绘制到窗体 我的代码的重要部分如下所示: (主游戏循环): (表格初始化代码) 另外,这不是确切的代码,我只是根据记忆写的,所以这就是任何打字错误或错误的地方 可能会出现错误的名称,或与初始化表单有关的一些重要代码行 这就是代码。 我的问题是,游戏肯定不是每15毫秒更新一次(大约60帧/秒),它的速度要慢得多,所以我要做的是每次将球
如何消除这种延迟?不要在每一帧创建一个新的
位图
不要使用线程。睡眠。取而代之的是查看Timer
组件(位于System.Windows.Forms
命名空间中的组件)。此代码存在几个问题,可能会严重影响应用程序性能:
每帧创建一个大的位图
缓冲区,但不处理它
在WinForms已经有了很好的这种行为实现的情况下实现双缓冲
UpdateName()是递归的,但不一定是递归的
在GUI线程中调用Thread.Sleep()
代码示例,它使用单独的线程计算游戏计时和WinForms内置双缓冲:
static class Program
{
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main()
{
Application.Run(new GameWindow());
}
class GameWindow : Form
{
private Thread _gameThread;
private ManualResetEvent _evExit;
public GameWindow()
{
Text = "Pong";
Size = new Size(800, 600);
StartPosition = FormStartPosition.CenterScreen;
FormBorderStyle = FormBorderStyle.Fixed3D;
DoubleBuffered = true;
SetStyle(
ControlStyles.AllPaintingInWmPaint |
ControlStyles.OptimizedDoubleBuffer |
ControlStyles.UserPaint,
true);
}
private void GameThreadProc()
{
IAsyncResult tick = null;
while(!_evExit.WaitOne(15))
{
if(tick != null)
{
if(!tick.AsyncWaitHandle.WaitOne(0))
{
// we are running too slow, maybe we can do something about it
if(WaitHandle.WaitAny(
new WaitHandle[]
{
_evExit,
tick.AsyncWaitHandle
}) == 0)
{
return;
}
}
}
tick = BeginInvoke(new MethodInvoker(OnGameTimerTick));
}
}
private void OnGameTimerTick()
{
// perform game physics here
// don't draw anything
Invalidate();
}
private void ExitGame()
{
Close();
}
protected override void OnPaint(PaintEventArgs e)
{
var g = e.Graphics;
g.Clear(Color.White);
// do all painting here
// don't do your own double-buffering here, it is working already
// don't dispose g
}
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
_evExit = new ManualResetEvent(false);
_gameThread = new Thread(GameThreadProc);
_gameThread.Name = "Game Thread";
_gameThread.Start();
}
protected override void OnClosed(EventArgs e)
{
_evExit.Set();
_gameThread.Join();
_evExit.Close();
base.OnClosed(e);
}
protected override void OnPaintBackground(PaintEventArgs e)
{
// do nothing
}
}
}
静态类程序
{
///
///应用程序的主要入口点。
///
[状态线程]
静态void Main()
{
运行(新建GameWindow());
}
类游戏窗口:窗体
{
私有线程(gameThread);;
私人手册重置事件_evExit;
公共游戏窗口()
{
Text=“Pong”;
尺寸=新尺寸(800600);
StartPosition=FormStartPosition.CenterScreen;
FormBorderStyle=FormBorderStyle.Fixed3D;
双缓冲=真;
固定方式(
ControlStyles.AllPaintingWimPaint|
ControlStyles.OptimizedDubleBuffer|
ControlStyles.UserPaint,
正确的);
}
私有void GameThreadProc()
{
IAsyncResult tick=null;
而(!\u evExit.WaitOne(15))
{
如果(勾选!=null)
{
如果(!勾选.AsyncWaitHandle.WaitOne(0))
{
//我们跑得太慢了,也许我们可以做点什么
if(WaitHandle.WaitAny)(
新WaitHandle[]
{
_埃维希特,
tick.AsyncWaitHandle
}) == 0)
{
返回;
}
}
}
tick=BeginInvoke(新方法调用程序(OnGameTimerTick));
}
}
私有void ongametemertick()
{
//在这里表演游戏物理
//不要画任何东西
使无效();
}
私有void ExitGame()
{
Close();
}
受保护的覆盖无效OnPaint(PaintEventArgs e)
{
var g=e.图形;
g、 清晰(颜色:白色);
//你都在这儿画画吗
//不要在这里做你自己的双缓冲,它已经在工作了
//不要丢弃垃圾
}
受保护的覆盖无效加载(事件参数e)
{
基础荷载(e);
_evExit=新手动重置事件(假);
_gameThread=新线程(GameThreadProc);
_gameThread.Name=“游戏线程”;
_gamesthread.Start();
}
关闭时受保护的覆盖无效(事件参数e)
{
_evExit.Set();
_gamesthread.Join();
_evExit.Close();
基础。一旦关闭(e);
}
PaintBackground上受保护的覆盖无效(PaintEventArgs e)
{
//无所事事
}
}
}
甚至可以做更多的改进(例如,仅使部分游戏屏幕无效)。我尝试了计时器,但它每55毫秒调用一次我的函数,这给了我一个坏消息FPS@Aaronman8:尝试使用timeBeginPeriod
使计时器和睡眠更准确。(但是你不需要timeBeginPeriod(1)
,5毫秒的精度应该足够了)好的,我使用了计时器,并且我还添加了下面条目中“max”建议的双缓冲。它现在运行良好,我唯一的问题是,虽然闪烁的次数少了很多,但我仍然会每秒看到一次。。。有什么方法可以完全删除这个吗?@Aaronman8:看一看OnPaintBackground
。重写它,不调用基本实现allpaintingwmpaint
应该会处理这个问题,但根据我的经验,并不总是这样。太好了!我实施了你的策略,我得到了一个很好的FPS,几乎没有任何闪烁!非常感谢!
void InitForm()
{
form = new Form();
form.Text = "Pong"
form.Size = new Size(800, 600);
form.FormBorderStyle = FormBorderStyle::Fixed3D;
form.StartLocation = StartLocation::CenterScreen;
Application::Run(form);
}
static class Program
{
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main()
{
Application.Run(new GameWindow());
}
class GameWindow : Form
{
private Thread _gameThread;
private ManualResetEvent _evExit;
public GameWindow()
{
Text = "Pong";
Size = new Size(800, 600);
StartPosition = FormStartPosition.CenterScreen;
FormBorderStyle = FormBorderStyle.Fixed3D;
DoubleBuffered = true;
SetStyle(
ControlStyles.AllPaintingInWmPaint |
ControlStyles.OptimizedDoubleBuffer |
ControlStyles.UserPaint,
true);
}
private void GameThreadProc()
{
IAsyncResult tick = null;
while(!_evExit.WaitOne(15))
{
if(tick != null)
{
if(!tick.AsyncWaitHandle.WaitOne(0))
{
// we are running too slow, maybe we can do something about it
if(WaitHandle.WaitAny(
new WaitHandle[]
{
_evExit,
tick.AsyncWaitHandle
}) == 0)
{
return;
}
}
}
tick = BeginInvoke(new MethodInvoker(OnGameTimerTick));
}
}
private void OnGameTimerTick()
{
// perform game physics here
// don't draw anything
Invalidate();
}
private void ExitGame()
{
Close();
}
protected override void OnPaint(PaintEventArgs e)
{
var g = e.Graphics;
g.Clear(Color.White);
// do all painting here
// don't do your own double-buffering here, it is working already
// don't dispose g
}
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
_evExit = new ManualResetEvent(false);
_gameThread = new Thread(GameThreadProc);
_gameThread.Name = "Game Thread";
_gameThread.Start();
}
protected override void OnClosed(EventArgs e)
{
_evExit.Set();
_gameThread.Join();
_evExit.Close();
base.OnClosed(e);
}
protected override void OnPaintBackground(PaintEventArgs e)
{
// do nothing
}
}
}