游戏设计之路不来推推箱子么?
大家好,失踪人口回归。新的一年,新的坑位(不是),今年就让我来带领小伙伴们一起设计一款独立游戏吧()。
言归正传,说起游戏,大家第一印象可能就是次时代主机,PC主机或手机上那些画面绚丽,剧情丰富的游戏:如《GTA系列》,《赛博朋克2077》,《梦幻西游》,《塞尔达传说》等等。但可惜的是,单单靠一个人,是不可能完成这样的大型游戏的。不是技术上的问题,而是人力与成本上的问题。
但不要灰心,市面上也有很多仅靠1,2个人做出的很多精美的甚至获得国际大奖的独立游戏:如《小小梦魇》,《地狱边境》等等。
但我们要记住:万丈高楼起于垒土。作为一个初学者,我们需要学习的知识还有很多。所以不要着急,让我们从地基开始,打好基础,一步一步地向最高峰发起进攻。
一些题外话:游戏设计光靠一片热血是不行的,一些必备的基础知识则是必需的。例如基本的C,JAVA等计算机语言的掌握,基本的开发环境的安装等。这里我默认读者是掌握了这些知识的。
创建项目
首先,我们需要在VS上创建一个空的控制台应用
接着我们可以给项目取一个名字,这里我就叫这个小游戏为《回家吧小箱子》
项目名称为GoHomeSmallBox
在项目创建成功后,我们可以看到VS自动为我们创建了一个GoHomeSmallBox。cpp文件includeiostreamintmain(){std::coutHelloWorld!;}
我们将上面的代码修改一下:includeiostreamusingnamespacestd;intmain(){charc;cinc;coutc;return0;}
cin,cout都是istream类和ostream类的全局变量,我们在包含头文件iostream后,并声明usingnamespacestd,就可以在任何位置使用了。
cin通过将输入值写入变量c,cout通过将变量值c输出。
对于大部分游戏程序来说,整个项目可以分为三步操作:while(true){getInput();获取键盘或者鼠标的输入信息updateGame();根据输入信息对游戏内容进行修改draw();对修改后的游戏内容进行绘制并输出结果}
游戏内容设计
我们设定箱子为符号Y,箱子的最终目的地为符号X,当箱子到达目的地后,X变为Z,表示箱子成功回到家。而推动的小人则设定为P。
思考:当我们设计好基本内容后,还需要进行进一步的思考当P到达边界后,继续向边界移动会发生什么?当P推动两个箱子时,会发生什么?朝着边界推箱子,会发生什么?
假如不解决上面的一些问题,我们直接进行编码实现的话,就会发现箱子有时候会飞出边界,人物P也会移动到边界之外。这样一来,就不符合我们所设定的游戏规则了。
程序设计
首先我们来定义一些基本的初始变量:表示墙壁,p表示玩家,X表示目的地,Y表示箱子constchargSceneData〔〕XXpYY;constintgSceneWidth8;constintgSceneHeight5;enumObject{OBJSPACE,空白空间OBJWALL,墙壁OBJGOAL,目标点XOBJBOX,盒子YOBJBOXONGOAL,盒子在目标点处ZOBJPLAYER,玩家pOBJPLAYERONGOAL,玩家在目标点处POBJUNKNOW未知};
这里我把最初的初始场景数据放在了一个全局变量char数组gSceneData中。然后我们定义了一个枚举变量,这里面使用了几个枚举值描述了所有的游戏状态。
接下就是我们的主循环的代码intmain(){创建初始状态数组ObjectstatenewObject〔gSceneWidthgSceneHeight〕;初始化场景initialize(state,gSceneWidth,gSceneHeight,gSceneData);游戏主循环while(true){绘制draw(state,gSceneWidth,gSceneHeight);通关检测if(check(state,gSceneWidth,gSceneHeight)){break;}获取输入coutw:向上;a:向左;s:向下;d:向右,请输入:endl;charinput;cininput;更新数据update(state,input,gSceneWidth,gSceneHeight);}胜利后的信息cout恭喜你成功过关!endl;delete〔〕state;state0;return0;}
注意到其中调用到了下面四个方法:初始化方法voidinitialize(Objectstate,intw,inth,constcharsceneData){constcharindexsceneData;intx0;inty0;while(index!){当字符不为NULL时Objectt;switch(index){case:{tOBJWALL;break;}case:{tOBJSPACE;break;}caseX:{tOBJGOAL;break;}caseY:{tOBJBOX;break;}caseZ:{tOBJBOXONGOAL;break;}casep:{tOBJPLAYER;break;}caseP:{tOBJPLAYERONGOAL;break;}case:{到下一行x0;x返回最左边y;y进入下一行tOBJUNKNOW;t暂时无数据break;}default:{tOBJUNKNOW;t为非法数据}}index;if(t!OBJUNKNOW){若t为非法数据,则略过state〔ywx〕t;向状态数据写入数据,这里的位置表示向下第y行,向右第x列的位置。这里是将一个二维数据存放在了一个一维数组里面x;}}}绘制,将state数组的数据绘制在控制台中voiddraw(constObjectstate,intw,inth){按照枚举值的顺序定义该数组constcharfront〔〕{,,X,Y,Z,p,P};for(inty0;yh;y){for(intx0;xw;x){Objectostate〔ywx〕;coutfront〔o〕;}coutendl;}}通关检测boolcheck(constObjectstate,intw,inth){检查若没有盒子后,则通关for(inti0;iwh;i){if(state〔i〕OBJBOX){returnfalse;}}returntrue;}更新数据voidupdate(Objectstate,charinput,intw,inth){首先获取移动的变换量intdx0;intdy0;这里我们注意一下,我们假设的是左上角为坐标原点,那么向上移动,y则为1;向下移动,y则为1,y轴正向为向下向左移动,x为1;向右移动,x为1switch(input){casea:dx1;break;向左移动cased:dx1;break;向右移动casew:dy1;break;向上移动cases:dy1;break;向下移动}查询玩家坐标,这里其实可以设置一个全局变量,记录上一次玩家所在位置,这样就不用每次来寻找一次玩家的位置inti1;for(i0;iwh;i){if(state〔i〕OBJPLAYERstate〔i〕OBJPLAYERONGOAL){break;}}intxiw;小人的x轴位置应当为i对宽度的余数intyiw;小人的y轴位置应当为i对宽度的商玩家移动后的坐标inttxxdx;inttyydy;对玩家的位置进行判断if(tx0ty0txwtyh){return;}移动位置为空白或者是目标点,则玩家移动intpywx;玩家的位置inttptywtx;玩家移动后的位置if(state〔tp〕OBJSPACEstate〔tp〕OBJGOAL){state〔tp〕(state〔tp〕OBJGOAL)?OBJPLAYERONGOAL:OBJPLAYER;若移动位置为目标点,则变为玩家站在目标点;否则则为玩家本身state〔p〕(state〔p〕OBJPLAYERONGOAL)?OBJGOAL:OBJSPACE;若当前位置为目标点,则变为目标点;否则变为空白位置}elseif(state〔tp〕OBJBOXstate〔tp〕OBJBOXONGOAL){如果移动位置为箱子,或者为箱子在目标点,并且箱子的下一个位置为空白或目标点,则可以移动inttx2txdx;intty2tydy;检查移动位置同方向的下一个位置是否为合法位置if(tx20ty20tx2wty2h){按键无效return;}inttp2ty2wtx2;移动位置同方向上的下一个位置if(state〔tp2〕OBJSPACEstate〔tp2〕OBJGOAL){按顺序更改三个位置的数据state〔tp2〕(state〔tp2〕OBJGOAL)?OBJBOXONGOAL:OBJBOX;state〔tp〕(state〔tp〕OBJBOXONGOAL)?OBJPLAYERONGOAL:OBJPLAYER;state〔p〕(state〔p〕OBJPLAYERONGOAL)?OBJGOAL:OBJSPACE;}}}
好啦,今天的任务就算完成啦。但我们可以发现,这仅仅是一个初始版的游戏,还有很多可以优化改进的地方。那么我们下一次还将继续深入,对我们的第一个小游戏进行优化。
谢谢阅读()