Creating an ammo class

Now that we have created our weapon system, it is time for us to create an ammunition system. Ammunition is what our weapons use to cause damage. It can be anything from a bullet to a magic spell, anything that results in damage caused by firing some weapon. Since we wish to achieve a modular and extensible design, we will accomplish this using interfaces once again.

Creating the IAmmo class

Since we need our game to support many ammunition types and we want to write clean, modular, OOP C++ code, it is essential that we create an ammo interface which we will call IAmmo. Let's do it:

  1. Create a new file called IAmmo.h that will hold our IAmmo interface declaration. It should look like this:
    #ifndef __IAMMO_HEADER__
    #define __IAMMO_HEADER__
    
    
    #include <CryString.h>
    #include <Cry_Math.h>
    
    
    
    /// <summary> The IAmmo Interface. Used To Represent Any Type Of Ammunition/Projectile. </summary>
    struct IAmmo
    {
      virtual ~IAmmo() {}
    
      /////////////////////////////////////////////////////////
      /// <summary> Launches This Instance In The Specified Direction. </summary>
      /// <param name="Pos"> The WorldSpace Position To Start The Launch From. </param>
      /// <param name="Dir">
      /// The Direction (And Velocity) To Launch This Instance (Not Normalized). The Magnitude Is
      /// Interpreted As the Velocity.
      /// </param>
      /////////////////////////////////////////////////////////
      virtual void Launch( const Vec3& Pos, const Vec3& Dir ) = 0;
    
      /////////////////////////////////////////////////////////
      /// <summary> Gets Whether This Instance Is Launched. </summary>
      /// <returns> Whether This Instance Is Launched. </returns>
      /////////////////////////////////////////////////////////
      virtual bool IsLaunched()const = 0;
    
      /////////////////////////////////////////////////////////
      /// <summary> Gets This Instance's Launch Direction And Velocity. </summary>
      /// <returns> This Instance's Launch Direction And Velocity. </returns>
      /////////////////////////////////////////////////////////
      virtual const Vec3& GetLaunchDir()const = 0;
    
      /////////////////////////////////////////////////////////
      /// <summary> Gets The Amount Of Damage This Instance Causes. </summary>
      /// <returns> The Amount Of Damage This Instance Causes. </returns>
      /////////////////////////////////////////////////////////
      virtual float GetPower()const = 0;
    
      /////////////////////////////////////////////////////////
      /// <summary> Gets This Instance's Ammo Class. Used To Identify This Type Of Ammo. </summary>
      /// <returns> This Instance's Ammo Class. </returns>
      /////////////////////////////////////////////////////////
      virtual const string& GetAmmoClass()const = 0;
    };
    
    
    #endif // !__IAMMO_HEADER__
  2. Notice how simple this interface is. Only methods that are generic enough to be used by all imaginable ammo types are added to this interface, as our game will need to know how to use the ammo without knowing about what underlying implementation it uses. Any method that is ammo-specific doesn't belong here.

Creating the AAmmo abstract class

