Ogre学习笔记系列-4:异形入侵

声明: 本文根据免费打工仔的外星人来袭游戏改编,只是免费打工仔的游戏需要使用免费打工仔的游戏开发框架,感谢免费打工仔提供了基本剧情和开放了所有源代码。

为了避免不必要的复杂度,本文使用VS2010开发,使用的库有:

  • Ogre SDK 1.7.1
  • boost 1.4.3
  • fmod
  • CEGUI 0.71

游戏使用到的媒体文件都是Ogre自带媒体文件,感谢Ogre开源社区。

有关库的下载请参考本系列博文之: Ogre学习笔记系列-3:相关依赖文件地址

第一节:建立基本框架

    最新版的ogre demo使用插件结构方式构建游戏演示,我们的游戏也作为一个插件提供给Sample_Browser使用,由Sample_Browser启动我们的游戏。首先我们来从头开始建立一个DLL项目,加载基本的文件从而让我们的游戏能在Sample中运行起来。

    首先打开OgreSDK/Ogre.sln,新建一个Win32项目Sample_Invade, 类型选为DLL,勾选空项目选项。关闭解决方案,将OgreSDK下Sample_Invade目录移动到Samples子目录下,并改名为Invade.重新打开Ogre.sln,并移除旧的Sample_Invade工程,同时加载Samples/Invade/Sample_Invade.vcproj. 打开新工程的配置属性,设置如下:

  • 常规
    • 输出目录:../../bin/Debug/
    • 中间目录:Sample_Invade.dir/Debug/
    • 目标文件名:Sample_Invade_d
    • 配置类型:动态库(DLL)
    • 字符集: 使用多字节字符集
  • C/C++【在加入C++文件后选项可以显示】
    •  常规
      • 附加包含路径:附加如下路径:
        • C:/Program Files/FMOD SoundSystem/FMOD Programmers API Win32/api/inc
        • ../boost_1_42
        • ../../include/OGRE
        • ../../include
        • ../../include/OGRE/PlugIns/PCZSceneManager
        • ../../include/OGRE/PlugIns/OctreeZone
        • ../../include/OIS
        • ../../Common/include
        • ../../include/OGRE/RTShaderSystem
        • ../../Samples/Invade/include
    • 优化
      • 内联函数扩展:已禁用(/ob0)
    • 输出文件
      • ASM列表位置:Debug
      • 程序数据库文件名:../../bin/Debug/Sample_Invade_d.pdb
    • 高级
      • 编译为:编译为 C++ 代码 (/TP)
    • 命令行
      • 其它选项:/Zm1000
  • 链接器
    • 输入
      • 附加依赖项:添加如下附加依赖,并取消从父级或项目默认设置继承
        • kernel32.lib
        • user32.lib
        • gdi32.lib
        • winspool.lib
        • shell32.lib
        • ole32.lib
        • oleaut32.lib
        • uuid.lib
        • comdlg32.lib
        • advapi32.lib
        • ../../lib/debug/OgreMain_d.lib
        • ../../lib/debug/libboost_thread-vc100-mt-gd-1_43.lib
        • ../../lib/debug/libboost_date_time-vc100-mt-gd-1_43.lib
        • ../../lib/debug/OgreRTShaderSystem_d.lib
        • ../../lib/debug/OIS_d.lib
        • ../../lib/debug/fmodex_vc.lib
    • 系统
      • 子系统:修改为:控制台 (/SUBSYSTEM:CONSOLE)
      • 堆栈保留大小:10000000
    • 高级
      • 导入库:修改为../../lib/Debug/Sample_Invade_d.lib
    • 命令行
      • 添加选项: /machine:X86 /debug

在工程目录下建立Include以及Src目录,分别在两个目录下建立文件Invade.h和Invade.cpp,并分别添加到工程的头文件和源文件分类之下。

两文件内容是:Invade.h

#ifndef __INVADE_H__
#define __INVADE_H__

#include  "SdkSample.h"

using  namespace Ogre ;
using  namespace OgreBites ;

//需要从SDKSample继承
class _OgreSampleClassExport Sample_Invade  :  public SdkSample
{
public :

    Sample_Invade ()
     {
     //设定相关信息以便SampleBrowser可以正确加载并显示本程序信息
        mInfo [ "Title" ]  =  "Invade" ;
        mInfo [ "Description" ]  =  "Show how to create a simple game" ;
        mInfo [ "Thumbnail" ]  =  "thumb_skybox.png" ;
        mInfo [ "Category" ]  =  "Test" ;
     }
protected :
    
     void setupContent ()
     {        
         // 设置环境光
        mSceneMgr ->setAmbientLight (ColourValue ( 0. 30. 30. 3) );
        mSceneMgr ->createLight ()->setPosition ( 208050) ;
    
        //设置天空盒
       mSceneMgr ->setSkyBox ( true ,  "Examples/SpaceSkyBox" ,  50 );

         //设置镜头的初始位置和朝向
        mCamera ->setPosition ( 00150) ;
        mCamera ->yaw (Degree ( 5) );
     }

