CRYENGINE Game Development Blueprints
上QQ阅读APP看书,第一时间看更新

Creating a weapon class

We could create a simple weapon class and be done with it. However, I want to create a weapon system with the ability to create many weapons and pass them around the code using a simple interface. This interface approach is powerful, as it hides code and removes the requirement that every system must know about your exact weapon class. For example, wouldn't it be nice if we could give any weapon to a player and the player wouldn't need to know anything about it to use it? Using this interface approach, it is entirely possible. Interfaces are extremely powerful and add so much to a library or application in general that it is highly recommended to read a good book about it. It is a pattern that lends itself to well-written Object-oriented programming (OOP) code.

Creating the IWeapon interface

Since we need our game to support many weapons and we want to write clean, modular, OOP C++ code, it is essential that we create a weapon interface. Let's call this interface IWeapon. Since CRYEGNINE has its own interface called IWeapon, we will add ours to a custom namespace to avoid any name conflict. Let's do this:

  1. Create a new file called IWeapon.h. Since this will hold an interface, there will not be a corresponding IWeapon.cpp file. Add only those methods to the interface that you would like all weapons to support and that are not weapon-specific; keep it general. It should look like this:
    #ifndef __IWEAPON_HEADER__
    #define __IWEAPON_HEADER__
    
    
    namespace PACKT
    {
      namespace GAS
      {
        namespace GAME
        {
          struct IWeapon
          {
            virtual ~IWeapon() {}
    
            ///////////////////////////////////////////////////
            /// <summary>
            /// Shoots This Instance, Ammo Must Be Present. See This Instance's AddAmmo() Method.
            /// </summary>
            /// <returns> True If This Instance Was Shot Successfully, False Otherwise. </returns>
            ///////////////////////////////////////////////////
            virtual bool Shoot()const = 0;
    
            ///////////////////////////////////////////////////
            /// <summary> Gets Whether This Instance Can Shoot Or Not. </summary>
            /// <returns> True If This Instance Can Shoot, False Otherwise. </returns>
            ///////////////////////////////////////////////////
            virtual bool CanShoot()const = 0;
    
            ///////////////////////////////////////////////////
            /// <summary> Adds The Specified Amount Of Ammunition To This Instance. </summary>
             /// <param name="iNumAmmo"> The Amount Of Ammo To Add To This Instance. </param>
            ///////////////////////////////////////////////////
            virtual void AddAmmo( std::size_t iNumAmmo ) = 0;
    
            ///////////////////////////////////////////////////
            /// <summary> Removes The Specified Amount Of Ammunition From This Instance. </summary>
            /// <param name="iNumAmmo">
            /// The Amount Of Ammo To Remove From This Instance (-1 Removes All Ammo).
            /// </param>
            ///////////////////////////////////////////////////
            virtual void RemoveAmmo( std::size_t iNumAmmo ) = 0;
    
            ///////////////////////////////////////////////////
            /// <summary> Gets This Instance's Ammo Count. </summary>
            /// <returns> This Instance's Ammo Count. </returns>
            ///////////////////////////////////////////////////
            virtual std::size_t GetAmmoCount()const = 0;
    
            ///////////////////////////////////////////////////
            /// <summary>
            /// Gets This Instance's Ammo Class. That Is, The Type Of Ammo That This Instance Is Compatible
            /// With.
            /// </summary>
            /// <returns> This Instance's Ammo Class. </returns>
            ///////////////////////////////////////////////////
            virtual const string& GetAmmoClass()const = 0;
    
          };
        }
      }
    }
    
    #endif // !__IWEAPON_HEADER__
  2. Notice how simple this interface is. Only methods that are generic enough to be used by all imaginable weapon types are added to this interface, as our game will need to know how to use the weapon without knowing about what underlying implementation it uses. Any method that is weapon-specific doesn't belong here.

