jig 发表于 2006-8-3 09:26

[原创]浅谈游戏设计思路 - “棋盘”游戏

<P>        <br>                                   浅谈游戏设计思路 - “棋盘”游戏<br>作者:孙靖(Jig)     时间:2006 - 07 - 31     <br>若要转贴或使用本文章介绍的技术,请在你发布的文章或作品中注明出处。</P>
<P>    先前在论坛中看了一位朋友让大家给他查看一下他写的推箱子游戏为什么出错,我第一想法就是这很难做到,别人写的程序,特别是实现流程和逻辑只有作者本人比较清楚,外人是要花费一定时间才能把握程序的中主体设计思想再进一步研究。我想除了学习或项目要求是很少人愿意花这样的精力去读别人的代码的,而粗看他的代码,我觉得是他本人在程序设计的思想上出了差错,而细想现在对引擎或硬件知识介绍的多却很少有讲解怎么实现对现实的抽象,即怎么合理的去设计游戏,实用软件......所以想写这样一篇文章来和大家探讨一下“棋盘”游戏的设计思路。<br>    “棋盘”游戏的种类丰富,俄罗斯方块,推箱子,象棋,五子棋......等等这些都可以归类到“棋盘”游戏中来。面对这类游戏设计的时候,我本人的见解是首先对“棋盘”做研究,实在不行再去研究“棋子”。换句话说,就是以游戏中的活动区域为研究对象,像象棋就是他的棋盘,俄罗斯方块就是他整个的方块的活动区域。我们这里姑且就把他叫做“棋盘法”。</P>
<P>    一. “棋盘法”基本实现方法       <br>    “棋盘法”实现起来其实很简单,一般来说就是把棋盘看作一个网格,那么我们就把他看作与之对应的二维数组。然后适当组织数据结构去抽象旗子,制定规<br>则,这样就很容易实现游戏。</P>
<P>    二. “棋盘法”的优缺点<br>    任何一个算法或思想都是有优缺点的,而我们的“棋盘法”的最大优点就是可以方便我们组织数据结构,简化了游戏规则的编写,使我们编程时思路比较清晰直观,通常就是直接根据游戏实际规则编写就可以了,这个我们会以实例加以说明。然而他最大的缺点就是可能耗费大量内存,试想,如果我们的棋盘规格很大,那我们就得开辟一个比较大的二维数组,并且如果你要将代码移植到单片机,ARM等硬件平台上。那你就不得不考虑自己的方法问题,在这些硬件平台上内存资源是极其宝贵的。也许有朋友会说那在PC机上总可以的。的确现在PC机资源丰富,功能强大,可有时候还是不能如你所愿。一个很经典的例子就是五子棋的人工智能,为了让五子棋在人机对战中具备攻防兼备的智能,我们只有开辟一个于棋盘规格相呼应的二维的树组来记录棋盘上各点的胜算几率,(有兴趣的朋友可以到网上去找找资料)这样的话,在DOS下你就只能开辟一个约 10X10 的棋盘,即使在WINDOS下也只能大约 16 X 16 的棋盘。(当然,这里是说直接开辟数组,采用特别技术的不算)。所以说,这个“棋盘法”也是有他自己的限制的。</P>
<P>    三. “棋盘法”的适用条件<br>    根据上面的介绍,我们应该很容易总结出“棋盘法”的适用条件。即棋盘规格适中,棋子会频繁的在棋盘中移动。游戏规则若复杂更应首先推选“棋盘法”。很好的一个例子就是象棋。象棋的走法规则复杂,而采用棋盘法就可以很直观的把规则做出来。当然其中要注意的是,程序员要考虑好使用内存的大小,考虑好游戏编写方法和内存数据管理技巧的相匹配。比如,一个很经典的游戏 -- 贪食蛇。他就不适合用“棋盘法”或是说用这个方法不合算。贪食蛇的棋盘很大,可蛇身并不会很大,若采用“棋盘法”那内存无疑是浪费,而且在规则实现上也是直接以蛇身作为研究对象好解决问题。所以在编写这类游戏时得在宏观上对他有个较全面的考虑。<br>    <br>    四. “棋盘法”的例子<br>    前面说过,可以用“棋盘法”编写的游戏很多,我们以前玩的小游戏我基本都写过,基于方便说明问题和论坛朋友提出的帮忙看看他的推箱子游戏。那下面我们就拿推箱子游戏来说明“棋盘法”的编写过程。</P>
<P>    1. 前期准备<br>    在编写这个游戏之前,我们先个游戏找个主题,我的是“植树”。即“箱子其实就是树,由一个小人搬运到树坑中。首先我们开辟一个 12X12 大小的二维数组作为棋盘。然后我们思考一下推箱子的棋子有哪些?一个是小人,可移动的数,还有树坑。我们的游戏过程也很简单,在棋盘上有一圈篱笆,这个是小人的活动方法,其中有几个固定的树坑,还有分散在不同地方的树,小人要推动这些树全部进入树坑就算游戏通关。有了以上思考,我们就着手来实现。</P>
<P>    2. 定义数据<br>    我们开辟一个 12X12 的二维数组表示棋盘,然后我们来定义个数值代表的含义。数值为 0 表示此格子为背景,1 表示为篱笆,2 表示为树,3 表示为树坑,4表示人小人,5 表示为树已移植到树坑。<br>    有了此定义,我们就写出显示各棋子的模块:</P>
<P>void Dot_pawn(char x, char y, char mode);    /* 在棋盘相应位置画出棋子,其中mode为 0 表示背景,1 表示篱笆,<br>                           2 表示未移植树,3 表示树坑,4 表示人, 5 表示树种坑中*/<br>void Dot_pawn(char x, char y, char mode)<br>{<br>    char *name_bmp;</P>
<P>    switch(mode)                /* 根据Mode决定显示的图片 */<br>    {<br>    case 0:<br>        name_bmp = "kong.bmp";<br>        break;</P>
<P>    case 1:<br>        name_bmp = "liba.bmp";<br>        break;<br>    <br>    case 2:<br>        name_bmp = "shuh.bmp";<br>        break;</P>
<P>    case 3:<br>        name_bmp = "huang.bmp";<br>        break;</P>
<P>    case 4:<br>        name_bmp = "ren.bmp";<br>        break;</P>
<P>    case 5:<br>        name_bmp = "shul.bmp";<br>        break;<br>    }</P>
<P>    if (x &gt; -1 &amp;&amp; x &lt; 13 &amp;&amp; y &gt; -1 &amp;&amp; y , 13)    /* 在棋盘内 */<br>    {<br>    Chessboard[y][x] = mode;<br>    show_bmp(name_bmp, 40 * x, 40 * y);<br>    }<br>}</P>
<P>    3. 实现规则<br>    我们以小人的运动为基础,很自然就可以写出具体的规则。首先我们要明确这么一点,当按动方向键后,要记录下小人将要走到的格子,然后进行规则判断:</P>
<P>    如果小人将到达的格子为篱笆(即阻挡小人的路线)<br>    {<br>    退出规则判断,棋面布局不做任何改变;<br>    }</P>
<P>    如果小人将到达的格子不为树(即小人只是自己移动,不用搬动树)<br>    {<br>    恢复小人原有格子背景;<br>    在小人将到达的格子绘制小人;<br>    }<br>    如果小人将到达的格子为树(即小人要搬动树)<br>    {<br>    如果将被小人搬动的树允许被搬动(即被搬动树的前方无其他树并且不是篱笆)<br>    {<br>        如果将被小人搬动的树的前方为背景<br>        {<br>        恢复将被小人搬动的树的原来背景;<br>        在树新位置绘制树;<br>        恢复小人原有格子背景;<br>        在小人将到达的格子绘制小人;<br>        }<br>        如果将被小人搬动的树的前方为树坑<br>        {<br>        恢复将被小人搬动的树的原来背景;<br>        在树新位置绘制已种植好的树;<br>            恢复小人原有格子背景;<br>        在小人将到达的格子绘制小人;<br>        }<br>    }<br>    }</P>
<P>    如果已种植好的树达到社顶数量<br>    {<br>    游戏通关,重新开始游戏;<br>    }</P>
<P><br>    看,以上就是对规则的一个文字实现,我们只要按照以上规则写出代码就实现了游戏规则的程序化。我想到这也充分体现了“棋盘法”的最大优势。游戏规则被简化,显的很直观易懂,容易编程实现。我们来看代码:</P>
<P>void Rule();                    /* 游戏规则 */</P>
<P>void Rule()<br>{<br>    char x, y, count  = 0;</P>
<P>    /* 如果人将要到的位置为篱笆,退出 */<br>    if (Chessboard[Man_will_y][Man_will_x] == 1)<br>    {<br>    return;<br>    }</P>
<P>    /* 如果人将要到的位置不为树,即不用推箱子 */<br>    if (Chessboard[Man_will_y][Man_will_x] != 2 &amp;&amp; Chessboard[Man_will_y][Man_will_x] != 5)    <br>    {<br>    Dot_pawn(Man_x, Man_y, Mode_old);        /* 恢复原有面貌 */<br>    Mode_old =  Chessboard[Man_will_y][Man_will_x];    /* 记录新,以便以后恢复背景 */<br>    Dot_pawn(Man_will_x, Man_will_y, 4);        /* 画出人 */</P>
<P>    Man_x = Man_will_x;                /* 记录下人的新位置 */<br>    Man_y = Man_will_y;    <br>    }<br>    else    /* 推箱子 */    <br>    {<br>    /* 判断被推箱子的前方是否有阻挡 */<br>    if (Chessboard[2 * Man_will_y - Man_y][2 * Man_will_x - Man_x] != 1         <br>        &amp;&amp;Chessboard[2 * Man_will_y - Man_y][2 * Man_will_x - Man_x] != 2<br>        &amp;&amp; Chessboard[2 * Man_will_y - Man_y][2 * Man_will_x - Man_x] != 5)<br>    {<br>        /* 如果被推的箱子为2,即未移植树 */<br>        if (Chessboard[Man_will_y][Man_will_x] == 2)<br>        {<br>        /* 前方为树坑 */<br>        if (Chessboard[2 * Man_will_y - Man_y][2 * Man_will_x - Man_x] == 3)<br>        {<br>            Dot_pawn(2 * Man_will_x - Man_x, 2 * Man_will_y - Man_y, 5);    /* 植好树 */<br>            Chessboard[2 * Man_will_y - Man_y][2 * Man_will_x - Man_x] = 5;<br>        }<br>        else    /* 前方为空地 */<br>        {<br>            Dot_pawn(2 * Man_will_x - Man_x, 2 * Man_will_y - Man_y, 2);    /* 推动树 */<br>            Chessboard[2 * Man_will_y - Man_y][2 * Man_will_x - Man_x] = 2;<br>        }</P>
<P>        Dot_pawn(Man_will_x, Man_will_y, 0);                /* 恢复原树背景 */<br>        Chessboard[Man_will_y][Man_will_x] = 0;<br>        }<br>        else    /* 被推的箱子为5 表示树种坑中 */<br>        {<br>        /* 前方为树坑 */<br>        if (Chessboard[2 * Man_will_y - Man_y][2 * Man_will_x - Man_x] == 3)<br>        {<br>            Dot_pawn(2 * Man_will_x - Man_x, 2 * Man_will_y - Man_y, 5);    /* 植好树 */<br>            Chessboard[2 * Man_will_y - Man_y][2 * Man_will_x - Man_x] = 5;<br>        }<br>        else    /* 前方为空地 */<br>        {<br>            Dot_pawn(2 * Man_will_x - Man_x, 2 * Man_will_y - Man_y, 2);    /* 推动树 */<br>            Chessboard[2 * Man_will_y - Man_y][2 * Man_will_x - Man_x] = 2;<br>        }</P>
<P>        Dot_pawn(Man_will_x, Man_will_y, 3);                /* 恢复原树背景 */<br>        Chessboard[Man_will_y][Man_will_x] = 3;<br>        }</P>
<P>        Dot_pawn(Man_x, Man_y, Mode_old);                /* 恢复人位置原有面貌 */<br>        Mode_old =  Chessboard[Man_will_y][Man_will_x];            /* 记录新,以便以后恢复背景 */<br>        Dot_pawn(Man_will_x, Man_will_y, 4);                /* 画出人 */</P>
<P>        Man_x = Man_will_x;                        /* 记录下人的新位置 */<br>        Man_y = Man_will_y;<br>    }<br>    }</P>
<P>    for (y = 0; y &lt; 12; y++)<br>    {<br>    for (x = 0; x &lt; 12; x++)<br>    {<br>        if (Chessboard[y][x] == 5)<br>        {<br>        count++;<br>        if (count == 6)<br>        {<br>            neo_printf(500, 400, "OK");<br>            init_pawn();<br>        }<br>        }<br>    }<br>    }<br>}</P>
<P>看以上代码就是对前面规则文字叙述的实现。好了,其实介绍到这里,我这个所谓的“棋盘法”也就完了。而对此种方法,我个人总结就是 —— 只要他能简化规则,让规则实现起来方便,即使多耗费点内存,美工多点工序我都是愿意采用此种方法。<br>    写这篇文章纯粹就是发表自己的一点观点,希望给想接触游戏编程的朋友一点启发,和游戏编程高手讨论方法。若是朋友你有其他的想法可以提出我们一同探讨。</P>
<P>界面<br>[attach]10450[/attach]<br><br>
[align=right][color=#000066][此贴子已经被作者于2006-11-22 18:57:52编辑过][/color][/align]

jig 发表于 2006-8-3 09:27

<P>下面给出这个例子的全部代码:</P>
<P>#include &lt;neo.h&gt;</P>
<P><BR>/* 定义按键 */<BR>#define ESC     15131<BR>#define REST    4722<BR>#define UP      21504    <BR>#define DOWN    21760<BR>#define LEFT    20992<BR>#define RIGHT     21248</P>
<P><BR>   <BR>char Chessboard[12][12];    /* 定义“棋盘”,12 X 12 大小 */<BR>char Man_x, Man_y;        /* 定义人的位置 */<BR>char Man_will_x, Man_will_y;    /* 记录人下步要到的位置 */<BR>char Mode_old;            /* 记录下人的位置原来的mode值 */</P>
<P>void Dot_pawn(char x, char y, char mode);    /* 在棋盘相应位置画出棋子,其中mode为 0 表示空白,1 表示篱笆,<BR>                           2 表示未移植树,3 表示树坑,4 表示人, 5 表示树种坑中*/<BR>void init_pawn();                /* 初始化棋盘 */<BR>void paly_game();                /* 开始游戏 */<BR>void Rule();                    /* 游戏规则 */<BR>    </P>
<P>void Dot_pawn(char x, char y, char mode)<BR>{<BR>    char *name_bmp;</P>
<P>    switch(mode)                /* 根据Mode决定显示的图片 */<BR>    {<BR>    case 0:<BR>        name_bmp = "kong.bmp";<BR>        break;</P>
<P>    case 1:<BR>        name_bmp = "liba.bmp";<BR>        break;<BR>    <BR>    case 2:<BR>        name_bmp = "shuh.bmp";<BR>        break;</P>
<P>    case 3:<BR>        name_bmp = "huang.bmp";<BR>        break;</P>
<P>    case 4:<BR>        name_bmp = "ren.bmp";<BR>        break;</P>
<P>    case 5:<BR>        name_bmp = "shul.bmp";<BR>        break;<BR>    }</P>
<P>    if (x &gt; -1 &amp;&amp; x &lt; 13 &amp;&amp; y &gt; -1 &amp;&amp; y , 13)    /* 在棋盘内 */<BR>    {<BR>    Chessboard[y][x] = mode;<BR>    show_bmp(name_bmp, 40 * x, 40 * y);<BR>    }<BR>}</P>
<P>void init_pawn()<BR>{<BR>    FILE *fp;<BR>    char x, y;<BR>    int mode;</P>
<P><BR>    /* 直接读取外部文件设置画面,很方便 */<BR>    fp = fopen("set.txt", "r+");<BR>    for (y = 0; y &lt; 12; y++)<BR>    {<BR>    for (x = 0; x &lt; 12; x++)<BR>    {<BR>        fscanf(fp, "%d", &amp;mode);<BR>        Dot_pawn(x, y, mode);</P>
<P>        if (mode == 4)<BR>        {<BR>        Mode_old = 0;<BR>         Man_x = x;<BR>        Man_y = y;<BR>        }<BR>    }<BR>    }<BR>    fclose(fp); <BR>}</P>
<P><BR>void paly_game()<BR>{<BR>    <BR>    while(1)<BR>    {<BR>        switch(readkey())    <BR>        {<BR>        case ESC:            /* 退出 */<BR>        return;<BR>        break;</P>
<P>        case REST:            /* 重复初始化 */<BR>        init_pawn();<BR>        break;</P>
<P>        case UP:            /* 上键 */<BR>        Man_will_x = Man_x;    /* 记录人将要到的位置 */<BR>        Man_will_y = Man_y - 1;<BR>        Rule();<BR>        break;</P>
<P>         case DOWN:            /* 下键 */    <BR>        Man_will_x = Man_x;<BR>        Man_will_y = Man_y + 1;<BR>        Rule();<BR>        break;</P>
<P>         case LEFT:            /* 左键 */<BR>        Man_will_x = Man_x - 1;<BR>        Man_will_y = Man_y;<BR>        Rule();<BR>        break;</P>
<P>         case RIGHT:            /* 右键 */<BR>        Man_will_x = Man_x + 1;<BR>        Man_will_y = Man_y;<BR>        Rule();<BR>        break;<BR>    <BR>        }<BR>    }<BR>}</P>
<P>void Rule()<BR>{<BR>    char x, y, count  = 0;</P>
<P>    /* 如果人将要到的位置为篱笆,退出 */<BR>    if (Chessboard[Man_will_y][Man_will_x] == 1)<BR>    {<BR>    return;<BR>    }</P>
<P>    /* 如果人将要到的位置不为树,即不用推箱子 */<BR>    if (Chessboard[Man_will_y][Man_will_x] != 2 &amp;&amp; Chessboard[Man_will_y][Man_will_x] != 5)    <BR>    {<BR>    Dot_pawn(Man_x, Man_y, Mode_old);        /* 恢复原有面貌 */<BR>    Mode_old =  Chessboard[Man_will_y][Man_will_x];    /* 记录新,以便以后恢复背景 */<BR>    Dot_pawn(Man_will_x, Man_will_y, 4);        /* 画出人 */</P>
<P>    Man_x = Man_will_x;                /* 记录下人的新位置 */<BR>    Man_y = Man_will_y;    <BR>    }<BR>    else    /* 推箱子 */    <BR>    {<BR>    /* 判断被推箱子的前方是否有阻挡 */<BR>    if (Chessboard[2 * Man_will_y - Man_y][2 * Man_will_x - Man_x] != 1         <BR>        &amp;&amp;Chessboard[2 * Man_will_y - Man_y][2 * Man_will_x - Man_x] != 2<BR>        &amp;&amp; Chessboard[2 * Man_will_y - Man_y][2 * Man_will_x - Man_x] != 5)<BR>    {<BR>        /* 如果被推的箱子为2,即未移植树 */<BR>        if (Chessboard[Man_will_y][Man_will_x] == 2)<BR>        {<BR>        /* 前方为树坑 */<BR>        if (Chessboard[2 * Man_will_y - Man_y][2 * Man_will_x - Man_x] == 3)<BR>        {<BR>            Dot_pawn(2 * Man_will_x - Man_x, 2 * Man_will_y - Man_y, 5);    /* 植好树 */<BR>            Chessboard[2 * Man_will_y - Man_y][2 * Man_will_x - Man_x] = 5;<BR>        }<BR>        else    /* 前方为空地 */<BR>        {<BR>            Dot_pawn(2 * Man_will_x - Man_x, 2 * Man_will_y - Man_y, 2);    /* 推动树 */<BR>            Chessboard[2 * Man_will_y - Man_y][2 * Man_will_x - Man_x] = 2;<BR>        }</P>
<P>        Dot_pawn(Man_will_x, Man_will_y, 0);                /* 恢复原树背景 */<BR>        Chessboard[Man_will_y][Man_will_x] = 0;<BR>        }<BR>        else    /* 被推的箱子为5 表示树种坑中 */<BR>        {<BR>        /* 前方为树坑 */<BR>        if (Chessboard[2 * Man_will_y - Man_y][2 * Man_will_x - Man_x] == 3)<BR>        {<BR>            Dot_pawn(2 * Man_will_x - Man_x, 2 * Man_will_y - Man_y, 5);    /* 植好树 */<BR>            Chessboard[2 * Man_will_y - Man_y][2 * Man_will_x - Man_x] = 5;<BR>        }<BR>        else    /* 前方为空地 */<BR>        {<BR>            Dot_pawn(2 * Man_will_x - Man_x, 2 * Man_will_y - Man_y, 2);    /* 推动树 */<BR>            Chessboard[2 * Man_will_y - Man_y][2 * Man_will_x - Man_x] = 2;<BR>        }</P>
<P>        Dot_pawn(Man_will_x, Man_will_y, 3);                /* 恢复原树背景 */<BR>        Chessboard[Man_will_y][Man_will_x] = 3;<BR>        }</P>
<P>        Dot_pawn(Man_x, Man_y, Mode_old);                /* 恢复人位置原有面貌 */<BR>        Mode_old =  Chessboard[Man_will_y][Man_will_x];            /* 记录新,以便以后恢复背景 */<BR>        Dot_pawn(Man_will_x, Man_will_y, 4);                /* 画出人 */</P>
<P>        Man_x = Man_will_x;                        /* 记录下人的新位置 */<BR>        Man_y = Man_will_y;<BR>    }<BR>    }</P>
<P>    for (y = 0; y &lt; 12; y++)<BR>    {<BR>    for (x = 0; x &lt; 12; x++)<BR>    {<BR>        if (Chessboard[y][x] == 5)<BR>        {<BR>        count++;<BR>        if (count == 6)<BR>        {<BR>            neo_printf(500, 400, "OK");<BR>            init_pawn();<BR>        }<BR>        }<BR>    }<BR>    }<BR>}</P>
<P>void main(void)<BR>{   <BR>    /* -------------------- NEO下的一系列初始化 ---------------------- */<BR>    SAMPLE *sample = NULL;</P>
<P>    neo_init();             <BR>    set_window_mode(0);<BR>    set_vbe_mode(VBE640X480X64K);<BR>    install_sound(DIGI_AUTODETECT, MIDI_AUTODETECT, NULL);<BR>    install_keyboard();</P>
<P>    sample = load_wav("bei.wav");<BR>    play_sample(sample, 255, 128, 0, 1);<BR>    /* --------------------------------------------------------------- */</P>
<P>    init_pawn();    /* 初始化棋盘 */<BR>    paly_game();    /* 开始游戏 */</P>
<P>    <BR>    destroy_sample(sample);<BR>}<BR>END_OF_MAIN();</P>

lhh344519065 发表于 2006-9-5 00:31

<P><FONT color=#ff0000><STRONG>楼主<BR>你厉害啊<BR>加我QQ:422293101<BR>有不懂的地方还请赐教哦.[em01]</STRONG></FONT></P>

无庸的 发表于 2006-11-1 16:13

<P>我怎么用NEO.H老出现问题<BR>有谁能告诉具体我怎么样吗?</P>

一笔苍穹 发表于 2006-11-1 16:19

也得看是什么问题了,能详细说说么?

showna 发表于 2006-11-6 15:01

用vc和tc都找不到neo.h,这是什么样的头文件阿[em13]

一笔苍穹 发表于 2006-11-6 15:36

<P>这是一个扩展图形库的头文件,你要去下载之后将其拷贝进去,地址是:<BR><a href="http://dongkai.ys168.com/" target="_blank" >http://dongkai.ys168.com/</A><BR>目前最新的版本是2.1.90修正版,有什么问题欢迎到论坛来讨论。</P>

zh_hn 发表于 2007-11-26 22:23



页: [1]

编程论坛