zoss/Engine/Tutorials/Tutorial1

Zen Engine Tutorials Base Starter Kit 101

This tutorial shows you how to create a game client using the Base Starter Kit. This game client does little more than initialize a few game engine components and display a blank screen.

The only additional thing this game client does is to bind the "Q" key to the "Quit" action, which allows you to quit the game by pressing the "Q" key.

The Base starter kit is a game engine starter kit that is used to simplify the use of Zen Engine. It contains code in support of behaviors, game objects, game groups and scriptable actions. It also contains the primary "game loop".

Note: This tutorial is being updated for 0.5.5 and does not match what you see for the 0.5.0 or prior releases.

Getting Started

The first tutorial is located at Zen/tutorials/Tutorial1/Tutorial1.sln. I'll explain how I created this solution later. For now, lets just break it down.

There are many projects in this solution, but don't freak out. :P

Most of these projects are libraries that you will be using to create your games, but you're not expected to have to understand how they work, nor should you need to modify any of the code.

The only library that you really have to mess with is:

  • GameClient - Client library for this game.
  • GameCommon - Common library for this game that contains code used by both the client and the server.

Normally when you're creating a game, you would use your own names instead of "Game". (Who calls their game "Game" anyway?) For instance, my game Gunships by Sarge I named them GunshipsClient, GunshipsCommon, and GunshipsServer. This tutorial only deals with a non-networked "game" (if you can call it that), otherwise you would also have a GameClient, GameCommon and GameServer library as I did with Gunships by Sarge.

Take a minute and browse through the GameClient project.

The other projects:

  • BaseClient - Client helper library for the Base Starter Kit.
  • BaseCommon - Common helper library for the Base Starter Kit.
  • ZenEngine - Zen Engine framework
  • !ZOgre - OGRE rendering and resource manager plugin.
  • !ZLua - Lua scripting plugin.

These other projects, although you are welcome to browse throuh them, you most likely will not need to use them.

For "binary only" distributions such as

Where's main()?!?!

If you're a C++ programmer, as you're looking through the GameClient library, you'll probably notice two things. It's a library and not an executable and there's no main() function.

As for the location of main(), it's located in the ZGameLoader project in src/main.cpp. The ZGameLoader is what actually loads the initial scripts that initialize your game client.

The logic behind this decision is that it allows you to replace ZGameLoader with another application, whether it be a game development IDE such as Zen Studio, or a browser plugin to allow you to execute your game client in a browser.

Feel free to take a look at main.cpp now if you like, but don't worry if it looks complicated. It's already written and normally you won't have to understand nor modify it.

Feel free to deviate from this approach once you understand how Zen Engine works, but make sure you understand the reasoning behind my decision to go this route before discarding it.

Where's the game loop?

If you're an experienced game developer, probably your next question is "Where is the game loop?"

The game loop is located in theBaseClient project, which is the Base Starter Kit's client library. You'll notice it's divided into "Public" and "Private". Essentially, files in the "Public" folder are files you're expected to need to use. Files in the "Private" folder are files that normally you won't need to worry about as they're the implementation, but they're in the project so you can reference it. For more details, look at the code organization? documentation. Note: This should refer to the correct bookmark in the "Zen Architecture" page. Please fix (I'm offline and I don't remember the location)

Again, I can't reiterate this enough... unless you know exactly what you're doing, it's best to leave all "Private" files alone unless they're in code you're writing (or in this case, an example or a tutorial such asGameClient).

InBaseClient.cpp you'll see!BaseClient::run() method implementation. This is the main game looop.

This implementation is essentially a loop that executes until something tells it to stop.

Inside the loop, it has a bit of code to clamp the frame rate, it process all input events from whatever input plugins you have loaded (keyboard, mouse, joystick, etc), invokes the onBeforeFrameRenderedEvent event, renders the frame, and then invokes the onAfterFrameRenderedEvent event.

The order of events in the game loop:

  1. Process inputs
  2. Invoke onBeforeFrameRender
  3. Render the frame
  4. Invoke onAfterFrameRender
  5. Pause (if necessary) to clamp the frame rate

As you can see, pretty much anything extra you want to do inside of the render loop can be done either at step 2 or step 4.

Physics, invoking an animation manager, processing incoming network events, etc should normally be done in step 2 before the frame is rendered. TODO: provide an example of what could be done after a frame is rendered.

Adding your code to the render loop

There will be times when you will want to add code to the main render loop, either after input has been processed, before or after the frame is rendered, etc. Although you might be tempted to modify the code in !BaseClient::run(), it's best not to succumb to the temptation. If you feel that you have a situation where this is absolutely necessary, please discuss it with me first. If the run() method needs more hooks then I'll add them. Possible modifications might include an overrde for clamping the frame rate, etc and I'm open to doing that if you need it.

Where possible, write some event handlers and subscribe to the onBeforeFrameRender and onAfterFrameRender events.

An example of this is in!GameClient::init()

