#3 Creating the plugin

Right, so we have our source code project set up, we have a Maratis project set up. Now what else do we need? Oh yes. Some code.

If you did as I suggested, you will already have some code from Anael’s tutorial compiling. So, assuming that, let me explain a little about the structure of Maratis.

Maratis uses something called an Entity/Component model. This means that game entities are treated as generic objects and they have modular components assigned to them to describe what functionality they have. In Maratis, these components are all subclasses of MBehavior. In effect, we can make small, generic components, such as a health component, and add it to all things in the world which should have health.

Maratis also will allow us to expose variables stored within the behaviors, edit them within MaratisEditor, and then save them to the .level file. Returning to the example of a health component, this could mean we could do things like set the max health from within the editor, allowing for simple tweaking.

Returning to the example. This is my tutorial plugin header:

#ifndef __TUTORIAL_PLUGIN_H__
#define __TUTORIAL_PLUGIN_H__

#ifdef WIN32
#   include
#   define DLLEXPORT __declspec(dllexport)
#else /*!WIN32*/
#   define DLLEXPORT
#endif /*WIN32*/

#ifdef __cplusplus
extern "C" {
#endif /*__cplusplus*/

DLLEXPORT void StartPlugin();
DLLEXPORT void EndPlugin();

#ifdef __cplusplus
}
#endif /*__cplusplus*/

#endif /*__TUTORIAL_PLUGIN_H__*/