   bool keyReleased ( const OIS ::KeyEvent & evt )
   {
     //屏蔽父类中的键盘释放函数
     return  true ;
   }

   bool keyPressed ( const OIS ::KeyEvent & evt )
   {
     //屏蔽父类中的键盘按下函数
     return  true ;
   }
  
   bool mousePressed ( const OIS ::MouseEvent & evt , OIS ::MouseButtonID id )
   {
      //屏蔽父类中的鼠标按下函数
      return  true ;
   }
  
   bool mouseReleased ( const OIS ::MouseEvent & evt , OIS ::MouseButtonID id )
   {
       //屏蔽父类中的鼠标控制函数
       return  true ;
   }
  
   bool mouseMoved ( const OIS ::MouseEvent & evt )
   {
       //屏蔽父类中的鼠标移动函数
       return   true ;
   }
  
   bool frameRenderingQueued ( const Ogre ::FrameEvent & evt )
   {
       //首先调用父类的渲染函数
      SdkSample ::frameRenderingQueued (evt );
       return  true ;
   }
};

#endif

Invade.cpp

#include  "SamplePlugin.h"
#include  "Invade.h"

using  namespace Ogre ;
using  namespace OgreBites ;

#ifndef OGRE_STATIC_LIB

SamplePlugin * sp ;
Sample * s ;

extern  "C" _OgreSampleExport  void dllStartPlugin ()
{
    s  =  new Sample_Invade ;
    sp  = OGRE_NEW SamplePlugin (s ->getInfo ()[ "Title" ]  +  " Sample" );
    sp ->addSample (s );
    Root ::getSingleton ().installPlugin (sp );
}

extern  "C" _OgreSampleExport  void dllStopPlugin ()
{
    Root ::getSingleton ().uninstallPlugin (sp ); 
    OGRE_DELETE sp ;
     delete s ;
}
# endif

编译成功之后,在OgreSDK/bin/debug路径下修改Samples_d.cfg,增加一行:SamplePlugin=Sample_Invade_d

运行SampleBrowser_d.exe,选择分类Test,运行Invade,点击Start Sample,呵呵,目前启动后看到一个太空背景。

第二节:构造战斗机

例中战斗机直接用Ogre的飞机模型,此类完成战斗机的渲染和移动控制,控制采取纵版控制方式,飞机只能进行左右方向的移动,弹药控制随后完成。要完成这些功能,结合Ogre的场景管理功能,类中应该保存场景管理器实例,也应该保存战斗机挂接的节点。同时应该用变量记录左右键按下的状态。因此类的头文件和实现文件如下:

BattlePlane.h

#ifndef __BATTLEPLANE_H_
#define __BATTLEPLANE_H_

#pragma once

#include  "OIS.h"
#include  "Ogre.h"

using  namespace Ogre ;

class BattlePlane
{
public :
  BattlePlane (SceneManager * sm );
   virtual ~BattlePlane ( void );

   void setupContent ();

   //当键盘按键按下
   bool onKeyPressed ( const OIS ::KeyEvent  & evt );    
   //当键盘按键释放
   bool onKeyReleased ( const OIS ::KeyEvent  & evt );        
   //更新
   void update ( void );

private :
  SceneManager * _sm ;
  SceneNode * _node ;
   bool _leftDown ;
   bool _rightDown ;
};

#endif

BattelPlane.cpp

#include  "BattlePlane.h"

BattlePlane ::BattlePlane (SceneManager * sm ):
_sm (sm ),
_leftDown ( false ),
_rightDown ( false ),
_node ( NULL )
{
}

BattlePlane ::~BattlePlane ( void )
{
}


void BattlePlane ::setupContent ()
{
   //通过飞机模型创建一个实体
  Entity  *ent  = _sm ->createEntity (  "razor" ,  "razor.mesh"  );

   //创建一个节点
  _node  = _sm ->getRootSceneNode ()->createChildSceneNode ();
   //设置位置
  _node ->setPosition (- 10- 500) ;
   //在创建一个节点用于调整飞机模型方向
  SceneNode  * sn  = _node ->createChildSceneNode ();
   //roll PI
  sn ->roll (Ogre ::Radian (Ogre ::Math ::PI ));
   //设置朝向
  sn ->setDirection ( 0. 0f1. 0f , 0. 0f );
  sn ->scale ( 0. 1f0. 1f0. 1f) ;
   //最后在节点上面挂接实体
  sn ->attachObject ( ent  );

}

bool BattlePlane ::onKeyPressed ( const OIS ::KeyEvent  & evt )
{ 
   //1处理键盘按下消息
   if (evt.key  == OIS ::KC_LEFT )
    _leftDown  =  true ;
   else  if (evt.key  == OIS ::KC_RIGHT )
    _rightDown  =  true ;
   return  true ;
}

