Skip to main content

History: BloodyMess Tutorial 5

Source of version: 5 (current)

Copy to clipboard
            {INCLUDE(page="BloodyMess tpl")}{INCLUDE}

__Disclaimer:__ This is not an official NxOgre tutorial. However, it has been revised and edited by the creator of NxOgre and BloodyMess, ((User:betajaen|betajaen)).

{img src="img/wiki_up/Forum icon question2.gif" alt="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 [http://www.ogre3d.org/addonforums/viewforum.php?f=6|NxOgre OgreAddons-Forum].

{maketoc}

!!{img src="img/wiki_up/NxOgreCube.png" alt="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.

{DIV(class="Bloody_box1")}{img src="img/wiki_up/CPP_small.png" alt="" width="22"}You can find the complete code for this tutorial ((BloodyMess Tutorial 5 Source|here)).{DIV}

{IMG(src="img/wiki_up/Bloody_mess_tut5_pic01.jpg", thumb="y", button="y", rel="box[g]", stylebox=border, width="150", title="Final scene", desc="Final Scene")}{IMG}

!!{img src="img/wiki_up/NxOgreCube.png" alt="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 ((-Entity|Entities))). The same applies for a ''KinematicActor'' and a ''KinematicBody''.

!!!{img src="img/wiki_up/NxOgreCube.png" alt="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.

{DIV(class="Bloody_box2")}__Note:__ If your fairly new with Ogre: It may be worth it modifying Ogre SkyBox sample instead of creating your own Ogre project.{DIV}

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

{CODE(wrap="1", colors="c++")}#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{CODE}

!!{img src="img/wiki_up/NxOgreCube.png" alt="NxOgreCube.png"} Get it started
!!!{img src="img/wiki_up/NxOgreCube.png" alt="NxOgreCube.png"} Extending our tutorial class
The first thing, we will do is adding a new member variable to our tutorial class:
{CODE(wrap="1", colors="c++")}OGRE3DKinematicBody* mKB;{CODE}
As you can see from the data type, that is our KinematicBody object.

Then, we will extend our ''createScene()'' function a bit:
{CODE(wrap="1", colors="c++")}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");{CODE}

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:
{CODE(wrap="1", colors="c++")}void createFrameListener()
{
    mFrameListener = new BloodyMessTutorial5Listener(mWindow, mCamera, mRenderSystem, mKB);
    mRoot->addFrameListener(mFrameListener);
}{CODE}

!!!{img src="img/wiki_up/NxOgreCube.png" alt="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):
{CODE(wrap="1", colors="c++")}NxOgre::TimeController*    mTimeController;
OGRE3DRenderSystem*         mRenderSystem;
OGRE3DKinematicBody*         mKB;
OGRE3DBody*             mCube;
NxOgre::Real             mYPos;
bool                 mDown;{CODE}

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:
{CODE(wrap="1", colors="c++")}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;
}{CODE}

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:
{CODE(wrap="1", colors="c++")}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;{CODE}

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 ((BloodyMess Tutorial 5|#Note|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.

!!!{img src="img/wiki_up/NxOgreCube.png" alt="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.

{CODE(wrap="1", colors="c++")}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;
}{CODE}

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.

!!{img src="img/wiki_up/NxOgreCube.png" alt="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!

!!{img src="img/wiki_up/NxOgreCube.png" alt="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.


---
{INCLUDE(page="NxOgre tpl")}{INCLUDE}