Now that our interface is complete, let's go ahead and create an abstract base class. We do this so that we can implement simple methods in the IAmmo interface that won't need to be overriden with custom logic. Doing this significantly reduces code redundancy and allows us to only focus on the key methods that are important. Could you imagine having to implement every method of this interface every time you create new ammunition? We create an abstract class that will implement most of the methods that don't need to be changed for each ammo class, and leave the other methods unimplemented so that specific ammo classes can define them. The steps are as follows:

  1. Create a new file called AAmmo.h that will hold our abstract ammo class's declaration. Since all ammunitions will be objects that you can actually place into the world, we will derive them from the IGameObjectExtension interface. The code should look like this:
    #ifndef __AAMMO_HEADER__
    #define __AAMMO_HEADER__
    
    
    #include "IAmmo.h"
    #include <IGameObject.h>
    
    
    ///////////////////////////////////////////////////////////
    /// <summary>
    /// The AAmmo Abstract Class.  Implements All Of The Trivial IGameObjectExtension Stuff, As To
    /// Serve As A Great Base Class For All Ammunition.
    /// </summary>
    /// <seealso cref="T:CGameObjectExtensionHelper{AAmmo, IGameObjectExtension}"/>
    ///////////////////////////////////////////////////////////
    class AAmmo : public IAmmo, public CGameObjectExtensionHelper < AAmmo, IGameObjectExtension >
    {
    public:
    
      AAmmo();
      virtual ~AAmmo();
    
      /////////////////IGameObjectExtension///////////////////
    
      virtual void GetMemoryUsage( ICrySizer *pSizer ) const override;
      virtual bool Init( IGameObject * pGameObject )override;
      virtual void PostInit( IGameObject * pGameObject )override;
      virtual void InitClient( int channelId )override;
      virtual void PostInitClient( int channelId )override;
      virtual bool ReloadExtension( IGameObject * pGameObject, const SEntitySpawnParams &params )override;
      virtual void PostReloadExtension( IGameObject * pGameObject, const SEntitySpawnParams &params )override;
      virtual bool GetEntityPoolSignature( TSerialize signature )override;
      virtual void Release()override;
      virtual void FullSerialize( TSerialize ser )override;
      virtual bool NetSerialize( TSerialize ser, EEntityAspects aspect, uint8 profile, int pflags )override;
      virtual void PostSerialize()override;
      virtual void SerializeSpawnInfo( TSerialize ser )override;
      virtual ISerializableInfoPtr GetSpawnInfo()override;
      virtual void Update( SEntityUpdateContext& ctx, int updateSlot )override;
      virtual void HandleEvent( const SGameObjectEvent& event )override;
      virtual void ProcessEvent( SEntityEvent& event )override;
      virtual void SetChannelId( uint16 id )override;
      virtual void SetAuthority( bool auth )override;
      virtual void PostUpdate( float frameTime )override;
      virtual void PostRemoteSpawn()override;
    
      /////////////////IAmmo///////////////////
    
      virtual bool IsLaunched()const override;
      virtual const Vec3& GetLaunchDir()const override;
      virtual float GetPower()const override;
      virtual const string& GetAmmoClass()const override;
    
    protected:
    
      /// <summary> The Amount Of Damage This Instance Causes. </summary>
      float m_fPower;
    
      /// <summary> Specifies Whether This Instances Was Launched From A Weapon. </summary>
      bool m_bLaunched;
    
      /// <summary> This Instance's Class. Used To Identify This Type Of Ammo. </summary>
      string m_strAmmoClass;
    
      /// <summary> This Instance's Launch Direction And Velocity. </summary>
      Vec3 m_LaunchDir;
    
    };
    
    
    #endif // !__AAMMO_HEADER__

    Notice how we leave the Launch() method undefined. This is because most ammunitions will need custom logic to carry out that task.

  2. Next, create a new file called AAmmo.cpp that will hold our simple implementation of the IGameObjectectExtention and IAmmo interfaces. We can see now why a nice abstract base class comes in handy, as we now have more than 25 methods for our ammo class. The implementation should look like this:
    #include "stdafx.h"
    #include "AAmmo.h"
    #include "CGASGame.h"
    #include "CGASGameRules.h"
    
    
    AAmmo::AAmmo()
    {
    }
    
    
    
    
    AAmmo::~AAmmo()
    {
    }
    
    
    /////////////////AAmmo::IGameObjectExtension///////////////
    
    
    void AAmmo::GetMemoryUsage( ICrySizer *pSizer ) const
    {
      pSizer->Add( *this ); /*Add This Instance's Size To The CRYENGINE Performance Monitor.*/
    }
    
    
    
    
    bool AAmmo::Init( IGameObject * pGameObject )
    {
      SetGameObject( pGameObject ); /*Ensure This Instance Knows About Its Owning Game Object. IMPORTANT*/ return true;
    }
    
    
    
    void AAmmo::PostInit( IGameObject * pGameObject )
    {
      pGameObject->EnableUpdateSlot( this, 0 ); /*Enable Updates To This Instance (Update() Will Be Called Every Frame.)*/
    }
    
    
    
    
    void AAmmo::InitClient( int channelId )
    {
    }
    
    
    
    
    void AAmmo::PostInitClient( int channelId )
    {
    }
    
    
    
    
    bool AAmmo::ReloadExtension( IGameObject * pGameObject, const SEntitySpawnParams &params )
    {
      ResetGameObject(); /*Ensure This Instance Knows About Its Owning Game Object. IMPORTANT*/ return true;
    }
    
    
    
    
    void AAmmo::PostReloadExtension( IGameObject * pGameObject, const SEntitySpawnParams &params )
    {
    }
    
    
    
    
    bool AAmmo::GetEntityPoolSignature( TSerialize signature )
    {
      return true;
    }
    
    
    
    
    void AAmmo::Release()
    {
      delete this;
    }
    
    
    
    
    void AAmmo::FullSerialize( TSerialize ser )
    {
    }
    
    
    
    
    bool AAmmo::NetSerialize( TSerialize ser, EEntityAspects aspect, uint8 profile, int pflags )
    {
      return true;
    }
    
    
    
    
    void AAmmo::PostSerialize()
    {
    }
    
    
    
    
    void AAmmo::SerializeSpawnInfo( TSerialize ser )
    {
    }
    
    
    ISerializableInfoPtr AAmmo::GetSpawnInfo()
    {
      return nullptr;
    }
    
    
    
    
    void AAmmo::Update( SEntityUpdateContext& ctx, int updateSlot )
    {
      //Check To See If We Were Launched By A Weapon.
      if( m_bLaunched )
      {
        //We Were Launched, Keep Us Moving In The Launched Direction.
        pe_action_set_velocity SetVelAction;
        SetVelAction.v = m_LaunchDir;
        GetEntity()->GetPhysics()->Action( &SetVelAction );
      }
    }
    
    
    
    
    void AAmmo::HandleEvent( const SGameObjectEvent& event )
    {
    }
    
    
    
    
    void AAmmo::ProcessEvent( SEntityEvent& event )
    {
      if( event.event == ENTITY_EVENT_COLLISION )
      {
        //Get Our Games' Instance.
        auto pGame = static_cast< CGASGame* >( gEnv->pGame );
    
        //Get The Games' GameRules, So That It Can Handle Logic For Item/Ammo Pickup.
        auto pGameRules = static_cast< CGASGameRules* >( pGame->GetGameRules() );
    
        //Get The Collision Struct For This Collision Event.
        auto pEventCollision = ( ( EventPhysCollision* )event.nParam[ 0 ] );
    
        //Get The Other Physical Entity Involved In This Collision.
        auto pPhysOther = pEventCollision->pEntity[ 0 ] == GetEntity()->GetPhysics() ? pEventCollision->pEntity[ 1 ] : pEventCollision->pEntity[ 0 ];
    
        //Check If This Instance Has Been Launched From A Weapon.
        if( m_bLaunched )
          pGameRules->OnAmmoHit( this, gEnv->pEntitySystem->GetEntityFromPhysics( pPhysOther ) );//Ammo Has Hit A Target. Notify GameRules About The Ammo Hit.
        else
          pGameRules->OnAmmoPickupRequest( this, gEnv->pEntitySystem->GetEntityFromPhysics( pPhysOther ) );//Ammo Is Waiting To Be Picked Up (Ammo Was Not Launched). Notify GameRules About The Ammo Wanting To Be Picked Up.
      }
    }
    
    
    
    
    void AAmmo::SetChannelId( uint16 id )
    {
    }
    
    
    
    
    void AAmmo::SetAuthority( bool auth )
    {
    }
    
    
    
    
    void AAmmo::PostUpdate( float frameTime )
    {
    }
    
    
    
    
    void AAmmo::PostRemoteSpawn()
    {
    }
    
    
    /////////////////AAmmo::IAmmo///////////////////
    
    
    bool AAmmo::IsLaunched()const
    {
      return m_bLaunched;
    }
    
    
    
    
    const Vec3& AAmmo::GetLaunchDir()const
    {
      return m_LaunchDir;
    }
    
    
    
    
    float AAmmo::GetPower()const
    {
      return m_fPower;
    }
    
    
    
    
    const string& AAmmo::GetAmmoClass()const
    {
      return m_strAmmoClass;
    }
  3. Notice the implementation of the ProcessEvent() method; try to figure out what is happening. Take a look at the Update() method. We simply checked whether we were launched, and if so, we updated our position in the launch direction using physics.

