我正在开发一款用于学习目的的游戏,我想只使用.NET-Framework和C#中的Windows窗体项目。
我希望将{screen'(可以在窗口上显示的内容)作为int[]
。修改数组并以缓冲方式将更改后的数组重新应用到“屏幕”(这样它就不会闪烁)。
我目前正在使用Panel
,我使用Bitmap
绘制Graphics
。 Bitmap
转换为int[]
然后我可以修改并重新应用Bitmap
并重新绘制。它很有效,但速度很慢,特别是因为我必须每帧都放大图像因为我的游戏只有300x160而屏幕是900x500。
建立:
// Renders 1 frame
private void Render()
{
// Buffer setup
_bufferedContext = BufferedGraphicsManager.Current;
_buffer = _bufferedContext.Allocate(panel_canvas.CreateGraphics(), new Rectangle(0, 0, _scaledWidth, _scaledHeight));
_screen.clear();
// Get position of player on map
_xScroll = _player._xMap - _screen._width / 2;
_yScroll = _player._yMap - _screen._height / 2;
// Indirectly modifies the int[] '_pixels'
_level.render(_xScroll, _yScroll, _screen);
_player.render(_screen);
// Converts the int[] into a Bitmap (unsafe method is faster)
unsafe
{
fixed (int* intPtr = &_screen._pixels[0])
{
_screenImage = new Bitmap(_trueWidth, _trueHeight, _trueWidth * 4, PixelFormat.Format32bppRgb, new IntPtr(intPtr));
}
}
// Draw generated image on buffer
Graphics g = _buffer.Graphics;
g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.NearestNeighbor;
g.DrawImage(_screenImage, new Rectangle(0, 0, 900, 506));
// Update panel buffered
_buffer.Render();
}
没有外部库可以更快地实现这项工作吗?
答案 0 :(得分:1)
我不确定不安全的代码,但我知道缓冲的图形管理器。我认为你应该为它创建一个类,而不是每次都创建一个新类。除了在负载上确定所有精灵的宽度和高度而不是缩放它们。这加快了我的小型游戏引擎。
class Spritebatch
{
private Graphics Gfx;
private BufferedGraphics bfgfx;
private BufferedGraphicsContext cntxt = BufferedGraphicsManager.Current;
public Spritebatch(Size clientsize, Graphics gfx)
{
cntxt.MaximumBuffer = new Size(clientsize.Width + 1, clientsize.Height + 1);
bfgfx = cntxt.Allocate(gfx, new Rectangle(Point.Empty, clientsize));
Gfx = gfx;
}
public void Begin()
{
bfgfx.Graphics.Clear(Color.Black);
}
public void Draw(Sprite s)
{
bfgfx.Graphics.DrawImageUnscaled(s.Texture, new Rectangle(s.toRec.X - s.rotationOffset.Width,s.toRec.Y - s.rotationOffset.Height,s.toRec.Width,s.toRec.Height));
}
public void drawImage(Bitmap b, Rectangle rec)
{
bfgfx.Graphics.DrawImageUnscaled(b, rec);
}
public void drawImageClipped(Bitmap b, Rectangle rec)
{
bfgfx.Graphics.DrawImageUnscaledAndClipped(b, rec);
}
public void drawRectangle(Pen p, Rectangle rec)
{
bfgfx.Graphics.DrawRectangle(p, rec);
}
public void End()
{
bfgfx.Render(Gfx);
}
}
这是我使用的一个例子。它的设置是为了模仿Xna中的Spritebatch。绘制图像Unscaled将真正提高它的速度。另外,创建一个缓冲图形和Context的实例将比每次渲染时创建一个新实例更快。所以我建议你改变g.DrawImage行(_screenImage,new Rectangle(0,0,900,506)); to DrawImageUnscaled(_screenImage,new Rectangle(0,0,900,506));
已编辑:如何在精灵加载上缩放代码的示例
public Sprite(Bitmap texture, float x, float y, int width, int height)
{
//texture is the image you originally start with.
Bitmap b = new Bitmap(width, height);
// Create a bitmap with the desired width and height
using (Graphics g = Graphics.FromImage(b))
{
g.DrawImage(texture, 0, 0, width, height);
}
// get the graphics from the new image and draw the old image to it
//scaling it to the proper width and height
Texture = b;
//set Texture which is the final picture to the sprite.
//Uppercase Texture is different from lowercase
答案 1 :(得分:0)
即使在没有任何插值的情况下完成,图像的缩放也足够昂贵。为了加快速度,你应该最小化内存分配:当你每帧创建全新的Bitmap时,它会导致对象创建和pixmap缓冲区分配。这一事实否定了您从BufferedGraphics获得的所有好处。我建议你做以下事情:
当然,使用某种硬件加速进行缩放操作是最优选的选项(例如,在opengl中,所有图像通常使用纹理矩形绘制,并且渲染此类矩形隐含地涉及"缩放&##的过程34;执行纹理采样时。)
答案 2 :(得分:0)
我想知道你为什么称之为“非常慢”,因为我做了一些测试并且性能似乎并不差。另外,您还要将渲染代码的性能测量到int[] '_pixels'
(遗憾的是您没有提供该代码)与位图操作分开,因为它可能是缓慢的部分。
关于你的具体问题。正如其他人所提到的,使用预先分配的缓冲图形和位图对象会加快它的速度
但你真的需要那个int[]
缓冲区吗? BufferedGraphics
已经在内部使用位图进行了支持,所以真正发生的是:
(1)填写int[]
缓冲区
(2)int[]
缓冲区被复制到新的/预分配的Bitmap
(3)将步骤2中的Bitmap
复制(应用比例)到BufferedGraphics
内部位图(通过DrawImage
)
(4)将BufferedGraphics
内部位图复制到屏幕上(通过Render
)
如您所见,有很多复制操作。 BufferedGraphics
的预期用途是:
(1)您通过BufferedGraphics
属性的绘图方法填充BufferedGraphics.Graphics
内部位图。如果设置,Graphics
将为您进行缩放(以及其他转换)
(2)将BufferedGraphics
内部位图复制到屏幕上(通过Render
)
我不知道您的绘图代码在做什么,但如果您能负担得起,这肯定应该提供最佳性能。
以下是我感兴趣的快速而肮脏的测试:
using System;
using System.Diagnostics;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.Threading;
using System.Windows.Forms;
namespace Test
{
enum RenderMode { NewBitmap, PreallocatedBitmap, Graphics }
class Screen
{
Control canvas;
public Rectangle area;
int[,] pixels;
BitmapData info;
Bitmap bitmap;
BufferedGraphics buffer;
float scaleX, scaleY;
public RenderMode mode = RenderMode.NewBitmap;
public Screen(Control canvas, Size size)
{
this.canvas = canvas;
var bounds = canvas.DisplayRectangle;
scaleX = (float)bounds.Width / size.Width;
scaleY = (float)bounds.Height / size.Height;
area.Size = size;
info = new BitmapData { Width = size.Width, Height = size.Height, PixelFormat = PixelFormat.Format32bppRgb, Stride = size.Width * 4 };
pixels = new int[size.Height, size.Width];
bitmap = new Bitmap(size.Width, size.Height, info.PixelFormat);
buffer = BufferedGraphicsManager.Current.Allocate(canvas.CreateGraphics(), bounds);
buffer.Graphics.InterpolationMode = InterpolationMode.NearestNeighbor;
ApplyMode();
}
public void ApplyMode()
{
buffer.Graphics.ResetTransform();
if (mode == RenderMode.Graphics)
buffer.Graphics.ScaleTransform(scaleX, scaleY);
}
public void FillRectangle(Color color, Rectangle rect)
{
if (mode == RenderMode.Graphics)
{
using (var brush = new SolidBrush(color))
buffer.Graphics.FillRectangle(brush, rect);
}
else
{
rect.Intersect(area);
if (rect.IsEmpty) return;
int colorData = color.ToArgb();
var pixels = this.pixels;
for (int y = rect.Y; y < rect.Bottom; y++)
for (int x = rect.X; x < rect.Right; x++)
pixels[y, x] = colorData;
}
}
public unsafe void Render()
{
if (mode == RenderMode.NewBitmap)
{
var bounds = canvas.DisplayRectangle;
using (var buffer = BufferedGraphicsManager.Current.Allocate(canvas.CreateGraphics(), bounds))
{
Bitmap bitmap;
fixed (int* pixels = &this.pixels[0, 0])
bitmap = new Bitmap(info.Width, info.Height, info.Stride, info.PixelFormat, new IntPtr(pixels));
buffer.Graphics.InterpolationMode = InterpolationMode.NearestNeighbor;
buffer.Graphics.DrawImage(bitmap, bounds);
buffer.Render();
}
}
else
{
if (mode == RenderMode.PreallocatedBitmap)
{
fixed (int* pixels = &this.pixels[0, 0])
{
info.Scan0 = new IntPtr(pixels); info.Reserved = 0;
bitmap.LockBits(area, ImageLockMode.WriteOnly | ImageLockMode.UserInputBuffer, info.PixelFormat, info);
bitmap.UnlockBits(info);
}
buffer.Graphics.DrawImage(bitmap, canvas.DisplayRectangle);
}
buffer.Render();
}
}
}
class Game
{
[STAThread]
public static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
var game = new Game();
game.Run();
}
Form form;
Control canvas;
Screen screen;
Level level;
Player player;
private Game()
{
form = new Form();
canvas = new Control { Parent = form, Bounds = new Rectangle(0, 0, 900, 506) };
form.ClientSize = canvas.Size;
screen = new Screen(canvas, new Size(300, 160));
level = new Level { game = this };
player = new Player { game = this };
}
private void Run()
{
bool toggleModeRequest = false;
canvas.MouseClick += (sender, e) => toggleModeRequest = true;
var worker = new Thread(() =>
{
int frameCount = 0;
Stopwatch drawT = new Stopwatch(), applyT = new Stopwatch(), advanceT = Stopwatch.StartNew(), renderT = Stopwatch.StartNew(), infoT = Stopwatch.StartNew();
while (true)
{
if (advanceT.ElapsedMilliseconds >= 3)
{
level.Advance(); player.Advance();
advanceT.Restart();
}
if (renderT.ElapsedMilliseconds >= 8)
{
frameCount++;
drawT.Start(); level.Render(); player.Render(); drawT.Stop();
applyT.Start(); screen.Render(); applyT.Stop();
renderT.Restart();
}
if (infoT.ElapsedMilliseconds >= 1000)
{
double drawS = drawT.ElapsedMilliseconds / 1000.0, applyS = applyT.ElapsedMilliseconds / 1000.0, totalS = drawS + applyS;
var info = string.Format("Render using {0} - Frames:{1:n0} FPS:{2:n0} Draw:{3:p2} Apply:{4:p2}",
screen.mode, frameCount, frameCount / totalS, drawS / totalS, applyS / totalS);
form.BeginInvoke(new Action(() => form.Text = info));
infoT.Restart();
}
if (toggleModeRequest)
{
toggleModeRequest = false;
screen.mode = (RenderMode)(((int)screen.mode + 1) % 3);
screen.ApplyMode();
frameCount = 0; drawT.Reset(); applyT.Reset();
}
}
});
worker.IsBackground = true;
worker.Start();
Application.Run(form);
}
class Level
{
public Game game;
public int pos = 0; bool right = true;
public void Advance() { Game.Advance(ref pos, ref right, 0, game.screen.area.Right - 1); }
public void Render()
{
game.screen.FillRectangle(Color.SaddleBrown, new Rectangle(0, 0, pos, game.screen.area.Height));
game.screen.FillRectangle(Color.DarkGreen, new Rectangle(pos, 0, game.screen.area.Right, game.screen.area.Height));
}
}
class Player
{
public Game game;
public int x = 0, y = 0;
public bool right = true, down = true;
public void Advance()
{
Game.Advance(ref x, ref right, game.level.pos, game.screen.area.Right - 5, 2);
Game.Advance(ref y, ref down, 0, game.screen.area.Bottom - 1, 2);
}
public void Render() { game.screen.FillRectangle(Color.Yellow, new Rectangle(x, y, 4, 4)); }
}
static void Advance(ref int pos, ref bool forward, int minPos, int maxPos, int delta = 1)
{
if (forward) { pos += delta; if (pos < minPos) pos = minPos; else if (pos > maxPos) { pos = maxPos; forward = false; } }
else { pos -= delta; if (pos > maxPos) pos = maxPos; else if (pos < minPos) { pos = minPos; forward = true; } }
}
}
}