bool BattlePlane ::onKeyReleased ( const OIS ::KeyEvent  & evt )
{  
   //处理键盘释放消息
   if (evt.key  == OIS ::KC_LEFT )
    _leftDown  =  false ;
   else  if (evt.key  == OIS ::KC_RIGHT )
    _rightDown  =  false ;
   return  true ;
}

void BattlePlane ::update ( void )
{
   //在每一frame中更新飞机位置
   if (_leftDown  && _rightDown )
     return ;
   if (_leftDown  && _node ->getPosition ().x  >  - 100. 0f)
    _node ->translate (- 2, 0, 0) ;
   if (_rightDown && _node ->getPosition ().x  <  70. 0f)
    _node ->translate ( 2, 0, 0) ;
}

 

更新Invade.h以便正确初始化战斗机模型并更新战斗机位置,利用boost的智能指针管理BattlePlane的实例.

#ifndef __INVADE_H__
#define __INVADE_H__

#include  "SdkSample.h"
#include  "BattlePlane.h"
#include  "boost/smart_ptr.hpp"

using  namespace Ogre ;
using  namespace OgreBites ;

//需要从SDKSample继承
class _OgreSampleClassExport Sample_Invade  :  public SdkSample
{
public :

  Sample_Invade ()
   {
     //设定相关信息以便SampleBrowser可以正确加载并显示本程序信息
    mInfo [ "Title" ]  =  "Invade" ;
    mInfo [ "Description" ]  =  "Show how to create a simple game" ;
    mInfo [ "Thumbnail" ]  =  "thumb_skybox.png" ;
    mInfo [ "Category" ]  =  "Test" ;
   }
private :
   //通过智能指针管理战斗机对象
  boost ::scoped_ptr <BattlePlane > _plane ;

protected :

   void setupContent ()
   {

     // 设置环境光
    mSceneMgr ->setAmbientLight (ColourValue ( 0. 30. 30. 3) );
    mSceneMgr ->createLight ()->setPosition ( 208050) ;

     //设置天空盒
    mSceneMgr ->setSkyBox ( true ,  "Examples/SpaceSkyBox" ,  50 );

     //设置镜头的初始位置和朝向
    mCamera ->setPosition ( 00150) ;
    mCamera ->yaw (Degree ( 5) );

     //构造飞机类对象并初始化战斗机模型
    _plane.reset ( new BattlePlane (mSceneMgr ));
    _plane ->setupContent ();

   }

   bool keyReleased ( const OIS ::KeyEvent & evt )
   {
     //屏蔽父类中的键盘释放函数
     return _plane ->onKeyReleased (evt );
   }

   bool keyPressed ( const OIS ::KeyEvent & evt )
   {
     //屏蔽父类中的键盘按下函数
     return _plane ->onKeyPressed (evt );
   }

   bool mousePressed ( const OIS ::MouseEvent & evt , OIS ::MouseButtonID id )
   {
     //屏蔽父类中的鼠标按下函数
     return  true ;
   }

   bool mouseReleased ( const OIS ::MouseEvent & evt , OIS ::MouseButtonID id )
   {
     //屏蔽父类中的鼠标控制函数
     return  true ;
   }

   bool mouseMoved ( const OIS ::MouseEvent & evt )
   {
     //屏蔽父类中的鼠标移动函数
     return   true ;
   }

   bool frameRenderingQueued ( const Ogre ::FrameEvent & evt )
   {
     //首先调用父类的渲染函数
    SdkSample ::frameRenderingQueued (evt );
    _plane ->update ();
     return  true ;
   }
};

#endif

编译成功运行例子,你将能使用左右键控制战斗机移动,哈哈,下次将为他设定一个对手。

第三节:迎接战斗

我们的战斗机已经准备就绪,可以与来势汹汹的外星怪兽进行近身搏击,我们就让ogre的怪兽头模型作为我们的怪兽敌人吧。作为主角,你将只能驾驶一架座机,敌人可是不会听你的,只派一个怪兽与你搏击,因此,敌人类将有一个列表或者数组保存所有的怪兽对象,同时也需要简单的时间变量来控制它进行移动和发射子弹,而不是傻乎乎的出现在屏幕上等待我们去消灭它。下面就是敌人类的代码:

EnemyPlanes.h:

#ifndef __ENEMYPLANES_H_
#define __ENEMYPLANES_H_

#pragma once

#include  "OIS.h"
#include  "Ogre.h"
#include  "boost/array.hpp"

using  namespace Ogre ;

class EnemyPlanes
{
   typedef boost ::array < Ogre ::SceneNode  *,  12> PlanesType ;
public :
  EnemyPlanes (SceneManager * sm );
   virtual ~EnemyPlanes ( void );
   //建立外星怪兽模型
   void setupContent ();
   //更新
   void update ( void );

private :
  SceneManager * _sm ;
   //用作所有敌人的根节点
  SceneNode * _node ;
   //时间记录,使敌机可以自动更新位置
   float _time ;
   //敌人节点
  PlanesType _planes ;
};

