现在的位置: 首页 > 综合 > 正文

X-Ray 游戏项目总结与反思

2017年10月06日 ⁄ 综合 ⁄ 共 10938字 ⁄ 字号 评论关闭

          X-Ray游戏项目,是由于要做设计模式的课程设计,而我偏爱于做游戏,所以干脆就做一个简单的弹幕射击游戏来作为课程设计好了。

          游戏的开发使用的是Cocos2d-x来进行的,没有用到很多cocos2d-x的功能,对于我来说,它的功能太强大,也就意味着复杂度会有所提高,所以在设计最初的时候,就避免使用过多的cocos2d-x的特性,这样我的游戏设计,就算以后有新的引擎,也只要稍微改动下代码,就能直接用在别的引擎上开发了。

         废话不多说,先来看一批截图吧,由于图片资源部分取之于网络,而且我美工技术有限,所以大家不必纠结这个了!!!

        

 由于时间限制,就开发了一关。而且对于游戏中各个敌机的数量,攻击力,飞行速度等,我都是简单地配置了下,所以可玩性,手感可能不是很好,暂时没有那么多的时间来慢慢的调节这些数据。对于Boss,我给它做了很多不同的弹幕类型,上图中只能看到几个。

  游戏至今还存在一个严重的bug,系统运行的时候,偶尔会出现一个迭代器错误,实在不知道为什么会有这样的错误,暂时放在这里,等以后知识充足了,在来改进。

好了,下面来反思下游戏开发中的路程吧。

确定游戏实体

在游戏中,我们需要一个玩家,并且能够通过键盘来控制玩家的行为,所以为此需要一个Player类,用于对玩家进行抽象。

玩家需要与敌人进行交战,所以为此,我们还需要抽象一个Enemy类,用来表示所有敌人对象的基类。并且,在此类的基础上,派生出不同的具体敌人类,如进行翻滚攻击的CircleGuyPlane,不顾一切向玩家进行冲撞的RushGuyPlane,还有就是能够发射涡旋形弹幕子弹的TwoEnemyPlane,除此之外,还有一个ThreeGuyPlane,这个敌机,向玩家不停的发射扇形的弹幕。

在有了玩家,有了敌人之后,我们还需要一个Boss,抽象一个Boss类,用来表示游戏中每个关卡出现的Boss对象。

有了具有AI思想的对象之后,我们还需要让玩家和敌人,boss互相进行攻击,所以需要一个子弹类Bullet,不同的子弹可以从这个类进行继承,然后实现不同的子弹类。

为了使对象和子弹之间不是那么的耦合,并且能够实现复杂的子弹发射方法,我们再在子弹的基础上抽象一个Weapon类。玩家和敌人,只需要调用Weapon类中的fire方法,来发射子弹即可,不同的发射方式,可以通过派生不同的Weapon来进行定义。

 

总结如下:

Player类:模拟玩家,控制玩家行为的类

Enemy: 所有敌人飞机的基类

Boss: 每一个关卡的Boss抽象

Bullet:游戏中发生的子弹的抽象

Weapon:游戏中用于对子弹进行统一创建发射的抽象

设计问题

问题1:上面确定的实体类,如何进行统一的管理,使得查询,删除等更容易实现?

问题2:不同实体之间如何进行通信?

问题3:当满屏的对象时,如何保证效率?

问题4:如何有组织的控制地方的敌人,而不是随机的出现,进行攻击?

问题5:实体具有不同的状态,在不同的状态下,需要完成不同的任务,如何弹性的实现这样的功能?

解决问题及分析

问题1:

通过将所有的上述实体类,继承与一个GameObject,并且在GameObject中为每一个对象生成一个独立的ID,然后创建一个GameObjectManager来对所有的这些GameObject进行管理。

为此,我们使用单例模式来设计GameObjectManager,部分实现代码如下

//File:GameObjectManager.h

class GameObjectManager:public Interface,
public Root

{

private:

      GameObjectManager();

      ~GameObjectManager();

      ......

      //Singleton getter

public:

      static GameObjectManager* sharedObjectManager() ;

}

 