bool
GameClient::init()
{
    base().onBeforeFrameRenderedEvent.connect(boost::bind(&GameClient::beforeRender, this, _1);
    base().onBeforeFrameRenderedEvent.connect(boost::bind(&GameClient::afterRender, this, _1);
...
}

...

void
GameClient::beforeRender(double _elapsedTime)
{
    // TODO: Do everything else that needs to be done before the screen is rendered

    // This updates all physics worlds we've created:
    base().getPhysicsService().stepSimulation(_elapsedTime);
}

...

void
GameClient::afterRender(double _elapsedTime)
{
    // TODO: Do everything else that needs to be done after the screen is rendered
}

First, you subscribe to the events in init() and then you handle the events appropriately. Add your own custom code by modifing !GameClient::beforeRender() and afterRender().

In this case _elapsedTime for both before and after rendering is the same, and the value is the number of seconds the previous frame took. 1.0 = 1 second, 1.5 is one and a half seconds, etc. Lets hope your frame rates are faster than this though. :P

Creating Actions

An Action is something that your game client does. It could increment the score of the game, it could make a player move forward, it could fire a weapon, etc. When you create an action, you bind it to a function.

To create a C++ action:

void
GameClient::createActions()
{
    // Create some actions
    game().getActionMap().createAction("Quit", boost::bind(&Zen::Engine::Base::I_BaseGameClient::quit, &m_base, _1));

}

This code creates an action called "Quit" and binds it to the I_BaseGameClient::quit method on the m_base object, which is the game client helper class located in the Base starter kit.

To do the same basic thing in Lua, except in this ccase we're creating a new action named "Test":

function GameClient:createActions()
    self:getActionMap():createAction("Test",!GameClient.testAction, self)
end

To bind an event to the action:

void
GameClient::createDefaultMapping()
{
    // Map some keys to actions
    base().getInputMap().mapKeyInput("q", game().getActionMap()["Quit"]);
}

This binds the "q" key to the "Quit" action that we just created.

The same thing in Lua:

function GameClient:createDefaultMappings()
    self:getInputMap():mapKeyInput("t", self:getActionMap():getAction("Test"))
end

Which binds the "t" key to the "Test" action.

You can mix and match actions and events across languages. This is the easiest way to call script functions from C++.

Handling a keyboard event that's bound to a C++ function:

void
BaseClient::quit(boost::any& _parameter)
{
    m_quitting = true;
}

This is actually a fairly bad example because it doesn't use the _parameter. I'll show you a better example in another tutorial.

The Lua example is better because it actually uses the event payload.

function GameClient:testAction(action)
    if action:getKeyState() then
        print("Yay, the T button was pressed!!!")
    else
        print("Yay, the T button was released!!!")
    end
end

Debugging

To debug the game client, right click on the GameClient project and go to the Debugging properties and change these settings:

Command$(OutDir)ZGameLoader_d.exe
Command Argumentsclient.xml GameClient lua scripts/client/init.lua
Working Directory$(SolutionDir)
EnvironmentPATH=$(OutDir);$(OGRE_LIB);D:\dev\newton\dll;D:\dev\ogre\Samples\Common\bin\Debug

The environment might change depending on where you have things located.

Initialization Sequence

Now might be a good time to compile the code (if you haven't already done so, and set a few breakpoints and debug the application to see the order in which things get loaded.

First, obviously, main() is called.

int main(int argc, const char* argv[])

The first bit of code parses the command line.

Next, we get the base game client from the Base Starter Kit.

if (argc >= 5)
{
        Zen::Engine::Base::I_BaseGameClient& base = Zen::Engine::Base::I_BaseGameClient::getSingleton();

Following that, we initialize the script engine using the third command-line parameter. In this example, that value is "lua" so that it will initialize the Lua script engine. It could be "python" or any other script engine for which you have a plugin.

base.initScriptEngine(argv[3]);

Next a script module is created. (todo link to an explanation of script modules)

Zen::Scripting::I_ScriptEngine& scriptEngine = base.getScriptEngine();

sm_pModule = scriptEngine.createScriptModule("ZenModule", "Zen Module");

Next, the "ZenType" script type (or script "class" if the scripting language supports classes) and "Environment" script type is created.

For the "ZenType" two methods are added. getEnvironment() and createGameClient(). For the "Environment" type, get() and set() methods are added, and then the module is activated.

Zen::Scripting::I_ScriptModule::pScriptType_type
        pZenType = sm_pModule->createScriptType("ZenType", "IndieZen Class", 0);
pZenType->addMethod("getEnvironment", "Get the Operating System Environment", getEnvironment);
pZenType->addMethod("createGameClient", "Create a Game Client", createGameClient);

sm_pEnvironmentType = sm_pModule->createScriptType("Environment", "Zen Core Environment", 0);
sm_pEnvironmentType->addMethod("get", "Get a value from the environment", ZenEnvironment::get);
sm_pEnvironmentType->addMethod("set", "Set a value in the environment", ZenEnvironment::set);

// Activate the ZenModule
sm_pModule->activate();

Next, we add a method to theGameClient script type (which was created when we initialized the script engine using base.initScriptEngine() earlier).

// Go ahead and add run() to the!GameClient, but it won't be activated until
// the I_GameClient is created by ZenType::createGameClient()
base.getGameClientScriptType()->addMethod("run", "Execute the game client's main loop",!GameClientRun);

Next we create a global script object "Zen" of the "ZenType" type. This allows our script to access this instantiated object.

Zen::Memory::managed_ptr<ZenRoot> pZenRootObject(new ZenRoot);
Zen::Scripting::ObjectReference<ZenRoot>* pScriptObject = 
        new Zen::Scripting::ObjectReference<ZenRoot>(sm_pModule, pZenType, pZenRootObject, "Zen");

And finally we execute the initialization script, which in this case is "scripts/client/init.lua" as indicated with the previously set debugging parameter.

scriptEngine.executeScript(argv[4]);

On the script side of things, (and skipping some of the Lua housekeeping; read a Lua book for more details on Lua) the environment "arg2" is used to create the game client.

Among other things, the environment is used to get access to the command line parameters using arg0 - argn. In this case, "arg2" environment variable is "GameClient".

gameClient = GameClient:new(environment["arg2"])

And here some magic happens, which at this point I won't go into the gory details, but this magically creates the GameClient class in your GameClient plugin.

gameClient:initialize()