Creating the CFireBallAmmo class

We created our nice IAmmo interface and AAmmo abstract base class, now what? Well, since all the ground work is complete, it's time to actually create our ammo class.

  1. Create a new file called CFireBallAmmo.h. This will hold our ammunition's declaration. Make a note of how many methods need to be implemented in order to create a new type of ammunition. The code should look like this:
    #ifndef __CFIREBALLAMMO_HEADER__
    #define __CFIREBALLAMMO_HEADER__
    
    
    #include "AAmmo.h"
    
    
    /// <summary> The FireBall Ammo Class. </summary>
    #define AMMO_CLASS_FIREBALL "FireBall"
    
    
    ///////////////////////////////////////////////////////////
    /// <summary> The CFireBallAmmo Class.  A Projectile Ammo That Causes Fire Damage. </summary>
    /// <seealso cref="T:AAmmo"/>
    ///////////////////////////////////////////////////////////
    class CFireBallAmmo : public AAmmo
    {
    public:
    
      CFireBallAmmo();
      virtual ~CFireBallAmmo();
    
      /////////////////IAmmo///////////////////
    
      virtual void Launch( const Vec3& Pos, const Vec3& Dir )override;
    
    private:
    };
    
    
    #endif // !__CFIREBALLAMMO_HEADER__
  2. Create a new file called CFireBallAmmo.cpp that will hold the implementation of the CFireBallAmmo ammunition class. Ok, now let's implement the Launch() method:
    #include "stdafx.h"
    #include "CFireBallAmmo.h"
    #include "IParticles.h"
    
    
    /////////////////CFireBallAmmo///////////////////
    
    
    CFireBallAmmo::CFireBallAmmo()
    {
      m_fPower = 25.0f;
      m_bLaunched = false;
      m_strAmmoClass = AMMO_CLASS_FIREBALL;
    }
    
    
    
    
    CFireBallAmmo::~CFireBallAmmo()
    {
    }
    
    
    /////////////////CFireBallAmmo::IAmmo///////////////////
    
    
    void CFireBallAmmo::Launch( const Vec3& Pos, const Vec3& Dir )
    {
      //Find Or Load The Specified Particle Effect.
      auto pParticleEffect = gEnv->pParticleManager->FindEffect( "Ammo.Projectile.FireBall" );
      if( pParticleEffect )
      {
        //Spawn The Found Particle Effect.
        auto pEmmiter = pParticleEffect->Spawn( true, QuatTS( IDENTITY, Pos ) );
        if( pEmmiter )
          m_bLaunched = true; //Enable Launch Mode (Update() Will Handle Logic From Here).
      }
    }
  3. If we take a look at the implementation of the Launch() method, we can see that all we do is instantiate a particle effect named Ammo.Projectile.FireBall and then let the Update() method do all the work (by enabling m_bLaunched, which is checked in every Update()).

Congratulations, you successfully created a complete ammunition system. In the next section, you will learn how to add ammo events to our game. Get ready for some more fun.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.216.240.230