GameObjectManager* GameObjectManager::sharedObjectManager()

{

      static GameObjectManager objmag ;

      return &objmag ;

}// end for shareObjectManager

 

 

方案优点:能够统一有效的对所有游戏中的对象进行管理,提供方便的接口让其他使用者来创建或者销毁对象。

方案缺点:功能太过庞杂,不符合设计原则中的单一责任原则,应该将这个类继续细化成完成不同工作的类。

 

 

 

问题2:实体之间经常需要进行通信,为了能够降低不同实体对象之间的耦合关系,使用事件驱动机制来构造一个消息通信方式。

首先,定义一个Message结构,如下:

//File: Message.h

/*

* Define the message structure

*/

typedef
struct
MESSAGE

{

      int   msg_type ;                           
//The message type

      _int64 send_id ;                  
//The id of the sender

      _int64 reci_id ;                   
//The id of the reciever

      unsigned
int
dely_tim ;                  //The delay time

      void* extra_info ;                          
//The extra infomation

      int   extra_info_size ;            
//The size of the extra info in byte

}*LPMESSAGE ;

 

在Message结构中,给定了发送者的ID,接受者的ID,消息类型,是否需要延迟发送,以及发送消息的额外信息和它的大小。

 

有了Message之后,不同的实体,只要对接受的消息进行解析,然后做相应的工作即可。

而如何进行发送和接受?

为此,抽象一个中间层MessageDispatch,这个类用于对延迟消息进行缓存,对立即消息,进行立刻发送。这个类需要知道接受者ID所对应的对象是谁,所以,在一定程度上,它需要和GameObjectManager进行交互,来获取ID所对应的对象,然后将消息传递给对象的消息处理函数。MessageDispatch的结构如下:

//File: MessageDispatch.h

/*

* Define the class

*/

class MessageDispatch:public Interface

{

private:

      MessageDispatch();

      ~MessageDispatch();

 

      //Singleton instance

public:

      static MessageDispatch* sharedMessageDispatch() ;

 

      //life method

public:

      bool init() ;

 

      void update(float dt) ;

 

      void pause();

 

      void resume();

 

      void reset();

     

      void destroy();

 

public:

      /*

      * biref : This DispatchMessage will dispatch the immediately message or delay-message

      */

      void dispatchMessage(int msg_type,

                                   _int64 send_id ,

                                   _int64 reci_id,

                                   int delay_time,

                                   void* extra_info,

                                   int extra_info_size) ;

 

      void dispatchMessage(MESSAGE msg);

 

      /*

      * brief : This DispatchDelayMessages will dispatch the delay message which need to be send right now. We should call this method

      *         at every game loop.

      */

      void dispatchDelayMessages() ;

 

private:

      std::list<MESSAGE*> m_PriorityQueue ;              
//Used to store the delayed message

      long
long
  m_nCurFrame ;

};

 

 

这个类也是使用单例设计方式,来实现的,最主要的函数调用为dispatchMessage和dispatchDelayMessage。以下是部分实现:

//File: MessageDispatch.cpp

Method: dispatchMessage

{

GameObject* obj = GameObjectManager::sharedObjectManager()->getObject(msg.reci_id);

 

      if(msg.dely_tim == NO_DELAY_TIME)

      {

           obj->handleMessage(msg);

      }

      else

      {

           msg.dely_tim += m_nCurFrame ;

           MESSAGE * _msg = new MESSAGE;

           memcpy(_msg, &msg,sizeof(msg));

           if(_msg->extra_info_size == 0)

                      _msg->extra_info = NULL ;

           else

           {

                 _msg->extra_info = (void*)malloc(msg.extra_info_size) ;

                 memcpy(_msg->extra_info,msg.extra_info, msg.extra_info_size );

           }// end if...else...

           m_PriorityQueue.push_back(_msg);

      }

}

 

//-------------------------------------------------------------------------------------

Method: dispatchDelayMessage

