先說了,如果不會GDI+,類的定義這些基礎東西的,請先搜索相關貼子學習。
這次教大家用C#做一個簡單的貪吃蛇游戲。
先介紹用到的技術:
GDI+
定義類
枚舉
因為C#是一門面向對象的語言,我們不用把所有代碼都寫在一個窗體上,應該有一個分層思想(界面還界面,數據處理還數據處理),如果還要網上對戰的話,搞個三層吧(界面層,數據處理或邏輯層,Socket層),這樣不僅方便維護,還為後面添加新Idea會容易一點。
廢話少講,現在開始
在編寫游戲代碼之前,先想一想,貪食蛇裡有什麼呢?
蛇,這是必要的。
食物,這也是必要的。
蛇應該是怎樣的一條蛇?
不斷向一個方向移動,那就是有方向了。方向可以用整數代表,不過為了方便以後編寫代碼,
還是定義一個枚舉好一點。還要會移動。還要有一個位置。還要有一個速度
蛇有一個長度,也要記錄下它各個身體的位置,因為蛇吃食物會不斷加長,所以他的身體數量(即長度)是會變化的,所以最好是定義一個泛型List<Point>來記錄身體上的所有位置。
蛇怎樣才能移動,原理是這樣,後面一個蛇身替代頭部的位置,然後頭部向前走一步。
食物呢?我們不搞傳統的貪吃蛇,食物可以是多個的,也應該有不同類型,吃到不同類型的食物,蛇會作出相應的變化。也就是說,食物也有類型,所以定義一個enum為食物類型
為了搞搞新意思,加上一個蛇的速度,蛇吃到快的食物,會加快速度,吃到慢的食物,會減慢速度。所以定義了速度
枚舉一般寫在一個Common.cs裡
總的來說,定義類應該是這樣:
蛇:方向(枚舉),狀態(枚舉),移動(方法),位置(頭部位置),長度,各身體的位置,速度
食物:位置,類型
不說太多,我將會在代碼裡寫詳盡的注釋。大家應該看得明白。
代碼如下:
using System;
using System.Collections.Generic;
using System.Text;
using System.Windows.Forms;
using System.Drawing;
namespace snake
{
public enum SnakeDirection
{
Up, Down, Left, Right
}
public enum SnakeState
{
Normal = 50, Fast = 20, Low = 100
}
public enum FoodType
{
Normal, Fast, Slow, Long, Short, LENGTH
}
}
using System;
using System.Collections.Generic;
using System.Text;
using System.Drawing;
namespace snake
{
public class Snake
{
public Snake(Point headLocation, int snakeLength, SnakeDirection direction)
{
bodiesLocation = new List<Point>();
bodiesLocation.Add(headLocation);
for (int i = 0; i < snakeLength; i++)
{
bodiesLocation.Add(new Point(-1, -1));
}
this.direction = direction;
this.speed = (int)SnakeState.Normal;
}
/// <summary>向前移動一格</summary>
public void Run()
{
//從蛇尾開始,前面身體的位置替代後面的身體位置
for (int i = bodiesLocation.Count - 1; i >= 1; i--)
{
bodiesLocation[i] = bodiesLocation[i - 1];
}
//頭部向前走一步
Point head = bodiesLocation[0];
switch (direction)
{
case SnakeDirection.Up:
head = new Point(head.X, head.Y - 1);
break;
case SnakeDirection.Down:
head = new Point(head.X, head.Y + 1);
break;
case SnakeDirection.Left:
head = new Point(head.X - 1, head.Y);
break;
case SnakeDirection.Right:
head = new Point(head.X + 1, head.Y);
break;
}
bodiesLocation[0] = head; //因為Point是Struct,是值類型,所以要賦值過來
}
/// <summary>移動方向</summary>
public SnakeDirection Direction
{
get { return direction; }
set { direction = value; }
}
/// <summary>狀態</summary>
public SnakeState State
{
get { return state; }
set
{
state = value;
speed = (int)state; //改變蛇的速度
}
}
/// <summary>頭部位置</summary>
public Point HeadLocation
{
get { return bodiesLocation[0]; }
}
/// <summary>蛇身體</summary>
public List<Point> BodiesLocation
{
get { return bodiesLocation; }
}
/// <summary>蛇的速度</summary>
public int Speed
{
get { return speed; }
set { speed = value; }
}
/// <summary>移動方向</summary>
private SnakeDirection direction;
/// <summary>狀態</summary>
private SnakeState state;
/// <summary>蛇身體的位置</summary>
private List<Point> bodiesLocation;
/// <summary>蛇的速度</summary>
private int speed = (int)SnakeState.Normal;
}
}
using System;
using System.Collections.Generic;
using System.Text;
using System.Windows.Forms;
using System.Drawing;
namespace snake
{
public class Food
{
public Food(Point location,FoodType type)
{
this.location = location;
this.type = type;
}
public Food() { }
/// <summary>食物類型</summary>
public FoodType Type
{
get { return type; }
set { type = value; }
}
/// <summary>位置</summary>
public Point Location
{
get { return location; }
set { location = value; }
}
/// <summary>食物類型</summary>
private FoodType type;
/// <summary>位置</summary>
private Point location;
}
}
為了方便維護,定義了一個類GameManager,專門處理游戲的邏輯,運算之類的。
首先這個類相當於一個游戲,所以蛇,食物是必不可少的,
游戲一定要有一個畫面的,所以要定義一個Draw()方法把畫面畫出來
要隨機產生食物,所以要定義一個produceFood()方法
蛇要吃到食物,所以要定義一個SnakeEatFood方法
還要判斷游戲是否結束,所以要定義一個IsGameOver方法
因為GameManager相當於邏輯層,snake不對外開放,所以定義了兩個屬性,只返回snake的兩個屬性,這樣方便把數據顯示在界面上。因為蛇是在GameManager裡的,不會在窗體出現,所以定義了一個方法SnakeRun(),調用snake.Run()就OK了。
還要定義很多常量參數,詳細的請看代碼
using System;
using System.Collections.Generic;
using System.Text;
using System.Drawing;
using System.Windows.Forms;
namespace snake
{
public class GameManager
{
/// <summary>
/// 產生食物
/// </summary>
public void ProduceFood()
{
Random rnd = new Random();
//隨機產生一個食物
Point newFoodLoaction; //新產生的食物位置
bool flag = false; //是否應該重新產生食物
do
{
newFoodLoaction = new Point(rnd.Next(FIELD_WIDTH), rnd.Next(FIELD_HEIGHT));
//如果食物在蛇的身上,則重新產生食物位置
foreach (Point snakeP in snake.BodiesLocation)
{
if (newFoodLoaction == snakeP)
{
flag = true;
break;
}
}
} while (flag);
Food f = new Food(newFoodLoaction, (FoodType)rnd.Next((int)FoodType.LENGTH));
foods.Add(f);
}
/// <summary>
/// 蛇吃食物
/// </summary>
public void SnakeEatFood()
{
foreach (Food food in foods)
{
//如果蛇的頭部碰到食物
if (snake.HeadLocation == food.Location)
{
foods.Remove(food); //刪去食物
//蛇吃食物的效果
Random rnd = new Random();
int r;
switch (food.Type)
{
case FoodType.Fast:
//狀態為快
snake.State = SnakeState.Fast;
break;
case FoodType.Slow:
//狀態為慢
snake.State = SnakeState.Low;
break;
case FoodType.Normal:
//速度變回正常
snake.State = SnakeState.Normal;
break;
case FoodType.Short:
//隨機縮短
if (snake.BodiesLocation.Count > 1) r = 0;
else if (snake.BodiesLocation.Count < 5) r = 1;
else r = rnd.Next(5);
for (int i = 0; i < r; i++)
{
snake.BodiesLocation.RemoveAt(snake.BodiesLocation.Count - 1);
}
break;
case FoodType.Long:
//隨機加長
r = rnd.Next(5);
for (int i = 0; i < r; i++)
{
snake.BodiesLocation.Add(new Point(-1, -1));
}
break;
//可以在這裡添加更多吃下食物後蛇的變化
}
break; //--break foreach
}
}
}
/// <summary>
/// 是否游戲完結了
/// </summary>
/// <returns>是否游戲完結</returns>
public bool IsGameOver()
{
Point head = snake.HeadLocation; //蛇頭
//如果蛇頭碰到身體,游戲結束
for (int i = 1; i < snake.BodiesLocation.Count; i++)
{
if (head == snake.BodiesLocation[i]) return true;
}
//如果蛇頭碰到牆壁,游戲結束
if (head.X < 0 || head.X > FIELD_WIDTH - 1 || head.Y < 0 || head.Y > FIELD_HEIGHT - 1) return true;
return false; //都不是,游戲還沒有結束
}
/// <summary>
/// 蛇向前行走
/// </summary>
public void SnakeRun()
{
snake.Run();
}
/// <summary>
/// 畫出游戲界面
/// </summary>
/// <param name="g"></param>
public void Draw(Graphics g)
{
//畫蛇
Color snakeColor = Color.Black;
foreach (Point p in snake.BodiesLocation)
{
Rectangle r = new Rectangle(new Point(p.X * GRID_WIDTH, p.Y * GRID_WIDTH), new Size(GRID_WIDTH, GRID_WIDTH));
g.FillRectangle(new SolidBrush(snakeColor), r);
}
//畫食物
foreach (Food f in foods)
{
Font font = new Font(new FontFamily("宋體"), 12); //字體
Rectangle layout = new Rectangle(new Point(f.Location.X * GRID_WIDTH, f.Location.Y * GRID_WIDTH), new Size(GRID_WIDTH, GRID_WIDTH)); //字體所在的矩形
Color fontColor = Color.Black; //字體顏色
Color backColor = Color.Yellow; //字體背景色
string s = null;
switch (f.Type)
{
case FoodType.Fast:
s = "快";
break;
case FoodType.Long:
s = "長";
break;
case FoodType.Slow:
s = "慢";
break;
case FoodType.Normal:
s = "N";
break;
case FoodType.Short:
s = "短";
break;
default:
Console.WriteLine("出錯了");
break;
}
g.FillEllipse(new SolidBrush(backColor), layout);
g.DrawString(s, font, new SolidBrush(Color.Black), layout);
}
}
/// <summary>
/// 蛇的方向
/// </summary>
public SnakeDirection SnakeDirection
{
get { return snake.Direction; }
set { snake.Direction = value; }
}
/// <summary>
/// 蛇的速度
/// </summary>
public int SnakeSpeed
{
get { return snake.Speed ; }
}
/// <summary>蛇</summary>
private Snake snake = new Snake(SNAKE_INIT_LOCATION, SNAKE_INIT_LENGTH, SNAKE_INIT_DIRECTION);
/// <summary>食物</summary>
private List<Food> foods = new List<Food>();
/// <summary>格子寬度</summary>
public const int GRID_WIDTH = 20;
/// <summary>游戲場地寬度</summary>
public const int FIELD_WIDTH = 30;
/// <summary>游戲場地高度</summary>
public const int FIELD_HEIGHT = 30;
/// <summary>蛇的初始化長度</summary>
public const int SNAKE_INIT_LENGTH = 5;
/// <summary>蛇開始運動的方向</summary>
public const SnakeDirection SNAKE_INIT_DIRECTION = SnakeDirection.Right;
/// <summary>蛇開始的位置</summary>
public static Point SNAKE_INIT_LOCATION = new Point(0, 0);
}
}
因為所有邏輯方面的東西都放在GameManager裡,所以,就算界面改N次都好,都可以輕松實現
窗體方面,主要定義兩個Timer,一個用於產生食物,一個用於控制蛇的運動。
還有別忘了每次更新數據後,要調用控件的Invalidate()方法更新畫面哦。不然什麼都不會動。
最後,菜單什麼的,隨便你怎樣加。
這個是窗體的代碼:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
namespace snake
{
public partial class GameForm : Form
{
private GameManager manager = new GameManager(); //控制游戲所有流程的對象
public GameForm()
{
InitializeComponent();
}
private void GameForm_Load(object sender, EventArgs e)
{
tmrGame.Interval = (int)SnakeState.Normal;
tmrGame.Start();
tmrFood.Start();
this.Height = GameManager.FIELD_HEIGHT * GameManager.GRID_WIDTH;
this.Width = GameManager.FIELD_WIDTH * GameManager.GRID_WIDTH;
}
private void tmrGame_Tick(object sender, EventArgs e)
{
if (manager.IsGameOver())
{
tmrGame.Stop();
tmrFood.Stop();
}
manager.SnakeRun();
manager.SnakeEatFood();
tmrGame.Interval = manager.SnakeSpeed; //改變速度
this.Invalidate();
}
private void GameForm_Paint(object sender, PaintEventArgs e)
{
manager.Draw(e.Graphics);
}
private void tmrFood_Tick(object sender, EventArgs e)
{
manager.ProduceFood();
this.Invalidate();
}
private void GameForm_KeyDown(object sender, KeyEventArgs e)
{
if (e.KeyCode == Keys.Up && manager.SnakeDirection != SnakeDirection.Down)
{
manager.SnakeDirection = SnakeDirection.Up;
}
else if (e.KeyCode == Keys.Down && manager.SnakeDirection != SnakeDirection.Up)
{
manager.SnakeDirection = SnakeDirection.Down;
}
else if (e.KeyCode == Keys.Left && manager.SnakeDirection != SnakeDirection.Right)
{
manager.SnakeDirection = SnakeDirection.Left;
}
else if (e.KeyCode == Keys.Right && manager.SnakeDirection != SnakeDirection.Left)
{
manager.SnakeDirection = SnakeDirection.Right;
}
}
}
}