#endif

EnemyPlanes.cpp:

#include  "EnemyPlanes.h"
#include  <boost /lexical_cast.hpp > 

EnemyPlanes ::EnemyPlanes (SceneManager * sm ):
_sm (sm ),
_planes (),
_time ( 0. 0f) ,
_node ( NULL )
{
}

EnemyPlanes ::~EnemyPlanes ( void )
{
}


void EnemyPlanes ::setupContent ()
{
   //创建根节点
  _node  = _sm ->getRootSceneNode ()->createChildSceneNode ();
   //遍历节点并初始化
   for (uint32 i  =  0; i < _planes.size ();  ++i )
   {
     //创建节点
    _planes [i ]  = _node ->createChildSceneNode ();
     //创建实体
     //(注Ogre中实体不能有相同的名称,所以我们通过i来作为名称的id)
    Ogre ::Entity  *ent
       = _sm ->createEntity (
      std ::string ( "head" )  + boost ::lexical_cast <std ::string >(i ), 
       "ogrehead.mesh" );
     //放置节点到正确的位置
    _planes [i ]->translate ((i % 4) *  100. 0f -  80. 0f500. 0f *(i / 4) ,  0. 0f) ;
     //缩放节点
    _planes [i ]->scale ( 0. 1f0. 1f0. 1f) ;
     //挂接实体
    _planes [i ]->attachObject (ent );
   }
}


