大家好,失踪人口回归。新的一年,新的坑位(不是),今年就让我来带领小伙伴们一起设计一款独立游戏吧(* *)。
言归正传,说起游戏,大家第一印象可能就是次时代主机,PC主机或手机上那些画面绚丽,剧情丰富的游戏:如《GTA系列》,《赛博朋克2077》,《梦幻西游》,《塞尔达传说》等等。但可惜的是,单单靠一个人,是不可能完成这样的大型游戏的。不是技术上的问题,而是人力与成本上的问题。
但不要灰心,市面上也有很多仅靠1,2个人做出的很多精美的甚至获得国际大奖的独立游戏:如《小小梦魇》,《地狱边境》等等。
但我们要记住:万丈高楼起于垒土。作为一个初学者,我们需要学习的知识还有很多。所以不要着急,让我们从地基开始,打好基础,一步一步地向最高峰发起进攻。
一些题外话:游戏设计光靠一片热血是不行的,一些必备的基础知识则是必需的。例如基本的C++,JAVA等计算机语言的掌握,基本的开发环境的安装等。这里我默认读者是掌握了这些知识的。
·创建项目
首先,我们需要在VS上创建一个空的控制台应用
接着我们可以给项目取一个名字,这里我就叫这个小游戏为《回家吧-小箱子》
项目名称为“GoHome_SmallBox”
在项目创建成功后,我们可以看到VS自动为我们创建了一个GoHome_SmallBox.cpp文件
#include
int main()
{
std::cout << "Hello World!
";
}
我们将上面的代码修改一下:
#include
using namespace std;
int main()
{
char c;
cin >> c;
cout << c;
return 0;
}
cin,cout都是istream类和ostream类的全局变量,我们在包含头文件iostream后,并声明using namespace std,就可以在任何位置使用了。
cin通过>>将输入值写入变量c,cout通过<<将变量值c输出。
对于大部分游戏程序来说,整个项目可以分为三步操作:
while(true){
getInput(); // 获取键盘或者鼠标的输入信息
updateGame(); // 根据输入信息对游戏内容进行修改
draw(); // 对修改后的游戏内容进行绘制并输出结果
}
·游戏内容设计
我们设定箱子为符号Y,箱子的最终目的地为符号X,当箱子到达目的地后,X变为Z,表示箱子成功回到家。而推动的小人则设定为P。
思考:当我们设计好基本内容后,还需要进行进一步的思考
假如不解决上面的一些问题,我们直接进行编码实现的话,就会发现箱子有时候会飞出边界,人物P也会移动到边界之外。这样一来,就不符合我们所设定的游戏规则了。
·程序设计
首先我们来定义一些基本的初始变量:
// #表示墙壁,p表示玩家,X表示目的地,Y表示箱子
const char gSceneData[] = "
########
# XX p #
# YY #
# #
########";
const int gSceneWidth = 8;
const int gSceneHeight = 5;
enum Object {
OBJ_SPACE, // 空白空间
OBJ_WALL, // 墙壁 #
OBJ_GOAL, // 目标点 X
OBJ_BOX, // 盒子 Y
OBJ_BOX_ON_GOAL, // 盒子在目标点处 Z
OBJ_PLAYER, // 玩家 p
OBJ_PLAYER_ON_GOAL, // 玩家在目标点处 P
OBJ_UNKNOW // 未知
};
这里我把最初的初始场景数据放在了一个全局变量char数组gSceneData中。然后我们定义了一个枚举变量,这里面使用了几个枚举值描述了所有的游戏状态。
接下就是我们的主循环的代码
int main()
{
// 创建初始状态数组
Object* state = new Object[gSceneWidth * gSceneHeight];
// 初始化场景
initialize(state, gSceneWidth, gSceneHeight, gSceneData);
// 游戏主循环
while (true) {
// 绘制
draw(state, gSceneWidth, gSceneHeight);
// 通关检测
if (check(state, gSceneWidth, gSceneHeight)) {
break;
}
// 获取输入
cout << "w:向上;a:向左;s:向下;d:向右,请输入:" << endl;
char input;
cin >> input;
// 更新数据
update(state, input, gSceneWidth, gSceneHeight);
}
// 胜利后的信息
cout << "恭喜你成功过关!" << endl;
delete[] state;
state = 0;
return 0;
}
注意到其中调用到了下面四个方法:
// 初始化方法
void initialize(Object* state, int w, int h, const char* sceneData) {
const char* index = sceneData;
int x = 0;
int y = 0;
while (*index != ' ') { // 当字符不为NULL时
Object t;
switch (*index) {
case '#': {
t = OBJ_WALL;
break;
}
case ' ': {
t = OBJ_SPACE;
break;
}
case 'X': {
t = OBJ_GOAL;
break;
}
case 'Y': {
t = OBJ_BOX;
break;
}
case 'Z': {
t = OBJ_BOX_ON_GOAL;
break;
}
case 'p': {
t = OBJ_PLAYER;
break;
}
case 'P': {
t = OBJ_PLAYER_ON_GOAL;
break;
}
case '
': { // 到下一行
x = 0; // x返回最左边
++y; // y进入下一行
t = OBJ_UNKNOW; // t暂时无数据
break;
}
default: {
t = OBJ_UNKNOW; // t为非法数据
}
}
++index;
if (t != OBJ_UNKNOW) { // 若t为非法数据,则略过
state[y * w + x] = t;// 向状态数据写入数据,这里的位置表示向下第y行,向右第x列的位置。这里是将一个二维数据存放在了一个一维数组里面
++x;
}
}
}
// 绘制,将state数组的数据绘制在控制台中
void draw(const Object* state, int w, int h) {
// 按照枚举值的顺序定义该数组
const char front[] = { ' ','#','X','Y','Z','p','P' };
for (int y = 0; y < h; ++y) {
for (int x = 0; x < w; ++x) {
Object o = state[y * w + x];
cout << front[o];
}
cout << endl;
}
}
// 通关检测
bool check(const Object* state, int w, int h) {
// 检查若没有盒子后,则通关
for (int i = 0; i < w * h; ++i) {
if (state[i] == OBJ_BOX) {
return false;
}
}
return true;
}
// 更新数据
void update(Object* state, char input, int w, int h) {
// 首先获取移动的变换量
int dx = 0;
int dy = 0;
// 这里我们注意一下,我们假设的是左上角为坐标原点,
// 那么向上移动,y则为-1;向下移动,y则为1,y轴正向为向下
// 向左移动,x为-1;向右移动,x为1
switch (input) {
case 'a':dx = -1; break;// 向左移动
case 'd':dx = 1; break;// 向右移动
case 'w':dy = -1; break;// 向上移动
case 's':dy = 1; break;// 向下移动
}
// 查询玩家坐标,这里其实可以设置一个全局变量,记录上一次玩家所在位置,这样就不用每次来寻找一次玩家的位置
int i = -1;
for (i = 0; i < w * h; ++i) {
if (state[i] == OBJ_PLAYER || state[i] == OBJ_PLAYER_ON_GOAL) {
break;
}
}
int x = i % w;// 小人的x轴位置应当为i对宽度的余数
int y = i / w;// 小人的y轴位置应当为i对宽度的商
//玩家移动后的坐标
int tx = x + dx;
int ty = y + dy;
// 对玩家的位置进行判断
if (tx < 0 || ty < 0 || tx >= w || ty >= h) {
return;
}
// 移动位置为空白或者是目标点,则玩家移动
int p = y * w + x;// 玩家的位置
int tp = ty * w + tx;// 玩家移动后的位置
if (state[tp] == OBJ_SPACE || state[tp] == OBJ_GOAL) {
state[tp] = (state[tp] == OBJ_GOAL) ? OBJ_PLAYER_ON_GOAL : OBJ_PLAYER;// 若移动位置为目标点,则变为玩家站在目标点;否则则为玩家本身
state[p] = (state[p] == OBJ_PLAYER_ON_GOAL) ? OBJ_GOAL : OBJ_SPACE;// 若当前位置为目标点,则变为目标点;否则变为空白位置
}
else if (state[tp] == OBJ_BOX || state[tp] == OBJ_BOX_ON_GOAL) { // 如果移动位置为箱子,或者为箱子在目标点,并且箱子的下一个位置为空白或目标点,则可以移动
int tx2 = tx + dx;
int ty2 = ty + dy;
// 检查移动位置同方向的下一个位置是否为合法位置
if (tx2 < 0 || ty2 < 0 || tx2 >= w || ty2 >= h) { //按键无效
return;
}
int tp2 = ty2 * w + tx2;// 移动位置同方向上的下一个位置
if (state[tp2] == OBJ_SPACE || state[tp2] == OBJ_GOAL) {
// 按顺序更改三个位置的数据
state[tp2] = (state[tp2] == OBJ_GOAL) ? OBJ_BOX_ON_GOAL : OBJ_BOX;
state[tp] = (state[tp] == OBJ_BOX_ON_GOAL) ? OBJ_PLAYER_ON_GOAL : OBJ_PLAYER;
state[p] = (state[p] == OBJ_PLAYER_ON_GOAL) ? OBJ_GOAL : OBJ_SPACE;
}
}
}
好啦,今天的任务就算完成啦。但我们可以发现,这仅仅是一个初始版的游戏,还有很多可以优化改进的地方。那么我们下一次还将继续深入,对我们的第一个小游戏进行优化。
谢谢阅读(* *)
页面更新:2024-04-23
本站资料均由网友自行发布提供,仅用于学习交流。如有版权问题,请与我联系,QQ:4156828
© CopyRight 2008-2024 All Rights Reserved. Powered By bs178.com 闽ICP备11008920号-3
闽公网安备35020302034844号