delta3d

GM Tutorial Parts 1 and 2

Hi, and welcome to the Delta3D Game Manager tutorial! The goal of this tutorial is to familiarize you with the tools you’ll need to start writing games and simulations using Delta3D’s Game Manager. This tutorial is based on code in version 1.3 of Delta3D. There’s a lot of new material in this tutorial, so let’s get started.

  • Part One: Introduction to the Game Manager
    • What is the game manager?
    • What is a game actor?
    • What is a game manager component?
    • What is a game message?
  • Part Two: Welcome to the Game Actor
    • The Hover Tank Game Actor
    • The Hover Tank Game Actor Proxy
    • Creating our Tutorial Actors Registry
    • Loading our Hover Tank in STAGE

Woah! That’s going to be a lot to cover. We’ve got lots of new concepts and code to get through. Fortunately, the complete source code for this project is provided here. Feel free to follow along in there or code it yourself. And, if you get stuck along the way, stop by the Delta3D Forums to get help.

1. Part One – Introduction to the Game Manager

This section introduces the guts of the Delta3D Game Manager infrastructure. It provides an overview of the Game Manager, Game Actors, Game Components, and Game Messages. The following diagram shows a very high level overview of these pieces.


Figure 1. Game Manager Overview

The Game Manager (GM)?

So, what is the Game Manager? Well, if you look at the above diagram, the first thing you should notice is that the Game Manager owns all the Game Actors, normal Actors, Components and Messages. Specifically, it manages the existence and interaction between all the actors and components in your world. It is the heart of your application.

So, what does that mean? Well, up to now, if you’ve been writing applications using Delta3D, you’ve been putting your applications together by hand. Sure, Delta3D gives you all the pieces you need including: scene management, physics, audio abilities, the ability to load objects, the Dynamic Actor Library, environmental effects, lighting, terrain support, cameras and character animation!

But, now that you have all the pieces, how are you going to put it all together to make a game? Who’s going to hold onto the list of all objects in the game? And, how are you managing communication? How does your new weapon get into the world so the player can pick it up? How do you tell the player he’s been shot? And, once you shoot him, how does he tell the rest of the network that he’s wounded? There’s a lot of stuff to worry about to manage your game. Fortunately the Game Manager provides the core architecture to make it all happen!

Messages and Actors and Components, Oh My!

To manage your game, the GM does three main things. The first, and most important, is the managing of messages: more precisely, Game Messages. Game Messages are what you’re going to use to communicate between Actors, Components, and the GM. Messages can be used to communicate just about anything: updates to the player’s health; the event for rescuing hostage #12; the fact that an NPC just entered the world; and even simple tick behavior. Messages communicate all the behavior and state changes within the system, but messages don’t send themselves. The GM does that – it receives and routes messages to and from actors and components.

The second job of the GM is to hold onto ALL actors within the system. This includes both regular actors and the new GameActor (see below). It tracks actors that you have created in code or in your map (created by STAGE). The GM tells actors to process messages, makes sure they get in and out of the scene at the right times, and even tells them to ‘tick’ so they can do stuff.

The third job of the GM is to support GMComponents. What’s a component, you ask? A component is basically a high-level object that works with Game Messages. But, isn’t that what Game Actors do? Yes and No. Game Actors do process messages; however, you will often have lots of instances of a particular actor in the game. Components are typically much higher level. They do things like networking behavior, game rules enforcement, logging, dead reckoning. Whereas game actors only receive specific messages they are interested in, GM Components receive all messages.

What is a Game Actor?

Hopefully you’re already familiar with Actors and ActorProxies. But, what’s a Game Actor? Simply put, a Game Actor is an Actor that is designed to work directly with the Game Manager. By the way, if you haven’t already, now would be a great time to learn the basic concepts of Actors and the Dynamic Actor Layer. We’re going to work with Actors a lot, so you might want to read the tutorials or design docs.

So, why do we need to distinguish between Actor and Game Actor? Regular actors or “non-game” actors are basically static objects in the game world. You might use them for level geometry, static meshes like houses, non-moving lights, trees, and maybe terrain. But, static objects are boring! Game Actors are what brings your game to life! Game Actors get ticked, can process Game Messages, and can generally interact with the world!


Figure 2. Game Actor Proxy