void EnemyPlanes ::update ( void )
{
   //在每一frame中更新怪兽位置
   //每次更新,固定时间间隔0.015秒。
  _time  +=  0. 015f;

   //分配时间,根据不同时间设定不同的移动速度
   float move  =  0. 0f;
   if (_time  <  1. 0f)
   {
    move  =  1. 0f;
   }
   else  if (_time  <  3. 0f)
   {
    move  =  - 1. 0f;
   }
   else  if (_time  <  4. 0f)
   {
    move  =  1. 0f;
   }
   else
   {
    _time  =  0. 0f;
   }

   //通过时间设置敌人移动状态,简单设定,敌人仅作左右方向漂移
   for (uint32 i  =  0; i < _planes.size ();  ++i )
   {
     if ((i / 4%  2 )
      _planes [i ]->translate (move ,  0. 0f0. 0f) ;
     else
      _planes [i ]->translate (-move ,  0. 0f0. 0f) ;
   }

}

同时我们还需要更新Invade.h以便敌人能被正确的渲染,下面仅给出代码片段,依照BattlePlane的方式插入Invade.h中就可以。

#include  "EnemyPlanes.h"

 

//通过智能指针管理敌人对象
 boost ::scoped_ptr <EnemyPlanes > _enemy ;

 

//构造怪兽敌人并初始化
   _enemy.reset ( new EnemyPlanes (mSceneMgr ));
   _enemy ->setupContent ();

 

_enemy ->update ();

 

编译代码重新运行,我们将看到有几个怪兽在屏幕上不停的飞来飞去,给我们强大的压力。

第四节:与敌人交战

敌人已经来到家门口,那我们就准备弹药开始开战吧。弹药应该是装在在战斗机上的,而且弹药应该有好几种状态,就绪,激活,飞行,击中或者失去目标。为了让我们的战斗机能有充足的战斗能力而且不至于产生成千上万的对象好进系统内存,我们仅建立为数不多的弹药,并进行回收,还是老规矩,我们需要场景管理器,需要节点挂接子弹,需要及时更新子弹飞行的轨迹并且对子弹的状态进行跟踪,本节子弹仅留在弹匣之中,下节中,敌人的日子将不好过。下面是子弹的实现文件:

Bullet.h:

#ifndef __BULLET_H_
#define __BULLET_H_

#pragma once

#include  "OIS.h"
#include  "Ogre.h"

using  namespace Ogre ;

class Bullet
{
public :
  Bullet (SceneManager * sm );
   virtual ~Bullet ( void );

   void SetupContent ();

   //更新子弹
   void update ( void );
   //检查子弹是否被使用,返回true就是正在被使用中
   bool active ( void );
   //开火,第一个参数表示开火位置,第二个参数表明开火时的力量
   void fire ( const Ogre ::Vector3  & pos ,  const Ogre ::Vector3  & force );        
   //检查子弹是否射中物体
   bool touch ( void );
   //执行爆炸动作
   void burst ( void );
   //检查是否飞行中(激活且没有爆炸)
   bool flying ( void );

private :
  SceneManager * _sm ;
  SceneNode * _node ;
  Ogre ::Entity  * _ent ;
   bool _active ;
  Ogre ::Vector3 _force ;
};

//子弹管理器
class BulletManager
{
   typedef boost ::shared_ptr <Bullet > BulletPtr ;
   typedef std ::vector <BulletPtr > BulletPtrList ;
public :
  BulletManager (Ogre ::SceneManager  * sm ,  int n );
  ~BulletManager (){  };
   //更新所有激活子弹
   void update ( void );
   //检查是否击中物体,如果击中,自动爆炸。
  Bullet  * touch ( void );  
   //开火,从已有子弹列表中选择一个没有激活的子弹,
   //调用其fire函数,第一个参数表示开火位置,第二个参数表明开火时的力量
   bool fire ( const Ogre ::Vector3  & pos ,  const Ogre ::Vector3  & force );
private :
   //子弹队列
  BulletPtrList _bullets ;
};

#endif

Bullet.cpp:

#include  "Bullet.h"

#include  <boost /lexical_cast.hpp > 


Bullet ::Bullet (SceneManager * sm ):
_sm (sm ),
  _force (),
  _node ( NULL ),
  _ent ( NULL ),
  _active ( false )

{
}

Bullet ::~Bullet ( void )
{
}


void Bullet ::SetupContent ()
{
   static  int i  =  0;
   //创建节点
  _node  = _sm ->getRootSceneNode ()->createChildSceneNode ();
   //这里临时用一个球体作为子弹模型,以后再美化
  _ent  = _sm ->createEntity (std ::string ( "bullet" )  + 
    boost ::lexical_cast <std ::string >(i ++),  "sphere.mesh" );
   //挂接模型实体
  _node ->attachObject (_ent );
   //缩放一下
  _node ->setScale ( 0. 1f0. 1f0. 1f) ;
   //先隐藏着 备用
  _ent ->setVisible ( false );  
}

void Bullet ::update ( void )
{
  _node ->translate (_force );
   //当子弹的y值大于400的时候,我们认为子弹已经飞出屏幕,在这里收回子弹
   if (_node ->getPosition ().y  >  400)
   {
    _ent ->setVisible ( false );
    _active  =  false ;
   }
}

bool Bullet ::active ()
{
   return _active ;
}

void Bullet ::burst ()
{
}

bool Bullet ::touch ()
{  
   return  false ;

}

bool Bullet ::flying ()
{
   return  false ;
}

void Bullet ::fire ( const Ogre ::Vector3  & pos ,  const Ogre ::Vector3  & force )
{
  _node ->setPosition (pos );
  _force  = force ;
  _node ->setVisible ( true );
  _active  =  true ;
}
BulletManager ::BulletManager (Ogre ::SceneManager  * sm ,  int n )
{
   //先放置n个子弹
   for ( int i  =  0; i < n ;  ++i )
   {
    Bullet * pBullet  =  new Bullet (sm );
    pBullet ->SetupContent ();
    _bullets.push_back (BulletPtr (pBullet ));
   }
}
void BulletManager ::update ( void )
{
   //更新所有激活的子弹(包括飞行核爆炸)
   for (BulletPtrList ::iterator it  = _bullets.begin (); it  != _bullets.end ();  ++it )
   {
     if ((*it )->active ())
       (*it )->update ();
   }
}

Bullet  * BulletManager ::touch ( void )
{
   //遍历所有 飞行的子弹,看看有没有击中的
   for (BulletPtrList ::iterator it  = _bullets.begin (); it  != _bullets.end ();  ++it )
   {
     if ((*it )->flying ())
       if ((*it )->touch ())
       {
         (*it )->burst ();
         return  (*it ).get ();
       }
   }
   return  NULL ;
}

bool BulletManager ::fire ( const Ogre ::Vector3  & pos , const Ogre ::Vector3  & force )
{
   //开火,在没有激活的子弹中选择一个。如果没有子弹可用就返回false;
   for (BulletPtrList ::iterator it  = _bullets.begin ();it  != _bullets.end ();  ++it )
   {
     if (!(*it )->active ())
     {
       (*it )->fire (pos , force );
       return  true ;
     }
   }
   return  false ;
}

编译成功,呵呵,子弹呢?怎么发射呢?别着急,马上回来。

第五节:开火,消灭敌人

战斗开始进入白热化状态,我们的战斗机不能光在天上飞着而不给敌人以痛击,因此我们需要在战斗机上加上控制系统,使战斗机能够发射子弹;我们还需要有雷达系统从而知道子弹是否击中目标;同时我们还需要更新怪兽,在它被击中之后能够从我们的雷达探测中消失。

战斗机更新:

BattlePlane.h:

#include  "Bullet.h"
//---省略段
   //粒子系统
  ParticleSystem  * _thrusters ;
   //子弹管理器
  BulletManager _bulletManager ;

BattlePlane.cpp:

BattlePlane ::BattlePlane (SceneManager * sm ):
//省略...
_bulletManager (sm ,  20)
{
//省略...
}
void BattlePlane ::setupContent ()
{
//省略...
//创建粒子系统
    _thrusters  = _sm ->createParticleSystem (  "ParticleSys1" ,  200 );

     //设置粒子系统的纹理
    _thrusters  ->setMaterialName (  "Examples/Flare"  );
    _thrusters  ->setDefaultDimensions (  2525 );


     //设置点发射器1&2
    ParticleEmitter  *emit1  = _thrusters  ->addEmitter (  "Point"  );
    ParticleEmitter  *emit2  = _thrusters  ->addEmitter (  "Point"  );

     //设置发射器1的参数
    emit1 ->setAngle ( Degree ( 3);
    emit1 ->setTimeToLive (  0. 2f );
    emit1 ->setEmissionRate (  70 );

    emit1 ->setParticleVelocity (  50 );

    emit1 ->setDirection (- Vector3 ::UNIT_Y );
    emit1 ->setColour ( ColourValue ::White , ColourValue ::Red );        

     //设置发射器2的参数
    emit2 ->setAngle ( Degree ( 3);
    emit2 ->setTimeToLive (  0. 2f );
    emit2 ->setEmissionRate (  70 );

    emit2 ->setParticleVelocity (  50 );

    emit2 ->setDirection (  -Vector3 ::UNIT_Y  );
    emit2 ->setColour ( ColourValue ::White , ColourValue ::Red  );

     //设置两个发射器位置
    emit1 ->setPosition ( Vector3 (  5. 7f0. 0f0. 0f )  );
    emit2 ->setPosition ( Vector3 (  - 18. 0f0. 0f0. 0f )  );

     //设置发射速度
    emit1 ->setParticleVelocity (  70 );
    emit2 ->setParticleVelocity (  70 );
    
     //把发射器挂接到飞机节点上。
    _node ->createChildSceneNode ( Vector3 (  13. 0f- 75. 0f0. 0f )  )->attachObject (_thrusters );
}
//省略...
bool BattlePlane ::onKeyReleased ( const OIS ::KeyEvent  & evt )
{
//省略...
   else  if (evt.key  == OIS ::KC_SPACE )  //如果释放空格键 发射子弹
        _bulletManager.fire (_node ->getPosition (), Ogre ::Vector3 ( 0. 0f10. 0f0. 0f) );
   return  true ;
}
void BattlePlane ::update ( void )
{
  _bulletManager.update ();
   //省略...
}

编译成功,空格可以发射子弹.怎么?不能击中敌人?别着急,我们继续让我们的子弹可以击中我们的敌人--外星怪兽。

Bullet.h:

class Bullet
{
public :
     //...
   inline uint32 getEnemyID ( void ){ return _enemyID ;}
private :
    //...
    //用于场景查询
  Ogre ::SphereSceneQuery  * _query ;
   //记录被击中敌人ID
  uint32 _enemyID ;
}

Bullet.cpp:

Bullet ::Bullet (SceneManager * sm ):
  _sm (sm ),
  _force (),
  _node ( NULL ),
  _ent ( NULL ),
  _active ( false ),
  _query ( NULL ),
  _enemyID ( 0)
{
}
Bullet ::~Bullet ( void )
{
   //销毁球形查询器
  _sm ->destroyQuery (_query );
}
void Bullet ::SetupContent ()
{
   static  int i  =  0;
   //创建节点
  _node  = _sm ->getRootSceneNode ()->createChildSceneNode ();
   //这里临时用一个球体作为子弹模型,以后再美化
  _ent  = _sm ->createEntity (std ::string ( "bullet" )  + 
    boost ::lexical_cast <std ::string >(i ++),  "sphere.mesh" );
   //挂接模型实体
  _node ->attachObject (_ent );
   //缩放一下
  _node ->setScale ( 0. 05f0. 05f0. 05f) ;
   //先隐藏着 备用
  _ent ->setVisible ( false );  

   //这里把子弹的碰撞检测掩码设置为0,即子弹实体不必检查被别人碰撞
  _ent ->setQueryFlags ( 0) ;
   //我们在这里创建一个球形场景查询器,
   //用于查询场景中和子弹碰撞的物体,第二个参数是场景查询掩码.
   //它将查询指定“and”的掩码的实体。
  _query  =  _sm ->createSphereQuery (Ogre ::Sphere (),  0x2) ;
}

void Bullet ::update ( void )
{
  _node ->translate (_force );
   //当子弹的y值大于400的时候,我们认为子弹已经飞出屏幕,在这里收回子弹
   if (_node ->getPosition ().y  >  400)
   {
    _ent ->setVisible ( false );
    _active  =  false ;
   }
}

bool Bullet ::active ()
{
   return _active ;
}

void Bullet ::burst ()
{
   //子弹被引爆,这里我们直接释放子弹,但是我们也可以在这里设置开始播放爆炸动画
  _ent ->setVisible ( false );
  _active  =  false ;
}

bool Bullet ::touch ()
{  
   //设置一个圆心为子弹位置,半径极小的球体作为子弹碰撞体
  _query ->setSphere (Ogre ::Sphere (_node ->getPosition (),  0. 01f) );
   //执行碰撞,并返回结果
  SceneQueryResult qres  =  _query ->execute ();
   //如果碰撞结果不是为空
   if (!qres.movables.empty ())
   { 
     //我们把第一个碰撞物体所储存的ID取出
     const Any & any  =  (*qres.movables.begin ())->getUserAny ();
     //保存ID
    _enemyID  = any. operator ()<uint32 >();
     //碰撞成功,返回true
     return  true ;
   }
   //否则碰撞失败返回false
   return  false ;

}

bool Bullet ::flying ()
{
   //如果子弹仍然显示,我们认为仍然在飞行状态中
   return _ent ->getVisible ();
}

敌人要能被击中:

EnemyPlanes.h

class EnemyPlanes :  public Singleton <EnemyPlanes >
{
//..
public :
   //...
   //增加一个被击中的函数
   void attacked (uint32 id );
public :
   static EnemyPlanes * getSingletonPtr ( void );
   static EnemyPlanes & getSingleton ( void );
//..
}

EnemyPlanes.cpp:

void EnemyPlanes::setupContent()
{
   //...
    //挂接实体
    _planes[i]->attachObject(ent);

    //在这一课我们临时把包围盒显示打开,便于观察
    _planes[i]->showBoundingBox(true);

    //设置碰撞掩码,这里与子弹碰撞检查掩码相同,表明可以被子弹击中物体。
    ent->setQueryFlags(0x2);
    //通过Any类型设置ID,注意的是,这个ID一定要在本动态库释放前删除,否则无法正确释放。
    ent->setUserAny(Ogre::Any(static_cast<uint32>(i)));
    //...
}
template <> EnemyPlanes * Singleton <EnemyPlanes >::ms_Singleton  =  0;
EnemyPlanes * EnemyPlanes ::getSingletonPtr ( void )
{
     return ms_Singleton ;
}
EnemyPlanes & EnemyPlanes ::getSingleton ( void )
{
    assert ( ms_Singleton  );   return  (  *ms_Singleton  );
}
void EnemyPlanes ::attacked (uint32 id )
{
     //在这里如果被击中我们首先设为不显示,然后把碰撞掩码设为0,这样现在的物体既看不见,又打不倒。
   //当然你也可以在这里开始播放爆炸动画。
    _planes [id ]->setVisible ( false );
    _planes [id ]->getAttachedObject ( 0) ->setQueryFlags ( 0) ;
}

再次更新Battle.cpp.

void BattlePlane ::update ( void )
{
  _bulletManager.update ();
   //寻找击中敌人的子弹
   while (Bullet * bullet  = _bulletManager.touch ())
   {
     //如果子弹击中敌人,把敌人的ID通知敌人管理器
    EnemyPlanes ::getSingleton ().attacked (bullet ->getEnemyID ());
   }
   //...
}

恭喜你,你已经能成功的将所有的敌人消灭殆尽,下面让我们的战鼓响起来,给我们英勇的战斗英雄助威吧。

第六节 让战鼓声想起来吧

这节我们将在程序中加入音乐,这是一个简单的Fmod封装类,参考免费打工仔和Fmod文档实现,此类存在一个问题Alt+F4结束程序不能释放fmodex,但不影响示范如何使用Fmod,问题待解决中,有解决的朋友请留言。

SoundPlayer.h:

#ifndef __SOUNDPLAYER_H_
#define __SOUNDPLAYER_H_

#pragma once

#include  "Ogre.h"
#include  <fmod.hpp >
#include  <fmod_errors.h >

using  namespace Ogre ;

class FmodSoundPlayer : public Singleton <FmodSoundPlayer >
{

public :
  FmodSoundPlayer ( const std ::string  & name ,  bool stream  =  false ):
      _sound ( NULL ),
        _channel ( NULL ),
        _stream (stream ),
        _name (name ),
        _system ( NULL )
       {

       }
       virtual ~FmodSoundPlayer ( void ){}
public :
   static FmodSoundPlayer * getSingletonPtr ( void );
   static FmodSoundPlayer & getSingleton ( void );
public :   
   virtual  bool load ();
   virtual  bool play (uint32 times );
   virtual  bool setVolume ( float volume );
   virtual  bool setPosition ( float posi );
   virtual  float getPosition ( void );
   virtual  float getLength ( void );
   virtual  bool stop ( void );
   virtual  bool setPaused ( bool paused );
   virtual  bool playing ( void );
   virtual  bool unload ( void );
   virtual  void quickPlay ( const std ::string  & fileName ,  bool stream  =  false );
   void initSystem ();

private :
  FMOD ::Sound      * _sound ;
  FMOD ::Channel    * _channel ;
  FMOD ::System     * _system ;
   bool _stream ;
  std ::string   _name ;
};

# endif

SoundPlayer.cpp:

#include  "SoundPlayer.h"

bool FmodSoundPlayer ::load ()
{
   if (_system  ==  NULL )
    initSystem ();
   if (_stream )
    _system ->createSound (_name.c_str (),  FMOD_SOFTWARE |FMOD_CREATESTREAM |FMOD_2D ,  0&_sound );
   else
    _system ->createSound (_name.c_str (),  FMOD_SOFTWARE |FMOD_2D ,  0&_sound );
   return  true ;
}
bool FmodSoundPlayer ::play (uint32 times )
{
  _system ->playSound (FMOD_CHANNEL_FREE , _sound ,  false ,  &_channel );

   if (times  <=  0)
    _channel ->setMode (FMOD_LOOP_NORMAL );
   else
    _channel ->setLoopCount (times ); //setLoopCount

   return  true ;
}

void FmodSoundPlayer ::quickPlay ( const std ::string  & fileName ,  bool stream )
{
   if (_system  ==  NULL )
    initSystem ();
  FMOD ::Sound      * sound ;
  FMOD ::Channel    * channel ;
   if (stream )
    _system ->createSound (fileName.c_str (),  FMOD_SOFTWARE |FMOD_CREATESTREAM |FMOD_2D ,  0&sound );
   else
    _system ->createSound (fileName.c_str (),  FMOD_SOFTWARE |FMOD_2D ,  0&sound );
  _system ->playSound (FMOD_CHANNEL_FREE , sound ,  false ,  &channel );
  channel ->setLoopCount ( 1) ;
}


bool FmodSoundPlayer ::setVolume ( float volume )
{
  _channel ->setVolume (volume );
   return  true ;
}


bool FmodSoundPlayer ::setPosition ( float posi )
{
  _channel ->setPosition ( static_cast < int >(posi  *  1000. 0f) , FMOD_TIMEUNIT_MS );
   return  true ;

}
float FmodSoundPlayer ::getPosition ( void )
{
   unsigned  int posi ;
  _channel ->getPosition (&posi , FMOD_TIMEUNIT_MS );
   return  static_cast < float >(posi ) / 1000. 0f;
}
float FmodSoundPlayer ::getLength ( void )
{
   unsigned  int length ;
  _sound ->getLength (&length ,FMOD_TIMEUNIT_MS  );
   return  static_cast < float >(length ) / 1000. 0f;
}


bool FmodSoundPlayer ::stop ( void )
{
  _channel ->stop ();
   return  true ;
}
bool FmodSoundPlayer ::setPaused ( bool paused )
{
  _channel ->setPaused (paused );
   return  true ;
}

bool FmodSoundPlayer ::playing ( void )
{
   bool playing ;
  _channel ->isPlaying (&playing );
   return playing ;
}
bool FmodSoundPlayer ::unload ( void )
{
  _sound ->release ();
   return  true ;
}

void FmodSoundPlayer ::initSystem ()
{
  FMOD_RESULT result ;
  result  = FMOD ::System_Create (&_system );
   if (result  != FMOD_OK )
   {
     return ;
   }

  result  = _system ->init ( 100, FMOD_INIT_NORMAL ,  0) ;
   if  (result  == FMOD_ERR_OUTPUT_CREATEBUFFER )
   {
    result  = _system ->setSpeakerMode (FMOD_SPEAKERMODE_STEREO );
     if (result  != FMOD_OK )
     {
       return ;
     }
    result  = _system ->init ( 100, FMOD_INIT_NORMAL ,  0) ;
     if (result  != FMOD_OK )
     {
       return ;
     }
   }
}

template <> FmodSoundPlayer * Singleton <FmodSoundPlayer >::ms_Singleton  =  0;
FmodSoundPlayer * FmodSoundPlayer ::getSingletonPtr ( void )
{
   return ms_Singleton ;
}
FmodSoundPlayer & FmodSoundPlayer ::getSingleton ( void )
{
  assert ( ms_Singleton  );   return  (  *ms_Singleton  );
}

Invade.h中添加初始化和播放背景音乐的代码段

#include  "SoundPlayer.h"
//...
private :
//...
  boost ::scoped_ptr <FmodSoundPlayer > _fmod ;
protected :
   void setupSound ()
   {
    _fmod.reset ( new FmodSoundPlayer ( "../../media/sound/bgm.wma" ,  true ));
    _fmod ->load ();
    _fmod ->play (- 1) ;
   }
   void setupContent ()
   {
     setupSound ()
      //...

Bullet.cpp中添加播放射击的声音

#include  "SoundPlayer.h"
//..
//fire函数末尾添加
FmodSoundPlayer ::getSingleton ().quickPlay ( "../../media/sound/fire.wav" );

EnemyPlanes.cp中添加击中目标爆炸的声音

#include  "SoundPlayer.h"
//..
//attacked函数末尾添加
FmodSoundPlayer ::getSingleton ().quickPlay ( "../../media/sound/boom.wav" );

至此我们完成了一个简单的飞机射击游戏游戏的全部代码,要全部实现一个完整的游戏,我们还需要很多的工作要做。朋友,动起来,加入不同的模型和不同操作方式,给游戏添加更多的乐趣。

尾章:所有源代码

本文涉及到的所有源代码在此下载


菊子曰 本文用 菊子曰发布
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值