C#游戏编程:《控制台小游戏系列》之《六、贪吃蛇实例》

一、游戏分析

1976年,Gremlin平台推出一款经典街机游戏Blockade,则为贪吃蛇的原型,这款简单的小游戏很受欢迎。作为未来程序员的我们,玩自己设计出的贪吃蛇比玩现有的更加有趣,我们可以添加我们所想到的各种特征,即使游戏多么另类都不觉得奇怪,我的游戏我做主!

贪吃蛇的设计并不复杂,是很多游戏编程爱好者入门的首选项目之一,老衲也一样。整观这个游戏,屏蔽掉其他花俏的特征,让我们把焦点放到这个游戏的两个主要对象上:蛇和食物。玩过这款小游戏的人(难道有人没玩过的?)都知道,为了让自己的小蛇疯狂生长,就必须东西南北寻找食物来吃,前提是不要碰上障碍物,否则蛇就挂了。蛇由玩家通过键盘方向键来控制爬行方向,而食物在蛇视野范围内随机生成,当蛇吃掉食物之后,蛇长一节,食物再次随机生成,当然,食物不会落到蛇身上,否则蛇也只好自断了结了。

通过上述,我们不难发现玩家的主要操作是控制蛇的爬行,对于蛇对象来说,它的特征就是会爬能爬,所以如何设计蛇的爬行并且如何反馈到画面上就是我们现在需要考虑的地方。蛇的爬行方法多种多样,但在这之前,我们还必须考虑蛇的表示方法,如何用数据结构表达一条蛇?

■或许可以想到用线性表来存储蛇的各个节点,当蛇移动时遍厉每个节点,更新为对应爬行方向之后的新值,这样每次蛇移动一步,则动全身:所有节点都要访问一次,可以看出最后的效率一定不会太高;

■或许可以想到用线性表来存储蛇的各个节点,当蛇移动时根据爬行方向把新的坐标节点添加到线性表中,然后把相对的“蛇尾”节点删除,最后遍历每个节点,调整各个节点在线形表的位置,防止线形表前后出现空缺的问题。这个方法还是避免不了要动全身,效率也不高。

■或许可以想到用链表来存储蛇的各个节点,当蛇移动时只需要操作链头链尾指针,其他节点无须逐一访问,效率相对前面两种来说提升不少。

然而在C#中,我们没必要考虑这么多,List<>就能很好的解决这个问题了,用List<>表达一条蛇,当蛇爬行的时候插入一个新节点,而删除相对尾端的尾节点,从而实现蛇的爬行效果;当蛇吃到食物时,只管往里添新节点即可,无须删除旧节点操作,这样蛇的身体就会生长一节。对于如何渲染蛇到画面上也是同样的思路(或许这是因为我们的蛇每个节点都是相同样式的情况),我们没有必要遍历每个节点然后把它们逐个渲染到画面上,而是采取“画头擦尾”的方式,只画改变的节点,不变的节点不需要考虑,从而使游戏性能大大提升,即使蛇的节点几千几万个,最终需要考虑的只有前后两个节点,蛇爬行起来腰不累了,吃嘛嘛香!以下用图来表达这种思想:

二、游戏实现

///Snake类实现

using System;using System.Collections.Generic;using CEngine;using CGraphics;namespace Snake{/// <summary>/// 贪吃蛇类/// </summary>internal class Snake{#region 字段/// <summary>/// 蛇身/// </summary>private List<CPoint> m_body;/// <summary>/// 爬行方向/// </summary>private CDirection m_dir;/// <summary>/// 蛇头/// </summary>private CPoint m_head;/// <summary>/// 蛇尾/// </summary>private CPoint m_tail;#endregion#region 构造函数/// <summary>/// 构造函数/// </summary>/// <param name="len"></param>/// <param name="dir"></param>public Snake(Int32 len, CDirection dir){this.m_dir = dir;this.m_body = new List<CPoint>();for (Int32 i = 0; i <= len; i++){m_body.Add(new CPoint(i+1, 2));}if (m_body.Count > 0){m_head = m_body[m_body.Count – 1];m_tail = m_body[0];}}#endregion#region 方法/// <summary>/// 设置方向/// </summary>/// <param name="dir"></param>public void setDirection(CDirection dir){this.m_dir = dir;}/// <summary>/// 获取方向/// </summary>/// <returns></returns>public CDirection getDirection(){return this.m_dir;}/// <summary>/// 获取蛇头/// </summary>/// <returns></returns>public CPoint getHead(){return m_body[m_body.Count – 1];}/// <summary>/// 获取蛇尾/// </summary>/// <returns></returns>private CPoint getTail(){return m_body[0];}/// <summary>/// 添加蛇body节点/// </summary>/// <param name="point">新节点</param>/// <param name="food">是否是食物节点</param>public void addBodyNode(CPoint point, bool food){//添加新节点this.m_body.Add(point);//非食物节点则移除尾巴if (!food){if (this.m_body.Count > 0){this.m_body.Remove(getTail());}}}/// <summary>/// 是否在某位置发生碰撞/// </summary>/// <param name="point"></param>/// <returns></returns>public Boolean isCollision(CPoint point){Boolean flag = false;foreach (CPoint p in m_body){flag = false;if (p == point){flag = true;break;}}return flag;}/// <summary>/// 是否与自身发生碰撞/// </summary>/// <returns></returns>public Boolean isSeftCollision(){Boolean flag = false;for (Int32 i = 0; i <m_body.Count-1; i++){flag = false;if (m_body[i] == getHead()){flag = true;break;}}return flag;}/// <summary>/// 蛇移动/// </summary>/// <returns></returns>public bool move(){CPoint head = getHead();switch (m_dir){case CDirection.Left:if (head.getX() == 1)return false;head.setX(head.getX() – 1);break;case CDirection.Right:if (head.getX() == 28)return false;head.setX(head.getX() + 1);break;case CDirection.Up:if (head.getY() == 1)return false;head.setY(head.getY() – 1);break;case CDirection.Down:if (head.getY() == 23)return false;head.setY(head.getY() + 1);break;default:break;}addBodyNode(head, false);return true;}/// <summary>/// 绘制蛇/// </summary>/// <param name="draw"></param>public void draw(CDraw draw){draw.setDrawSymbol(CSymbol.RING_SOLID);draw.fillRect(getHead().getX(), getHead().getY(), 1, 1, ConsoleColor.Yellow);draw.setDrawSymbol(CSymbol.DEFAULT);draw.fillRect(getTail().getX(), getTail().getY(), 1, 1, ConsoleColor.Black);}#endregion}}蛇方向是一个枚举值,具体实现为下:

///CDirection枚举实现

因害怕失败而不敢放手一搏,永远不会成功

C#游戏编程:《控制台小游戏系列》之《六、贪吃蛇实例》

相关文章:

你感兴趣的文章:

标签云: