简单快速的实时图形C#电脑游戏(WinForms)
本文关键字:电脑游戏 WinForms 图形 实时 简单 | 更新日期: 2023-09-27 18:06:14
作为C#winforms项目的一部分,我正在尝试使整数数组和位图协调工作,以实现快速PictureBox编辑(避免使用缓慢的SetPixel命令(。
我在表单上添加了一个按钮和一个图片框,一个点击事件和一个关闭事件
表单的代码现在是这样的:
public partial class Form1 : Form
{
uint[] _Pixels { get; set; }
Bitmap _Bitmap { get; set; }
GCHandle _Handle { get; set; }
IntPtr _Addr { get; set; }
public Form1()
{
InitializeComponent();
int imageWidth = 100; //1920;
int imageHeight = 100; // 1080;
PixelFormat fmt = PixelFormat.Format32bppRgb;
int pixelFormatSize = Image.GetPixelFormatSize(fmt);
int stride = imageWidth * pixelFormatSize;
int padding = 32 - (stride % 32);
if (padding < 32)
{
stride += padding;
}
_Pixels = new uint[(stride / 32) * imageHeight + 1];
_Handle = GCHandle.Alloc(_Pixels, GCHandleType.Pinned);
_Addr = Marshal.UnsafeAddrOfPinnedArrayElement(_Pixels, 0);
_Bitmap = new Bitmap(imageWidth, imageHeight, stride / 8, fmt, _Addr);
pictureBox1.Image = _Bitmap;
}
private void button1_Click(object sender, EventArgs e)
{
for (int i = 0; i < _Pixels.Length; i++)
{
_Pixels[i] = ((uint)(255 | (255 << 8) | (255 << 16) | 0xff000000));
}
}
private void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
_Addr = IntPtr.Zero;
if (_Handle.IsAllocated)
{
_Handle.Free();
}
_Bitmap.Dispose();
_Bitmap = null;
_Pixels = null;
}
}
运行时,开头会出现预期的黑色图像。
当点击按钮时,图像应该会变成白色,但我遗漏了一些东西。
我忘了做什么了?
WinForms控件不会"仅仅因为"重新绘制自己,它们必须有这样做的理由。
一些数据源能够告诉其容器它们已经更新。Bitmap类没有。它无法告诉包含的PictureBox(或用于显示它的任何控件(它已更新。当您调用SetPixel()
来设置单个像素时,或者在操作BitmapData
实例后调用UnlockBits()
时,它可以,但当位图是使用固定数组构造的时,它不能,无论如何,您完全在位图类的控制之外操作该固定数组。
因此,Bitmap类没有事件或其他方式来通知其容器更新。
这意味着您需要告诉包含控件其数据源已更新,以便控件可以重新绘制自己。
您可以按照"如何刷新PictureBox"中的说明进行操作,即使用pictureBox.Refresh()
。这会导致PictureBox控件自身失效,并在下一次(立即(重新绘制时重新读取现在更改的位图数据。
另请参阅MSDN博客:Control.Invalidate、Control.Update和Control.Refresh之间有什么区别?。
解决方案
更新_Pixels
数组后,添加pictureBox1.Refresh()
。
这种更新速度很快,能够以高分辨率渲染平滑的视频。
添加对System.Runtime.InteropServices(可通过nuget获得(的引用
表单的代码现在是这样的:
public partial class Form1 : Form
{
uint[] _Pixels { get; set; }
Bitmap _Bitmap { get; set; }
GCHandle _Handle { get; set; }
IntPtr _Addr { get; set; }
public Form1()
{
InitializeComponent();
int imageWidth = 100; //1920;
int imageHeight = 100; // 1080;
PixelFormat fmt = PixelFormat.Format32bppRgb;
int pixelFormatSize = Image.GetPixelFormatSize(fmt);
int stride = imageWidth * pixelFormatSize;
int padding = 32 - (stride % 32);
if (padding < 32)
{
stride += padding;
}
_Pixels = new uint[(stride / 32) * imageHeight + 1];
_Handle = GCHandle.Alloc(_Pixels, GCHandleType.Pinned);
_Addr = Marshal.UnsafeAddrOfPinnedArrayElement(_Pixels, 0);
_Bitmap = new Bitmap(imageWidth, imageHeight, stride / 8, fmt, _Addr);
pictureBox1.Image = _Bitmap;
}
private void button1_Click(object sender, EventArgs e)
{
for (int i = 0; i < _Pixels.Length; i++)
{
_Pixels[i] = ((uint)(255 | (255 << 8) | (255 << 16) | 0xff000000));
}
pictureBox1.Refresh();
}
private void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
_Addr = IntPtr.Zero;
if (_Handle.IsAllocated)
{
_Handle.Free();
}
_Bitmap.Dispose();
_Bitmap = null;
_Pixels = null;
}
}
测试
当然,我想测试这个方法的性能,看看它是否足够快,可以渲染平滑的视频。
将按钮点击替换为计时器勾选
到目前为止,我的测试一开始很简单。我用每毫秒触发一次的timer tick event
替换了button click event
,并用随机颜色值填充_Pixels
数组。
为了确保PictureBox
在当前刷新完成之前不会尝试刷新,我使用了一个bool IsRefreshing
变量。
每秒帧数计算
我还通过增加单个int HzCount
变量,并在每次PictureBox
刷新开始时将该变量重置为零,来测量每次刷新之间的时间。就在我重置HzCount
*之前,我在TextBox
中显示该值。
英特尔(R(酷睿(TM(i7-5600U CPU@2.60GHz 2.60GHz,16.0 GB RAM结果:
72Hz@2560x1440
*赫兹或赫兹是指每秒经过的帧数(或某事物的次数(。从广义上讲,这意味着兆赫转换为每秒数百万次,千兆赫兹转换为每秒千万次(美国数十亿次(。