Creating the AWeapon abstract base 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 IWeapon interface that most likely won't need to be overridden 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 created a new weapon? We will create an abstract class that will implement most of the methods that will likely not need to be changed for each weapon class, and leave the other methods unimplemented so that specific weapon classes can define them.

  1. Create a new file called AWeapon.h that will hold our abstract weapon class's declaration. Since all weapons will be an object that you can actually place into the world, we will derive this from the IGameObjectExtension interface. The IGameObjectExtension interface simply represents an extension to a game object. It is a class that allows adding extra code and functionality to game objects modularly without having to touch the actual game object code. Game objects are any objects that can exist in a CRYENGINE world/game and usually represent some specific, unique, game logic. The code should look like this:
    #ifndef __AWEAPON_HEADER__
    #define __AWEAPON_HEADER__
    
    
    #include "IWeapon.h"
    #include "IGameObject.h"
    
    
    
    ///////////////////////////////////////////////////////////
    /// <summary>
    /// The AWeapon Abstract Class.  Implements All Of The Trivial IGameObjectExtension And IWeapon
    /// Stuff, As To Serve As A Great Base Class For All Weapons.
    /// </summary>
    /// <seealso cref="T:IWeapon"/>
    /// <seealso cref="T:CGameObjectExtensionHelper<AWeapon, IGameObjectExtension>"/>
    ///////////////////////////////////////////////////////////
    class AWeapon : public PACKT::GAS::GAME::IWeapon, public CGameObjectExtensionHelper < AWeapon, IGameObjectExtension >
    {
    public:
    
      AWeapon();
      virtual ~AWeapon();
    
      /////////////////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;
    
      /////////////////IWeapon///////////////////
    
      virtual bool CanShoot()const override;
      virtual void AddAmmo( std::size_t iNumAmmo )override;
      virtual void RemoveAmmo( std::size_t iNumAmmo )override;
      virtual std::size_t GetAmmoCount()const override;
      virtual const string& GetAmmoClass()const override;
    
    protected:
    
      /// <summary> The Amount Of Ammo This Instance Has. </summary>
      std::size_t m_iAmmoCount;
    
      /////////////////////////////////////////////////////////
      /// <summary>
      /// The Ammo Class That This Weapon Is Compatible With.  (The Ammo Class That This Instance Can
      /// Use).
      /// </summary>
      /////////////////////////////////////////////////////////
      string m_strAmmoClass;
    
    };
    
    
    #endif // !__AWEAPON_HEADER__

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

  2. Next, create a new file called AWeapon.cpp that will hold our simple implementation of the IGameObjectExtension and IWeapon interfaces. We can see why a nice abstract base class comes in handy, as we now have more than 25 methods for our weapon class. The implementation should look like this:
    #include "stdafx.h"
    #include "AWeapon.h"
    
    
    AWeapon::AWeapon():
    m_iAmmoCount(25)
    {
    
    }
    
    
    
    
    AWeapon::~AWeapon()
    {
    
    }
    
    
    /////////////////AWeapon::IGameObjectExtension///////////////////
    
    
    void AWeapon::GetMemoryUsage( ICrySizer *pSizer ) const
    {
      pSizer->Add( *this ); /*Add This Instance's Size To The CRYENGINE Performance Monitor.*/
    }
    
    
    
    
    bool AWeapon::Init( IGameObject * pGameObject )
    {
      SetGameObject( pGameObject ); /*Ensure This Instance Knows About Its Owning Game Object. IMPORTANT*/ return true;
    }
    
    
    
    
    void AWeapon::PostInit( IGameObject * pGameObject )
    {
    
    }
    
    
    
    
    void AWeapon::InitClient( int channelId )
    {
    }
    
    
    
    
    void AWeapon::PostInitClient( int channelId )
    {
    }
    
    
    
    
    bool AWeapon::ReloadExtension( IGameObject * pGameObject, const SEntitySpawnParams &params )
    {
      ResetGameObject(); /*Ensure This Instance Knows About Its Owning Game Object. IMPORTANT*/
      return true;//This Extension Should Be Kept.
    }
    
    
    
    
    void AWeapon::PostReloadExtension( IGameObject * pGameObject, const SEntitySpawnParams &params )
    {
    }
    
    
    
    
    bool AWeapon::GetEntityPoolSignature( TSerialize signature )
    {
      return true; //Our Signature Is Ok.
    }
    
    
    
    
    void AWeapon::Release()
    {
      delete this; //Delete This Instance As Our Owning Game Object Has Been Deleted.
    }
    
    
    
    
    void AWeapon::FullSerialize( TSerialize ser )
    {
    }
    
    
    
    
    bool AWeapon::NetSerialize( TSerialize ser, EEntityAspects aspect, uint8 profile, int pflags )
    {
      return true;//Our Serialization State Is Up To Date An Valid.
    }
    
    
    
    
    void AWeapon::PostSerialize()
    {
    }
    
    
    
    
    void AWeapon::SerializeSpawnInfo( TSerialize ser )
    {
    }
    
    
    
    
    ISerializableInfoPtr AWeapon::GetSpawnInfo()
    {
      return nullptr;//No Need For Spawn Information.
    }
    
    
    
    
    void AWeapon::Update( SEntityUpdateContext& ctx, int updateSlot )
    {
    }
    
    
    
    
    void AWeapon::HandleEvent( const SGameObjectEvent& event )
    {
    }
    
    
    
    
    void AWeapon::ProcessEvent( SEntityEvent& event )
    {
    
    }
    
    
    
    
    void AWeapon::SetChannelId( uint16 id )
    {
    }
    
    
    
    
    void AWeapon::SetAuthority( bool auth )
    {
    }
    
    
    
    
    void AWeapon::PostUpdate( float frameTime )
    {
    }
    
    
    
    
    void AWeapon::PostRemoteSpawn()
    {
    }
    
    
    /////////////////IWeapon///////////////////
    
    
    bool AWeapon::CanShoot()const
    {
      return m_iAmmoCount;
    }
    
    
    
    
    void AWeapon::AddAmmo( std::size_t iNumAmmo )
    {
      m_iAmmoCount += iNumAmmo;
    }
    
    
    
    
    void AWeapon::RemoveAmmo( std::size_t iNumAmmo )
    {
      ( iNumAmmo == -1 ) ? ( m_iAmmoCount = 0 ) : (m_iAmmoCount -= iNumAmmo);
    }
    
    
    
    
    std::size_t AWeapon::GetAmmoCount()const
    {
      return m_iAmmoCount;
    }
    
    
    
    
    const string& AWeapon::GetAmmoClass()const
    {
      return m_strAmmoClass;
    }
  3. Notice how simple it was to implement; there would absolutely be no need to keep implementing these methods for each and every weapon class. Of course, we can override any of them if needed.