GameActor is built on top of the actor concepts introduced by the DAL. So naturally, a GameActor will need a GameActorProxy. In fact, the Game Manager only understands Proxies – which means you must use them! By focusing exclusively on Actor Proxies, the GM becomes extremely flexible. Just like STAGE, it’s critical that the GameManager is completely generic. It can’t know the difference between a tank and a player, between a missile and a tent. If the GM wasn’t generic, you’d never be able to implement nifty behavior like logging and playback (see the testLogger example).

What is a Game Manager Component?

In its simplest form, a GMComponent is an object managed by the Game Manager which can process and send messages. Unlike GameActors, GMComponents receive every message in the system. Components typically provide high-level system wide behaviors, but they can do pretty much anything you want. GM Components are the primary way to add custom behavior to the Game Manager.

You may wonder why we need Components at all. Why not just make a subclass of the Game Manager and put your special code in there? The answer is flexibility. Imagine that you write behavior for dead-reckoning objects. That’s when you estimate where an object is based on its last known position and motion information. Now, imagine that you put your new code directly into your GM. And, then you add some behavior to send messages across a network and stick that in your GM too. And, then you want to log messages to a log file to support playback, so you put that code in your GM. Finally, one day your boss asks you to run your whole game with different networking code! Or, he wants you to take out the logging code depending on whether you’re on the client or the server. But, over time, you have created an interdependent mess of code in your GM that is very difficult to pull apart! Instead, we recommend putting the behavior in components like a DeadReckoningComponent, a NetworkingComponent, and a LoggerComponent. Those components then become aggregate behavior that you can put together however you like. You can add or remove them, dynamically, at runtime. The point is that your core GameManager would be completely untouched.

Since components get all messages in the system, they have the opportunity to know about all actors and everything that happens! You can build simple components that wait for specific messages or listen for the keyboard/mouse. You can have complex components that hold onto large lists of actors and use hierarchies of helper objects. Components are the extendable architecture for adding important behaviors to your game. If you’re still not sold on GMComponent, don’t worry. Later on in this tutorial, you will construct your own GMComponent and that should help clear things up.

Helpful Hint – Try to avoid making a subclass of the GameManager. Delta3D completely supports this, but it is generally better to try to use components to achieve the behavior you need, rather than subclassing the GM.

What is a Game Message?

Now that you understand more about Components and Game Actors, let’s go back to the first feature of the GM: the Game Message. Game messages are simply the way actors and components communicate with each other. Messages are typically used for sending data (ex: property changes) or behavior (requests or commands). The following diagram shows a high level overview of the flow of messages to/from Game Actors, the Game Manager, and Game Manager Components.


Firgure 3. Game Message Diagram

There are a few basic methods you’ll need to be familiar with to work with messages:

  1. SendMessage() on GameManager
  2. SendNetworkMessage() on GameManager
  3. Invokables on GameActorProxy
  4. ProcessMessage on GMComponent
  5. DispatchNetworkMessage on GMComponent

The Game Manager has two primary mechanisms for handling messages: SendMessage() and SendNetworkMessage(). Hopefully, these are mostly straightforward. To send a message from anywhere in your game, you call SendMessage() on the GameManager. To send a message that you want to go out over the network, you call SendNetworkMessage() on the GameManager. Typically, you want to call SendMessage(). Only a few components should concern themselves with the network version.

The Components also have two methods: ProcessMessage() and DispatchNetworkMessage(). Components receive all messages. So, you override ProcessMessage() to receive normal messages. Then, if you are some sort of network component, you override DispatchNetworkMessage() to receive messages that are ready to be sent over the network. Typically the RulesComponent is responsible for taking normal messages and resending them as network messages.

For actors it is a little different. Actors never receive network messages, they only get normal messages. Since there are often LOTS of actors in the system, we don’t want to send all messages to all actors. Instead, actors register to receive certain types of messages by using Invokables. We’ll explain this in more detail later. For now, you should concentrate on the 3 default Invokables that are provided for you on GameActor: TickLocal(), TickRemote(), and ProcessMessage().


Figure 4. ProcessMessage() Diagram

In this diagram, the TankProxy (a Game Actor Proxy) has some updated data that it needs to tell the world about. It builds an ActorUpdateMessage and then tells the GameManager to deal with it by calling SendMessage(). The GM puts that message in its Message Queue. When the GM gets to the message (later in the same tick), it will first call ProcessMessage() on all components. Then, it will call the Invoke() callback on any game actors that registered to receive ActorUpdateMessages. We will dive much deeper into this topic when we create some Invokables for ticking and game event handling. For now, it’s time to move from theory to code!

Helpful Hint – Typically, you want to call SendMessage(), not SendNetworkMessage() when you want to push a message around the system. And, unless you are some type of networking component, you probably want to override ProcessMessage(), not DispatchNetworkMessage (). If you are writing an Actor, overriding ProcessMessage() and using one of the versions of RegisterForMessages() is the easiest way to receive messages.

Part Two: Welcome to the Game Actor

The introduction above should have given you a good understanding of the Game Actor. In this section we are going to create our very own GameActor. Our Game Actor will represent a hover tank - yeah, it’s simple, but this is a TUTORIAL after all! We will code the Actor, put it in an Actor Library, load it into STAGE, and create a little map with our game actor inside. Let’s get started!

Library Export (for Windows)

Our first bit of code is administrative. We need to create a simple header file that defines our import/export tag. We use this tag in front of our classes so that the library will export symbols correctly when building on Windows. This header will compile properly on other platforms, but doesn’t do anything.

In “export.h”:


#if defined(_MSC_VER) || defined(__CYGWIN__) || defined(__MINGW32__) || defined( __BCPLUSPLUS__)  || defined( __MWERKS__)
#  ifdef TUTORIAL_HOVER_LIBRARY
#    define TUTORIAL_HOVER_EXPORT __declspec(dllexport)
#  else
#    define TUTORIAL_HOVER_EXPORT __declspec(dllimport)
#  endif
#else
#  define TUTORIAL_HOVER_EXPORT
#endif

It’s not important for now that you understand why libraries need this. Just add a header file, ‘export.h’ to your project and include it in all of your headers. Then, in your project, add “TUTORIAL_HOVER_LIBRARY” to your project defines as follows:


Figure 5. Library Preprocessor Definition in Visual Studio

Note: If your project currently contains no .cpp files, you will not have the C/C++ property option. Adjust this setting after you have added a .cpp file to the project, as required below.

The Hover Tank Game Actor

Now, let’s move on to our HoverTankActor. For that, you’ll need to make a header and cpp file. To that, add includes for the base code in dtGame and the export.h. See the following code snippet.

In “HoverTankActor.h”:


#include "export.h"
#include <dtCore/refptr.h>
#include <dtActors/gamemeshactor.h>
#include <dtCore/particlesystem.h>

/**
 * This class is the actor that represents a hover tank of sorts.  It knows how to 
 * respond to keyboard presses (I, J, K, L to steer). It also listens for 
 * "ToggleEngine" & "SpeedBoost" GameEvents.  This actor correctly handles 
 * TickLocal versus TickRemote.
 */
class TUTORIAL_HOVER_EXPORT HoverTankActor : public dtActors::GameMeshActor
{
   public:
      // Constructs the tank actor.
      HoverTankActor(dtGame::GameActorProxy &proxy);

All right, we have some code! It’s not much, but it’s a start. So in the code we have so far, we note that our HoverTankActor class extends the base GameMeshActor class. If you are wondering where the actor proxy is, just hold your horses, we’ll get to that soon enough. For now, we’ll focus on the actor. As you can see in the code, the constructor takes a reference to a GameActorProxy. This is important, as all GameActors must have this constructor.

Now, let’s add some properties to this actor. For this tutorial we want three properties on our tank actor: the tank mesh, the velocity, and the turn rate. We’ll use these properties to control the look and movement of the tank when we get to the application portion of the tutorial. The mesh is provided already by our superclass GameMeshActor, so all we need now are getters and setters for the other two properties. Additionally, we are going to need methods to handle ticks. Here’s the code:

In “HoverTankActor.h”:


/**
       * Sets the velocity of our tank actor.  
       * @param velocity The new velocity.
       * @note This may cause an actor update message
       */
      void SetVelocity(float velocity);
      
      // @return The actor's current velocity.
      float GetVelocity() const { return mVelocity; }

      /**
       * Sets the turn rate of our tank actor.  
       * @param rate The new turn rate in degrees per second.
       * @note This may cause an actor update message
       */
      void SetTurnRate(float rate);

      // @return The actor's current turn rate.
      float GetTurnRate() const { return mTurnRate; }

      /**
       * This invokable is called when an object is local and receives a tick.
       * @param tickMessage A message containing tick related information.
       */
      virtual void TickLocal(const dtGame::Message &tickMessage);

      /**
       * This invokable is called when an object is remote and receives a tick.
       * @param tickMessage A message containing tick related information.
       */
      virtual void TickRemote(const dtGame::Message &tickMessage);

      /**
       * Generic handler for messages. Overridden from base class.
       * This is the default invokable on GameActorProxy.
       */
      virtual void ProcessMessage(const dtGame::Message &message);

Now we have some properties and placeholders to support our tick behavior (explained later). To finish up, we need our member variables and some behaviors for moving the tank.


   protected:
      virtual ~HoverTankActor();

   private:
      // do our internal velocity/turn calculations based on keyboard status
      // only relevant for Local mode (ie, not when remote)
      void ComputeVelocityAndTurn(float deltaSimTime);

      // calculate new position based on turn rate and velocity
      // relevant in both local and remote
      void MoveTheTank(float deltaSimTime);

      // private vars
      dtCore::RefPtr<dtCore::ParticleSystem> mDust;
      float mVelocity;
	   float mAddOnVelocity;
      float mTurnRate;
      bool mIsEngineRunning;
      float mLastReportedVelocity;
};

That’s basically it for our actor interface. Let’s actually implement this sucker!

In “HoverTankActor.h”:

Here’s our “HoverTankActor.cpp” code:


#include "HoverTankActor.h"
#include <dtDAL/enginepropertytypes.h>
#include <dtDAL/actorproxyicon.h>
#include <dtCore/loadable.h>
#include <dtCore/isector.h>
#include <dtGame/gamemanager.h>
#include <dtGame/actorupdatemessage.h>
#include <dtGame/basemessages.h>
#include <dtABC/application.h>
#include <dtCore/camera.h>
#include <dtCore/keyboard.h>

#include <dtCore/particlesystem.h>

///////////////////////////////////////////////////////////////////////////////
const std::string HoverTankActor::EVENT_HANDLER_NAME("HandleGameEvent");
const float MAXTANKVELOCITY = 15.0f;

///////////////////////////////////////////////////////////////////////////////
HoverTankActor::HoverTankActor(dtGame::GameActorProxy &proxy) : 
   dtActors::GameMeshActor(proxy), 
   mDust(NULL), 
   mVelocity(0.0f), 
   mAddOnVelocity(0),
   mTurnRate(0.0f),
   mIsEngineRunning(false),
   mLastReportedVelocity(0.0f)
{
   SetName("HoverTank");
}

///////////////////////////////////////////////////////////////////////////////
HoverTankActor::~HoverTankActor()
{
}

///////////////////////////////////////////////////////////////////////////////
void HoverTankActor::SetTurnRate(float rate)
{
   // Described in more detail later in the tutorial
   mTurnRate = rate;
}

///////////////////////////////////////////////////////////////////////////////
void HoverTankActor::SetVelocity(float velocity)
{
   // Described in more detail later in the tutorial
   mVelocity = velocity;
}

///////////////////////////////////////////////////////////////////////////////
void HoverTankActor::OnEnteredWorld()
{
   // add our dust particle when we are born
   mDust = new dtCore::ParticleSystem();
   mDust->LoadFile("Particles/dust.osg",true);
   mDust->SetEnabled(false); // turn it off at first
   AddChild(mDust.get()); 

   dtActors::GameMeshActor::OnEnteredWorld();
}

///////////////////////////////////////////////////////////////////////////////
void HoverTankActor::ProcessMessage(const dtGame::Message &msg)
{
   // Described later in the tutorial
}

///////////////////////////////////////////////////////////////////////////////
void HoverTankActor::TickLocal(const dtGame::Message &tickMessage)
{
   // Described later in the tutorial
}

///////////////////////////////////////////////////////////////////////////////
void HoverTankActor::TickRemote(const dtGame::Message &tickMessage)
{
   // Described later in the tutorial
}

///////////////////////////////////////////////////////////////////////////////
void HoverTankActor::ComputeVelocityAndTurn(float deltaSimTime)
{
   // code removed to simplify the tutorial – please see the provided project
}

///////////////////////////////////////////////////////////////////////////////
void HoverTankActor::MoveTheTank(float deltaSimTime)
{
   // code removed to simplify tutorial – please see the provided project
}

Most of these methods are just stubbed out for now. We’re going to add the details as we move through this tutorial.

The Hover Tank Game Actor Proxy

