Extending nGENE - example with camera

I often claim that nGENE Tech is easy to extend. In this tutorial we will look at how to create new camera class (CameraFirstPersonSliding) which will be created the very same way it was done with CameraFirstPerson in the previous tutorials. Our camera will behave differenty, it won't stop instantly after user released key but will slowly slow down until complete stop. This tutorial's code will build upon code from tutorial 1.5 so be sure to check it out before going any further.

We're dropping character controller which was used there.

First lets introduce the concept of enumerators which are widely used within the nGENE Tech. Enumerators of nGENE are classes which gather together factories creating objects of the same category (cameras, force fields, joints and so on). As they are all singleton it is possible to use syntax like that:

ClassName* pObject = Enumerator::getSingleton().createClassName(L"default");

where ClassName is some class, Enumerator allows to create objects of this class and L"default" is type of object we want to create. Internally Enumerator looks for the factory which registered itself in Enumerator under the name "default". If it is found, it returns an object created by factory associated with it. Otherwise error arises (something like class for type "default" does not exist). By default most factories register them in the proper enumerator in their constructor (by calling its addFactory() method) but it is not necessary and for external applications it can be better idea to provide method for that, as by the time factory class object is created enumerator could still be not initialized what will cause error due to unregistered singleton class.

Ok. So first lets create CameraFirstPersonSliding class:

namespace nGENE
{
    class CameraFirstPersonSliding: public CameraFirstPerson
    {
        EXPOSE_TYPE
    protected:
        Real m_fCurrentVelocity;
        Real m_fSlowDownRatio;
 
        Vector3 m_vecMovementDir;
 
    public:
        CameraFirstPersonSliding();
        virtual ~CameraFirstPersonSliding();
 
        void updateCamera();
 
        void setSlowDownSpeed(Real _value);
 
        void move(Real _direction);
        void strafe(Real _direction);
 
        void serialize(ISerializer* _serializer, bool _serializeType, bool _serializeChildren);
        void deserialize(ISerializer* _serializer);
    };
}

Quite a lot of code. Lets go through it:

  • EXPOSE_TYPE is a macro allowing to define new type for nGENE RTTI.
  • m_fCurrentVelocity and m_vecMovementDirectory tell what is the speed and current direction movement. Direction will be changed by calls to move() and strafe() methods but velocity will be updated automatically by substracting each frame m_fSlowDownRatio * time delta.
  • updateCamera() will update velocity and position of the camera. This method is called each frame by the engine on the active camera.
  • move() and strafe() methods are responsible for setting movement direction.
  • serialize() and deserialize() methods are used to allow class to be saved and loaded to and from a file what is necessary to allow players to save their game.

Here is .cpp file:

TypeInfo CameraFirstPersonSliding::Type(L"CameraFirstPersonSliding", &CameraFirstPerson::Type);
 
CameraFirstPersonSliding::CameraFirstPersonSliding():
    m_fSlowDownRatio(2.0f)
{
}
//----------------------------------------------------------------------
CameraFirstPersonSliding::~CameraFirstPersonSliding()
{
}
//----------------------------------------------------------------------
void CameraFirstPersonSliding::updateCamera()
{
    float fDiff = float(Engine::getSingleton().getTimer().getMilliseconds() - m_lPrevTime) * 0.001f;
    m_vecPosition += (m_quatOrientation * (m_vecMovementDir * (m_fCurrentVelocity * fDiff)));
 
    m_fCurrentVelocity -= m_fSlowDownRatio * fDiff;
    if(m_fCurrentVelocity < 0.0f)
        m_fCurrentVelocity = 0.0f;
 
    m_bChanged = true;
 
    CameraFirstPerson::updateCamera();
}
//----------------------------------------------------------------------
void CameraFirstPersonSliding::move(Real _direction)
{
    Maths::clamp <float> (_direction, -1.0f, 1.0f);
 
    m_vecMovementDir.z = _direction;
    m_fCurrentVelocity = m_fVelocity;
 
    m_bChanged = true;
    m_bHasChanged = true;
}
//----------------------------------------------------------------------
void CameraFirstPersonSliding::strafe(Real _direction)
{
    Maths::clamp <float> (_direction, -1.0f, 1.0f);
 
    m_vecMovementDir.x = _direction;
    m_fCurrentVelocity = m_fVelocity;
 
    m_bChanged = true;
    m_bHasChanged = true;
}
//----------------------------------------------------------------------
void CameraFirstPersonSliding::setSlowDownSpeed(Real _value)
{
    m_fSlowDownRatio = _value;
}
//----------------------------------------------------------------------
void CameraFirstPersonSliding::serialize(ISerializer* _serializer, bool _serializeType, bool _serializeChildren)
{
    if(!m_bIsSerializable)
        return;
 
    if(_serializeType) _serializer->addObject(this->Type);
        CameraFirstPerson::serialize(_serializer, false, _serializeChildren);
        Property <float> prSlowDown(m_fSlowDownRatio);
        _serializer->addProperty("SlowDown", prSlowDown);
    if(_serializeType) _serializer->endObject(this->Type);
}
//----------------------------------------------------------------------
void CameraFirstPersonSliding::deserialize(ISerializer* _serializer)
{
    CameraFirstPerson::deserialize(_serializer);
    Property <float> prSlowDown(m_fSlowDownRatio);
    _serializer->getProperty("SlowDown", prSlowDown);
}

Code should be pretty obvious now. The only new part is serialization/deserialization. For each of the fields you want to save/load you have to create Property <FieldType> object and add/get it to or from serializer providing its name as the first parameter (it is necessary for saving object to xml/text files).

It is also necessary to create factory which will create our new camera class objects and register itself in the enumerator. The factory definition is specified below:

class SlidingCameraFactory: public CameraFactory
{
public:
    SlidingCameraFactory();
    virtual ~SlidingCameraFactory();
 
    Camera* createCamera() {return new CameraFirstPersonSliding();}
};

Note that createCamera() method returns Camera* not CameraFirstPersonSliding*!

To register this class in the enumerator just call:

CameraEnumerator::getSingleton().addFactory(referenceToFactoryObject, L"CameraFirstPersonSliding");

somewhere in your code, where "CameraFirstPersonSliding" is the name under which this factory will be available.

Now it would be possible to create camera this way:

SceneManager* sm;
...
CameraFirstPersonSliding* pCamera = (CameraFirstPersonSliding*)sm->createCamera(L"CameraFirstPersonSliding", L"Camera");

For full source code see tutorial 3.3 in the nGENE sources.

Unless otherwise stated, the content of this page is licensed under Creative Commons Attribution-ShareAlike 3.0 License