幻想森林

 找回密码
 立即注册
搜索
热搜: 活动 交友 discuz
查看: 6926|回复: 14

[通用编程] [第二部分]为状态写代码

[复制链接]

50

主题

742

帖子

402

积分

版主

自定义头衔

Rank: 7Rank: 7Rank: 7

积分
402
发表于 2006-11-4 14:32:04 | 显示全部楼层 |阅读模式
为状态写代码
(前言:不是什么新东西,不过我自己还没有听到过类似的概念,所以就自己先来个写写)

游戏好比一个具有状态的机器(不要理解为状态机,那是复杂的数据结构的陷阱),我们在决定了草案(请参考第一部分关于设计的一丁点内容)以后往往不知道怎么搭起框架,本部分希望能够在AC开发的设计理念基础上为大家准备好一个通用的框架,而基础的核心,就是为状态写代码

通常游戏的循环部分(我省略了加载和卸载部分内容)可以更细地划分为如下一些部分:(以AC为例,如果
不知道AC这个是什么,可以到我自己的GOOGLEPAGE上先看看)
游戏被划分为很多步骤(STEP): [s:8]
  1.     enum STEP {
  2.         STEP_STARTANIM,         
  3.         STEP_CREATE,            
  4.         STEP_DELAY,            
  5.         STEP_MOVEDOWN,         
  6.         STEP_AUTODOWN,         
  7.         STEP_CHECK,            
  8.         STEP_CHECKEFFECT,         
  9.         STEP_ATTACK,            
  10.         STEP_ATTACKEFFECT,         
  11.         STEP_DEFENCE,         
  12.         STEP_ENDANIM,         
  13.         STEP_EXIT,            
  14.         STEP_COUNT,            
  15.     } ;
复制代码
我很乐意给每个步骤加上注释,但是“代码与注释脱离”告诉我们应该避免让具名的标签加上注释
(AVC很喜欢不写注释,但是他的变量你很少看到X1 Y3这类的名字),每个单位表示一个具体的流程(就是状态名),最后COUNT表示总计个数,然后你:
  1. typedef void (Player::*Flow)( void ) ;
  2. Flow flowfunction[STEP_COUNT] ;
复制代码
现在所有的步骤就是索引了(这里有一个小小问题,STEP和INDEX可能不兼容,为了方便你就STATIC_CAST好了,嗯……不知道什么是STATIC_CAST的人……你干脆想个别的办法,代替STEP名称巴……)当你创建每个步骤的时候,你所做的事情就是在 进行状态分离 (当然你根据你自己划分,尽可能细,简单的划分就是你游戏的处理环节,你这样想,游戏会不断重复这个步骤,是不是每次重复的内容都类似?举个例子:AC的小球下落被进行状态分离,首先小球会被创建i.e.CREATE,然后小球会先在高处停留一会,就是DELAY,接着开始下落MOVE,就这样分成了三步,然后你想想每次会执行一个微小的步骤,唉你学过高等数学的微积分多好,DELTA X dx对,你会很高兴发现你原来已经知道了游戏是怎么运作了,你的思路会从整体中被分离开,每次一个dx^^)

一大堆,实际就是一个enum和一个function然后怎么运作这个函数的呢? [s:8]
  1.     flowfunction[STEP_STARTANIM] = &Player::startAnim;
  2.     flowfunction[STEP_CREATE] = &Player::createBall;
  3.     flowfunction[STEP_DELAY] = &Player::delay;
  4.     flowfunction[STEP_MOVEDOWN] = &Player::moveDown;
  5.     flowfunction[STEP_AUTODOWN] = &Player::autoDown;
  6.     flowfunction[STEP_CHECK] = &Player::check;
  7.     flowfunction[STEP_CHECKEFFECT] = &Player::checkeffect;
  8.     flowfunction[STEP_ATTACK] = &Player::attack;
  9.     flowfunction[STEP_ATTACKEFFECT] = &Player::attackeffect;
  10.     flowfunction[STEP_DEFENCE] = &Player::defence;
  11.     flowfunction[STEP_ENDANIM] = &Player::endAnim;
  12.     flowfunction[STEP_EXIT] = &Player::exitGame;
复制代码
这么多!oh no!就一句话罢了……让你的每个player的函数注册到你步骤中去……看到了吗?为了方便,所有的函数就是对应步骤的名称的去STEP+小写罢了,那么这些事情是什么时候做得?
Player:layer(),构造函数……不懂的话……那你就放在init过程里面好了

嗯,现在是回顾一下的时候了,你在Manager部分(参考第一部分)创建了Player,Player构造的时候呢,注册了步骤对应的函数,然后你就不用关心流程怎么做了,你记住,现在你的函数的调用被简化到了你的一个标记上
  1. UINT_PTR attack_flag;
复制代码
UINT_PTR是什么,你大叫……嗯,让我解释不如让你自己换一个好了unsigned int,嗯,你点头,我可以用int吗(你说:我比较懒- -)完全可以^^,不过不要有负数^^

第一个部分中,我写了我是用一个MOVE一个RENDER实现的代码(没说清,就是Player::Move(), Player::Render() ),所以这里我们看看MOVE实作手法:
  1. void Player::move( void )
  2. {
  3.     (this->*flowfunction[attack_flag])() ;
  4. }
复制代码
哦……就一句话,看不懂吗- - [s:4] ,反正就是这样调用注册版函数……

偓哦!原来你要这么做:
Manager:oop --> 其中调用每个一个Player对象的 Player::Move() -->
后者调用flowfunction,根据当前的attackflag调用现在的函数,这叫一帧Frame,然后你那个函数作了什么(那我不知道了,反正你游戏作啥?偶很乐意知道哦^^),然后每次重复,所以要LOOP!

一个要求,flowfunction需要知道什么时候,怎么样改变状态?那是,比如AC中时间一到,下落,letschange state!
  1. ++attack_flag; return;
复制代码
就这样,真的,我觉得编写的生活突然美好了很多,不需要再去调用具体的函数,反正下一次就是另外的状态了……(就是DELAY状态对应的函数被调用了)
C语言代码写作的人很喜欢用小函数,我从她们那里偷了这个思路^^,暂时就写这么点,希望大家能够从这么点东西中受到启发,而且我很乐意和大家讨论,中间也有很多不足,很希望高手指点,谢谢!

参考书籍:《WINDOWS游戏编程大师技巧》
[s:1]
Style-C
回复

使用道具 举报

136

主题

1751

帖子

548

积分

版主

Rank: 7Rank: 7Rank: 7

积分
548
发表于 2006-11-5 16:07:36 | 显示全部楼层
看了三遍了,老实说,完全没有看懂... [s:6]
强烈要求放出直观的流程图 [s:2]
え~え~お!!!
回复 支持 反对

使用道具 举报

1

主题

12

帖子

161

积分

③业余

积分
161
发表于 2006-11-5 17:17:51 | 显示全部楼层
引用第1楼shawind2006-11-05 16:07发表的:
看了三遍了,老实说,完全没有看懂... [s:6]
强烈要求放出直观的流程图 [s:2]
一句话.函数指针数组,每一个状态都对应数组内的一个函数指针. c style
还有当一个状态可以转向好几个状态时或者转上上面的状态.++flag就不行了.要直接flag = .....还是一样.
回复 支持 反对

使用道具 举报

136

主题

1751

帖子

548

积分

版主

Rank: 7Rank: 7Rank: 7

积分
548
发表于 2006-11-5 17:33:53 | 显示全部楼层
哦,原来是这样。
一直在想00方面的事情,没想到这是c风格的。
这么一说就明白了。
え~え~お!!!
回复 支持 反对

使用道具 举报

50

主题

742

帖子

402

积分

版主

自定义头衔

Rank: 7Rank: 7Rank: 7

积分
402
 楼主| 发表于 2006-11-5 18:38:27 | 显示全部楼层