We have a game actor, now we need a proxy. GameActorProxies are VERY important within the GameManager framework. Although a bulk of your actor code is located in the actor itself, the GameManager only understands Actor Proxies. The proxy exposes our properties generically and can handle messages from the GM via Invokables. Here’s our Proxy header file:


/**
 * Our proxy class for the hover tank actor.  The proxy contains properties,
 * invokables, and the hover tank actor. 
 */
class TUTORIAL_HOVER_EXPORT HoverTankActorProxy : public dtActors::GameMeshActorProxy
{
   public:

      // Constructs the proxy.
      HoverTankActorProxy();
      
      // Creates the properties that are custom to the hover tank proxy.
      virtual void BuildPropertyMap();
      
      // Builds invokables to hook the proxy into the game manager.
      // We don’t need this in our tutorial, because we use the default Invokables
      // virtual void BuildInvokables();
      
   protected:
      virtual ~HoverTankActorProxy();

      // Creates an instance of our hover tank actor 
      virtual void CreateActor();

      // Called when this proxy is added to the game manager (ie, the "world")
      // You can respond to OnEnteredWorld on either the proxy or actor or both.
      virtual void OnEnteredWorld();
};

The interface to the HoverTankActorProxy should be basically familiar if you have gone through the DAL tutorial. However, there are a few new methods that we should talk about.

First, let’s discuss BuildInvokables(). We don’t use this method in the tutorial, but it’s where you would create your own custom Invokables. Overload this in order to create message handlers (Invokables) to various messages that occur throughout the game. It is called when the actor is first created (from CreateActor() in the GM). The GameActorProxy version of BuildInvokables() automatically creates 3 Invokables for you: TickLocal(), TickRemote(), and ProcessMessage().

Second, let’s discuss OnEnteredWorld(). This is a virtual method that we are overloading so we can perform various actions when the actor enters the world. It is called inside AddActor() from the GM. It is called after the actor’s properties and Invokables have been created. Specific implementations of this method could play a sound, cause a game event to fire, or do any other sort of “Hey I’m a new actor in the world, look at me!” sort of thing. For this tutorial, we will be registering to receive tick and game event messages. This is also where you would register any Invokables that you built in BuildInvokables().

Now let’s take a look at the simple implementation, in “HoverTankActor.cpp”:


///////////////////////////////////////////////////////////////////////////////
HoverTankActorProxy::HoverTankActorProxy()
{
   SetClassName("HoverTank");
}

///////////////////////////////////////////////////////////////////////////////
void HoverTankActorProxy::BuildPropertyMap()
{
   const std::string GROUP = "HoverTank";

   dtActors::GameMeshActorProxy::BuildPropertyMap();
   HoverTankActor &actor = static_cast<HoverTankActor&>(GetGameActor());

   // Add the "Velocity" property
   AddProperty(new dtDAL::FloatActorProperty("Velocity","Velocity",
      dtDAL::MakeFunctor(actor,&HoverTankActor::SetVelocity),
      dtDAL::MakeFunctorRet(actor,&HoverTankActor::GetVelocity),
      "Sets/gets the hover tank's velocity.",GROUP));

   // Add the "Turnrate" property
   AddProperty(new dtDAL::FloatActorProperty("Turnrate","Turn Rate",
      dtDAL::MakeFunctor(actor,&HoverTankActor::SetTurnRate),
      dtDAL::MakeFunctorRet(actor,&HoverTankActor::GetTurnRate),
      "Sets/gets the hover tank's turn rate in degrees per second.",GROUP));
}

///////////////////////////////////////////////////////////////////////////////
HoverTankActorProxy::~HoverTankActorProxy()
{
}

///////////////////////////////////////////////////////////////////////////////
void HoverTankActorProxy::CreateActor()
{
   SetActor(*new HoverTankActor(*this));
}

///////////////////////////////////////////////////////////////////////////////
void HoverTankActorProxy::OnEnteredWorld()
{
   //Register an invokable for Game Events...
   RegisterForMessages(dtGame::MessageType::INFO_GAME_EVENT);

   // Register an invokable for tick messages. Local or Remote only, not both!
   if (IsRemote())
      RegisterForMessages(dtGame::MessageType::TICK_REMOTE, dtGame::GameActorProxy::TICK_REMOTE_INVOKABLE);
   else
      RegisterForMessages(dtGame::MessageType::TICK_LOCAL, dtGame::GameActorProxy::TICK_LOCAL_INVOKABLE);

   dtActors::GameMeshActorProxy::OnEnteredWorld();
}

