BloodyMess Tutorial 5        
NxOgre   This page is related to a deprecated version of NxOgre, called BloodyMess. Some code from here may be adopted to newer versions with sufficient care and knowledge.   NxOgre


Disclaimer: This is not an official NxOgre tutorial. However, it has been revised and edited by the creator of NxOgre and BloodyMess, betajaen.

Forum icon question2.gif Help: For any problems you encounter while working on these tutorials, as well as for detailed questions and propositions, please visit the NxOgre OgreAddons-Forum.

NxOgreCube.png Introduction

In the previous tutorials, we had quite different types of physical objects in our scene: We had simple cubes flying around reacting to forces as well as applying some to other objects, we had the floor which didn't react to anything at all and we had the trigger volumes affecting other objects while not being visible.

But what if you need some kind of elevator or some kind of moving platforms? Normal actors would react to other objects, a floor doesn't move at all and a trigger is not visible...So what to do? The key are Kinematics! They are quite handy in a lot of situations and luckily really easy to use as you will see in the following.

Image You can find the complete code for this tutorial here.

Final Scene
Final Scene

NxOgreCube.png Kinematics?

Well, Kinematics are special physical objects, that affect other objects while not being affected by them, they are visible and can be moved, so the perfect solution for all kind of moving platforms such as elevators.

And that's exactly what we are going to implement in this tutorial? An up and down moving platform with a cube on it, as well as the functionality to shoot more cubes by hitting the space key.

Note: As always in NxOgre, you have to distinguish between a Body and an Actor. An Actor is just the physical representation of an physical object and is not visible whereas a Body is an Actor plus a visualization (e.g. via Ogre Entities). The same applies for a KinematicActor and a KinematicBody.

NxOgreCube.png The initial code

Create a C++ (BloodyMessTutorial5.cpp) file in your -IDE. We will create the necessary code to make Ogre run using the ExampleApplication and ExampleFrameListener classes provided by the Ogre Samples.

Note: If your fairly new with Ogre: It may be worth it modifying Ogre SkyBox sample instead of creating your own Ogre project.


Please add the following code to that file, it should compile and run with no errors.

#include "ExampleApplication.h"

#include <NxOgre.h>
#include <NxOgreOGRE3D.h>

class BloodyMessTutorial5Listener : public ExampleFrameListener
{ 
public:
    BloodyMessTutorial5Listener(RenderWindow *win, Camera *cam) 
        : ExampleFrameListener(win, cam)
    {
        mTimeController = NxOgre::TimeController::getSingleton();
    }

    bool frameStarted(const FrameEvent& evt)
    {
        mTimeController->advance(evt.timeSinceLastFrame);
        return ExampleFrameListener::frameStarted(evt);
    }

protected:
    NxOgre::TimeController* mTimeController;
};

class BloodyMessTutorial5 : public ExampleApplication
{
protected:
    NxOgre::World*            mWorld;
    NxOgre::Scene*            mScene;
    NxOgre::TimeController*        mTimeController;
    OGRE3DRenderSystem*        mRenderSystem;
    
    void createScene()
    {
        // Set ambient light
        mSceneMgr->setAmbientLight(ColourValue(0.5f, 0.5f, 0.5f));

        // Create a light
        Light* l = mSceneMgr->createLight("MainLight");
        l->setPosition(20, 80, 50);

        // Position the camera
        mCamera->setPosition(0, 40, 80);
        mCamera->lookAt(0, 20, 0);

        // Create the world
        mWorld = NxOgre::World::createWorld();

        // Create scene description
        NxOgre::SceneDescription sceneDesc;
        sceneDesc.mGravity = NxOgre::Vec3(0, -9.8f, 0);
        sceneDesc.mName = "DemoScene";

        // Create scene
        mScene = mWorld->createScene(sceneDesc);

        // Set some physical scene values
        mScene->getMaterial(0)->setStaticFriction(0.5);
        mScene->getMaterial(0)->setDynamicFriction(0.5);
        mScene->getMaterial(0)->setRestitution(0.1);

        // Create render system
        mRenderSystem = new OGRE3DRenderSystem(mScene);

        //Create time controller
        mTimeController = NxOgre::TimeController::getSingleton();
    }

    // Create a new frame listener
    void createFrameListener()
    {
        mFrameListener = new BloodyMessTutorial5Listener(mWindow, mCamera);
        mRoot->addFrameListener(mFrameListener);
    }
};

#if OGRE_PLATFORM == OGRE_PLATFORM_WIN32
#define WIN32_LEAN_AND_MEAN
#include "windows.h"
#endif

#ifdef __cplusplus
extern "C" {
#endif

#if OGRE_PLATFORM == OGRE_PLATFORM_WIN32
    INT WINAPI WinMain(HINSTANCE hInst, HINSTANCE, LPSTR strCmdLine, INT)
#else
    int main(int argc, char **argv)
#endif
    {
        // Create application object
        BloodyMessTutorial5 app;

        try {
            app.go();
        } catch(Exception& e) {
#if OGRE_PLATFORM == OGRE_PLATFORM_WIN32
            MessageBoxA(NULL, e.getFullDescription().c_str(),
                "An exception has occurred!", MB_OK | MB_ICONERROR | MB_TASKMODAL);
#else
            std::cerr << "An exception has occurred: " << e.getFullDescription();
#endif
        }

        return 0;
    }

#ifdef __cplusplus
}
#endif