{

std::list<MESSAGE*>::iterator it = m_PriorityQueue.begin();

 

      for(; it != m_PriorityQueue.end() ; )

      {

           MESSAGE* msg = *it ;

           std::list<MESSAGE*>::iterator temp = it ;

           it ++ ;

           if(msg->dely_tim == m_nCurFrame)// Check if meet the delay time

           {

                 //Check if the message is going to be sent to render system

                 if(msg->reci_id == RenderSystem::sharedRenderSystem()->getID())

                 {

                       RenderSystem::sharedRenderSystem()->handleMessage(*msg);

 

                 }// end for render system

                 else
if
(msg->reci_id == GameObjectManager::sharedObjectManager()->getID())//Check if the message is going to be sent to game object manager

                 {

                       GameObjectManager::sharedObjectManager()->handleMessage(*msg);

                 }else

                 {

                      //Check if the message is going to be sent to the object

                      GameObject* obj = GameObjectManager::sharedObjectManager()->getObject(msg->reci_id);

 

                      //Check if the object exist

                      if(obj != NULL)

                            obj->handleMessage(*msg);

                 }// end if...else....

 

                 if(msg->extra_info)

                 {

                      free(msg->extra_info);

                      msg->extra_info = NULL ;

                 }// end if

 

                 if(msg)

                 {

                      delete msg ;

                      msg = NULL ;

                 }// end if

                     

                 //And remove the message from the prority queue

                 m_PriorityQueue.erase(temp);

           }// end if

      }// end for

}

 

 

以下是不同实体对象之间进行交互的示意图:

 

 

方案优点:能够有效的进行消息实体之间消息通信,降低了实体之间的耦合性

方案缺点:不能够检测到实体是否已经具备接受消息的能力。

 

问题3:

游戏的效率,是游戏是否值得玩的重要指标。为了能够实现,同时处理创建多个对象的CPU消耗问题,使用内存池和工厂方法组合的方式进行。通过工厂方法封装对象的创建过程,便于在工厂方法中添加对配置文件的支持。使用内存池的方式,将在先前已经创建过的对象缓存在系统中,而在下次需要使用的时候,将这个对象重新初始化,然后继续使用,避免重复创建对象的开销,从而达到重复利用对象的目的。实现这样的功能主要通过GameObjectManager中的以下几个接口:

 ...........

public:

      /*

      * brief : Accquire the bullet

      */

      ID accquireBullet(int type,
float x, float y,
float vx, float vy);

 

      /*

      * brief : Accquire the Enemy

      */

      ID accquireEnemey(int type,
float x, float y,
float vx, float vy);

 

      /*

      * brief : Accquire the item

      */

      ID accquireItem(int type,float x,
float y, float vx,
float vy);

 

      /*

      * brief : Release the bullet

      */

      bool releaseBullet(ID);

 

      /*

      * brief : Release the enemy

      */

      bool releaseEnemy(ID);

 

      /*

      * brief : Release the item

      */

      bool releaseItem(ID);

   ........................................

 

 

方案优点:通过这样的方式,有效的提高了系统在运行时的效率,不至于出现卡帧的情况。

方案缺点:系统总是维持峰值的对象数目,无法做到适时释放一些内存,使得内存占用过大。

 

 

问题4:

通过抽象一个名为Team(队)的类,来统一的创建,更新多个敌方士兵。通过这样的方式,就能够实现有组织的控制多个士兵进行移动,攻击,或者组成阵型来对玩家进行打击。

 

以下是抽象Team的定义:

//File:Team.h

/*

* brief : Define the class

*/

class Team :
public
GameObject

{

public:

      Team();

      virtual ~Team();

 

public:

      virtual
bool
init() = 0 ;

      virtual
void
update(float dt) = 0 ;

      virtual
bool
handleMessage(MESSAGE msg) = 0 ;

      virtual
void
reset() = 0;

 

public:

      //setter

      void setFrame(int frame);

      void setEnemyType(int type);

 

      //getter

      int getFrame()
const
;

      int getEnemyType()
const
;

 

protected:

      std::vector<ID> m_SoldierList ;

      int           m_nCurFrame ;

      int           m_nEnemyType ;

};

 

 

 

方案优点:

