与其白手起家,不如学习高手。恐龙飞行是个不错的入门例子,实现如下。
1.运行 VS2010,新建 WinForm 项目 x01.Game.Dragon,将 Form1.cs 改名为 MainForm.cs,在 MainForm 类中,添加字段
bool isOver = false;
添加引擎函数 Run(),代码如下:
void Run() { while (!isOver) { Application.DoEvents(); } Application.Exit(); }
添加 override 函数 OnKeyDown(),代码如下:
protected override void OnKeyDown(KeyEventArgs e) { switch (e.KeyCode) { case Keys.Enter: Run(); break; case Keys.Escape: GameOver(); break; case Keys.Up: break; case Keys.Right: break; case Keys.Down: break; case Keys.Left: break; } }
添加退出函数 GameOver(),代码如下:
private void GameOver() { isOver = true; }
如此,游戏框架完成。虽然现在什么都不干,但看 OnKeyDown() 函数,不难发现,Enter 键开始,Esc 键结束,箭头键控制飞行方向。
2.游戏说白了,就是操作图形,GDI+ 是个不错的选择。Graphics 提供操作方法,Bitmap 提供被操作对象,将 Bitmap 提供给 PictureBox 的 Image 属性,即可实现游戏的基本场景。添加类 GameBox,继承自 PictureBox。其代码文件 GameBox.cs 的内容如下:
class GameBox : PictureBox { Graphics device; Bitmap surface; Font font; public GameBox(int width, int height) { BackColor = Color.Black; surface = new Bitmap(width, height); Image = surface; device = Graphics.FromImage(surface); font = new Font("Arial", 18, FontStyle.Regular, GraphicsUnit.Pixel); } protected override void Dispose(bool disposing) { device.Dispose(); surface.Dispose(); font.Dispose(); base.Dispose(disposing); } public Graphics Device { get { return device; } } public void Refresh() { Image = surface; } public void Print(int x, int y, string text, Brush brush = null) { if (brush == null) { brush = Brushes.White; } Device.DrawString(text, font, brush, x, y); } }
Print() 是为了输出文本,Device 是为了提供操作,如此而已。
3.终于到了核心的部分:Sprite 类。其代码文件 Sprite.cs 内容如下:
class Sprite { public enum FrameDirectionE { Forward = 1, Backward = -1, None = 0 } public enum FrameWrapE { Wrap, Bounce } GameBox gameBox; public Sprite(ref GameBox box) { gameBox = box; Rate = 30; FrameDirection = FrameDirectionE.Forward; } public PointF Position { get; set; } public PointF Offset { get; set; } public Size Size { get; set; } public Bitmap Image { get; set; } public int Columes { get; set; } public int Frames { get; set; } public int CurrentFrame { get; set; } public FrameDirectionE FrameDirection { get; set; } public FrameWrapE FrameWrap { get; set; } public int Rate { get; set; } public Rectangle Bound { get { return new Rectangle((int)Position.X, (int)Position.Y, Size.Width, Size.Height); } } public void Animation(int startFram, int endFrame) { int lastTime = 0; int time = Environment.TickCount; if (time > lastTime + Rate) { lastTime = time; CurrentFrame += (int)FrameDirection; switch (FrameWrap) { case FrameWrapE.Wrap: if (CurrentFrame < startFram) { CurrentFrame = endFrame; } else if (CurrentFrame > endFrame) { CurrentFrame = startFram; } break; case FrameWrapE.Bounce: if (CurrentFrame < startFram) { CurrentFrame = startFram; FrameDirection = FrameDirectionE.Forward; } else if (CurrentFrame > endFrame) { CurrentFrame = endFrame; FrameDirection = FrameDirectionE.Backward; } break; default: break; } } } public void Draw() { Rectangle frame = new Rectangle(); frame.X = (CurrentFrame % Columes) * Size.Width; frame.Y = (CurrentFrame / Columes) * Size.Height; frame.Width = Size.Width; frame.Height = Size.Height; gameBox.Device.DrawImage(Image, Bound, frame, GraphicsUnit.Pixel); } }
仔细一看,只有两个方法:Animation() 和 Draw()。Draw() 比较简单,就是把需要的 Frame 绘制到指定的位置。Animation() 则是通过设置控制 Frame 的移动,以形成动画效果。
4.有了 GameBox 和 Sprite,再回到 MainForm 就简单了。最后的代码文件 MainForm.cs 内容如下:
public partial class MainForm : Form { const string ImgDir = @"F:\Resource\Image\game\"; GameBox gameBox; Sprite sprite; Bitmap dragon; Bitmap grass; PointF offset; bool isOver = false; int direction = 2; public MainForm() { InitializeComponent(); Init(); } protected override void OnClosed(EventArgs e) { GameOver(); } protected override void OnKeyDown(KeyEventArgs e) { switch (e.KeyCode) { case Keys.Enter: Run(); break; case Keys.Escape: GameOver(); break; case Keys.Up: direction = 0; break; case Keys.Right: direction = 2; break; case Keys.Down: direction = 4; break; case Keys.Left: direction = 6; break; } } void Run() { int currentTime = 0, startTime = 0; while (!isOver) { currentTime = Environment.TickCount; if (currentTime > startTime + 16) { startTime = currentTime; Draw(); Application.DoEvents(); gameBox.Refresh(); } } Application.Exit(); } private void GameOver() { isOver = true; } private void Init() { Text = "Dragon Game"; Size = new Size(800, 600); gameBox = new GameBox(800, 600); gameBox.Parent = this; gameBox.Dock = DockStyle.Fill; gameBox.Print(10, 10, "Enter: 开始"); dragon = new Bitmap(ImgDir + "dragon.png"); grass = new Bitmap(ImgDir + "grass.bmp"); sprite = new Sprite(ref gameBox); sprite.Image = dragon; sprite.Size = new Size(256, 256); sprite.Columes = 8; sprite.Frames = 64; sprite.Position = new PointF(250, 150); } void Draw() { gameBox.Device.DrawImageUnscaled(grass, 0, 0, 800, 600); switch (direction) { case 0: // up offset = new PointF(0, -1); break; case 2: // right offset = new PointF(1, 0); break; case 4: // down offset = new PointF(0, 1); break; case 6: // left offset = new PointF(-1, 0); break; default: break; } sprite.Position = new PointF(sprite.Position.X + offset.X, sprite.Position.Y + offset.Y); sprite.Animation(direction * 8 + 1, direction * 8 + 8); sprite.Draw(); gameBox.Print(0, 0, "Esc: 结束\t->: 方向"); } }
其中 ImgDir 应作适当调整。dragon.png 文件是 8*8 的 Frame 集合,0,2,4,6 行分别表示上、右、下、左方向。相关资源,可到我的 x01.download 中下载。
5.本例学自《Virtual C# Game Programming for Teens》,但作了点精简,以突出主干。一本好书胜过一个好老师,的确如此。运行效果图如下: