即使走的再远,也勿忘启程时的初心
C/C++ 游戏开发
Hello,米娜桑们,这里是君兮_,最近开始正式的步入学习游戏开发的正轨,想要通过写博客的方式来分享自己学到的知识和经验,这就是开设本专栏的目的。希望这些独立的C#小项目能对做游戏的你有所帮助,毕竟学会游戏开发的最好的上手方式就是自己做一款游戏!!
勇士斗恶龙
- 前言
- 一.游戏场景的初始化
- 二.玩家移动以及战斗逻辑
- [三. 游戏源码](#三. 游戏源码)
- 总结
前言
- 今天我们接着来讲勇士斗恶龙的第二部分,上回咱们讲过了有关开始界面的实现以及控制台的初始化,这次我们来讲讲这个小游戏的核心内容游戏场景的初始化以及玩家的移动以及和BOSS的战斗相关代码的实现
- 还是先把咱们游戏的整体流程图放在这里
一.游戏场景的初始化
- 上回我们说实现界面之间的转换,只需要一个while+switch就能实现 此时我们把nowSceneID(游戏场景编号)改为2, 开始对我们的游戏场景进行编辑。
- 首先呢,我们得先把最基础的那些不需要每次都发生变化的东西先设置好
1.不变场景的初始化
- 在这个游戏场景中,不需要我们每次都发生变化的无疑是四周的红墙了,我们优先先把这些红墙给设置好
csharp
Console.Clear();
#region 4 不变的红墙
//设置颜色为红色
Console.ForegroundColor = ConsoleColor.Red;
//画墙
//上方墙
for (int i = 0; i < w; i += 2)
{
//上方墙
Console.SetCursorPosition(i, 0);
Console.Write("■");
//下方墙
Console.SetCursorPosition(i, h - 1);
Console.Write("■");
//中间墙
Console.SetCursorPosition(i, h - 6);
Console.Write("■");
}
//左边的墙
for (int i = 0; i < h; i++)
{
//左边的墙
Console.SetCursorPosition(0, i);
Console.Write("■");
//右边的墙
Console.SetCursorPosition(w - 2, i);
Console.Write("■");
}
#endregion
- 非常简单啊,就是通过循环把设置好位置颜色的墙打印在控制台界面上,这里就不细讲了。
- 唯一需要注意的是 对于控制台来说,一单位纵轴等于二单位的横轴,所以每次横轴要想与纵轴对应,是一次跳过两个格子的,同时我们要考虑的边界问题,注意这里红墙如果不专门设置位置左上角为(0,0) 因此我们的横轴设置的最右边的边界为w-2,最下面的边界为h-1。(如果你不理解这段话的话,自己敲代码的时候边界改成别的参数试一下,你就能清晰透彻这里这样做的原因了)
2.玩家以及BOSS属性相关设置
- 在开始相关游戏逻辑讲解前,我们先来对游戏中的玩家和BOSS进行一下初始化,设置一下位置,以及角色对应的表示的图标
- 同时呢,我们的玩家和BOSS是需要进行战斗的,我们在初始化的时候也需要设置一下血量和两者的攻击力方便之后战斗逻辑的编写()
csharp
#region 5 boss属性相关
//boss坐标
int bossX = 24;
int bossY = 15;
//boss攻击力的最大最小值
int bossAtkMin = 7;
int bossAtkMax = 13;
//boss血量
int bossHp = 100;
string bossIcon = "■";
//申明一个 颜色变量
ConsoleColor bossColor = ConsoleColor.Green;
#endregion
#region 6 玩家属性相关
int playerX = 4;
int playerY = 5;
//玩家攻击力的最大最小值
int playerAtkMin = 8;
int playerAtkMax = 12;
//玩家血量
int playerHp = 100;
string playerIcon = "●";
ConsoleColor playerColor = ConsoleColor.Yellow;
//玩家输入的内容 外面申明 节约性能
//检测玩家的输入 方便之后的移动和攻击逻辑
char playerInput;
#endregion
#region 7 玩家战斗相关
//战斗状态
bool isFight = false;//在玩家与boss战斗或者未进行战斗时应该有不同的逻辑,通过一个bool变量来判断应该走哪种逻辑
#endregion
二.玩家移动以及战斗逻辑
1.玩家的移动
- 和正常的游戏一样,我们希望我们的玩家能够按住"WASD"来进行上下左右的移动,而移动的基本原理是什么呢?就是改变在游戏画面上显示的玩家的坐标,也就是我们的控制台上,同时呢,我们得把控制台上上一个玩家的图标给擦除掉
- 这里又有一个问题了,我们的玩家是不能随意的进行移动的,我们必须给他的移动加上一定的范围限制,否则难不成把你的玩家移动到我们的画面外吗?因此这里又有一个毕竟复杂的问题,也就是边界讨论
玩家移动的边界问题
- 刚才我们提到了玩家移动的边界问题,那什么是边界问题呢?我们结合我们这个游戏的的具体画面来分析一下
- 首先,在我们的游戏画面中目前是有三种物体的,第一个就是不变的墙,第二个是玩家,第三个就是我们的BOSS,我们所能控制的就是游戏界面中玩家的移动。
- 我们玩家的移动必须在规定的范围内,也就是红墙里,这是我们设定的游戏区域,同时,我们的玩家不能与红墙的坐标重合,我们在前面说了,红墙是不变的物体,并不会随着玩家的控制而发生变化,因此我们没有必要在执行游戏的每次循环中都重复打印它,只需要在切换到游戏场景时打印它就足够了,因此如果玩家与红墙重合的话,该坐标的红墙就会消失并且在游戏没结束时就不会再打印了,同时BOSS也是,我们同样也不能让玩家与BOSS的坐标重合,尽管BOSS会在每一次循环中都打印,但是我们让玩家和BOSS能够重合,就不好处理玩家和BOSS战斗的逻辑了。
- 通过以上的分析,我们可以得到以下玩家移动的代码
csharp
#region 6 玩家移动相关
//擦除之前位置的玩家图标
Console.SetCursorPosition(playerX, playerY);
Console.Write(" ");
//改位置
switch (playerInput)
{
case 'W':
case 'w':
--playerY;//往上移动
if (playerY < 1)//边界检查 如果玩家到红墙上边缘就把玩家坐标拉回去
{
playerY = 1;
}
//位置如果和boss重合了 并且boss没有死
else if (playerX == bossX && playerY == bossY && bossHp > 0)
{
//拉回去
++playerY;
}
break;
case 'A':
case 'a':
playerX -= 2;//向左移动
if (playerX < 2)边界检查 如果玩家到红墙左边缘就把玩家坐标拉回去
{
playerX = 2;
}
else if (playerX == bossX && playerY == bossY && bossHp > 0)
{
//拉回去
playerX += 2;
}
break;
case 'S':
case 's':
++playerY;//向下移动
if (playerY > h - 7) 边界检查 如果玩家到红墙下边缘就把玩家坐标拉回去
{
playerY = h - 7;
}
else if (playerX == bossX && playerY == bossY && bossHp > 0)
{
//拉回去
--playerY;
}
break;
case 'D':
case 'd':
playerX += 2; //向右移动
if (playerX > w - 4)//边界检查 如果玩家到红墙右边缘就把玩家坐标拉回去
{
playerX = w - 4;
}
else if (playerX == bossX && playerY == bossY && bossHp > 0)
{
//拉回去
playerX -= 2;
}
break;
}
#endregion
- 如图,现在我们已经能在地图上自由的移动啦,接下来我们就来进行战斗相关的设置
2.战斗相关代码逻辑
怎么进入战斗
- 那么怎样和BOSS进行战斗呢?首先,我们得先把我们的玩家移动到BOSS的身边,然后按下某个键进入战斗,同时把玩家的状态切换到战斗状态,此时,我们也得在下面的方框下打印一些信息来提醒玩家进入战斗可以攻击BOSS了
csharp
case 'J':
case 'j':
//开始战斗
if ((playerX == bossX && playerY == bossY - 1 ||
playerX == bossX && playerY == bossY + 1 ||
playerX == bossX - 2 && playerY == bossY ||
playerX == bossX + 2 && playerY == bossY) && bossHp > 0)//玩家处于BOSS周围
{
isFight = true;//玩家状态切换至战斗状态
//可以开始战斗,提醒一下玩家应该做什么,以及玩家和BOSS此时的血量
Console.SetCursorPosition(2, h - 5);
Console.ForegroundColor = ConsoleColor.White;
Console.Write("开始和Boss战斗了,按J键继续");
Console.SetCursorPosition(2, h - 4);
Console.Write("玩家当前血量为{0}", playerHp);
Console.SetCursorPosition(2, h - 3);
Console.Write("boss当前血量为{0}", bossHp);
}
//进入战斗状态就要让玩家不能再移动
//同时下方能够显示信息,提示双方造成的伤害和此时剩下的血量
break;
战斗逻辑
- 我们上面定义了一个bool类型的IsFight来判断此时的玩家是否进入战斗,当进入战斗后,我们就得进入战斗逻辑而不再是移动逻辑了,那战斗时应该是怎样的逻辑呢?
- 首先,我们在每次进入战斗状态时都得判断一下此时双方的血量,无论双方谁的血量降为0都要进入下一环节或者游戏失败或者进入营救公主的环节
- 然后,我们来通过双方的攻击力来轮流对对方进行攻击,我们在设置玩家和BOSS时都给予了攻击力的最大值和最小值,这样我们每次攻击时,双方都应该造成最大攻击和最小攻击之间的随机伤害,对方扣除相应的血量,这样基本的思路就捋清楚了,接一下则进入一个游戏分支
- 当BOSS的血先减少为0时,就应该在地图上销毁BOSS并且标记出公主的位置,同时恢复玩家的行动让玩家能够去到公主身边营救公主
- 当玩家的血量先减少为0时,说明此时玩家未能战胜BOSS,此时就应该更改场景ID进入到游戏结束界面
csharp
//游戏场景的死循环 专门用来 检测 玩家输入相关循环
while (true)
{
//boss活着时才绘制
if (bossHp > 0)
{
//绘制boss图标
Console.SetCursorPosition(bossX, bossY);
Console.ForegroundColor = bossColor;
Console.Write(bossIcon);
}
//画出玩家
Console.SetCursorPosition(playerX, playerY);
Console.ForegroundColor = playerColor;
Console.Write(playerIcon);
//得到玩家输入
playerInput = Console.ReadKey(true).KeyChar;
//战斗状态处理什么逻辑
if (isFight)
{
#region 7 玩家战斗相关
//如果是战斗状态 你做什么
//只会处理J键
if (playerInput == 'J' || playerInput == 'j')
{
//在这判断 玩家或者怪物 是否死亡 如果死亡了 继续之后的流程
if (playerHp <= 0)
{
//游戏结束
//输掉了 应该直接显示 我们的 游戏结束界面
nowSceneID = 3;
break;
}
else if (bossHp <= 0)
{
//去营救公主
//boss擦除
Console.SetCursorPosition(bossX, bossY);
Console.Write(" ");
isFight = false;//重新让玩家能够按wasd键移动
}
else
{
//去处理按J键打架
// 玩家打怪物
Random r = new Random();
//得到随机攻击了
int atk = r.Next(playerAtkMin, playerAtkMax);
//血量减对应的攻击力
bossHp -= atk;
//打印信息
Console.ForegroundColor = ConsoleColor.Green;
//先擦除这一行 上次显示的内容
Console.SetCursorPosition(2, h - 4);
Console.Write(" ");
//再来写新的信息
Console.SetCursorPosition(2, h - 4);
Console.Write("你对恶龙造成了{0}点伤害,boss剩余血量为{1}", atk, bossHp);
// 怪物打玩家
if (bossHp > 0)
{
//得到随机攻击了
atk = r.Next(bossAtkMin, bossAtkMax);
playerHp -= atk;
//打印信息
Console.ForegroundColor = ConsoleColor.Yellow;
//先擦除这一行 上次显示的内容
Console.SetCursorPosition(2, h - 3);
Console.Write(" ");
//再来写新的信息
//boss如果把玩家打死了 做什么
if (playerHp <= 0)
{
Console.SetCursorPosition(2, h - 3);
Console.Write("很遗憾,你未能通过boss的试炼,战败了");
}
else
{
Console.SetCursorPosition(2, h - 3);
Console.Write("恶龙对你造成了{0}点伤害,你的剩余血量为{1}", atk, playerHp);
}
}
else
{
//擦除之前的战斗信息
Console.SetCursorPosition(2, h - 5);
Console.Write(" ");
Console.SetCursorPosition(2, h - 4);
Console.Write(" ");
Console.SetCursorPosition(2, h - 3);
Console.Write(" ");
//显示恭喜胜利的信息
Console.SetCursorPosition(2, h - 5);
Console.Write("你战胜了boss,快去营救公主");
Console.SetCursorPosition(2, h - 4);
Console.Write("前往公主身边按J键继续");
}
}
}
- 上面的逻辑我想我已经讲清楚了,现在唯一需要提一点的就是我们每次在打印新的信息时都需要先擦除上一次打印的信息,而由于我们的如果使用Clear清屏的话会把画面上所有内容都给消除,因此我们这里选择在打印新信息前先把对应这一行置为空白,这样一来就能达到我们想要的效果
- 好了,讲到这里,今天有关游戏场景相关的知识也就讲的差不多了,接下来把源码放在这里以供大家参考和自己修改
三. 游戏源码
csharp
//游戏场景
case 2:
Console.Clear();
#region 4 不变的红墙
//设置颜色为红色
Console.ForegroundColor = ConsoleColor.Red;
//画墙
//上方墙
for (int i = 0; i < w; i += 2)
{
//上方墙
Console.SetCursorPosition(i, 0);
Console.Write("■");
//下方墙
Console.SetCursorPosition(i, h - 1);
Console.Write("■");
//中间墙
Console.SetCursorPosition(i, h - 6);
Console.Write("■");
}
//左边的墙
for (int i = 0; i < h; i++)
{
//左边的墙
Console.SetCursorPosition(0, i);
Console.Write("■");
//右边的墙
Console.SetCursorPosition(w - 2, i);
Console.Write("■");
}
#endregion
#region 5 boss属性相关
int bossX = 24;
int bossY = 15;
int bossAtkMin = 7;
int bossAtkMax = 13;
int bossHp = 100;
string bossIcon = "■";
//申明一个 颜色变量
ConsoleColor bossColor = ConsoleColor.Green;
#endregion
#region 6 玩家属性相关
int playerX = 4;
int playerY = 5;
int playerAtkMin = 8;
int playerAtkMax = 12;
int playerHp = 100;
string playerIcon = "●";
ConsoleColor playerColor = ConsoleColor.Yellow;
//玩家输入的内容 外面申明 节约性能
char playerInput;
#endregion
#region 7 玩家战斗相关
//战斗状态
bool isFight = false;
#endregion
//游戏场景的死循环 专门用来 检测 玩家输入相关循环
while (true)
{
#region 5 boss属性相关
//boss活着时才绘制
if (bossHp > 0)
{
//绘制boss图标
Console.SetCursorPosition(bossX, bossY);
Console.ForegroundColor = bossColor;
Console.Write(bossIcon);
}
#endregion
#region 6 玩家移动相关
//画出玩家
Console.SetCursorPosition(playerX, playerY);
Console.ForegroundColor = playerColor;
Console.Write(playerIcon);
//得到玩家输入
playerInput = Console.ReadKey(true).KeyChar;
#endregion
//战斗状态处理什么逻辑
if (isFight)
{
#region 7 玩家战斗相关
//如果是战斗状态 你做什么
//只会处理J键
if (playerInput == 'J' || playerInput == 'j')
{
//在这判断 玩家或者怪物 是否死亡 如果死亡了 继续之后的流程
if (playerHp <= 0)
{
//游戏结束
//输掉了 应该直接显示 我们的 游戏结束界面
nowSceneID = 3;
break;
}
else if (bossHp <= 0)
{
//去营救公主
//boss擦除
Console.SetCursorPosition(bossX, bossY);
Console.Write(" ");
isFight = false;
}
else
{
//去处理按J键打架
// 玩家打怪物
Random r = new Random();
//得到随机攻击了
int atk = r.Next(playerAtkMin, playerAtkMax);
//血量减对应的攻击力
bossHp -= atk;
//打印信息
Console.ForegroundColor = ConsoleColor.Green;
//先擦除这一行 上次显示的内容
Console.SetCursorPosition(2, h - 4);
Console.Write(" ");
//再来写新的信息
Console.SetCursorPosition(2, h - 4);
Console.Write("你对boss造成了{0}点伤害,boss剩余血量为{1}", atk, bossHp);
// 怪物打玩家
if (bossHp > 0)
{
//得到随机攻击了
atk = r.Next(bossAtkMin, bossAtkMax);
playerHp -= atk;
//打印信息
Console.ForegroundColor = ConsoleColor.Yellow;
//先擦除这一行 上次显示的内容
Console.SetCursorPosition(2, h - 3);
Console.Write(" ");
//再来写新的信息
//boss如果把玩家打死了 做什么
if (playerHp <= 0)
{
Console.SetCursorPosition(2, h - 3);
Console.Write("很遗憾,你未能通过boss的试炼,战败了");
}
else
{
Console.SetCursorPosition(2, h - 3);
Console.Write("boss对你造成了{0}点伤害,你的剩余血量为{1}", atk, playerHp);
}
}
else
{
//擦除之前的战斗信息
Console.SetCursorPosition(2, h - 5);
Console.Write(" ");
Console.SetCursorPosition(2, h - 4);
Console.Write(" ");
Console.SetCursorPosition(2, h - 3);
Console.Write(" ");
//显示恭喜胜利的信息
Console.SetCursorPosition(2, h - 5);
Console.Write("你战胜了boss,快去营救公主");
Console.SetCursorPosition(2, h - 4);
Console.Write("前往公主身边按J键继续");
}
}
}
#endregion
}
//非战斗状态处理什么逻辑
else
{
#region 6 玩家移动相关
//擦除
Console.SetCursorPosition(playerX, playerY);
Console.Write(" ");
//改位置
switch (playerInput)
{
case 'W':
case 'w':
--playerY;
if (playerY < 1)
{
playerY = 1;
}
//位置如果和boss重合了 并且boss没有死
else if (playerX == bossX && playerY == bossY && bossHp > 0)
{
//拉回去
++playerY;
}
break;
case 'A':
case 'a':
playerX -= 2;
if (playerX < 2)
{
playerX = 2;
}
else if (playerX == bossX && playerY == bossY && bossHp > 0)
{
//拉回去
playerX += 2;
}
break;
case 'S':
case 's':
++playerY;
if (playerY > h - 7)
{
playerY = h - 7;
}
else if (playerX == bossX && playerY == bossY && bossHp > 0)
{
//拉回去
--playerY;
}
break;
case 'D':
case 'd':
playerX += 2;
if (playerX > w - 4)
{
playerX = w - 4;
}
else if (playerX == bossX && playerY == bossY && bossHp > 0)
{
//拉回去
playerX -= 2;
}
break;
case 'J':
case 'j':
//开始战斗
if ((playerX == bossX && playerY == bossY - 1 ||
playerX == bossX && playerY == bossY + 1 ||
playerX == bossX - 2 && playerY == bossY ||
playerX == bossX + 2 && playerY == bossY) && bossHp > 0)
{
isFight = true;
//可以开始战斗
Console.SetCursorPosition(2, h - 5);
Console.ForegroundColor = ConsoleColor.White;
Console.Write("开始和Boss战斗了,按J键继续");
Console.SetCursorPosition(2, h - 4);
Console.Write("玩家当前血量为{0}", playerHp);
Console.SetCursorPosition(2, h - 3);
Console.Write("boss当前血量为{0}", bossHp);
}
//要让玩家不能再移动
//下方能够显示信息
break;
}
#endregion
}
}
break;
总结
- 今天的内容到这里就结束了,游戏场景的相关设置才是这款游戏的核心以及难点,如果你真的想学好这方面的内容不妨跟着博主自己动手尝试一下,毕竟很多问题只有自己动手试过了才能发现,如果你在实现过程中遇到任何问题,欢迎在评论区指出或者私信我!!
- 后面的内容很快更新,感兴趣不妨关注一下错过后面的内容哦!!
新人博主创作不易,如果感觉文章内容对你有所帮助的话不妨三连一下再走呗。你们的支持就是我更新的动力!!!
**(可莉请求你们三连支持一下博主!!!点击下方评论点赞收藏帮帮可莉吧)**