我想尽量把OO说进去,但是无奈选材不好……唉……

话说怎么样是OO风格啊……

看来我的理解不对,一直以为对象拥有状态,就是一种OO设计了……原来是C风格的,我不知道,那么怎么样才是OO风格啊?一定要有VIRTUAL吗? [s:3]
还有当一个状态可以转向好几个状态时或者转上上面的状态.++flag就不行了.要直接flag = .....还是一样
从代码上来看一样,实际上理解会不一样,因为流程应该是顺序的,显式调用flag 说明你在对流程不断进行操作,说明本身的流程是不清晰的,当然某些操作必须显式进行切换状态(比如死亡、逆转返回等情况)你这个时候就应该特别关注……等等,我想………………
[s:5]
Style-C
回复 支持 反对

使用道具 举报

125

主题

288

帖子

1387

积分

⑥精研

积分
1387
发表于 2006-11-5 20:03:53 | 显示全部楼层
为什么会提到我不喜欢写注释的啊~_~
我通常用一个仅包含static const的interface而不是enum来存放状态和标志,然后一些需要使用const的class就可以继承自这个interface,这样让程序明确不去引用无关的常量,似乎也不错
回复 支持 反对

使用道具 举报

136

主题

1751

帖子

548

积分

版主

Rank: 7Rank: 7Rank: 7

积分
548
发表于 2006-11-6 11:25:04 | 显示全部楼层
oo风格的,这个我哪能说清楚。
感觉应该是像krkr2的kag引擎那样的写法吧。
当然那种思路的运行效率是不如这样的来得快的。
え~え~お!!!
回复 支持 反对

使用道具 举报

1

主题

12

帖子

161

积分

③业余

积分
161
发表于 2006-11-6 18:51:13 | 显示全部楼层
其实我说这个是C STYLE 的原因是方便大家理解的.c style 本来就不是坏事.这种少数状态的状态机就应该用C STYLE 写.又方便又清楚.用OO 反而麻烦了很多.例如用state模式.每一个state 都要派生出一个子类.当state多的时候就会出现类爆炸.还有一种方法是适用于大量状态或状态图变化频繁的状态机.我说一下这个方法.其实也是基于c style 的思路.但是效率比较低.
  1. //CState.h
  2. #ifndef YUKI_CSTATE_H
  3. #define YUKI_CSTATE_H
  4. #include <string>
  5. class CStateMap;
  6. typedef std::string STATE_ID;
  7. typedef STATE_ID (*STATE_FUNC) (void * params) ;
  8. class CState
  9. {
  10. public:
  11.     STATE_ID executeState(void * params){
  12.         return stateFunc(params);
  13.     }
  14. protected:
  15.     friend class CStateMap;
  16.     CState(STATE_FUNC _stateFunc)
  17.         :stateFunc(_stateFunc){}
  18. private:
  19.     STATE_FUNC stateFunc;
  20. };
  21. #endif
  22. // CStateMap.h
  23. #ifndef YUKI_CSTATEMAP_H
  24. #define YUKI_CSTATEMAP_H
  25. #include <map>
  26. #include "CState.h"
  27. class CStateMap
  28. {
  29. public:
  30.     bool setStartState(STATE_ID id);
  31.     bool registerState(STATE_ID id, STATE_FUNC func);
  32.     bool execute(void * params);
  33.     STATE_ID getNowState();
  34. private:
  35.     typedef std::map<STATE_ID,CState> STATE_MAP;
  36.     STATE_MAP stateMap;
  37.     STATE_ID nowState;
  38. };
  39. #endif
  40. //CStateMap.cpp
  41. #pragma warning(disable:4786)
  42. #include "CStateMap.h"
  43. bool CStateMap::setStartState(STATE_ID id)
  44. {
  45.     if (stateMap.find(id) != stateMap.end())
  46.     {
  47.         nowState = id;
  48.         return true;
  49.     }
  50.     return false;
  51. }
  52. bool CStateMap::registerState(STATE_ID id, STATE_FUNC func)
  53. {
  54.     std::pair<STATE_MAP::iterator,bool> ret;
  55.     ret = stateMap.insert( STATE_MAP::value_type( id,CState(func) ) );
  56.     return ret.second;
  57. }
  58. bool CStateMap::execute(void * params)
  59. {
  60.     STATE_MAP::iterator state_it = stateMap.find(nowState);
  61.     if (state_it != stateMap.end())
  62.     {
  63.         nowState = (*state_it).second.executeState(params);
  64.         return true;
  65.     }
  66.     return false;
  67. }
  68. STATE_ID CStateMap::getNowState()
  69. {
  70.     return nowState;
  71. }
  72. //main.cpp
  73. #pragma warning(disable:4786)
  74. #include <stdio.h>
  75. #include "CStateMap.h"
  76. STATE_ID Start(void * params)
  77. {
  78.     printf ("Start !! \\n");
  79.     return "Run";
  80. }
  81. STATE_ID Run(void * params)
  82. {
  83.     static int i = 0;
  84.     i ++;
  85.     if (i >= 24)
  86.         return "Exit";
  87.     if (i % 3 == 0)
  88.         return "Pause";   
  89.     char * pChar = (char *)params;
  90.     printf (pChar);   
  91.     return "Run";
  92. }
  93. STATE_ID Pause(void * params)
  94. {
  95.     printf("Pause!! \\n");
  96.     return "Run";
  97. }
  98. STATE_ID Exit(void * params)
  99. {
  100.     printf("Exit !!\\n");
  101.     return "";
  102. }
  103. int main ()
  104. {
  105.     CStateMap c;
  106.     c.registerState("Start",Start);
  107.     c.registerState("Run",Run);
  108.     c.registerState("Pause",Pause);
  109.     c.registerState("Exit",Exit);
  110.     c.setStartState("Start");
  111.     while (true)
  112.     {
  113.         bool running ;
  114.         if (c.getNowState() == "Run")
  115.             running = c.execute("Run!!!\\n");
  116.         else running = c.execute(0);
  117.         if (running == false)
  118.             break;
  119.     }
  120.     return 0;
  121. }