NxOgreCube.png Get it started

NxOgreCube.png Extending our tutorial class

The first thing, we will do is adding a new member variable to our tutorial class:

OGRE3DKinematicBody* mKB;

As you can see from the data type, that is our KinematicBody object.

Then, we will extend our createScene() function a bit:

mKB = mRenderSystem->createKinematicBody(new NxOgre::Box(12, 1, 12), NxOgre::Vec3(0, 12, 0), "Cube.mesh");
mRenderSystem->createBody(new NxOgre::Box(1, 1, 1), NxOgre::Vec3(0, 14, 0), "cube.1m.mesh");


The first line actually creates the new KinematicBody. You just need to tell the RenderSystem of what shapes it is, where to put it and which *.mesh file to use as the visual representation. The second line just creates a regular body just above the KinematicBody as shown in the previous tutorials.

The last change in our tutorial class is to alter the createFrameListener() function to meet the needs of our new (still to be created) tutorials listener. So it should like this now:

void createFrameListener()
{
    mFrameListener = new BloodyMessTutorial5Listener(mWindow, mCamera, mRenderSystem, mKB);
    mRoot->addFrameListener(mFrameListener);
}

NxOgreCube.png Let it move

First of all, we will add some new member variable to our listener. So all in all, we should now have these variables (our new plus those already being there):

NxOgre::TimeController*    mTimeController;
OGRE3DRenderSystem*         mRenderSystem;
OGRE3DKinematicBody*         mKB;
OGRE3DBody*             mCube;
NxOgre::Real             mYPos;
bool                 mDown;


Then, we extend the signature of our constructor. We want it to additionally take the RenderSystem and our newly created OGRE3DKinematicBody. Also, we will set some standard values to the our member variables:

BloodyMessTutorial5Listener(RenderWindow *win, Camera *cam, OGRE3DRenderSystem* pRenderSystem, OGRE3DKinematicBody* pKB) 
    : ExampleFrameListener(win, cam)
{
    mTimeController = NxOgre::TimeController::getSingleton();
    mRenderSystem = pRenderSystem;
    mKB = pKB;
    mYPos = mKB->getGlobalPosition().y;
    mDown = false;
}


Now comes the tricky part: Making our KinematicBody (KB) move and the shooting of the cubes. As you should have recognized, we added some still unused variables some steps ago. This is the time when the join the game. mYPos represents the current vertical position of our KB. The boolean mDown indicates whether our platform is currently moving up- or downwards. The next lines (that have to be put in the frameStarted() function) will implement the needed behavior:

if(mDown)
    mYPos -= 2.5f * evt.timeSinceLastFrame;
else
    mYPos += 2.5f * evt.timeSinceLastFrame;

mKB->moveGlobalPosition(NxOgre::Vec3(0, mYPos, 0));

if(mYPos > 20)
    mDown = true;
else if(mYPos < 0)
    mDown = false;


So what is happening here? First, we calculate the new vertical position of our KB. If we are currently moving downwards, we have to decrease it otherwise increase it. In order to make the movement frame rate independent, we take the timeSinceLastFrame into account. Then, we order our KB to move to the calculated position via moveGlobalPosition() (please have a look at the tutorial notes for the difference between moveGlobalPosition() and setGlobalPosition()).

At a last step, we have to check whether we still need to move downwards or whether we have already reached the lowest point our KB is meant to go. In this case we have to change mDown to false.

NxOgreCube.png Let's shoot them

Now, we will take care of our last feature: Shooting cubes into the scene. The next few lines also come in the frameStarted() function and are everything we need to bring this feature to life.

if(mKeyboard->isKeyDown(OIS::KC_SPACE) && mTimeUntilNextToggle <= 0)
{
    mCube = mRenderSystem->createBody(new NxOgre::Box(1, 1, 1), NxOgre::Vec3(mCamera->getPosition().x, 
                mCamera->getPosition().y, mCamera->getPosition().z), "cube.1m.mesh"); 
    mCube->addForce(NxOgre::Vec3(mCamera->getDirection().x * 10000, mCamera->getDirection().y * 10000,
                mCamera->getDirection().z * 10000), NxOgre::Enums::ForceMode_Force, true);
    mTimeUntilNextToggle = 0.2;
}


So what is happening here? Nothing special! We first check whether the space key is hit and whether it has passed enough time since the last time we hit a key. If that's true, we create a new body at the position of our camera and then apply a force to it, directed where we (= the camera) are looking at. Then nothing more is left than setting the key toggle time value to the time we want to wait until being able to shoot again.

NxOgreCube.png Conclusion

Well, that's it! We now have a moving platform, with a cube sitting on top of it that can be shoot down via hitting the space key (and a bit of aiming...). You should also remark that no matter how often you shoot at the KinematicBody, it won't react to anything at all, however affects all the objects it hits!

NxOgreCube.png Note

If you should wonder why there is the moveGlobalPosition() and setGlobalPosition() function, just keep this in mind: moveGlobalPosition() means to actually go the destination position, so every object in the way will be affected (pushed away or whatever...). setGlobalPosition() is more like teleporting, so the objects between the old position and the new one, will never know that there was a position change as the Kinematic never came by.