能够实现有组织的控制敌方士兵进行攻击,让玩家觉得敌人更加的智能话,同时便于系统进行统一的创建管理。

方案缺点:

使用Team对敌方士兵进行了又一次的封装,使得对单体士兵的操控变得更加困难。

 

 

问题5:

让实体在不同的状态下进行不同的工作,可以使用状态设计模式和模板方法结合的方法来实现这样的功能。

以下是实现状态的模板方法的定义:

//定义FSM状态机

template<class element_type_ptr>

class FSM

{

public:

      FSM()

      :m_Owner(NULL),

      m_CurrentState(NULL),

      m_PreviousState(NULL),

      m_GlobleState(NULL)

      {

 

      }

      ~FSM()

      {

      }

 

      //状态机的操作函数

public:

      void Update()                                                            
//进行状态机的更新

      {

           //先判断是否有需要进行全局状态的操作

           if(GetGlobleState())

                 GetGlobleState()->Excute(GetOwner());

 

           //运行当前状态

           if(GetCurrentState())

                 GetCurrentState()->Excute(GetOwner());

      }

 

      void ResetPreviousState()                                         
//恢复到先前状态

      {

           ChangeCurrentState(GetCurrentState());

      }

 

      void ChangeCurrentState(State<element_type_ptr>* state)          
//改变当前状态

      {

           //运行退出状态的函数

           GetCurrentState()->Exit(GetOwner());

 

           //将当前状态赋给先前状态

           SetPreviousState(GetCurrentState());

 

           //设置新的状态

           SetCurrentState(state);

 

           //运行进入状态时的函数

           GetCurrentState()->Enter(GetOwner());

      }

 

      bool IsState(State<element_type_ptr>* state)                   
//判断是否为给定的状态类型

      {

           if(m_CurrentState == state)

                 return
true
;

           else

                 return
false
;

      }

 

      bool HandleMessage(const MESSAGE & msg)                             
//处理传递过来的消息

      {

           //首先交由当前状态来处理

           if(m_CurrentState && (m_CurrentState->onMessage(m_Owner, msg)))

           {

                 return
true
;

           }

 

           //当前状态不处理交由全局状态处理

           if(m_GlobleState && m_GlobleState->onMessage(m_Owner, msg))

           {

                 return
true
;

           }

 

           return
false
;

      }

      //操作成员属性的方法

public:

      void SetCurrentState(State<element_type_ptr>* state)      
//设置当前状态

      {

           m_CurrentState = state ;

      }

 

      State<element_type_ptr>* GetCurrentState()
const
          //获取当前状态

      {

           return m_CurrentState ;

      }

 

      void SetPreviousState(State<element_type_ptr>*state)       
//设置先前状态

      {

           m_PreviousState = state ;

      }

 

      State<element_type_ptr>* GetPreviousState()
const
        //获取先前状态

      {

           return m_PreviousState ;

      }

 

      void SetGlobleState(State<element_type_ptr>* state)             
//设置全局状Á

      {

           m_GlobleState = state ;

      }

 

      State<element_type_ptr>* GetGlobleState()
const
      //获取全局状态

      {

           return m_GlobleState ;

      }

 

      void SetOwner(element_type_ptr owner)                            
//设置拥有者

      {

           m_Owner = owner ;

      }

 

      element_type_ptr GetOwner() const                             
//获取拥有者

      {

           return m_Owner ;

      }

 

 

private:

      element_type_ptr             m_Owner;                            
//拥有状态机的对象

      State<element_type_ptr>*  m_CurrentState ;                   
//当前状态

      State<element_type_ptr>*  m_PreviousState;                  
//先前状Á

      State<element_type_ptr>*  m_GlobleState ;                    
//全局状态

};

 

 

以下是状态的继承类图:

方案优点:

能够方便的进行不同状态之间的转化,并且在不同状态下完成不同的任务。

方案缺点:

每多一个状态,就需要多一个类,这样会造成类爆炸的情况出现。

好了,项目总结就到这里,希望以后能开发出更加酷炫的游戏出来!期待ing....

抱歉!评论已关闭.