C#核心实践小项目 -- 贪吃蛇
必备知识点--多脚本文件
(可观看CSharp核心--52集进行了解)
必备知识点--UML类图
必备知识点--七大原则
贪吃蛇
项目展示
控制方向的是:WSAD
确定键是:J
需求分析(UML类图)
自个先写--贪吃蛇
结合自己所学进行开发(UML类图是老师提供的,因为自己暂时还不太会绘制,主要是代码逻辑还不够清晰)
补充知识点:
检测键盘是否激活
Console.KeyAvailable == true;
按照UML类图逐个去写逐个去实现(但是有些模块我没有用上)
主要精力是放在了功能实现上
下面是我实现的过程
一、万事开头难--游戏类
(我先确定了开始着手的地方--游戏类,因为它是所有类和方法的汇聚地)
cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
//游戏类
namespace 自个先写CSharp核心小项目_贪吃蛇_
{
enum E_Scene
{
BeginID,
GamingID,
FinishID,
}
class Game
{
public static int x;
public static int y;
public static E_Scene scene = new E_Scene();
Begin begin = new Begin();
Finish finish = new Finish();
public Game()
{
x = 100;
y = 30;
scene = E_Scene.BeginID;
}
//初始化控制台
public void Consoles()
{
//隐藏光标
Console.CursorVisible = false;
//设置舞台大小
Console.SetWindowSize(x, y);
Console.SetBufferSize(x, y);
}
//游戏主循环
public void MajorCycle()
{
while (true)
{
//思考一下,为什么把开始场景和结束场景的类申明放在外面,而游戏场景的类申明放循环里面
//因为开始结束场景是一成不变的,只需申明一次就够用了
//而游戏场景进入一次就会执行出结果出来,每次结果都将不一样,所以每次都得重新申明
switch (scene)
{
case E_Scene.BeginID:
Console.Clear();
begin.newers();
break;
case E_Scene.GamingID:
Console.Clear();
GameScene gameScene = new GameScene();
gameScene.newers();
break;
case E_Scene.FinishID:
Console.Clear();
finish.newers();
break;
default:
break;
}
}
}
//场景切换
public void SceneMove()
{
}
}
}
二、游戏帧更新接口
1.更新接口
2.开始和结束场景基类
开始场景类
cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
//开始场景
namespace 自个先写CSharp核心小项目_贪吃蛇_
{
class Begin : BeginAndFinish
{
public Begin()
{
str = "贪吃蛇";
str1 = "开始游戏";
str2 = "结束游戏";
}
//重写更新方法
public override void newers()
{
Console.SetCursorPosition(48, 10);
Console.ForegroundColor = ConsoleColor.White;
Console.WriteLine(str);
Console.SetCursorPosition(47, 13);
Console.ForegroundColor = key == 1 ? ConsoleColor.Red : ConsoleColor.White;
Console.WriteLine(str1);
Console.SetCursorPosition(47, 15);
Console.ForegroundColor = key == 2 ? ConsoleColor.Red : ConsoleColor.White;
Console.WriteLine(str2);
char c = Console.ReadKey(true).KeyChar;
switch (c)
{
case 'W':
case 'w':
key = 1;
break;
case 'S':
case 's':
key = 2;
break;
case 'J':
case 'j':
if (key == 2)
{
//关闭控制台
Environment.Exit(0);
}
Game.scene = (E_Scene)key;
break;
default:
break;
}
}
}
}
结束场景类
cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
//结束场景
namespace 自个先写CSharp核心小项目_贪吃蛇_
{
class Finish : BeginAndFinish
{
string str0;
public static int num;
public Finish()
{
key = 0;
str = "游戏结束";
str0 = "本次游戏的长度为:";
str1 = "回到开始界面";
str2 = "结束游戏";
}
//重写更新方法
public override void newers()
{
Console.SetCursorPosition(47, 10);
Console.ForegroundColor = ConsoleColor.White;
Console.WriteLine(str);
Console.SetCursorPosition(42, 12);
Console.ForegroundColor = ConsoleColor.White;
Console.WriteLine(str0 + num);
Console.SetCursorPosition(45, 15);
Console.ForegroundColor = key == 0 ? ConsoleColor.Red : ConsoleColor.White;
Console.WriteLine(str1);
Console.SetCursorPosition(47, 17);
Console.ForegroundColor = key == 2 ? ConsoleColor.Red : ConsoleColor.White;
Console.WriteLine(str2);
char c = Console.ReadKey(true).KeyChar;
switch (c)
{
case 'W':
case 'w':
key = 0;
break;
case 'S':
case 's':
key = 2;
break;
case 'J':
case 'j':
if (key == 2)
{
//关闭控制台
Environment.Exit(0);
}
Game.scene = (E_Scene)key;
break;
default:
break;
}
}
}
}
(这里面其实可以把这些方法提取到开始和结束场景基类里面的,但我懒,没有去整!!!)
3.游戏场景类
(第二个大类,游戏里的墙壁、食物、蛇、各种方法等等都汇聚在这个类中)
三、游戏场景中的各类
1.绘制接口
2.游戏对象类
(讲真的这个类没怎么用上,具体怎么用我还得看看老师是怎么用的)
3.位置结构体
(这个是完全没有用上!!)
4.地图墙壁类
5.食物类
6.蛇类--(最复杂的类)
蛇身体类--没用上
蛇类
cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
//蛇类
namespace 自个先写CSharp核心小项目_贪吃蛇_
{
//移动方向枚举
enum E_Move
{
Up,
Down,
Left,
Right,
}
class Snake
{
string snakeHead = "●";
string snakeBody = "◎";
int x = 10;
int y = 5;
E_Move move = E_Move.Down;
char c;
Foods foods = new Foods();
int bodyNum = 0;
//标识符
int[] num1 = new int[10000];
int[] num2 = new int[10000];
//打印出长度
public string longs = "当前长度为:";
//蛇绘制
public void SnakePlan()
{
//打印长度
Console.SetCursorPosition(2, 1);
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine(longs + bodyNum);
Finish.num = bodyNum;
//蛇头的绘制
Console.SetCursorPosition(x, y);
Console.ForegroundColor = ConsoleColor.Yellow;
Console.WriteLine(snakeHead);
//蛇身的绘制
for (int i = 0; i < bodyNum; i++)
{
Console.SetCursorPosition(num1[i], num2[i]);
Console.ForegroundColor = ConsoleColor.Green;
Console.WriteLine(snakeBody);
}
}
//蛇清除
public void SnakeClear()
{
//打印长度清除
Console.SetCursorPosition(2, 1);
Console.WriteLine(" ");
//蛇头的清除
Console.SetCursorPosition(x, y);
Console.WriteLine(" ");
//蛇身的清除
for (int i = 0; i < bodyNum; i++)
{
Console.SetCursorPosition(num1[i], num2[i]);
Console.WriteLine(" ");
}
}
//蛇转向
public void SnakeTurn()
{
//老师漏讲的知识点,Console.KeyAvailable -- 检测键盘是否被激活
if (Console.KeyAvailable == true)
{
c = Console.ReadKey(true).KeyChar;
switch (c)
{
case 'W':
case 'w':
if (move == E_Move.Down && bodyNum != 0)
{
move = E_Move.Down;
}
else
{
move = E_Move.Up;
}
break;
case 'S':
case 's':
if (move == E_Move.Up && bodyNum != 0)
{
move = E_Move.Up;
}
else
{
move = E_Move.Down;
}
break;
case 'A':
case 'a':
if (move == E_Move.Right && bodyNum != 0)
{
move = E_Move.Right;
}
else
{
move = E_Move.Left;
}
break;
case 'D':
case 'd':
if (move == E_Move.Left && bodyNum != 0)
{
move = E_Move.Left;
}
else
{
move = E_Move.Right;
}
break;
default:
break;
}
}
}
//吃食物
//死亡
//蛇移动 -- (包含了蛇绘制、蛇转向、吃食物)
public void SnakeMove()
{
if(foods.x == 0 || foods.y == 0)
{
foods.Plan();
}
SnakeTurn();
switch (move)
{
case E_Move.Up:
SnakeClear();
y -= 1;
//判断是否死亡
//撞墙死亡
if (y == 0)
{
Game.scene = E_Scene.FinishID;
GameScene.bo = false;
break;
}
//撞身体死亡
for (int i = 0; i < bodyNum; i++)
{
if(num1[i] == x && num2[i] == y)
{
Game.scene = E_Scene.FinishID;
GameScene.bo = false;
break;
}
}
if (foods.x == x && foods.y == y)
{
foods.Plan();
//给个判断,让生成的food不会出现在有蛇身体的位置上
for (int i = 0; i < bodyNum; i++)
{
if (foods.x == num1[i] && foods.y == num2[i])
{
Console.SetCursorPosition(foods.x, foods.y);
Console.WriteLine(" ");
foods.Plan();
i = 0;
}
}
bodyNum += 1;
for (int i = bodyNum - 1; i > 0; i--)
{
num1[i] = num1[i - 1];
num2[i] = num2[i - 1];
}
num1[0] = x;
num2[0] = y + 1;
}
else
{
for (int i = bodyNum - 1; i > 0; i--)
{
num1[i] = num1[i - 1];
num2[i] = num2[i - 1];
}
num1[0] = x;
num2[0] = y + 1;
}
SnakePlan();
break;
case E_Move.Down:
SnakeClear();
y += 1;
//判断是否死亡
//撞墙死亡
if (y == 29)
{
Game.scene = E_Scene.FinishID;
GameScene.bo = false;
break;
}
//撞身体死亡
for (int i = 0; i < bodyNum; i++)
{
if (num1[i] == x && num2[i] == y)
{
Game.scene = E_Scene.FinishID;
GameScene.bo = false;
break;
}
}
if (foods.x == x && foods.y == y)
{
foods.Plan();
//给个判断,让生成的food不会出现在有蛇身体的位置上
for (int i = 0; i < bodyNum; i++)
{
if (foods.x == num1[i] && foods.y == num2[i])
{
Console.SetCursorPosition(foods.x, foods.y);
Console.WriteLine(" ");
foods.Plan();
i = 0;
}
}
bodyNum += 1;
for (int i = bodyNum - 1; i > 0; i--)
{
num1[i] = num1[i - 1];
num2[i] = num2[i - 1];
}
num1[0] = x;
num2[0] = y - 1;
}
else
{
for (int i = bodyNum - 1; i > 0; i--)
{
num1[i] = num1[i - 1];
num2[i] = num2[i - 1];
}
num1[0] = x;
num2[0] = y - 1;
}
SnakePlan();
break;
case E_Move.Left:
SnakeClear();
x -= 2;
//判断是否死亡
//撞墙死亡
if (x == 0)
{
Game.scene = E_Scene.FinishID;
GameScene.bo = false;
break;
}
//撞身体死亡
for (int i = 0; i < bodyNum; i++)
{
if (num1[i] == x && num2[i] == y)
{
Game.scene = E_Scene.FinishID;
GameScene.bo = false;
break;
}
}
if (foods.x == x && foods.y == y)
{
foods.Plan();
//给个判断,让生成的food不会出现在有蛇身体的位置上
for (int i = 0; i < bodyNum; i++)
{
if (foods.x == num1[i] && foods.y == num2[i])
{
Console.SetCursorPosition(foods.x, foods.y);
Console.WriteLine(" ");
foods.Plan();
i = 0;
}
}
bodyNum += 1;
for (int i = bodyNum - 1; i > 0; i--)
{
num1[i] = num1[i - 1];
num2[i] = num2[i - 1];
}
num1[0] = x + 2;
num2[0] = y;
}
else
{
for (int i = bodyNum - 1; i > 0; i--)
{
num1[i] = num1[i - 1];
num2[i] = num2[i - 1];
}
num1[0] = x + 2;
num2[0] = y;
}
SnakePlan();
break;
case E_Move.Right:
SnakeClear();
x += 2;
//判断是否死亡
//撞墙死亡
if (x == 98)
{
Game.scene = E_Scene.FinishID;
GameScene.bo = false;
break;
}
//撞身体死亡
for (int i = 0; i < bodyNum; i++)
{
if (num1[i] == x && num2[i] == y)
{
Game.scene = E_Scene.FinishID;
GameScene.bo = false;
break;
}
}
if (foods.x == x && foods.y == y)
{
foods.Plan();
//给个判断,让生成的food不会出现在有蛇身体的位置上
for (int i = 0; i < bodyNum; i++)
{
if (foods.x == num1[i] && foods.y == num2[i])
{
Console.SetCursorPosition(foods.x, foods.y);
Console.WriteLine(" ");
foods.Plan();
i = 0;
}
}
bodyNum += 1;
for (int i = bodyNum - 1; i > 0; i--)
{
num1[i] = num1[i - 1];
num2[i] = num2[i - 1];
}
num1[0] = x - 2;
num2[0] = y;
}
else
{
for (int i = bodyNum - 1; i > 0; i--)
{
num1[i] = num1[i - 1];
num2[i] = num2[i - 1];
}
num1[0] = x - 2;
num2[0] = y;
}
SnakePlan();
break;
default:
break;
}
}
}
}
全部代码文件:看资源
实现视频展示:
C#核心实践项目(自个先写)--贪吃蛇
总结:
代码有点屎山,but跑起来就好!!!
还是知识点运用的不够,很多没用上。
跟着老师实现--贪吃蛇
一.需求分析 -- 就是UML类图
二.游戏对象和场景更新接口
1.游戏类 ---- Game
场景类型枚举 ---- E_SceneType
2.场景更新接口 ---- ISceneUpdate
三、实现多场景切换
1.游戏场景类
2.开始和结束场景基类
3.开始场景
将Game类中的nowScene 改为静态的
将Game类中的场景切换方法也改静态的
4.结束场景
Game中的调用也改
四、游戏场景逻辑实现
1.游戏对象基类的实现
绘制接口
游戏对象类
位置结构体
2.继承游戏对象基类的对象
地图墙壁类
食物类
蛇身子类
3.地图对象
在GameScene实现Map中的方法
到这里可以实现的功能有:三个场景
4.蛇对象
5.蛇对象移动 -- (Lesson7 部分)
cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using 贪吃蛇.Lesson3;
using 贪吃蛇.Lesson4;
namespace 贪吃蛇.Lesson6
{
/// <summary>
/// 蛇的移动方向
/// </summary>
enum E_MoveDir
{
Up,
Down,
Left,
Right,
}
class Snake : IDraw
{
SnakeBody[] bodys;
//记录当前蛇的长度
int nowNum;
//当前移动方向
E_MoveDir dir;
public Snake(int x, int y)
{
//粗暴的方法 直接申明200个空间 来装蛇身体的数组
bodys = new SnakeBody[200];
bodys[0] = new SnakeBody(E_SnakeBody_Type.Head, x, y);
nowNum = 1;
dir = E_MoveDir.Down;
}
public void Draw()
{
//画一节一节的身子
for (int i = 0; i < nowNum; i++)
{
bodys[i].Draw();
}
}
#region Lesson7 蛇的移动
public void Move()
{
//移动前
//擦除最后一个位置
Console.SetCursorPosition(bodys[nowNum - 1].pos.x, bodys[nowNum - 1].pos.y);
Console.WriteLine(" ");
//再移动
switch (dir)
{
case E_MoveDir.Up:
--bodys[0].pos.y;
break;
case E_MoveDir.Down:
++bodys[0].pos.y;
break;
case E_MoveDir.Left:
bodys[0].pos.x -= 2;
break;
case E_MoveDir.Right:
bodys[0].pos.x += 2;
break;
default:
break;
}
}
#endregion
}
}
6.蛇对象改变移动方向(Lesson8 部分)
cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using 贪吃蛇.Lesson3;
using 贪吃蛇.Lesson4;
namespace 贪吃蛇.Lesson6
{
/// <summary>
/// 蛇的移动方向
/// </summary>
enum E_MoveDir
{
Up,
Down,
Left,
Right,
}
class Snake : IDraw
{
SnakeBody[] bodys;
//记录当前蛇的长度
int nowNum;
//当前移动方向
E_MoveDir dir;
public Snake(int x, int y)
{
//粗暴的方法 直接申明200个空间 来装蛇身体的数组
bodys = new SnakeBody[200];
bodys[0] = new SnakeBody(E_SnakeBody_Type.Head, x, y);
nowNum = 1;
dir = E_MoveDir.Down;
}
public void Draw()
{
//画一节一节的身子
for (int i = 0; i < nowNum; i++)
{
bodys[i].Draw();
}
}
#region Lesson7 蛇的移动
public void Move()
{
//移动前
//擦除最后一个位置
Console.SetCursorPosition(bodys[nowNum - 1].pos.x, bodys[nowNum - 1].pos.y);
Console.WriteLine(" ");
//再移动
switch (dir)
{
case E_MoveDir.Up:
--bodys[0].pos.y;
break;
case E_MoveDir.Down:
++bodys[0].pos.y;
break;
case E_MoveDir.Left:
bodys[0].pos.x -= 2;
break;
case E_MoveDir.Right:
bodys[0].pos.x += 2;
break;
default:
break;
}
}
#endregion
#region Lesson8 改变方向
public void ChangeDir(E_MoveDir dir)
{
//只有头部的时候 可以直接左转右 右转左 上转下 下转上
//有身体时 这些情况就不能直接转
if (this.dir == dir ||
nowNum > 1 &&
(this.dir == E_MoveDir.Up && dir == E_MoveDir.Down ||
this.dir == E_MoveDir.Down && dir == E_MoveDir.Up ||
this.dir == E_MoveDir.Left && dir == E_MoveDir.Right ||
this.dir == E_MoveDir.Right && dir == E_MoveDir.Left))
{
return;
}
//只要没有 return 就记录外面传入的方向 之后就会按照这个方向去移动
this.dir = dir;
}
#endregion
}
}
在GameScene里面实现调用
7.撞墙撞身体结束游戏 -- (Lesson9 部分)
cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using 贪吃蛇.Lesson3;
using 贪吃蛇.Lesson4;
using 贪吃蛇.Lesson5;
namespace 贪吃蛇.Lesson6
{
/// <summary>
/// 蛇的移动方向
/// </summary>
enum E_MoveDir
{
Up,
Down,
Left,
Right,
}
class Snake : IDraw
{
SnakeBody[] bodys;
//记录当前蛇的长度
int nowNum;
//当前移动方向
E_MoveDir dir;
public Snake(int x, int y)
{
//粗暴的方法 直接申明200个空间 来装蛇身体的数组
bodys = new SnakeBody[200];
bodys[0] = new SnakeBody(E_SnakeBody_Type.Head, x, y);
nowNum = 1;
dir = E_MoveDir.Down;
}
public void Draw()
{
//画一节一节的身子
for (int i = 0; i < nowNum; i++)
{
bodys[i].Draw();
}
}
#region Lesson7 蛇的移动
public void Move()
{
//移动前
//擦除最后一个位置
Console.SetCursorPosition(bodys[nowNum - 1].pos.x, bodys[nowNum - 1].pos.y);
Console.WriteLine(" ");
//再移动
switch (dir)
{
case E_MoveDir.Up:
--bodys[0].pos.y;
break;
case E_MoveDir.Down:
++bodys[0].pos.y;
break;
case E_MoveDir.Left:
bodys[0].pos.x -= 2;
break;
case E_MoveDir.Right:
bodys[0].pos.x += 2;
break;
default:
break;
}
}
#endregion
#region Lesson8 改变方向
public void ChangeDir(E_MoveDir dir)
{
//只有头部的时候 可以直接左转右 右转左 上转下 下转上
//有身体时 这些情况就不能直接转
if (this.dir == dir ||
nowNum > 1 &&
(this.dir == E_MoveDir.Up && dir == E_MoveDir.Down ||
this.dir == E_MoveDir.Down && dir == E_MoveDir.Up ||
this.dir == E_MoveDir.Left && dir == E_MoveDir.Right ||
this.dir == E_MoveDir.Right && dir == E_MoveDir.Left))
{
return;
}
//只要没有 return 就记录外面传入的方向 之后就会按照这个方向去移动
this.dir = dir;
}
#endregion
#region Lesson9 撞墙撞身体结束逻辑
public bool CheckEnd(Map map)
{
for (int i = 0; i < map.walls.Length; i++)
{
if (bodys[0].pos == map.walls[i].pos)
{
return true;
}
}
for (int i = 1; i < nowNum; i++)
{
if (bodys[0].pos == bodys[i].pos)
{
return true;
}
}
return false;
}
#endregion
}
}
GameScene中调用
8.蛇吃食物
Snake类里面添加的相关方法 -- (Lesson10 部分)
GameScene类中调用
9.蛇长身体
Snake类中添加方法
(加了Lesson11--长身体AddBody方法,在Lesson10吃食物方法里面调用了AddBody方法,然后在Lesson7中添加了蛇尾跟着蛇头移动的逻辑处理)
至此跟着老师进行的制作的功能都已实现
视频展示
C#核心实践--贪吃蛇(老师实现的)
完整代码在资源里。
总结一下下
还是得多敲多练,多想想怎么让代码更精简,逻辑怎么更清晰!
多挤些时间啊!