Creating the CBlaster weapon class

We created a nice IWeapon interface and an AWeapon abstract base class, now what? Well, since all the ground work is complete, it's time to actually create our weapon class:

  1. Create a new file called CBlaster.h. This will hold our weapon's declaration. Make a note of how many methods need to be implemented in order to create a new weapon. The code should look like this:
    #ifndef __CBLASTER_HEADER__
    #define __CBLASTER_HEADER__
    
    
    #include "AWeapon.h"
    
    
    ///////////////////////////////////////////////////////////
    /// <summary> The CBlaster Class. A Ranged Weapon That Shoots Fire Balls. </summary>
    /// <seealso cref="T:AWeapon"/>
    ///////////////////////////////////////////////////////////
    class CBlaster : public AWeapon
    {
    public:
    
      CBlaster();
      virtual ~CBlaster();
    
      /////////////////CBlaster///////////////////
    
      virtual bool Shoot()const override;
    
    private:
    
    };
    
    
    #endif

    Simple, and fast right?

  2. Create a new file called CBlaster.cpp that will hold the implementation of the CBlaster weapon class. Ok, now let's implement the Shoot() method:
    #include "stdafx.h"
    #include "CBlaster.h"
    #include "CGASGame.h"
    #include "CGASGameRules.h"
    #include "CFireBallAmmo.h"
    
    
    /////////////////CBlaster///////////////////
    
    
    CBlaster::CBlaster()
    {
      //Set The Ammo Class That This Instance Is Compatible With.
      m_strAmmoClass = AMMO_CLASS_FIREBALL;
    }
    
    
    
    
    CBlaster::~CBlaster()
    {
    }
    
    
    
    
    bool CBlaster::Shoot()const
    {
      //Check If We Can Shoot.
      if( !CanShoot() )
        return false;
    
      //Get This Instance's Entity.
      auto pEnt = GetEntity();
      if( !pEnt )
        return false;
    
      //Get This Instance's Geometry.
      auto pStatObj = pEnt->GetStatObj( 0 );
      if( !pStatObj )
        return false;
    
      //Spawn The CFireBallAmmo Class.
      SEntitySpawnParams SpawnParams;
      SpawnParams.nFlags = ENTITY_FLAG_CASTSHADOW | ENTITY_FLAG_CALC_PHYSICS;
      SpawnParams.pClass = gEnv->pEntitySystem->GetClassRegistry()->FindClass( "FireBallAmmo" ); //The Name Used In REGISTER_GAME_OBJECT.
      SpawnParams.qRotation = IDENTITY;
      SpawnParams.vPosition = pStatObj->GetHelperPos( "BulletExit" );//Spawn At The Correct Position On The Weapon. (Weapon Model Must Have A Child Helper Named "BulletExit" ).
      SpawnParams.vScale = Vec3( 1, 1, 1 );
    
      //Actually Spawn The Entity.
      auto pAmmoEnt = gEnv->pEntitySystem->SpawnEntity( SpawnParams );
      if( !pAmmoEnt )
        return false;
    
      //Get It's Game Object.
      auto pAmmoGO = gEnv->pGame->GetIGameFramework()->GetGameObject( pAmmoEnt->GetId() );
      if( !pAmmoGO )
      {
        gEnv->pEntitySystem->RemoveEntity( pAmmoEnt->GetId(), true );
        return false;
      }
    
      //Get Our CFireBallAmmo Extension From It.
      auto pAmmoExt = pAmmoGO->QueryExtension( "FireBallAmmo" );
      if( !pAmmoExt )
      {
        gEnv->pEntitySystem->RemoveEntity( pAmmoEnt->GetId(), true );
        return false;
      }
    
      //Launch The Ammo.
      static_cast< CFireBallAmmo* >( pAmmoExt )->Launch( SpawnParams.vPosition, pEnt->GetForwardDir() );
    
      return true;
    }

Congratulations, we created a complete weapon system. In the next section, you will do the same for ammunition. Get ready for some more fun.