So, the first thing to mention is the huge mess of preprocessor directives (#something). The first, second and last lines are what are called include guards. They just make sure that the file isn’t included multiple times, which will result in multiple redefinitions, and that confuses Mr Compiler. Skipping ahead to the StartPlugin and EndPlugin lines, this is the start and end of the plugin, usually will be the start and end of the game. On a Unix system, simply declaring the function prototype here will suffice, for Windows, it expects any functions that are exported from the DLL to be declared. To do this I’ve made a small section at the top to export anything marked with DLLEXPORT. Finally, when you compile a C++ program, it mangles the names of the functions (if you want to read about it, have a look on Wikipedia) so we also need to declare the start and end plugin definitions to be C functions, in order to not mangle the names, so Maratis can find the functions again. That just about wraps it up for the header. At this stage, the source file just looks like this:

#include "TutorialPlugin.h"

void StartPlugin()
{
}

void EndPlugin()
{
}

So go ahead, compile that, see how it goes. At this stage, it will do little more than a totally empty project, but if it compiles without errors, we’ve done things right.

Now, we need to decide what we’re going to be working on. At this stage, it might be useful to have some kind of user interaction, so we’ll make a behaviour which let’s us do something. How about, for now, if we hold a button (say, the space bar) then the cube will slide left and right. Seems boring and maybe pointless for a game, but keep with me. It will get us more comfortable with the Maratis behavior system, allowing us to put some values into the editor for non-code-savvy designers to tweak (the amount the box moves and maybe the speed) It will also give us an idea of how to add human interaction to a Maratis game. (I used to help someone with an introductory programming course. When they got stuck, and frustrated, the question was always the same, “How does this make Mario jump?” The answer is simple. Building blocks. Learn something with a trivial example, take the knowledge and apply it to your specific problem.)

To begin with, let’s start by making an empty behaviour that does nothing and go from there. Unfortunately there’s a lot of setup text required to make an empty behaviour. In a later tutorial we will tidy some of this away into macros to make life easier for us, until then, take a look at what the code does, you’ll probably need to know it at some point anyway. First of all, our header file

#ifndef __TUTORIAL_BEHAVIOUR_H__
#define __TUTORIAL_BEHAVIOUR_H__

#include 

class TutorialBehaviour : public MBehavior
{
public:
	TutorialBehaviour(MObject3d * parentObject);
	TutorialBehaviour(TutorialBehaviour & behavior, MObject3d * parentObject);
	~TutorialBehaviour();

	void destroy();
	static MBehavior* getNew(MObject3d* parentObject);
	MBehavior* getCopy(MObject3d* parentObject);

	static const char* getStaticName() { return "Tutorial Controller"; }
	const char* getName() { return getStaticName(); }

	unsigned int getVariablesNumber();
	MVariable getVariable(unsigned int id);

	void update();
	void runEvent(int param){}
};

#endif /*__TUTORIAL_BEHAVIOUR_H__*/

So, what do we have? First of all, we have the constructors and destructors. I don’t think I should have to explain them. Next we have some helper functions for creating and destroying the behaviour. These are basically a Factory Design Pattern. Next we have the name of the behaviour, which is what displays in Maratis Editor. In order to access variables within the behaviour, we need to expose them, Maratis does this by considering them (externally to the behaviour) similarly to an array. So we need to be able to tell how many values we’re exposing, and then allow access to them by index. Again, we might be able to tidy this up a bit later to make it easier for us, but for now, we’ll just leave it as it is.

Moving on to the source file. There’s really not much here, as I’ve said, for now we’re not having the behaviour do anything. All we have, then, is empty stubs for the functions. For the variable accessors, we just let Maratis know we don’t have anything to fiddle with yet, and give back an empty variable in case it still tries to ask us.

#include "TutorialBehaviour.h"

TutorialBehaviour::TutorialBehaviour(MObject3d* parentObject)
:MBehavior(parentObject)
{
}

TutorialBehaviour::TutorialBehaviour(TutorialBehaviour& behavior,
						MObject3d* parentObject)
:MBehavior(parentObject)
{
}

TutorialBehaviour::~TutorialBehaviour(void)
{
}

void TutorialBehaviour::destroy()
{
	delete this;
}

MBehavior* TutorialBehaviour::getNew(MObject3d* parentObject)
{
	return new TutorialBehaviour(parentObject);
}

MBehavior* TutorialBehaviour::getCopy(MObject3d* parentObject)
{
	return new TutorialBehaviour(*this, parentObject);
}

unsigned int TutorialBehaviour::getVariablesNumber()
{
	return 0;
}

MVariable TutorialBehaviour::getVariable(unsigned int id)
{
	switch(id)
	{
	default:
		return MVariable("NULL", NULL, M_VARIABLE_NULL);
	}
}

void TutorialBehaviour::update()
{
}

So, can I run off and test this in Maratis Editor yet? Nope. Not quite, we haven’t actually told Maratis that our new behaviour exists. For now, we’ll do that in StartPlugin. So do the following and then compile.

// Standard include
#include "TutorialPlugin.h"

// Project includes
#include "TutorialBehaviour.h"

// System includes

void StartPlugin()
{
	MEngine* engine = MEngine::getInstance();
	MBehaviorManager * behaviorManager = engine->getBehaviorManager();
	behaviorManager->addBehavior(TutorialBehaviour::getStaticName(),
						M_OBJECT3D,
						TutorialBehaviour::getNew);
}

void EndPlugin()
{
}

Now we’re talking. We have our awesome plugin compiled with our amazing behaviour, right? So fire up Maratis Editor and select our little cube. If you have a look in the Behaviours tab (3rd from the left) you can add a new behaviour, if our behaviour is in there, select it. Bingo, we’re awesome. Ok, it doesn’t do anything now, but that’s proof that our plugin is loading properly and Maratis is recognising our new behaviour.

What do we need to make our cube move? Well, let’s think about how we want to do this. There are a few things we need to keep track of. First of all, we should probably keep track of where we placed the cube in the world, so we will get the root position when we’re created. Secondly, we probably want to limit a range for the movement, but also expose that to the editor. In case we want to move it in multiple dimensions, we’ll limit it to the range in the X direction. Also, to make some of the tests easier, we’ll store the current delta in the X direction, rather than the absolute position. Finally, we’ll also expose the amount we want to move each frame. This setup isn’t ideal, but it’s quick and it demonstrates what we want, so let’s have a look.

#ifndef __TUTORIAL_BEHAVIOUR_H__
#define __TUTORIAL_BEHAVIOUR_H__

#include 

class TutorialBehaviour : public MBehavior
{
public:
	TutorialBehaviour(MObject3d * parentObject);
	TutorialBehaviour(TutorialBehaviour & behavior, MObject3d * parentObject);
	~TutorialBehaviour();

	void destroy();
	static MBehavior* getNew(MObject3d* parentObject);
	MBehavior* getCopy(MObject3d* parentObject);

	static const char* getStaticName() { return "Tutorial Controller"; }
	const char* getName() { return getStaticName(); }

	unsigned int getVariablesNumber();
	MVariable getVariable(unsigned int id);

	void update();
	void runEvent(int param){}

private:
	// Exposed values
	float		m_RangeX;
	float		m_StepX;

	// Member variables
	float		m_CurrDeltaX;
	MVector3	m_RootPosition;

	// Private interface
	void Setup(float stepx, float deltax, float rangex);
};

#endif /*__TUTORIAL_BEHAVIOUR_H__*/

As you can see, I also added a setup function, this is because we can do some setup in the constructor initialiser lists, but as we have 2 places, it’s always easier to change something in one central place.

TutorialBehaviour::TutorialBehaviour(MObject3d * parentObject)
:MBehavior(parentObject)
{
	Setup(0, 0, 10.0f);
}

TutorialBehaviour::TutorialBehaviour(TutorialBehaviour& behavior,
						MObject3d* parentObject)
:MBehavior(parentObject)
{
	Setup(behavior.m_StepX, behavior.m_CurrDeltaX, behavior.m_RangeX);
}

void TutorialBehaviour::Setup(float stepx, float deltax, float rangex)
{
	m_StepX = 0.0f;
	m_CurrDeltaX = 0.0f;
	m_RangeX = 10.0f;

	if(MObject3d* parent = getParentObject())
		m_RootPosition = parent->getPosition();
}

We now have some variables we need to expose to Maratis Editor, we have to change the accessors a bit for this. So let’s do that.

unsigned int TutorialBehaviour::getVariablesNumber()
{
	return 2;
}

MVariable TutorialBehaviour::getVariable(unsigned int id)
{
	switch(id)
	{
	case 0:
		return MVariable("XRange", &m_RangeX, M_VARIABLE_FLOAT);
		break;
	case 1:
		return MVariable("XStep", &m_StepX, M_VARIABLE_FLOAT);
		break;
	default:
		return MVariable("NULL", NULL, M_VARIABLE_NULL);
	}
}

That should take care of that (note 1). The only thing left is to change the update. This is where the magic happens. Let’s have a look at it and then explain it.

void TutorialBehaviour::update()
{
	MEngine* engine = MEngine::getInstance();

	MGame* game = engine->getGame();

	if(game == 0  || !game->isRunning())
		return;

	MObject3d* parent = getParentObject();

	if(!parent)
		return;

	if(MInputContext* input = engine->getInputContext())
	{
		if(input->isKeyPressed("SPACE"))
		{
			m_CurrDeltaX += m_StepX;

			if( abs(m_CurrDeltaX) >= abs(m_RangeX) )
				m_StepX = -m_StepX;
		}
	}

	MVector3 pos = m_RootPosition;
	pos.x += m_CurrDeltaX;

	parent->setPosition(pos);
}

To begin with, we have some checks to make sure everything is as we are expecting. For this we need to make sure we definitely have a parent set, and that we’re in a running game. This stops behaviour logic happening during the editor update. In some situations you may want to do something at this point, but we don’t. Ignoring lines 15-24 for now, the idea is to get the root position and then add the current delta to it. That seems fairly sensible, right? Now, the bit we missed out. First of all, we need to check whether the space bar is down and increase the current delta if it is. The only thing left is to bounce off the edges. This bit is simple, we already have the range, we just need to check if the delta is bigger than it. The problem is that one (or both) could be negative, so we use abs() to make sure they’re both positive. If we get to the limit, we just invert the step.

So there we have it. Hit compile, fire up Maratis Editor and play around with the exposed variables, then click on pacman and watch the cube… it doesn’t move? Try pressing the space bar. Tadaa! Done.

So where can we go from here? Well, patience. We’ll take a break from programming next for me to have a rant about source control.

What else can you do with your new behaviour? You can try and move it around. Attach a camera to the cube in the editor and use WASD keys to move the cube around. Then you’ll almost have a 3rd person character.

Let me know how you get on.

<-Previous Next->

Discuss

Notes:

  1. I was having an issue where the level was failing to load. It turns out that, the way the level file is layed out, all variable names must be without spaces. Silly me. I’ve modified it in the tutorial. Something to keep in mind.

One thought on “#3 Creating the plugin”

  1. Hey, I do not know if you’ll be reading this any time soon, but I’m running into errors when attempting to compile your example, might just be that I am using Codeblocks rather than Visual Studio, if you have any advice, feel free to Email me or respond in whichever way you deem convenient.

    Much obliged, DZ.

Leave a Reply

Your email address will not be published. Required fields are marked *

Diary of a Slacker