That’s it! That will give us an Actor that can be placed in the world as well as stubs for everything we will need through the end of the tutorial. Notice how we register for messages in the OnEnteredWorld method. We register for INFO_GAME_EVENT messages, which will go to the default ProcessMessage() invokable. And, when we’re in local mode, we register for ticks by using TICK_LOCAL and the default TickLocal() Invokable.

Expose Our Actor via the Actor Registry

Now that we have our TankActor and TankProxy, we need to put it in our Actor Registry. We do this so that STAGE and the GM can find our cool new actor type. Creating a registry is pretty simple, as you can see from the code below. If you’re not clear on this part, refer to the DAL tutorial.

“TutorialGameActorsRegistry.h”:


#include "export.h"
#include <dtDAL/actorpluginregistry.h>

class TUTORIAL_HOVER_EXPORT TutorialGameActorsRegistry : public dtDAL::ActorPluginRegistry
{
   public:

      // Constructs our registry.  Creates the actor types easy access when needed.
      TutorialGameActorsRegistry();

      // Registers actor types with the actor factory in the super class.
      virtual void RegisterActorTypes();

   private:
      dtCore::RefPtr<dtDAL::ActorType> mHoverTankActorType;
};

TutorialGameActorsRegistry.cpp”:


///////////////////////////////////////////////////////////////////////////////
extern "C" TUTORIAL_HOVER_EXPORT dtDAL::ActorPluginRegistry* CreatePluginRegistry()
{
   return new TutorialGameActorsRegistry();
}

///////////////////////////////////////////////////////////////////////////////
extern "C" TUTORIAL_HOVER_EXPORT void DestroyPluginRegistry(dtDAL::ActorPluginRegistry *registry)
{
   delete registry;
}

//////////////////////////////////////////////////////////////////////////
TutorialGameActorsRegistry::TutorialGameActorsRegistry() :
   dtDAL::ActorPluginRegistry("TutorialGameActors")
{
   SetDescription("This is a library of a couple of game actors used by the "
      "Game Manager Tutorial series.");
}

//////////////////////////////////////////////////////////////////////////
void TutorialGameActorsRegistry::RegisterActorTypes()
{
   mHoverTankActorType = new dtDAL::ActorType("HoverTank","TutorialActors",
      "Simple tank that moves and acts like a basic hover craft.");

   mActorFactory->RegisterType<HoverTankActorProxy>(mHoverTankActorType.get());
}

That’s the entire registry file. It exposes a single actor type that can be loaded by the Game Manager or STAGE.

Helpful Hint – You might find it helpful to create public static references to your ActorTypes. That makes it easier to reference commonly used ActorTypes directly. See “engineactorregistry.h” in dtActors for an example of this.

Helpful Hint – Sometimes, people will register their shader files in the Actor Registry. This is especially useful if you use special shaders for any of the actors in your library. To load a shader definition file here, you would add something like this to CreatePluginRegistry():


ShaderManager::GetInstance().LoadShaderDefinitions("Shaders/TutorialShaderDefs.xml", false);

Load Our Actor In STAGE

Well, that’s all for the actor and proxy (at least the easy parts). Now we’re ready to build a little playground map in STAGE to house our tank actor. Here’s what you should do:

  1. Run STAGE
  2. Create a new map (“HoverMap”)
  3. Import your new actor library
  4. Create your actors – including one HoverTank

We’re going to load this map in the next section of the tutorial where we’ll get a handle to our tank and control the movement and other behaviors. Here’s a screenshot of STAGE with our HoverTankActor. The map contains a static mesh terrain, an Environment game actor, and of course our HoverTank game actor. For a STAGE refresher, check out the Getting to Know STAGE Tutorial.


Figure 6. HoverTankActor In STAGE

Remember, if you are stuck, you can get the entire code base here.

Well that concludes Part 1 and 2! Click here to continue to Parts 3 and 4.

Trackback

Trackback URL for this entry: http://delta3dengine.org/trackback.php?id=20060620123144266

No trackback comments for this entry.

About delta3d

delta3d is a game and simulation engine appropriate for a wide variety of simulation and entertainment applications. delta3d uses best-of-breed open source technologies to create a fully integrated game engine and with content creation tools.MORE

Twitter

User Functions





Don't have an account yet? Sign up as a New User!

Lost your password?