介绍
使用 Silverlight 3.0(c#) 开发一个贪吃蛇游戏
玩法
W/S/A/D 或 ↑/↓/←/→ 控制蛇的移动
截图
思路
1、贪吃蛇的每一段为 16×16 像素,场景为 640×480 像素,也就说网格为 40×30 个,每个网格的边长为 16
2、食物的出现位置以及贪吃蛇的运动方向的改变都要在相关的网格内进行
3、贪吃蛇的运动用即时运算的方法计算,当贪吃蛇运动到网格内(蛇某一段的像素位置%网格的边长<蛇在某时间单位下的移动偏移量)时做如下工作:修正蛇的位置使其正好在网格内,更新蛇的每一段的运动方向,判断是否吃到了食物、是否发生了碰撞等
4、贪吃蛇的每一段的运动方向的修改:蛇头的运动方向根据用户的操作改变,蛇的每一段的运动方向设置为此段的前一段的运动方向(计算时要从尾部向头部逐段计算)(注:运动方向的改变要在蛇移动到网格内时进行。其中如果蛇的某一段移动到了网格内,则表明其它各段都在网格内)
下面我们看看他的关键代码都有哪些。
#p#
关键代码
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Windows.Media.Imaging;
using System.Threading;
namespace YYSnake.Core
{
public partial class Main : UserControl
{
private int _columns; // 网格列数
private int _rows; // 网格行数
private Dictionary<Body, CellPoint> _bodies = new Dictionary<Body, CellPoint>(); // 贪吃蛇每一段的集合
private Dictionary<Bean, CellPoint> _beans = new Dictionary<Bean, CellPoint>(); // 豆的集合
private Dictionary<Skin, CellPoint> _skins = new Dictionary<Skin, CellPoint>(); // 蜕下来的皮的集合
private List<CellPoint> _emptyCells = new List<CellPoint>(); // 空网格的集合
private bool _enabled = false; // 游戏是否运行
private double _dt = 0.01; // 多少毫秒计算一次
private int _decimals = 1; // 计算小数时所保留的小数位
private double _speed = 80; // 蛇的运行速度
private Direction _moveDirection = Direction.Up; // 蛇的运行方向
private int _selfLength = 5; // 蛇的最小长度
private int _beansCount = 5; // 豆的***出现数量
private int _ateCapacity = 10; // 食量(超过则蜕皮)
private bool _needRaiseAteEvent = false; // 在“蛇头所处位置进入了网格点区域内”时是否需要触发吃豆事件
private int _needBeansCount = 0; // 还需要增加的豆的数量
Random _random = new Random();
public Main()
{
InitializeComponent();
this.Loaded += new RoutedEventHandler(Main_Loaded);
}
void Main_Loaded(object sender, RoutedEventArgs e)
{
this.Width = App.Width; // 640
this.Height = App.Height; // 480
_columns = (int)(Width / App.CellSize); // 40
_rows = (int)(Height / App.CellSize); // 30
// 防止动画飞出去
RectangleGeometry rg = new RectangleGeometry();
rg.Rect = new Rect(0, 0, App.Width, App.Height);
LayoutRoot.Clip = rg;
bg.Width = App.Width;
bg.Height = App.Height;
ripple.RippleBackground = bg;
CompositionTarget.Rendering += new EventHandler(CompositionTarget_Rendering);
}
/// <summary>
/// 初始化
/// </summary>
public void Init()
{
_enabled = false;
canvasBean.Children.Clear();
canvasSnake.Children.Clear();
canvasSkin.Children.Clear();
_beans.Clear();
_bodies.Clear();
_skins.Clear();
_emptyCells.Clear();
for (int i = 0; i < _columns; i++)
{
for (int j = 0; j < _rows; j++)
{
_emptyCells.Add(new CellPoint(i, j));
}
}
_moveDirection = Direction.Up;
InitSnake();
}
/// <summary>
/// 蛇的初始化
/// </summary>
private void InitSnake()
{
InitHead();
for (int i = 0; i < _selfLength - 1; i++)
AddTail();
for (int i = 0; i < _beansCount; i++)
AddBean();
}
/// <summary>
/// 蛇头的初始化
/// </summary>
private void InitHead()
{
Body head = new Body(BodyType.Head);
head.MoveDirection = _moveDirection;
CellPoint point = new CellPoint((int)(_columns / 2), (int)(_rows / 2));
head.SetValue(Canvas.LeftProperty, point.X * App.CellSize);
head.SetValue(Canvas.TopProperty, point.Y * App.CellSize);
_bodies.Add(head, point);
canvasSnake.Children.Add(head);
}
/// <summary>
/// 增加一个尾巴
/// </summary>
private void AddTail()
{
var prevBody = _bodies.Last().Key;
var prevBodyPoint = _bodies.Last().Value;
Body tail = new Body(BodyType.Tail);
tail.MoveDirection = prevBody.MoveDirection;
CellPoint tailPoint = new CellPoint(prevBodyPoint.X, prevBodyPoint.Y);
switch (prevBody.MoveDirection)
{
case Direction.Up:
tail.SetValue(Canvas.LeftProperty, (double)prevBody.GetValue(Canvas.LeftProperty));
tail.SetValue(Canvas.TopProperty, (double)prevBody.GetValue(Canvas.TopProperty) + App.CellSize);
tailPoint.Y++;
break;
case Direction.Down:
tail.SetValue(Canvas.LeftProperty, (double)prevBody.GetValue(Canvas.LeftProperty));
tail.SetValue(Canvas.TopProperty, (double)prevBody.GetValue(Canvas.TopProperty) - App.CellSize);
tailPoint.Y--;
break;
case Direction.Left:
tail.SetValue(Canvas.LeftProperty, (double)prevBody.GetValue(Canvas.LeftProperty) + App.CellSize);
tail.SetValue(Canvas.TopProperty, (double)prevBody.GetValue(Canvas.TopProperty));
tailPoint.X++;
break;
case Direction.Right:
tail.SetValue(Canvas.LeftProperty, (double)prevBody.GetValue(Canvas.LeftProperty) - App.CellSize);
tail.SetValue(Canvas.TopProperty, (double)prevBody.GetValue(Canvas.TopProperty));
tailPoint.X--;
break;
}
tailPoint = CorrectCellPoint(tailPoint);
_bodies.Add(tail, tailPoint);
canvasSnake.Children.Add(tail);
}
/// <summary>
/// 增加一个豆
/// </summary>
private void AddBean()
{
if (_needBeansCount < _beansCount)
_needBeansCount++;
}
private DateTime _prevAddBeanDateTime = DateTime.Now;
/// <summary>
/// 生成豆
/// </summary>
void UpdateBean()
{
if (_needBeansCount > 0 && (DateTime.Now - _prevAddBeanDateTime).TotalSeconds > 3)
{
List<CellPoint> emptyCells = GetEmptyCells();
if (emptyCells.Count == 0)
{
GameOver(this, EventArgs.Empty);
return;
}
CellPoint point = emptyCells[_random.Next(0, emptyCells.Count)];
Bean bean = new Bean();
bean.SetValue(Canvas.LeftProperty, point.X * App.CellSize);
bean.SetValue(Canvas.TopProperty, point.Y * App.CellSize);
_beans.Add(bean, point);
canvasBean.Children.Add(bean);
bean.ani.Completed += delegate
{
ripple.ShowRipple(new Point(point.X * App.CellSize + App.CellSize / 2, point.Y * App.CellSize + App.CellSize / 2));
player.PlayDrop();
};
_needBeansCount--;
_prevAddBeanDateTime = DateTime.Now;
}
}
private DateTime _prevDateTime = DateTime.Now;
private double _leftoverLength = 0d;
void CompositionTarget_Rendering(object sender, EventArgs e)
{
double length = (DateTime.Now - _prevDateTime).TotalSeconds + _leftoverLength;
while (length > _dt)
{
Update();
length -= _dt;
}
_leftoverLength = length;
_prevDateTime = DateTime.Now;
}
/// <summary>
/// 即时计算
/// </summary>
private void Update()
{
if (!_enabled)
return;
double offset = Math.Round(_speed * _dt, _decimals);
// 蛇头所处位置进入了网格点区域内
if (Math.Abs(Math.Round((double)_bodies.First().Key.GetValue(Canvas.TopProperty) % App.CellSize, _decimals)) < offset && Math.Abs(Math.Round((double)_bodies.First().Key.GetValue(Canvas.LeftProperty) % App.CellSize, _decimals)) < offset)
{
UpdateDirection();
CorrectPosition();
UpdateBodyCell();
CheckEat();
CheckSkin();
CheckCollision();
UpdateBean();
if (_needRaiseAteEvent)
{
Ate(this.Ate, EventArgs.Empty);
_needRaiseAteEvent = false;
}
}
UpdatePosition();
}
/// <summary>
/// 蜕皮
/// </summary>
private void CheckSkin()
{
if (_bodies.Count >= _ateCapacity + _selfLength)
AddSkin(_ateCapacity);
}
/// <summary>
/// 碰撞检测
/// </summary>
private void CheckCollision()
{
if (_skins.Any(p => p.Value == _bodies.First().Value) || _bodies.Where(p => p.Key.BodyType == BodyType.Tail).Any(p => p.Value == _bodies.First().Value))
{
_enabled = false;
player.PlayOver();
GameOver(this, EventArgs.Empty);
}
}
/// <summary>
/// 吃豆
/// </summary>
private void CheckEat()
{
// 是否有被吃的豆
var bean = _beans.FirstOrDefault(p => p.Value == _bodies.First().Value).Key;
if (bean != null)
{
_beans.Remove(bean);
canvasBean.Children.Remove(bean);
player.PlayEat();
AddTail();
AddBean();
_needRaiseAteEvent = true;
}
}
/// <summary>
/// 更新蛇的每一段的运动方向
/// </summary>
private void UpdateDirection()
{
for (int i = _bodies.Count - 1; i > -1; i--)
{
if (i == 0)
_bodies.ElementAt(i).Key.MoveDirection = _moveDirection;
else
_bodies.ElementAt(i).Key.MoveDirection = _bodies.ElementAt(i - 1).Key.MoveDirection;
}
}
/// <summary>
/// 更新蛇的每一段的位置
/// </summary>
private void UpdatePosition()
{
double offset = Math.Round(_speed * _dt, _decimals);
foreach (var body in _bodies.Keys)
{
if (body.MoveDirection == Direction.Up)
body.SetValue(Canvas.TopProperty, Math.Round((double)body.GetValue(Canvas.TopProperty) - offset, _decimals));
else if (body.MoveDirection == Direction.Down)
body.SetValue(Canvas.TopProperty, Math.Round((double)body.GetValue(Canvas.TopProperty) + offset, _decimals));
else if (body.MoveDirection == Direction.Left)
body.SetValue(Canvas.LeftProperty, Math.Round((double)body.GetValue(Canvas.LeftProperty) - offset, _decimals));
else if (body.MoveDirection == Direction.Right)
body.SetValue(Canvas.LeftProperty, Math.Round((double)body.GetValue(Canvas.LeftProperty) + offset, _decimals));
}
}
/// <summary>
/// 蜕指定数量的皮
/// </summary>
private void AddSkin(int count)
{
player.PlaySkin();
while (count > 0)
{
KeyValuePair<Body, CellPoint> body = _bodies.ElementAt(_bodies.Count - 1);
CellPoint skinPoint = body.Value;
Skin skin = new Skin();
skin.SetValue(Canvas.LeftProperty, skinPoint.X * App.CellSize);
skin.SetValue(Canvas.TopProperty, skinPoint.Y * App.CellSize);
_skins.Add(skin, skinPoint);
canvasSkin.Children.Add(skin);
_emptyCells.Remove(skinPoint);
canvasSnake.Children.Remove(body.Key);
_bodies.Remove(body.Key);
count--;
}
}
#region 辅助方法
/// <summary>
/// 修正指定的位置信息为整数
/// </summary>
private int CorrectPosition(double position)
{
double result;
double offset = Math.Round(_speed * _dt, _decimals);
double temp = Math.Round(position % App.CellSize, _decimals);
if (Math.Abs(temp) < offset)
result = Math.Round(position - temp);
else
result = Math.Round(position - temp) + Math.Sign(temp) * App.CellSize;
return (int)result;
}
/// <summary>
/// 修正蛇的每一段的位置为整数
/// </summary>
private void CorrectPosition()
{
foreach (Body body in _bodies.Keys)
{
double x = CorrectPosition((double)body.GetValue(Canvas.LeftProperty));
double y = CorrectPosition((double)body.GetValue(Canvas.TopProperty));
if (x == App.Width)
x = 0d;
else if (x == -App.CellSize)
x = App.Width - App.CellSize;
else if (y == App.Height)
y = 0d;
else if (y == -App.CellSize)
y = App.Height - App.CellSize;
body.SetValue(Canvas.LeftProperty, x);
body.SetValue(Canvas.TopProperty, y);
}
}
/// <summary>
/// 更新蛇的每一段的网格位置信息
/// </summary>
private void UpdateBodyCell()
{
for (int i = 0; i < _bodies.Count; i++)
{
UpdateBodyCell(_bodies.ElementAt(i).Key);
}
}
/// <summary>
/// 更新指定的 Body 的网格位置信息
/// </summary>
private void UpdateBodyCell(Body body)
{
CellPoint point = new CellPoint((int)((double)body.GetValue(Canvas.LeftProperty) / App.CellSize), (int)((double)body.GetValue(Canvas.TopProperty) / App.CellSize));
if (body.MoveDirection == Direction.Up)
point.Y--;
else if (body.MoveDirection == Direction.Down)
point.Y++;
else if (body.MoveDirection == Direction.Left)
point.X--;
else if (body.MoveDirection == Direction.Right)
point.X++;
point = CorrectCellPoint(point);
_bodies[body] = point;
}
/// <summary>
/// 修正网格位置
/// </summary>
private CellPoint CorrectCellPoint(CellPoint point)
{
if (point.X > _columns - 1)
point.X = _columns - point.X;
else if (point.X < 0)
point.X = point.X + _columns;
if (point.Y > _rows - 1)
point.Y = _rows - point.Y;
else if (point.Y < 0)
point.Y = point.Y + _rows;
return point;
}
/// <summary>
/// 获取空网格集合
/// </summary>
private List<CellPoint> GetEmptyCells()
{
List<CellPoint> emptyCells = new List<CellPoint>();
List<CellPoint> aroundHeadCells = new List<CellPoint>();
CellPoint headPoint = _bodies.First().Value;
for (int i = -5; i < 5; i++)
{
for (int j = -5; j < 5; j++)
{
CellPoint point = new CellPoint(headPoint.X + i, headPoint.Y + j);
point = CorrectCellPoint(point);
aroundHeadCells.Add(point);
}
}
// skin 的占位情况因为确定了就不变了,所以在 AddSkin() 处计算
// 为了以下 LINQ 的可用,需要重写 CellPoint 的 public override bool Equals(object obj)
emptyCells = _emptyCells.Where(p => !_bodies.Select(x => x.Value).Contains(p)).ToList();
emptyCells = emptyCells.Where(p => !_beans.Select(x => x.Value).Contains(p)).ToList();
emptyCells = emptyCells.Where(p => !aroundHeadCells.Contains(p)).ToList();
return emptyCells;
}
#endregion
#region 属性
public Direction MoveDirection
{
set
{
Body head = _bodies.First().Key;
if (head.MoveDirection == Direction.Up && value == Direction.Down)
return;
if (head.MoveDirection == Direction.Down && value == Direction.Up)
return;
if (head.MoveDirection == Direction.Left && value == Direction.Right)
return;
if (head.MoveDirection == Direction.Right && value == Direction.Left)
return;
_moveDirection = value;
}
}
public bool Enabled
{
get { return _enabled; }
set { _enabled = value; }
}
public double Speed
{
get { return _speed; }
set { _speed = value; }
}
public int AteCapacity
{
get { return _ateCapacity; }
set { _ateCapacity = value; }
}
#endregion
#region 事件 GameOver 和 Ate
public event EventHandler GameOver;
public event EventHandler Ate;
#endregion
}
}
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
- 14.
- 15.
- 16.
- 17.
- 18.
- 19.
- 20.
- 21.
- 22.
- 23.
- 24.
- 25.
- 26.
- 27.
- 28.
- 29.
- 30.
- 31.
- 32.
- 33.
- 34.
- 35.
- 36.
- 37.
- 38.
- 39.
- 40.
- 41.
- 42.
- 43.
- 44.
- 45.
- 46.
- 47.
- 48.
- 49.
- 50.
- 51.
- 52.
- 53.
- 54.
- 55.
- 56.
- 57.
- 58.
- 59.
- 60.
- 61.
- 62.
- 63.
- 64.
- 65.
- 66.
- 67.
- 68.
- 69.
- 70.
- 71.
- 72.
- 73.
- 74.
- 75.
- 76.
- 77.
- 78.
- 79.
- 80.
- 81.
- 82.
- 83.
- 84.
- 85.
- 86.
- 87.
- 88.
- 89.
- 90.
- 91.
- 92.
- 93.
- 94.
- 95.
- 96.
- 97.
- 98.
- 99.
- 100.
- 101.
- 102.
- 103.
- 104.
- 105.
- 106.
- 107.
- 108.
- 109.
- 110.
- 111.
- 112.
- 113.
- 114.
- 115.
- 116.
- 117.
- 118.
- 119.
- 120.
- 121.
- 122.
- 123.
- 124.
- 125.
- 126.
- 127.
- 128.
- 129.
- 130.
- 131.
- 132.
- 133.
- 134.
- 135.
- 136.
- 137.
- 138.
- 139.
- 140.
- 141.
- 142.
- 143.
- 144.
- 145.
- 146.
- 147.
- 148.
- 149.
- 150.
- 151.
- 152.
- 153.
- 154.
- 155.
- 156.
- 157.
- 158.
- 159.
- 160.
- 161.
- 162.
- 163.
- 164.
- 165.
- 166.
- 167.
- 168.
- 169.
- 170.
- 171.
- 172.
- 173.
- 174.
- 175.
- 176.
- 177.
- 178.
- 179.
- 180.
- 181.
- 182.
- 183.
- 184.
- 185.
- 186.
- 187.
- 188.
- 189.
- 190.
- 191.
- 192.
- 193.
- 194.
- 195.
- 196.
- 197.
- 198.
- 199.
- 200.
- 201.
- 202.
- 203.
- 204.
- 205.
- 206.
- 207.
- 208.
- 209.
- 210.
- 211.
- 212.
- 213.
- 214.
- 215.
- 216.
- 217.
- 218.
- 219.
- 220.
- 221.
- 222.
- 223.
- 224.
- 225.
- 226.
- 227.
- 228.
- 229.
- 230.
- 231.
- 232.
- 233.
- 234.
- 235.
- 236.
- 237.
- 238.
- 239.
- 240.
- 241.
- 242.
- 243.
- 244.
- 245.
- 246.
- 247.
- 248.
- 249.
- 250.
- 251.
- 252.
- 253.
- 254.
- 255.
- 256.
- 257.
- 258.
- 259.
- 260.
- 261.
- 262.
- 263.
- 264.
- 265.
- 266.
- 267.
- 268.
- 269.
- 270.
- 271.
- 272.
- 273.
- 274.
- 275.
- 276.
- 277.
- 278.
- 279.
- 280.
- 281.
- 282.
- 283.
- 284.
- 285.
- 286.
- 287.
- 288.
- 289.
- 290.
- 291.
- 292.
- 293.
- 294.
- 295.
- 296.
- 297.
- 298.
- 299.
- 300.
- 301.
- 302.
- 303.
- 304.
- 305.
- 306.
- 307.
- 308.
- 309.
- 310.
- 311.
- 312.
- 313.
- 314.
- 315.
- 316.
- 317.
- 318.
- 319.
- 320.
- 321.
- 322.
- 323.
- 324.
- 325.
- 326.
- 327.
- 328.
- 329.
- 330.
- 331.
- 332.
- 333.
- 334.
- 335.
- 336.
- 337.
- 338.
- 339.
- 340.
- 341.
- 342.
- 343.
- 344.
- 345.
- 346.
- 347.
- 348.
- 349.
- 350.
- 351.
- 352.
- 353.
- 354.
- 355.
- 356.
- 357.
- 358.
- 359.
- 360.
- 361.
- 362.
- 363.
- 364.
- 365.
- 366.
- 367.
- 368.
- 369.
- 370.
- 371.
- 372.
- 373.
- 374.
- 375.
- 376.
- 377.
- 378.
- 379.
- 380.
- 381.
- 382.
- 383.
- 384.
- 385.
- 386.
- 387.
- 388.
- 389.
- 390.
- 391.
- 392.
- 393.
- 394.
- 395.
- 396.
- 397.
- 398.
- 399.
- 400.
- 401.
- 402.
- 403.
- 404.
- 405.
- 406.
- 407.
- 408.
- 409.
- 410.
- 411.
- 412.
- 413.
- 414.
- 415.
- 416.
- 417.
- 418.
- 419.
- 420.
- 421.
- 422.
- 423.
- 424.
- 425.
- 426.
- 427.
- 428.
- 429.
- 430.
- 431.
- 432.
- 433.
- 434.
- 435.
- 436.
- 437.
- 438.
- 439.
- 440.
- 441.
- 442.
- 443.
- 444.
- 445.
- 446.
- 447.
- 448.
- 449.
- 450.
- 451.
- 452.
- 453.
- 454.
- 455.
- 456.
- 457.
- 458.
- 459.
- 460.
原文链接:http://www.cnblogs.com/webabcd/archive/2009/09/14/1566082.html