当我刚开始编程时,我认为和许多人一样,我想做游戏。 但是在我之前,有很多架构问题,我不知道如何解决,我什至没有听说过双缓冲,我想尽快得到结果。 因此,我最近决定编写一个项目,在其中可以编写简单的游戏而没有任何问题。 可以像GameBoy一样创建此项目中的游戏,即:俄罗斯方块,蛇等。 但是您也可以使用鼠标单击它。
链接到GitHub上的项目 。
在本文中,我想找出一条蛇的创造。
首先,您需要创建自己的游戏类并从Game基类继承。
class Snake : Game
它已经实现了游戏环境和游戏从一种状态转换到另一种状态时发生的事件。 本质上,我们需要做的就是声明事件处理。
public Snake() : base() { OnPreview += BasePreview; OnNewGame += Snake_OnNewGame; OnUpdateGame += Snake_OnUpdateGame; OnGameOver += DrawScore; }
对于OnPreview和OnGameOver事件,Game类中已经有现成的存根,您不能实现它们。 它仅用于初始化新游戏并处理更新事件。
private GameBlock head; private List<GameBlock> body; private GameBlock eat; private void Snake_OnNewGame() { head = new GameBlock() { X = 10, Y = 10, Vector = Vector.Up, Color = GameColor.Green }; body = new List<GameBlock>(); body.Add( head ); body.Add( new GameBlock() { X = 10, Y = 11, Vector = Vector.Up, Color = GameColor.Black } ); body.Add( new GameBlock() { X = 10, Y = 12, Vector = Vector.Up, Color = GameColor.Black } ); CreateEat(); DrawField(); }
您可以直接使用它来绘制字段,也可以使用现成的GameBlock类,它实现诸如位置,运动方向和颜色之类的东西。
在此功能中,我们声明了蛇的身体,制作了第一块食物,并显示了场上发生的事情。
private void CreateEat() { var emptyBlocks = new List<GameBlock>(); for( int i = 0; i < MainForm.FIELD_SIZE; i++ ) for( int j = 0; j < MainForm.FIELD_SIZE; j++ ) if( CheckEmptyBlock( i, j ) ) emptyBlocks.Add(new GameBlock() { X = i, Y = j, Color = GameColor.Red } ); if (emptyBlocks.Count > 0) eat = emptyBlocks[random.Next( emptyBlocks.Count )]; }
要制作一顿饭,我们得到一个空块列表,并借助一个随机化器(已在游戏中声明)选择随机。 万一蛇占据了整个领域,可以检查一下清单的大小。
实际上,检查空单元格的功能是:
private bool CheckEmptyBlock(int x, int y) => !( x < 0 || y < 0 || x == MainForm.FIELD_SIZE || y == MainForm.FIELD_SIZE ) && !body.Exists( a => a.Equals( new GameBlock() { X = x, Y = y } ) );
字段的呈现如下:
private void DrawField() { Field.Clear( GameColor.White ); Field.DrawGameBlock( eat ); Field.DrawGameBlocks( body ); WriteScore(); }
由于不难猜测,该字段被清除为白色,并显示带有蛇的食物。 WriteScore是另一个用于在特殊状态栏中显示分数的标准功能。
因此,我们转到游戏更新事件,该事件以300毫秒的频率发生。
private void Snake_OnUpdateGame( Controller controller ) { ControlMove( controller.GameKey ); if( CheckGameOver() ) GameOver(); else SnakeMove(); }
其中发生了四件事:改变运动方向,检查游戏结束,调用游戏结束事件以及在一切正常的情况下移动蛇。
private void ControlMove( GameKey key ) { switch( key ) { case GameKey.Left: head.Vector = head.Vector == Vector.Right ? Vector.Right : Vector.Left; break; case GameKey.Right: head.Vector = head.Vector == Vector.Left ? Vector.Left : Vector.Right; break; case GameKey.Up: head.Vector = head.Vector == Vector.Down ? Vector.Down : Vector.Up; break; case GameKey.Down: head.Vector = head.Vector == Vector.Up ? Vector.Up : Vector.Down; break; default: break; } }
要更改蛇的运动方向,我们需要更改其头部的向量。 因此,在运动控制中,需要检查向量反转的情况,以使蛇不会开始自行爬升。
private bool CheckGameOver() { switch( head.Vector ) { case Vector.Up: return !CheckEmptyBlock( head.X, head.Y - 1 ); case Vector.Down: return !CheckEmptyBlock( head.X, head.Y + 1 ); case Vector.Left: return !CheckEmptyBlock( head.X - 1, head.Y ); case Vector.Right: return !CheckEmptyBlock( head.X + 1, head.Y ); default: throw new NotImplementedException(); } }
要检查游戏结束,只需检查方向上的障碍物是否空闲。 您可能会猜到,支票中的食物被忽略了。
它仍然可以解析蛇的运动功能:
private void SnakeMove() { var temp = body.Last().Copy(); foreach( var block in body ) block.Move(); for( int i = body.Count - 1; i > 0; i-- ) body[i].Vector = body[i - 1].Vector; if( head.Equals( eat ) ) { score++; body.Add( temp ); CreateEat(); } DrawField(); }
复制尾巴的末端,以便在到达食物时将其添加为蛇的延伸。 移动块并不困难,因为此功能已在块类中实现。 然后,将向量分布在蛇的运动上,并检查与食物的交叉点。 如果找到食物,账单就会增加,蛇会长出来,并会产生新的食物。 为了使我们的游戏出现在游戏列表中,您需要将其添加到初始化窗体中:
List<Game> games = new List<Game>(); games.Add( new Snake() ); games.Add( new Tetris() ); games.Add( new Life() ); Application.Run( new MainForm( games ) );
仅此而已。 整个游戏代码仅占用102行。 从示例中可以看到,俄罗斯方块和游戏寿命已添加到项目中。 您可以在下面找到结果。
游戏选择菜单
游戏过程
游戏结束