复制代码
其实就是用一个函数指针map.和cstyle差不多.就是添加状态比较方便而已...游戏的状态机还是用c style好.
回复 支持 反对

使用道具 举报

50

主题

742

帖子

402

积分

版主

自定义头衔

Rank: 7Rank: 7Rank: 7

积分
402
 楼主| 发表于 2006-11-6 19:50:00 | 显示全部楼层
引用第5楼Advance2006-11-05 20:03发表的:
为什么会提到我不喜欢写注释的啊~_~
上班了才知道的,不是什么注释都应该写,我的意思是说,AVC比较清楚什么时候更应该用确切的VARIABLE NAME 代替无聊的注释……有时候注释反而是绊脚石啊……
我通常用一个仅包含static const的interface而不是enum来存放状态和标志,然后一些需要使用const的class就可以继承自这个interface,这样让程序明确不去引用无关的常量,似乎也不错
enum主要是可以不关心确切的数值,而且看起来比较清晰,

其实一开始3个状态的时候偶确实是STATIC CONST INT 的。。。 [s:4]
Style-C
回复 支持 反对

使用道具 举报

125

主题

288

帖子

1387

积分

⑥精研

积分
1387
发表于 2006-11-7 00:06:34 | 显示全部楼层
flowfunction[STEP_STARTANIM] = &layer::startAnim;
flowfunction[STEP_CREATE] = &layer::createBall;
flowfunction[STEP_DELAY] = &layer::delay;


那你的代码就不能像上面这样写了,没有为enum成员指定明确值的时候最好不要将它转换成任何数据类型
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

Archiver|手机版|小黑屋|幻想森林

GMT+8, 2024-4-19 17:09 , Processed in 0.027076 second(s), 20 queries .

Powered by Discuz! X3.4

© 2001-2017 Comsenz Inc.

快速回复 返回顶部 返回列表