Quantcast
Channel: GameDev.net
Viewing all 17825 articles
Browse latest View live

How to Handle Circular Dependencies with Templates in C++

$
0
0
Although templates in C++ are well known, and more and more people actually do know how to wrap their head around them, there are a couple of less-common circumstances which can leave the programmer rather clueless. This is a problem I encountered while tuning the Message-Component-Entity system in our game under development, Burnt Islands.

The problem


In the C++ language, there are not many features that are as powerful as templates. But their strength is also contributing to the complexity of templates.

With standard classes and structs, you can hide the implementation of the methods within the class, and you can put those in separate compilation units (cpp files).

With templates, there is a whole other story. When the compiler sees a template (class and method), it must know the internals of that particular method. This is because templates doesn't really exist until they are used, and with varying template parameters, the classes and structs are generated, and is in reality completely separate classes for different parameters.

Here is one quirk: circular dependencies with templated classes and classes with templated methods. All code in the post (and build scripts) is available at https://github.com/Studiofreya/code-samples/tree/master/circular-templates.

In this particular case, Visual Studio is sort of stupid. It deliberately accepts code which it really shouldn't accept. This isn't really a problem if you only target the Microsoft stack and environment. If you plan to deploy to other platforms, it'll be a problem. The best advice is to get up both build systems early in the development, so any problems are caught early.

Code (problematic)


But on with the problem. Consider the following file.

// This file only compiles in Visual Studio 2010 newer

template<typename T>
class Message
{
    public:
        typedef T       type;

        static const int MsgId   = 0;


        explicit Message( const T & t )
            : data(t)
        {
        }


        type        data;
};

typedef Message<int>    MsgInt;
typedef Message<long>   MsgLong;
typedef Message<float>  MsgFloat;
typedef Message<double> MsgDouble;

class MessageHandler
{
    public:

        template<typename MSG, typename T>
        void setHandler( const T & msghandler )
        {
            /* do stuff */
            auto id = MSG::MsgId; // Make sure this is a message type
        }
};


// Forward declaration of classes
class Component;
class Entity;

class Component
{
    public:

        template<typename MSG>
        void setEntityHandler( Entity & entity )
        {
            auto handleMsg = [&] ( const MSG & msg )
            {
                /* do stuff */
            };

            entity.m_MessageHandler.setHandler<MSG>( handleMsg );
        }


        MessageHandler  m_MessageHandler;


};

class Entity
{
    public:


        template<typename MSG>
        void setComponentHandler( Component & component )
        {
            auto handleMsg = [&] ( const MSG & msg )
            {
                /* do stuff */
            };

            component.m_MessageHandler.setHandler<MSG>( handleMsg );
        }

        MessageHandler  m_MessageHandler;
};


int main()
{
    Component c;
    Entity e;

    e.setComponentHandler<MsgDouble>(c);

	return 0;
}


The problem is in this line entity.m_MessageHandler.setHandler( handleMsg ); in the Component class. It uses an undefined class which has been forward declared. Visual Studio is trying to be smart. It can see forwards and there is an implementation (definition) of the Entity class further down below. GCC (g++) is not trying to be smart, and thus the code above fail to compile with g++.

Here is the error with g++.

g++-4.8.1 -std=c++11 circular-templates.cpp -o circdep
circular-templates.cpp: In member function 'void Component::setEntityHandler(Entity&)':
circular-templates.cpp:55:19: error: invalid use of incomplete type 'class Entity'
             entity.m_MessageHandler.setHandler( handleMsg );
                   ^
circular-templates.cpp:41:7: error: forward declaration of 'class Entity'
 class Entity;
       ^
circular-templates.cpp:55:51: error: expected primary-expression before '>' token
             entity.m_MessageHandler.setHandler( handleMsg );
                                                   ^

Clean code (ideal)


Here is the fixed code where it will compile in both MSVC and g++.

// This file compiles in both Visual Studio 2010 (and newer) and G++ with C++11.

template<typename T>
class Message
{
    public:
        typedef T       type;

        static const int MsgId   = 0;


        explicit Message( const T & t )
            : data(t)
        {
        }


        type        data;
};

typedef Message<int>    MsgInt;
typedef Message<long>   MsgLong;
typedef Message<float>  MsgFloat;
typedef Message<double> MsgDouble;

class MessageHandler
{
    public:

        template<typename MSG, typename T>
        void setHandler( const T & msghandler )
        {
            /* do stuff */
            auto id = MSG::MsgId; // Make sure this is a message type
        }
};


// Forward declaration of classes
class Component;
class Entity;

class Component
{
    public:

        template<typename MSG>
        void setEntityHandler( Entity & entity );

        MessageHandler  m_MessageHandler;

};

class Entity
{
    public:


        template<typename MSG>
        void setComponentHandler( Component & component );

        MessageHandler  m_MessageHandler;
};


// Implementation of circular dependencies
template<typename MSG>
void Component::setEntityHandler( Entity & entity )
{
    auto handleMsg = [&] ( const MSG & msg )
    {
        /* do stuff */
    };

    entity.m_MessageHandler.setHandler<MSG>( handleMsg );
}

template<typename MSG>
void Entity::setComponentHandler( Component & component )
{
    auto handleMsg = [&] ( const MSG & msg )
    {
        /* do stuff */
    };

    component.m_MessageHandler.setHandler<MSG>( handleMsg );
}

// Main
int main()
{
    Component c;
    Entity e;

    e.setComponentHandler<MsgDouble>(c);

	return 0;
}


This file will compile in both MSVC and G++, and with MSVC it'll produce identical assembly code for both files.

The clue to solving this problem is to declare both classes before providing the definitions (implementations). It's not possible to split the declaration and definition into separate files, but you can structure them as if they were in separate files. Here both Entity and Component are declared before any implementation code.

In the end, this might or might not be a problem you will encounter. But when you encounter it, you'll know about it and remember there are solutions available.

All code in the post (and build scripts) is available at https://github.com/Studiofreya/code-samples/tree/master/circular-templates.

Programming By Example - Adding AngelScript to a Game Part 1

$
0
0
Using interpreted scripting languages in games instead of compiling to native code has become very common in games. Using scripting languages allows developers to make functional changes to their programs without having to compile and link the program and they allow developers to be able to separate the game logic from the game engine. These are good things, but how can a developer new to scripting successfully use a scripting language in his or her project? This article will explain how to add AngelScript to a game by using the XACTGame example program given in the Direct X SDK.

AngelScript Concepts Covered
  • Loading a script with CScriptBuilder add-on
  • Binding a C++ Interface to the script
    • Registering a value type
    • Registering an enum
    • Registering global variables
    • Registering global functions
    • Registering a namespace
  • Executing Scripts

Adding AngelScript to a Game


AngelScript is a scripting language with a syntax that's very similar to C++. It is a strictly-typed language with many of the types being the same as in C++. This article will explain some concepts of how to use AngelScript, but a basic knowledge of the language and API will be needed to understand all of it. See this page, "AngelScript: Your first script", in the AngelScript documentation for details. The main focus of this article is not to teach AngelScript or its API, but to show how to apply it in an actual game. Before you begin with this tutorial, make sure you have installed the Direct X SDK. I'll be using the June 2010 update. Also, you'll need to go to www.AngelCode.com to get the latest version of the AngelScript SDK and follow the instructions on how to set up AngelScript with your compiler. Here are some details on my current setup: I'm using Angel Script 2.28 and I'm using Microsoft Visual Studio 2010 Professional.

When adding scripting to an existing program, taking an accurate survey of the code is very important. Other than the Direct X files, the XACTGame example is made up of 5 source files -- audio.cpp, audio.h, game.cpp, game.h, and main.cpp. For now, I'll ignore the audio files. I could script some of the capabilities, but I'd rather not make this article too long. When examining the files, you'll notice that not all of the functions have forward declarations, but we'll need to know all of the functions so we can decide what to script.

Here's the complete list:

void InitApp();
bool CALLBACK IsDeviceAcceptable( D3DCAPS9* pCaps, D3DFORMAT AdapterFormat,
                                  D3DFORMAT BackBufferFormat, bool bWindowed, void* pUserContext );
static int __cdecl SortAspectRatios( const void* arg1, const void* arg2 );
bool CALLBACK ModifyDeviceSettings( DXUTDeviceSettings* pDeviceSettings, void* pUserContext );
HRESULT SplitIntoSeperateTriangles( IDirect3DDevice9* pd3dDevice, ID3DXMesh* pInMesh, CDXUTXFileMesh* pOutMesh );
HRESULT CALLBACK OnCreateDevice( IDirect3DDevice9* pd3dDevice, const D3DSURFACE_DESC* pBackBufferSurfaceDesc,
                                 void* pUserContext );
void ComputeMeshScaling( CDXUTXFileMesh& Mesh, D3DXMATRIX* pmScalingCenter, float fNewRadius );
void ComputeMeshScalingBox( CDXUTXFileMesh& Mesh, D3DXMATRIX* pmScalingCenter, D3DXVECTOR3 vNewMin,
                            D3DXVECTOR3 vNewMax );
void SetEffectTechnique();
HRESULT CALLBACK OnResetDevice( IDirect3DDevice9* pd3dDevice,
                                const D3DSURFACE_DESC* pBackBufferSurfaceDesc, void* pUserContext );
void FireAmmo();
float GetDistFromWall( D3DXVECTOR3 P1, D3DXVECTOR3 P2, D3DXVECTOR3 P3, D3DXVECTOR3 N );
void DroidPickNewDirection( int A );
void DroidChooseNewTask( int A );
void HandleDroidAI( float fElapsedTime );
void CheckForAmmoToDroidCollision( int A );
void CheckForInterAmmoCollision( float fElapsedTime );
void CheckForAmmoToWallCollision( int A );
void HandleAmmoAI( float fElapsedTime );
void CALLBACK OnFrameMove( double fTime, float fElapsedTime, void* pUserContext );
void CreateAmmo( int nIndex, D3DXVECTOR4 Pos, D3DXVECTOR4 Vel );
void RenderAmmo( int A, D3DXMATRIXA16& mView, D3DXMATRIXA16& mProj );
void CreateDroid();
void RenderDroid( IDirect3DDevice9* pd3dDevice, int A, D3DXMATRIXA16& mView, D3DXMATRIXA16& mProj, bool bExplode );
void CALLBACK OnFrameRender( IDirect3DDevice9* pd3dDevice, double fTime, float fElapsedTime, void* pUserContext );
void RenderText();
LRESULT CALLBACK MsgProc( HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam, bool* pbNoFurtherProcessing,
                          void* pUserContext );
void UpdateAspectRatioList( DXUTDeviceSettings* pDS );
void UpdateResolutionList( DXUTDeviceSettings* pDS );
void CALLBACK OnGUIEvent( UINT nEvent, int nControlID, CDXUTControl* pControl, void* pUserContext );
void ToggleMenu();
void CALLBACK KeyboardProc( UINT nChar, bool bKeyDown, bool bAltDown, void* pUserContext );
void CALLBACK OnLostDevice( void* pUserContext );
void CALLBACK OnDestroyDevice( void* pUserContext );

Of all of these functions, we now to choose suitable candidates for scripting. Again, to limit the scope of this article, I'll only script functions related to the game's logic. That leaves the following functions that will either be partially scripted or completely scripted:

void InitApp(); 
void FireAmmo();
void DroidPickNewDirection( int A );
void DroidChooseNewTask( int A );
void HandleDroidAI( float fElapsedTime );
void HandleAmmoAI( float fElapsedTime );

While AngelScript is a C++-style language, we can't just write the script code and be done. Our C++ code will need to be able to communicate with AngelScript, and our scripts need to be informed of the data structures and classes that we'll use. Again, an accurate examination of the base code will be needed. Let's determine the dependencies that each of the above functions have. Then we'll be able to define our script bindings.

void InitApp();
  • RENDER_STATE - This is a structure defined in game.h that details everything that needs to be rendered. Only parts of this structure are needed by InitApp()
  • CDXUTDialog - This class is defined in DXUTgui.h and it defines a GUI dialog. The following methods will be needed:
HRESULT AddStatic( int ID, LPCWSTR strText, int x, int y, int width, int height, bool bIsDefault=false,
      CDXUTStatic** ppCreated=NULL );
HRESULT AddButton( int ID, LPCWSTR strText, int x, int y, int width, int height, UINT nHotkey=0,
      bool bIsDefault=false, CDXUTButton** ppCreated=NULL );
HRESULT AddCheckBox( int ID, LPCWSTR strText, int x, int y, int width, int height, bool bChecked=false,
      UINT nHotkey=0, bool bIsDefault=false, CDXUTCheckBox** ppCreated=NULL );
HRESULT AddRadioButton( int ID, UINT nButtonGroup, LPCWSTR strText, int x, int y, int width,
      int height, bool bChecked=false, UINT nHotkey=0, bool bIsDefault=false,
      CDXUTRadioButton** ppCreated=NULL );
HRESULT AddComboBox( int ID, int x, int y, int width, int height, UINT nHotKey=0, bool bIsDefault=
      false, CDXUTComboBox** ppCreated=NULL );
HRESULT AddSlider( int ID, int x, int y, int width, int height, int min=0, int max=100, int value=50,
      bool bIsDefault=false, CDXUTSlider** ppCreated=NULL );

  • GAME_STATE g_GameState; - The global game state. GAME_STATE is defined in game.h
    • int nAmmoCount;
    • float fAmmoColorLerp;
    • D3DXCOLOR BlendFromColor;
    • bool bDroidMove;
    • bool bAutoAddDroids;
    • GAME_MODE gameMode;
  • D3DXCOLOR - defined in d3dx9math.h

  • GAME_MODE - enum of game modes. Defined in game.h
Many things need to be done just to get all the bindings for InitApp(). So before proceeding to the other functions, let's first build a version of XACTGame that uses AngelScript for the InitApp() function.

First, I'll start by adding two new files to the project as_scripting.h and as_scripting.cpp. The XACTGame sample application does everything in free functions so for simplicity, I'll continue to use that style.

We'll need to add some includes to the as_scripting.h file. angelscript.h is the file we need for all of the basic angelscript classes. scriptbuilder.h is in the add_on directory and it's a extra class that will help us load our scripts.

// Include the definitions of the script library and the add-ons we'll use.
// The project settings may need to be configured to let the compiler where
// to find these headers. Don't forget to add the source modules for the
// add-ons to your project as well so that they will be compiled into the 
// application.
#include <angelscript .h="">
#include <scriptbuilder scriptbuilder.h="">
 
#include "game.h"</scriptbuilder></angelscript>

Initially, I was going to use globals for the sake of keeping the same style as the XACTGame sample, but I've found a much better approach. To do this, I'll provide a structure called ScriptContextData, that I'll pass to the game functions and DXUT callbacks that need it. It would be easier to just use globals, but I want to show this approach because AngelScript allows multiple modules and script context to be used. A module is a compiled (compiled into VM bytecode) script and a context is an instance of the virtual machine. Complex games will probably have multiple modules and may have multiple context.

For now, I'll keep the structure simple.

enum ScriptFunctionIDs
{
 Function_InitApp = 0
};
 
const unsigned int max_script_functions = 1;
 
struct ScriptContextData
{
 asIScriptContext *ctx;
 asIScriptFunction *script_functions[max_script_functions];
 
 void ExecuteFunction(ScriptFunctionIDs func_id);
};

The structure keeps the context (the virtual machine) and an array of the script functions that we can call from C++. To simplify things, I'm also adding a function that will run the function and check for exceptions.

And for now I'll write these two functions:

int StartScriptingSystem(asIScriptEngine *&scriptengine, CScriptBuilder &scriptbuilder, ScriptContextData &contextdata)
{
 int result;
 
 // Create the script engine
 scriptengine = asCreateScriptEngine(ANGELSCRIPT_VERSION);
 
 // Set the message callback to receive information on errors in human readable form.
 result = scriptengine->SetMessageCallback(asFUNCTION(MessageCallback), 0, asCALL_CDECL);
 if(result < 0) return result; // an error has occurred
 
 result = RegisterGameInterface(scriptengine);
 if(result < 0) return result; // an error has occurred
 
 ...
}
 
void ShutdownScriptingSystem(asIScriptEngine *&scriptengine, asIScriptContext *&ctx)
{
 // Why check to see if this is NULL? This function will be called at the end of the program
 // as a way to clean it up even if an error has occurred. If an error occurs during initialization
 // one or both of these variables may be null.
 if(ctx)
 {
  ctx->Release();
  ctx = NULL; // i don't like leaving old pointers that don't point to valid data
 }
 
 if(scriptengine)
 {
  scriptengine->Release();
  scriptengine = NULL; // i don't like leaving old pointers that don't point to valid data
 }
}

The RegisterGameInterface() function performs all of the bindings between the script engine and the C++ code.

Registering an Enum


Let's start with the easiest binding, the GAME_MODE enum.

int RegisterEnumGAME_MODE(asIScriptEngine *scriptengine)
{
 int result;
 
 result = scriptengine->RegisterEnum("GAME_MODE");
 if(result < 0) return result;
 
 result = scriptengine->RegisterEnumValue("GAME_MODE", "GAME_RUNNING", (int)GAME_RUNNING);
 if(result < 0) return result;
 
    result = scriptengine->RegisterEnumValue("GAME_MODE", "GAME_MAIN_MENU", (int)GAME_MAIN_MENU);
 if(result < 0) return result;
 
    result = scriptengine->RegisterEnumValue("GAME_MODE", "GAME_AUDIO_MENU", (int)GAME_AUDIO_MENU);
 if(result < 0) return result;
 
    result = scriptengine->RegisterEnumValue("GAME_MODE", "GAME_VIDEO_MENU", (int)GAME_VIDEO_MENU);
 
 return result;
}

Adding enumerations are simple. First use the RegisterEnum() function to register the type. Then use the RegisterEnumValue() to add each value. Sorry, there's no shorter way, but writing the code is very straight forward.

Registering a Value Type


Next, I'll add the D3DXVECTOR3 type. I'll register is as a value type; however, to not clutter up the as_scripting.cpp and angelscript.h files, I'll create separate files for this type. I'll do the same with D3DXCOLOR. Registering a type as a value type means that it will be located on the stack or locally as a member of another object. Value types don't support handles and can be passed by value or reference to application registered functions. The files are a little long so please check the included source files for details.

The following will register our D3DXVECTOR with AngelScript:

r = engine->RegisterObjectType("D3DXVECTOR3", sizeof(D3DXVECTOR3), asOBJ_VALUE | asOBJ_APP_CLASS |
                               asOBJ_APP_CLASS_CONSTRUCTOR | asOBJ_APP_CLASS_COPY_CONSTRUCTOR |
                               asOBJ_APP_CLASS_DESTRUCTOR);

With value types, we should give the name of the type and the size so AngelScript will know how much memory on the stack it needs to reserve for the variable. The flags at the end tell what behaviors we will define for our class. We should register the constructors and destructor as behaviors, but the other methods are registered as object methods. Here's the function that registers the entire D3DXVECTOR3 object type.

int RegisterD3DXVECTOR3(asIScriptEngine *engine)
{
	int r;

	// Register the string type
	r = engine->RegisterObjectType("D3DXVECTOR3", sizeof(D3DXVECTOR3), asOBJ_VALUE | 
		                           asOBJ_APP_CLASS | asOBJ_APP_CLASS_CONSTRUCTOR | asOBJ_APP_CLASS_COPY_CONSTRUCTOR | asOBJ_APP_CLASS_DESTRUCTOR);
	if(r < 0) return r;

	// Register the object operator overloads
	r = engine->RegisterObjectBehaviour("D3DXVECTOR3", asBEHAVE_CONSTRUCT,  "void f()",                    asFUNCTION(ConstructVector3), asCALL_CDECL_OBJLAST);
	if(r < 0) return r;
	r = engine->RegisterObjectBehaviour("D3DXVECTOR3", asBEHAVE_CONSTRUCT,  "void f(const D3DXVECTOR3 &in)", asFUNCTION(CopyConstructVector3), asCALL_CDECL_OBJLAST);
	if(r < 0) return r;
	r = engine->RegisterObjectBehaviour("D3DXVECTOR3", asBEHAVE_CONSTRUCT,  "void f(float, float, float)", asFUNCTION(ConstructVector3FromFloats), asCALL_CDECL_OBJLAST);
	if(r < 0) return r;
	r = engine->RegisterObjectBehaviour("D3DXVECTOR3", asBEHAVE_DESTRUCT, "void f()", asFUNCTION(DestructVector3), asCALL_CDECL_OBJLAST);
	if(r < 0) return r;
	r = engine->RegisterObjectMethod("D3DXVECTOR3", "D3DXVECTOR3 &opAssign(const D3DXVECTOR3 &in)", asMETHODPR(D3DXVECTOR3, operator =, (const D3DXVECTOR3&), D3DXVECTOR3&), asCALL_THISCALL);
	if(r < 0) return r;

	r = engine->RegisterObjectMethod("D3DXVECTOR3", "D3DXVECTOR3 &opAddAssign(const D3DXVECTOR3 &in)", asFUNCTION(AddAssignTwoVectors), asCALL_CDECL_OBJLAST);
	if(r < 0) return r;
	r = engine->RegisterObjectMethod("D3DXVECTOR3", "D3DXVECTOR3 &opSubAssign(const D3DXVECTOR3 &in)", asFUNCTION(MinusAssignTwoVectors), asCALL_CDECL_OBJLAST);
	if(r < 0) return r;
	r = engine->RegisterObjectMethod("D3DXVECTOR3", "D3DXVECTOR3 &opMulAssign(float)", asFUNCTION(MulAssignWithFloat), asCALL_CDECL_OBJLAST);
	if(r < 0) return r;
	r = engine->RegisterObjectMethod("D3DXVECTOR3", "D3DXVECTOR3 &opDivAssign(float)", asFUNCTION(DivAssignWithFloat), asCALL_CDECL_OBJLAST);
	if(r < 0) return r;

	r = engine->RegisterObjectMethod("D3DXVECTOR3", "D3DXVECTOR3 opAdd(const D3DXVECTOR3 &in)", asFUNCTION(AddTwoVectors), asCALL_CDECL_OBJLAST);
	if(r < 0) return r;
	r = engine->RegisterObjectMethod("D3DXVECTOR3", "D3DXVECTOR3 opSub(const D3DXVECTOR3 &in)", asFUNCTION(MinusTwoVectors), asCALL_CDECL_OBJLAST);
	if(r < 0) return r;
	r = engine->RegisterObjectMethod("D3DXVECTOR3", "D3DXVECTOR3 opMul(float)", asFUNCTION(MulVectorScalar), asCALL_CDECL_OBJFIRST);
	if(r < 0) return r;
	r = engine->RegisterObjectMethod("D3DXVECTOR3", "D3DXVECTOR3 opMul_r(float)", asFUNCTION(MulVectorScalar), asCALL_CDECL_OBJLAST);
	if(r < 0) return r;
	r = engine->RegisterObjectMethod("D3DXVECTOR3", "D3DXVECTOR3 opDiv(float)", asFUNCTION(DivVectorScalar), asCALL_CDECL_OBJFIRST);
	if(r < 0) return r;

	r = engine->RegisterObjectMethod("D3DXVECTOR3", "bool opEquals(const D3DXVECTOR3 &in) const", asFUNCTION(VectorsEqual), asCALL_CDECL_OBJFIRST);
	if(r < 0) return r;

	r = engine->RegisterObjectProperty("D3DXVECTOR3", "float x", asOFFSET(D3DXVECTOR3,x));
	if(r < 0) return r;
	r = engine->RegisterObjectProperty("D3DXVECTOR3", "float y", asOFFSET(D3DXVECTOR3,y));
	if(r < 0) return r;
	r = engine->RegisterObjectProperty("D3DXVECTOR3", "float z", asOFFSET(D3DXVECTOR3,z));

	return r;
}

The type is registered using the native calling convention. To registers a method use RegisterObjectMethod(). The RegisterObjectMethod() takes four parameters. The first parameter is the name of the type as it'll be used in AngelScript. The second is the AngelScript function declaration. The third is the C++ function pointer to the function to bind, and the last parameter is the calling convention. AngelScript also supports asCALL_THISCALL, which will allow you to register C++ class members directly. However, since I wrapped all of the functions, I used either asCALL_CDECL_OBJLAST or asCALL_CDECL_OBJFIRST. This allows me to use non-member functions with a parameter that mimics the "this" pointer. asCALL_CDECL_OBJLAST means the last parameter should get the "this" pointer, and asCALL_CDECL_OBJFIRST means the first parameter should recieve the "this" pointer. Here's one of the functions that I'm registering:

static D3DXVECTOR3& DivAssignWithFloat/* operator /= */ ( FLOAT f, D3DXVECTOR3 &dest/*acts like the this pointer*/ )
{
	dest /= f;

	return dest;
}

RegisterObjectBehaviour() is similar. This will allow us to register constructors and destructors. The first parameter is the type. The second is the behavior that you're defining. The third one is the AngelScript function declaration. The fourth is the C++ function pointer, and the final one is the calling convention. Use RegisterObjectProperty() to register member variables.

There's one last thing I want to say about this type. Notice that when registering operators, special names have to be used. For a list of the operators, you can check the AngelScript manual.

Registering Global Functions and Namespaces


Next, since the render state is a global variable in XACT, I'm going to simplify my life a little and not create bindings for the CDXUTDialog. Instead, I'm going to create a new enum XACTGAMEDIALOG, and I'll make and register some free functions with the script engine.

enum XACTGAMEDIALOG
{
 IDMainMenuDlg,
 IDVideoMenuDlg,
 IDAudioMenuDlg
};
 
void AddStaticToDialog(XACTGAMEDIALOG dialogID, int ID, const std::string & strText, int x, int y, int width,
 int height, bool bIsDefault);
void AddButtonToDialog(XACTGAMEDIALOG dialogID, int ID, const std::string & strText, int x, int y, int width,
 int height, UINT nHotkey, bool bIsDefault);
void AddCheckBoxToDialog(XACTGAMEDIALOG dialogID, int ID, const std::string & strText, int x, int y, int width,
 int height, bool bChecked, UINT nHotkey, bool bIsDefault);
void AddRadioButtonToDialog(XACTGAMEDIALOG dialogID, int ID, UINT nButtonGroup, const std::string & strText,
 int x, int y, int width, int height, bool bChecked, UINT nHotkey, bool bIsDefault);
void AddComboBoxToDialog(XACTGAMEDIALOG dialogID, int ID, int x, int y, int width, int height,
 UINT nHotKey, bool bIsDefault);
void AddSliderToDialog(XACTGAMEDIALOG dialogID, int ID, int x, int y, int width, int height,
 int min, int max, int value,  bool bIsDefault);

There's no reason why the interface that you supply to AngelScript has to be exactly like the C++ one. The AngelScript interface also can't handle standard C-style strings which is what the CDXUTDialog methods require for text so we'd still have to wrap them in another function either way. To make the interface a little cleaner, I'll put it in a namespace. This can be done by calling the SetDefaultNamespace() method before we register the global functions.

int RegisterDialogInterface(asIScriptEngine *scriptengine)
{
 int result;
 
 // set the namespace
 result = scriptengine->SetDefaultNamespace("dialogs"); 
 if(result < 0) return result;
 
 // first register our enum
 result = scriptengine->RegisterEnum("XACTGAMEDIALOG");
 if(result < 0) return result;
 
 result = scriptengine->RegisterEnumValue("XACTGAMEDIALOG", "IDMainMenuDlg", (int)IDMainMenuDlg);
 if(result < 0) return result;
 
 ...
 
 // register the global functions
 result = scriptengine->RegisterGlobalFunction("void AddStaticToDialog(XACTGAMEDIALOG, int, const string &in, int, int, int, int, bool)",
  asFUNCTION(AddStaticToDialog), asCALL_CDECL);
 if(result < 0) return result;
 
 ...
 
 // reset back to global namespace
 result = scriptengine->SetDefaultNamespace(""); 
  
 return result;
}

We should register the enum just as before. Use the RegisterGlobalFunction() method to add each of the new functions. The method needs the declaration of the function, a function pointer, and the calling convention. When using reference parameters, you must use 'in' or 'out' as the parameter names in the declaration you supply to AngelScript so it will know how it can optimize how it uses the parameter.

Now, if we need anymore functionality from RENDER_STATE, we can just add more functions to the interface instead of giving the script direct access. This will be simple to do and safer as we can add checks to our wrapper functions. We'll do the same thing with the CFirstPersonCamera class.

// declare the global g_Camera variable as extern here so we can use the one defined in game.cpp
extern CFirstPersonCamera  g_Camera;
 
static void CameraSetViewParams( D3DXVECTOR3 &pvEyePt, D3DXVECTOR3 &pvLookatPt )
{
 g_Camera.SetViewParams(&pvEyePt, &pvLookatPt);
}
 
static void CameraSetEnableYAxisMovement( bool bEnableYAxisMovement )
{
 g_Camera.SetEnableYAxisMovement(bEnableYAxisMovement);
}
 
...
 
int RegisterCameraInterface(asIScriptEngine *scriptengine)
{
 int result;
 
 // set the namespace
 result = scriptengine->SetDefaultNamespace("FirstPersonCamera"); 
 if(result < 0) return result;
 
 // register the global functions
 result = scriptengine->RegisterGlobalFunction("void SetViewParams( D3DXVECTOR3 &in, D3DXVECTOR3 &in )", asFUNCTION(CameraSetViewParams), asCALL_CDECL);
 if(result < 0) return result;
 
 result = scriptengine->RegisterGlobalFunction("void SetEnableYAxisMovement( bool )", asFUNCTION(CameraSetEnableYAxisMovement), asCALL_CDECL);
 if(result < 0) return result;
 
 ...
 
 
 // reset back to global namespace
 result = scriptengine->SetDefaultNamespace(""); 
  
 return result;
}

Registering Global Properties(variables)


Now the last thing we need to make an interface for so that we can script the InitApp() function is GAME_STATE. There are a few possible ways to do this. One way would be to make a GAME_STATE object type in AngelScript and then register g_GameState as a global property. A second way would be to provide a set of accessor (get/set) functions and register them as global functions in a namespace. A third option would be to use a namespace and then register individual member variables of the GAME_STATE struct as global properties in AngelScript. Since there is only one g_GameState in the XACTGame sample, I think registering a new type would be a waste. There's also no need to make getters and setters since I'm not going to add any checking so I'll use the third option.

int RegisterGameStateInterface(asIScriptEngine *scriptengine)
{
 int result;
 
 // set the namespace
 result = scriptengine->SetDefaultNamespace("GAME_STATE"); 
 if(result < 0) return result;
 
 // Register a primitive property that can be read and written to from the script.
 result = scriptengine->RegisterGlobalProperty("int nAmmoCount", &g_GameState.nAmmoCount);
 if(result < 0) return result;
 
 result = scriptengine->RegisterGlobalProperty("float fAmmoColorLerp", &g_GameState.fAmmoColorLerp);
 if(result < 0) return result;
 
 result = scriptengine->RegisterGlobalProperty("D3DXCOLOR BlendFromColor", &g_GameState.BlendFromColor);
 if(result < 0) return result;
 
 result = scriptengine->RegisterGlobalProperty("bool bDroidMove", &g_GameState.bDroidMove);
 if(result < 0) return result;
 
 result = scriptengine->RegisterGlobalProperty("bool bAutoAddDroids", &g_GameState.bAutoAddDroids);
 if(result < 0) return result;
 
 result = scriptengine->RegisterGlobalProperty("GAME_MODE gameMode", &g_GameState.gameMode);
 if(result < 0) return result;
 
 // reset back to global namespace
 result = scriptengine->SetDefaultNamespace(""); 
 
 return result;
}

Loading and Executing a Script


That's it for the interface for now, I can add more properties later if needed. Now we have the entire interface that will be needed to script the InitApp() function with AngelScript. Now we need to be able to load the script. The following code will do that:

int LoadScript(asIScriptEngine *scriptengine, CScriptBuilder &scriptbuilder, ScriptContextData &contextdata)
{
 int result;
 
 // The CScriptBuilder helper is an add-on that loads the file,
 // performs a pre-processing pass if necessary, and then tells
 // the engine to build a script module.
 CScriptBuilder builder;
 result = builder.StartNewModule(scriptengine, "BasicModule"); 
 if( result < 0 ) 
 {
  // If the code fails here it is usually because there
  // is no more memory to allocate the module
  MessageBoxA(NULL, "Unrecoverable error while starting a new module.", "AngelScript Message", MB_OK);
  return result;
 }
 result = builder.AddSectionFromFile("xactgamescript.as");
 if( result < 0 ) 
 {
  // The builder wasn't able to load the file. Maybe the file
  // has been removed, or the wrong name was given, or some
  // preprocessing commands are incorrectly written.
  MessageBoxA(NULL,"Please correct the errors in the script and try again.", "AngelScript Message", MB_OK);
  return result;
 }
 result = builder.BuildModule();
 if( result < 0 ) 
 {
  // An error occurred. Instruct the script writer to fix the 
  // compilation errors that were listed in the output stream.
  MessageBoxA(NULL,"Please correct the errors in the script and try again.", "AngelScript Message", MB_OK);
  return result;
 }
 
 // Find the function that is to be called. 
 asIScriptModule *mod = scriptengine->GetModule("BasicModule");
 contextdata.script_functions[Function_InitApp] = mod->GetFunctionByDecl("void InitApp()");
 if( contextdata.script_functions[Function_InitApp] == 0 )
 {
  // The function couldn't be found. Instruct the script writer
  // to include the expected function in the script.
  MessageBoxA(NULL,"The script must have the function 'void InitApp()'. Please add it and try again.", "AngelScript Message", MB_OK);
  return -1;
 }
 
 return result;
}

So now let's script it. First let's take a look at the C++ version of the InitApp() function:

void InitApp()
{
    srand( 0 );
 
    g_Render.pEffect = NULL;
    g_Render.pDefaultTex = NULL;
    g_Render.UseFixedFunction = 0.0f;
    g_Render.ForceShader = 0;
    g_Render.MaximumResolution = 4096.0f;
    g_Render.DisableSpecular = 0.0f;
    g_Render.bDetectOptimalSettings = true;
 
    // Initialize dialogs
    g_Render.MainMenuDlg.Init( &g_Render.DialogResourceManager );
    g_Render.MainMenuDlg.SetCallback( OnGUIEvent ); int iY = ( ( 300 - 30 * 6 ) / 2 );
    g_Render.MainMenuDlg.AddButton( IDC_AUDIO, L"Audio", ( 250 - 125 ) / 2, iY += 30, 125, 22 );
    g_Render.MainMenuDlg.AddButton( IDC_VIDEO, L"Video", ( 250 - 125 ) / 2, iY += 30, 125, 22 );
    g_Render.MainMenuDlg.AddButton( IDC_RESUME, L"Resume", ( 250 - 125 ) / 2, iY += 30, 125, 22 );
    g_Render.MainMenuDlg.AddButton( IDC_QUIT, L"Quit", ( 250 - 125 ) / 2, iY += 60, 125, 22, 'Q' );
 
    g_Render.AudioMenuDlg.Init( &g_Render.DialogResourceManager );
    g_Render.AudioMenuDlg.SetCallback( OnGUIEvent ); iY = 60;
    g_Render.AudioMenuDlg.AddStatic( IDC_STATIC, L"Music Volume", ( 250 - 125 ) / 2, iY += 24, 125, 22 );
    g_Render.AudioMenuDlg.AddSlider( IDC_MUSIC_SCALE, ( 250 - 100 ) / 2, iY += 24, 100, 22, 0, 100, 100 );
    g_Render.AudioMenuDlg.AddStatic( IDC_STATIC, L"Sound Effects Volume", ( 250 - 125 ) / 2, iY += 35, 125, 22 );
    g_Render.AudioMenuDlg.AddSlider( IDC_SOUNDFX_SCALE, ( 250 - 100 ) / 2, iY += 24, 100, 22, 0, 100, 100 );
    g_Render.AudioMenuDlg.AddButton( IDC_BACK, L"Back", ( 250 - 125 ) / 2, iY += 40, 125, 22 );
 
    g_Render.VideoMenuDlg.Init( &g_Render.DialogResourceManager );
    g_Render.VideoMenuDlg.SetCallback( OnGUIEvent ); iY = 0;
    g_Render.VideoMenuDlg.AddCheckBox( IDC_FULLSCREEN, L"Full screen", ( 250 - 200 ) / 2, iY += 30, 200, 22, true );
    g_Render.VideoMenuDlg.AddStatic( IDC_STATIC, L"Aspect:", 50, iY += 22, 50, 22 );
    g_Render.VideoMenuDlg.AddComboBox( IDC_ASPECT, 100, iY, 100, 22 );
    g_Render.VideoMenuDlg.AddStatic( IDC_STATIC, L"Resolution:", 30, iY += 22, 75, 22 );
    g_Render.VideoMenuDlg.AddComboBox( IDC_RESOLUTION, 100, iY, 125, 22 );
    g_Render.VideoMenuDlg.AddCheckBox( IDC_ANTI_ALIASING, L"Anti-Aliasing", ( 250 - 200 ) / 2, iY += 26, 200, 22,
                                       false );
    g_Render.VideoMenuDlg.AddCheckBox( IDC_HIGH_MODEL_RES, L"High res models", ( 250 - 200 ) / 2, iY += 26, 200, 22,
                                       true );
    g_Render.VideoMenuDlg.AddStatic( IDC_MAX_DROIDS_TEXT, L"Max Droids", ( 250 - 125 ) / 2, iY += 26, 125, 22 );
    g_Render.VideoMenuDlg.AddSlider( IDC_MAX_DROIDS, ( 250 - 150 ) / 2, iY += 22, 150, 22, 1, MAX_DROID, 10 );
    g_Render.VideoMenuDlg.AddButton( IDC_APPLY, L"Apply", ( 250 - 125 ) / 2, iY += 35, 125, 22 );
    g_Render.VideoMenuDlg.AddButton( IDC_BACK, L"Back", ( 250 - 125 ) / 2, iY += 30, 125, 22 );
 
    // Setup the camera
    D3DXVECTOR3 MinBound( g_MinBound.x + CAMERA_SIZE, g_MinBound.y + CAMERA_SIZE, g_MinBound.z + CAMERA_SIZE );
    D3DXVECTOR3 MaxBound( g_MaxBound.x - CAMERA_SIZE, g_MaxBound.y - CAMERA_SIZE, g_MaxBound.z - CAMERA_SIZE );
    g_Camera.SetClipToBoundary( true, &MinBound, &MaxBound );
    g_Camera.SetEnableYAxisMovement( false );
    g_Camera.SetRotateButtons( false, false, true );
    g_Camera.SetScalers( 0.001f, 4.0f );
    D3DXVECTOR3 vecEye( 0.0f, -GROUND_Y + 0.7f, 0.0f );
    D3DXVECTOR3 vecAt ( 0.0f, -GROUND_Y + 0.7f, 1.0f );
    g_Camera.SetViewParams( &vecEye, &vecAt );
 
    ZeroMemory( &g_GameState, sizeof( GAME_STATE ) );
    g_GameState.gameMode = GAME_RUNNING;
    g_GameState.nAmmoCount = 0;
    g_GameState.fAmmoColorLerp = 1000.0f;
    g_GameState.BlendFromColor = D3DXCOLOR( 0.6f, 0, 0, 1 );
    g_GameState.bAutoAddDroids = false;
    g_GameState.bDroidMove = true;
 
    // Store the rcWork of each monitor before a fullscreen D3D device is created 
    // This is used later to ensure the supported window mode 
    // resolutions will fit inside the desktop
    IDirect3D9* pD3D = DXUTGetD3D9Object();
    UINT numAdapters = pD3D->GetAdapterCount();
    for( UINT adapterOrdinal = 0; adapterOrdinal < numAdapters && adapterOrdinal < 10; adapterOrdinal++ )
    {
        MONITORINFO miAdapter;
        miAdapter.cbSize = sizeof( MONITORINFO );
        DXUTGetMonitorInfo( pD3D->GetAdapterMonitor( adapterOrdinal ), &miAdapter );
        g_Render.rcAdapterWork[adapterOrdinal] = miAdapter.rcWork;
    }
 
    // Make a list of supported windowed mode resolutions.  
    // The list of fullscreen mode resolutions are gathered from the D3D device directly.
    D3DDISPLAYMODE dm = {0, 0, 0, D3DFMT_UNKNOWN};
    dm.Width = 640; dm.Height = 480; g_Render.aWindowedDMList.Add( dm ); // 4:3
    dm.Width = 800; dm.Height = 600; g_Render.aWindowedDMList.Add( dm ); // 4:3
    dm.Width = 1024; dm.Height = 768; g_Render.aWindowedDMList.Add( dm ); // 4:3
    dm.Width = 1280; dm.Height = 960; g_Render.aWindowedDMList.Add( dm ); // 4:3
    dm.Width = 1600; dm.Height = 1200; g_Render.aWindowedDMList.Add( dm ); // 4:3
 
    dm.Width = 852; dm.Height = 480; g_Render.aWindowedDMList.Add( dm ); // 16:9
    dm.Width = 1067; dm.Height = 600; g_Render.aWindowedDMList.Add( dm ); // 16:9
    dm.Width = 1280; dm.Height = 720; g_Render.aWindowedDMList.Add( dm ); // 16:9
    dm.Width = 1920; dm.Height = 1080; g_Render.aWindowedDMList.Add( dm ); // 16:9
}

Some things can be scripted, but some things would be better left done in C++. Now I'll rewrite it and only leave in the things that shouldn't be scripted.

void InitApp(ScriptContextData &context_data)
// Changed 2013-12-25 By Dominque Douglas for AngelScript Example
{
    srand( 0 );
 
    g_Render.pEffect = NULL;
    g_Render.pDefaultTex = NULL;
    g_Render.UseFixedFunction = 0.0f;
    g_Render.ForceShader = 0;
    g_Render.MaximumResolution = 4096.0f;
    g_Render.DisableSpecular = 0.0f;
    g_Render.bDetectOptimalSettings = true;
 
    // Initialize dialogs
    g_Render.MainMenuDlg.Init( &g_Render.DialogResourceManager );
    g_Render.MainMenuDlg.SetCallback( OnGUIEvent );
 // we'll script adding all the GUI elements
 
    g_Render.AudioMenuDlg.Init( &g_Render.DialogResourceManager );
    g_Render.AudioMenuDlg.SetCallback( OnGUIEvent );
 // we'll script adding all the GUI elements
 
    g_Render.VideoMenuDlg.Init( &g_Render.DialogResourceManager );
    g_Render.VideoMenuDlg.SetCallback( OnGUIEvent );
 // we'll script adding all the GUI elements
 
 // script setting up the camera
 
 // script setting the inital game state
 
    // Store the rcWork of each monitor before a fullscreen D3D device is created 
    // This is used later to ensure the supported window mode 
    // resolutions will fit inside the desktop
    IDirect3D9* pD3D = DXUTGetD3D9Object();
    UINT numAdapters = pD3D->GetAdapterCount();
    for( UINT adapterOrdinal = 0; adapterOrdinal < numAdapters && adapterOrdinal < 10; adapterOrdinal++ )
    {
        MONITORINFO miAdapter;
        miAdapter.cbSize = sizeof( MONITORINFO );
        DXUTGetMonitorInfo( pD3D->GetAdapterMonitor( adapterOrdinal ), &miAdapter );
        g_Render.rcAdapterWork[adapterOrdinal] = miAdapter.rcWork;
    }
 
    // Make a list of supported windowed mode resolutions.  
    // The list of fullscreen mode resolutions are gathered from the D3D device directly.
    D3DDISPLAYMODE dm = {0, 0, 0, D3DFMT_UNKNOWN};
    dm.Width = 640; dm.Height = 480; g_Render.aWindowedDMList.Add( dm ); // 4:3
    dm.Width = 800; dm.Height = 600; g_Render.aWindowedDMList.Add( dm ); // 4:3
    dm.Width = 1024; dm.Height = 768; g_Render.aWindowedDMList.Add( dm ); // 4:3
    dm.Width = 1280; dm.Height = 960; g_Render.aWindowedDMList.Add( dm ); // 4:3
    dm.Width = 1600; dm.Height = 1200; g_Render.aWindowedDMList.Add( dm ); // 4:3
 
    dm.Width = 852; dm.Height = 480; g_Render.aWindowedDMList.Add( dm ); // 16:9
    dm.Width = 1067; dm.Height = 600; g_Render.aWindowedDMList.Add( dm ); // 16:9
    dm.Width = 1280; dm.Height = 720; g_Render.aWindowedDMList.Add( dm ); // 16:9
    dm.Width = 1920; dm.Height = 1080; g_Render.aWindowedDMList.Add( dm ); // 16:9
 
 // execute the script here
 context_data.ExecuteFunction(Function_InitApp);
}

With that done, now we can write the AngelScript script for our InitApp() function. I'll also add some constants to the script that were defined in the game.h file. A possible future enhancement would be to allow setting these constants in AngelScript and the allowing the C++ code use the values.

// THIS IS NOT C++. This is AngelScript
const float GROUND_Y = 3.0f; // -GROUND_Y is the Y coordinate of the ground.
const float CAMERA_SIZE = 0.2f; // CAMERA_SIZE is used for clipping camera movement
const uint MAX_DROID = 50;
 
// MinBound and MaxBound are the bounding box representing the cell mesh.
const D3DXVECTOR3           g_MinBound( -6.0f, -GROUND_Y, -6.0f );
const D3DXVECTOR3           g_MaxBound( 6.0f, GROUND_Y, 6.0f );
 
//--------------------------------------------------------------------------------------
// UI control IDs
//--------------------------------------------------------------------------------------
const uint IDC_STATIC              = 1;
const uint IDC_AUDIO               = 2;
const uint IDC_VIDEO               = 3;
const uint IDC_RESUME              = 4;
const uint IDC_QUIT                = 5;
const uint IDC_BACK                = 8;
const uint IDC_SOUNDFX_SCALE       = 6;
const uint IDC_MUSIC_SCALE         = 7;
const uint IDC_RESOLUTION          = 9;
const uint IDC_ANTI_ALIASING       = 10;
const uint IDC_MAX_DROIDS          = 11;
const uint IDC_HIGH_MODEL_RES      = 12;
const uint IDC_MAX_DROIDS_TEXT     = 13;
const uint IDC_APPLY               = 14;
const uint IDC_FULLSCREEN          = 15;
const uint IDC_ASPECT              = 16;
 
void InitApp()
{
 int iY = ( ( 300 - 30 * 6 ) / 2 );
    dialogs::AddButtonToDialog(dialogs::IDMainMenuDlg, IDC_AUDIO, "Audio", ( 250 - 125 ) / 2, iY += 30, 125, 22 );
    dialogs::AddButtonToDialog(dialogs::IDMainMenuDlg, IDC_VIDEO, "Video", ( 250 - 125 ) / 2, iY += 30, 125, 22 );
    dialogs::AddButtonToDialog(dialogs::IDMainMenuDlg, IDC_RESUME, "Resume", ( 250 - 125 ) / 2, iY += 30, 125, 22 );
    dialogs::AddButtonToDialog(dialogs::IDMainMenuDlg, IDC_QUIT, "Quit", ( 250 - 125 ) / 2, iY += 60, 125, 22, 81/*'Q'*/ );
 
 iY = 60;
    dialogs::AddStaticToDialog(dialogs::IDAudioMenuDlg, IDC_STATIC, "Music Volume", ( 250 - 125 ) / 2, iY += 24, 125, 22 );
    dialogs::AddSliderToDialog(dialogs::IDAudioMenuDlg, IDC_MUSIC_SCALE, ( 250 - 100 ) / 2, iY += 24, 100, 22, 0, 100, 100 );
    dialogs::AddStaticToDialog(dialogs::IDAudioMenuDlg, IDC_STATIC, "Sound Effects Volume", ( 250 - 125 ) / 2, iY += 35, 125, 22 );
    dialogs::AddSliderToDialog(dialogs::IDAudioMenuDlg, IDC_SOUNDFX_SCALE, ( 250 - 100 ) / 2, iY += 24, 100, 22, 0, 100, 100 );
    dialogs::AddButtonToDialog(dialogs::IDAudioMenuDlg, IDC_BACK, "Back", ( 250 - 125 ) / 2, iY += 40, 125, 22 );
 
 iY = 0;
    dialogs::AddCheckBoxToDialog(dialogs::IDVideoMenuDlg, IDC_FULLSCREEN, "Full screen", ( 250 - 200 ) / 2, iY += 30, 200, 22, true );
    dialogs::AddStaticToDialog(dialogs::IDVideoMenuDlg, IDC_STATIC, "Aspect:", 50, iY += 22, 50, 22 );
    dialogs::AddComboBoxToDialog(dialogs::IDVideoMenuDlg, IDC_ASPECT, 100, iY, 100, 22 );
    dialogs::AddStaticToDialog(dialogs::IDVideoMenuDlg, IDC_STATIC, "Resolution:", 30, iY += 22, 75, 22 );
    dialogs::AddComboBoxToDialog(dialogs::IDVideoMenuDlg, IDC_RESOLUTION, 100, iY, 125, 22 );
    dialogs::AddCheckBoxToDialog(dialogs::IDVideoMenuDlg, IDC_ANTI_ALIASING, "Anti-Aliasing", ( 250 - 200 ) / 2, iY += 26, 200, 22,
                                       false );
    dialogs::AddCheckBoxToDialog(dialogs::IDVideoMenuDlg, IDC_HIGH_MODEL_RES, "High res models", ( 250 - 200 ) / 2, iY += 26, 200, 22,
                                       true );
    dialogs::AddStaticToDialog(dialogs::IDVideoMenuDlg, IDC_MAX_DROIDS_TEXT, "Max Droids", ( 250 - 125 ) / 2, iY += 26, 125, 22 );
    dialogs::AddSliderToDialog(dialogs::IDVideoMenuDlg, IDC_MAX_DROIDS, ( 250 - 150 ) / 2, iY += 22, 150, 22, 1, MAX_DROID, 10 );
    dialogs::AddButtonToDialog(dialogs::IDVideoMenuDlg, IDC_APPLY, "Apply", ( 250 - 125 ) / 2, iY += 35, 125, 22 );
    dialogs::AddButtonToDialog(dialogs::IDVideoMenuDlg, IDC_BACK, "Back", ( 250 - 125 ) / 2, iY += 30, 125, 22 );
 
    // Setup the camera
    D3DXVECTOR3 MinBound( g_MinBound.x + CAMERA_SIZE, g_MinBound.y + CAMERA_SIZE, g_MinBound.z + CAMERA_SIZE );
    D3DXVECTOR3 MaxBound( g_MaxBound.x - CAMERA_SIZE, g_MaxBound.y - CAMERA_SIZE, g_MaxBound.z - CAMERA_SIZE );
    FirstPersonCamera::SetClipToBoundary( true, MinBound, MaxBound );
    FirstPersonCamera::SetEnableYAxisMovement( false );
    FirstPersonCamera::SetRotateButtons( false, false, true );
    FirstPersonCamera::SetScalers( 0.001f, 4.0f );
    D3DXVECTOR3 vecEye( 0.0f, -GROUND_Y + 0.7f, 0.0f );
    D3DXVECTOR3 vecAt ( 0.0f, -GROUND_Y + 0.7f, 1.0f );
    FirstPersonCamera::SetViewParams( vecEye, vecAt );
 
    GAME_STATE::gameMode = GAME_RUNNING;
    GAME_STATE::nAmmoCount = 0;
    GAME_STATE::fAmmoColorLerp = 1000.0f;
    GAME_STATE::BlendFromColor = D3DXCOLOR( 0.6f, 0, 0, 1 );
    GAME_STATE::bAutoAddDroids = false;
    GAME_STATE::bDroidMove = true;
 
}

When all of these changes have been made, the program will run exactly the same with the exception that it's now using C++ and AngelScript. Changes can now be made by altering the AngelScript file.

Conclusion


All of this may seem like a lot of work, especially getting all the bindings with the script, so many wonder "is it worth it?" That is a valid question that all should ask themselves when they are considering adding scripting support. For such a small program such as the Direct X XACTGame sample application, it's probably not neccessary, but as your projects increase in size the value of using scripting languages will become more apparent. The binding code for AngelScript is needed because AngelScript is a general purpose scripting language and it needs to know about the application to accurately communicate with the C++ code, but AngelScript has a nice interface, and after some practice, you'll see that doing the bindings is fairly straight forward.

So that's it for now. In the next part of this article, I'll script some of the other functions. If you download the source code, you'll have to make sure the include and library directories for the AngelScript SDK in the project properties are correct.

Coding style in this article


Listed in the best practices for AngelScript is to always check the return value for every function. In most of the AngelCode examples and in the manual an assert is used to check for errors. I don't use the assert, instead I've been using if(result < 0) return result;. This can easily be replaced by assert(r >= 0); as is used in the AngelScript documentation.

Also, my goal with this project was to change the XACTGame sample as little as possible. The XACTGame sample was designed to show certain techniques such as adding graphics and audio, and it uses a simple framework.

Getting AngelScript


You can download the latest version of the AngelScript SDK from the AngelCode website.
http://www.angelcode.com/
You'll find an excellent manual that explains the API in detail.

Note on Microsoft Source Code


Because Microsoft code was used in this program, I want to state some terms from the Direct X SDK EULA. The XACTGame sample was created by Microsoft and Microsoft owns the copyright. Changes made by Dominque Douglas have been clearly marked. Use of the source code provided does not change the license agreement for using Microsoft code. Microsoft code cannot be modified to work on non-Microsoft operating systems and Microsoft is not responsible for any claims related to the distribution of this program. Refer to the license agreement in the Direct X SDK for details.

Downloading This Project


The source code can be downloaded here: Attached File  XACTGameAngelScript-Part1.zip   475.83KB   0 downloads

Download note: Because of the size, this does not include the media files needed by the project such as the audio files and graphics files. You'll need to copy the "media" folder from the XACTGame sample in the Direct X SDK. The project is a Visual Studio 2010 solution.

NOTE: This article was originally posted on the Squared'D Programming Blog with some modifications.
http://squaredprogramming.blogspot.com/2013/12/programming-by-example-adding.html

Article Update Log


27 Dec 2013: Initial release

Why a Degree in Game Design Is a Bad Idea

$
0
0
For the thousands upon thousands of high school gamers all over the world, a degree in ‘game design’, ‘game development’, or ‘game art and animation’ is a perfect fit. With the sheer number of new degree programs relating itself to the booming video games industry, many students may not think beyond the title of the degree. Being a part of a company that creates the most amazing video games is within easier reach after obtaining that diploma. However, they fail to realize that this path isn’t the only way towards the goal.

In fact, many career advisers would convince you against trekking that one-way narrow path. Instead of shoehorning into a space where everyone else and their mothers are filling up, there exists other wider paths that actually lead somewhere.

A Games Design Degree Only Limits You


Acquiring a degree is supposed to open up new opportunities for employment. However, in relation to other courses, a BA or BS in game design wouldn’t help much in convincing target companies into hiring you. This is because there is less flexibility for a game design graduate to move beyond the scope of the field.

For example, someone who has a degree in computer science can easily get into the video games industry because the demand for top-notch programmers and coders never dwindle. On the flip side, game design graduates have a harder time getting both into a developer startup or big coder-friendly companies like Google or Amazon. The sheer amount of flexibility offered by a computer science degree severely outweighs anything positive about acquiring a degree in game design. This is a sentiment echoed by many game developers from Reddit in this discussion from over a year ago.

“Get a CS degree, and barring that, at least take enough programming courses (with an eye towards games) to know how to actually make a game, not just design it),” commented user Soviyet.

“Let me say it again to really drive it home. Get a CS degree,” pointed out independent game dev James Dalby from FrenchRoastGames.com. “What is a degree in game design going to do to help move you laterally into a similar industry? Not much,” Dalby added.

Jack of All Trades, Master of None


There is an underlying root for whatever reason we love video games. Whether it is the gameplay and mechanics, the lore and writing, the aesthetic design, or the sound – the passion to play and get inspired by games can be traced to a certain element whether we are conscious about it or not. It is better to find one’s true passion and spend four to five years of college fueling and honing that passion instead of studying about the other fields. It is important to create a holistic knowledge of the industry, but having no expertise has been the undoing of many graduates.

There are more specialized and well-rounded degrees available for each part of game development. Some examples are degrees in creative writing for concept and story development, media and visual communication for the community and design aspect, even finance and business degrees can possibly enter the scene with ease. The same cannot be said for a games design degree which dips a little bit in each part of the development spectrum without staying long enough to acquire mastery.

“Initially you may not know what area of games design you will be best suited to. Ask yourself these questions: What do I enjoy about games? Am I artistic? Am I good at problem solving?” advised Joshua Brown on his list of tips for aspiring game designers on How2Become.

A Strong Portfolio Weighs More than a Degree


The most important thing to note is that no matter how long your resume’s list of academic achievements is, if you can’t back it up with a strong portfolio then it’s pointless. One thing that game design school can help you with is to build a body of work. However, aspiring programmers, artists, and others can create the same with more focus on their field of expertise, which is more attractive to game companies.

Ultimately, someone who is dead-serious about a long career in the video games industry as a game designer or developer should just get a degree in computer science. What are your thoughts?

GameDev.net Soapbox logo design by Mark "Prinz Eugn" Simpson

Programming By Example - Adding AngelScript to a Game Part 2

$
0
0
For Part 1 of this series please click here: Programming By Example - Adding AngelScript to a Game Part 1

Introduction


This is part 2 of the article Adding AngelScript to an Existing Game. This article series focuses on how to add a scripting language to a game and goes beyond just teaching a scripting language's API. Using the XACTGame example from the Microsoft Direct X SDK, I will detail the entire process. In part 1, I discussed some AngelScript basics and I showed how to add some bindings so the script can communicate with the C++ code. Then I scripted the InitApp() function. This article will build on the concepts from part 1 and in it, I'll code the remaining bindings so the script can call the proper C++ functions. In part 3, I'll write the remaining scripts.

AngelScript Concepts Covered
  • Binding a C++ Interface to the script
    • Registering a Scoped Reference Type
    • Registering a POD Vale Type
    • Registering an Array as a Global Property
    • Registering functions with overloads

Adding Scripting to More Functions


As we did in part 1, we need to continue to survey the code to see what the dependencies are. Then we'll be able to figure out what bindings we'll need. Here are the remaining functions that I'll script and their dependencies.

void FireAmmo();
  • D3DXVECTOR4
  • D3DXMATRIXA16
  • D3DXMatrixInverse()
  • D3DXVec4Transform()
  • g_Camera.GetViewMatrix()
  • AMMO_STATE
  • PlayAudioCue( g_audioState.iAmmoFire );
void DroidPickNewDirection( int A );
  • D3DXQUATERNION
  • D3DXQuaternionRotationYawPitchRoll
  • D3DXMatrixRotationQuaternion
  • DROID_STATE
  • AI_STATE
  • GAME_STATE - DROID_STATE DroidQ[MAX_DROID];
void DroidChooseNewTask( int A );
  • rand()
  • GAME_STATE - DROID_STATE DroidQ[MAX_DROID];
  • D3DXMatrixRotationQuaternion
  • D3DXMATRIXA16
  • Play3DAudioCue( g_audioState.iDroidScan, &g_GameState.DroidQ[A].vPosition );
void HandleDroidAI( float fElapsedTime );
  • GAME_STATE - DROID_STATE DroidQ[MAX_DROID];
  • D3DXQuaternionSlerp
  • D3DXMATRIXA16
  • D3DXMatrixRotationQuaternion
  • D3DXQUATERNION
void HandleAmmoAI( float fElapsedTime );
CheckForInterAmmoCollision()
CheckForAmmoToWallCollision( A );
CheckForAmmoToDroidCollision( A );
  • GAME_STATE
  • DROID_STATE
  • AMMO_STATE

While I'd prefer to not write any more object types, after looking at that list, it seems like I'll need to make these new types:
  • D3DXVECTOR4
  • D3DXMATRIXA16
  • D3DXQUATERNION
  • DROID_STATE
  • AMMO_STATE

Registering a Scoped Reference Type for D3DXMATRIXA16


Adding the D3DXVECTOR4 and D3DXQUATERNION won't be hard considering they're almost exactly like D3DXVECTOR3. All it will take is some copying and pasting and some searching and replacing. The XACTGame sample application also uses D3DXMATRIXA16. This class inherits from D3DXMATRIX and overloads the new and delete operators to keep things 16 bit aligned. Because of this special memory requirement, we can't create the object as a value type, instead we'll need to create it as a reference type. A reference type is an object that resides in dynamic memory; however, to use reference types we'd have to add reference counting to the object which is not something I need to add. I don't need references that point to the same variable. Thankfully AngelScript provides a special kind of reference type just for this situation called a scoped reference type. Regarding scoped reference types, the manual says, "a scoped reference type will have the life time controlled by the scope of the variable that instanciate it, i.e. as soon as the variable goes out of scope the instance is destroyed."

To create a scoped reference type we'll need a function call like this:

r = engine->RegisterObjectType("D3DXMATRIXA16", 0, asOBJ_REF | asOBJ_SCOPED);

With all reference types, we don't register constructors. Instead we register factories. A factory should create the object using dynamic memory. We also need a release function that AngelScript will use to delete the object. This is what we'll need for the D3DXMATRIXA16 type.

static D3DXMATRIXA16 *D3DXMATRIXA16_Factory()
{
  return new D3DXMATRIXA16;
}

static D3DXMATRIXA16 *D3DXMATRIXA16_FactoryCopy(const D3DXMATRIXA16 &other)
{
  return new D3DXMATRIXA16(other);
}

static D3DXMATRIXA16 *D3DXMATRIXA16_FactoryFromfloats(float _11, float _12, float _13, float _14,
                                                      float _21, float _22, float _23, float _24,
                                                      float _31, float _32, float _33, float _34,
                                                      float _41, float _42, float _43, float _44)
{
  return new D3DXMATRIXA16(_11, _12, _13, _14,
                           _21, _22, _23, _24,
                           _31, _32, _33, _34,
                           _41, _42, _43, _44);
}

static void D3DXMATRIXA16_Release(D3DXMATRIXA16 *s)
{
  if( s ) delete s;
}

I'll register the operators the same way I did with D3DXVECTOR3 and D3DXVECTOR4 with one exception. When using scoped reference types, "any function that either takes or returns the type by value in C++ must be wrapped in order to permit AngelScript to manage the life time of the values." (from the AngelScript manual) This means that we will need to changed the binary operators so they return a pointer in C++, and a handle in AngelScript.

static D3DXMATRIXA16 *Matrix_opMul /* operator * */ ( const D3DXMATRIXA16 &lhs, const D3DXMATRIXA16 &rhs)
{
	// (From AngelScript Manual) any function that either takes or returns the type by value in C++ must
	// be wrapped in order to permit AngelScript to manage the life time of the values
	return new D3DXMATRIXA16(lhs * rhs);
}

Here's the function for registering the D3DXMATRIXA16 type. Redundant parts of the code have been omitted.

int RegisterD3DXMATRIXA16(asIScriptEngine *engine)
{
	int r;

	// Register the string type
	r = engine->RegisterObjectType("D3DXMATRIXA16", 0, asOBJ_REF | asOBJ_SCOPED);
	if(r < 0) return r;

	// with reference types we register factores and not constructors
	r = engine->RegisterObjectBehaviour("D3DXMATRIXA16", asBEHAVE_FACTORY, "D3DXMATRIXA16 @f()", asFUNCTION(D3DXMATRIXA16_Factory), asCALL_CDECL);
	r = engine->RegisterObjectBehaviour("D3DXMATRIXA16", asBEHAVE_FACTORY, "D3DXMATRIXA16 @f(const D3DXMATRIXA16 &in)", asFUNCTION(D3DXMATRIXA16_FactoryCopy), asCALL_CDECL);
	r = engine->RegisterObjectBehaviour("D3DXMATRIXA16", asBEHAVE_FACTORY, 
		"D3DXMATRIXA16 @f(float, float, float, float, float, float, float, float, float, float, float, float, float, float, float, float)",
		asFUNCTION(D3DXMATRIXA16_FactoryFromfloats), asCALL_CDECL);
	r = engine->RegisterObjectBehaviour("D3DXMATRIXA16", asBEHAVE_RELEASE, "void f()", asFUNCTION(D3DXMATRIXA16_Release), asCALL_CDECL_OBJLAST);

	r = engine->RegisterObjectMethod("D3DXMATRIXA16", "D3DXMATRIXA16 &opMulAssign( const D3DXMATRIXA16 &in)", asFUNCTION(Matrix_opMulAssign), asCALL_CDECL_OBJLAST);
	if(r < 0) return r;
	r = engine->RegisterObjectMethod("D3DXMATRIXA16", "D3DXMATRIXA16 &opAddAssign( const D3DXMATRIXA16 &in)", asFUNCTION(Matrix_opAddAssign), asCALL_CDECL_OBJLAST);
	if(r < 0) return r;
	r = engine->RegisterObjectMethod("D3DXMATRIXA16", "D3DXMATRIXA16 &opSubAssign( const D3DXMATRIXA16 &in)", asFUNCTION(Matrix_opSubAssign), asCALL_CDECL_OBJLAST);
	if(r < 0) return r;
	r = engine->RegisterObjectMethod("D3DXMATRIXA16", "D3DXMATRIXA16 &opMulAssign( float)", asFUNCTION(Matrix_opMulAssignF), asCALL_CDECL_OBJLAST);
	if(r < 0) return r;
	r = engine->RegisterObjectMethod("D3DXMATRIXA16", "D3DXMATRIXA16 &opDivAssign( float)", asFUNCTION(Matrix_opDivAssign), asCALL_CDECL_OBJLAST);
	if(r < 0) return r;

	// instead of returning a value, the operator must return a handle because this is a scoped reference type
	r = engine->RegisterObjectMethod("D3DXMATRIXA16", "D3DXMATRIXA16 @Matrix_opMul(const D3DXMATRIXA16 &in)", asFUNCTION(Matrix_opMul), asCALL_CDECL_OBJFIRST);
	if(r < 0) return r;
	r = engine->RegisterObjectMethod("D3DXMATRIXA16", "D3DXMATRIXA16 @Matrix_opAdd(const D3DXMATRIXA16 &in)", asFUNCTION(Matrix_opAdd), asCALL_CDECL_OBJFIRST);
	if(r < 0) return r;
	r = engine->RegisterObjectMethod("D3DXMATRIXA16", "D3DXMATRIXA16 @Matrix_opSub(const D3DXMATRIXA16 &in)", asFUNCTION(Matrix_opSub), asCALL_CDECL_OBJFIRST);
	if(r < 0) return r;

	// some code omitted
	...

	r = engine->RegisterObjectMethod("D3DXMATRIXA16", "D3DXMATRIXA16 &opAssign(const D3DXMATRIXA16 &in)", asMETHODPR(D3DXMATRIXA16, operator =, (const D3DXMATRIXA16&), D3DXMATRIXA16&), asCALL_THISCALL);
	if(r < 0) return r;

	r = engine->RegisterObjectMethod("D3DXMATRIXA16", "bool opEquals(const D3DXMATRIXA16 &in) const", asFUNCTION(MatricesEqual), asCALL_CDECL_OBJFIRST);
	if(r < 0) return r;

	// register the properties
	r = engine->RegisterObjectProperty("D3DXMATRIXA16", "float _11", asOFFSET(D3DXMATRIXA16,_11));
	if(r < 0) return r;
	r = engine->RegisterObjectProperty("D3DXMATRIXA16", "float _12", asOFFSET(D3DXMATRIXA16,_12));
	if(r < 0) return r;
	r = engine->RegisterObjectProperty("D3DXMATRIXA16", "float _13", asOFFSET(D3DXMATRIXA16,_13));

	// some code omitted
	...

	return r;
}

There's one more thing I want to mention about this type. The D3DXMATRIXA16 type is derived from D3DMATRIX which has a union so the data can be accessed through an array or through individual floats. When I add this to AngelScript, I won't add support for the array.

typedef struct _D3DMATRIX {
    union {
        struct {
            float        _11, _12, _13, _14;
            float        _21, _22, _23, _24;
            float        _31, _32, _33, _34;
            float        _41, _42, _43, _44;

        };
        float m[4][4];
    };
} D3DMATRIX;

Registering a POD Vale Type


The DROID_STATE struct is a little special in that it doesn't have any constructors, destructors, or pointers. Also, copying and assignment can be done using a bit-to-bit copy. Because of this, we can register it with the flag asOBJ_POD. When using this flag, we don't have to supply a constructor, destructor, or assignment operator.

int RegisterDROID_STATE(asIScriptEngine *scriptengine)
{
	int r;

	// register AI_STATE enum
	r = scriptengine->RegisterEnum("AI_STATE");
	if(r < 0) return r;

	r = scriptengine->RegisterEnumValue("AI_STATE", "AI_TURNING", (int)AI_TURNING);
	if(r < 0) return r;

	r = scriptengine->RegisterEnumValue("AI_STATE", "AI_MOVING", (int)AI_MOVING);
	if(r < 0) return r;

	r = scriptengine->RegisterEnumValue("AI_STATE", "AI_STOPPED", (int)AI_STOPPED);
	if(r < 0) return r;

	// Register DROID_STATE as POD Value type
	// Register a primitive type, that doesn't need any special management of the content
	r = scriptengine->RegisterObjectType("DROID_STATE", sizeof(DROID_STATE), asOBJ_VALUE | asOBJ_APP_CLASS | asOBJ_POD);

	r = scriptengine->RegisterObjectProperty("DROID_STATE", "bool bActive", asOFFSET(DROID_STATE,bActive));
	if(r < 0) return r;
	r = scriptengine->RegisterObjectProperty("DROID_STATE", "D3DXVECTOR3 vPosition", asOFFSET(DROID_STATE,vPosition));
	if(r < 0) return r;
	r = scriptengine->RegisterObjectProperty("DROID_STATE", "D3DXVECTOR3 vVelocity", asOFFSET(DROID_STATE,vVelocity));
	if(r < 0) return r;
	r = scriptengine->RegisterObjectProperty("DROID_STATE", "D3DXVECTOR3 vNudgeVelocity", asOFFSET(DROID_STATE,vNudgeVelocity));
	if(r < 0) return r;
	r = scriptengine->RegisterObjectProperty("DROID_STATE", "AI_STATE aiState", asOFFSET(DROID_STATE,aiState));
	if(r < 0) return r;
	r = scriptengine->RegisterObjectProperty("DROID_STATE", "float fTargetRotation", asOFFSET(DROID_STATE,fTargetRotation));
	if(r < 0) return r;
	r = scriptengine->RegisterObjectProperty("DROID_STATE", "D3DXQUATERNION qTarget", asOFFSET(DROID_STATE,qTarget));
	if(r < 0) return r;
	r = scriptengine->RegisterObjectProperty("DROID_STATE", "D3DXQUATERNION qStart", asOFFSET(DROID_STATE,qStart));
	if(r < 0) return r;
	r = scriptengine->RegisterObjectProperty("DROID_STATE", "D3DXQUATERNION qCurrent", asOFFSET(DROID_STATE,qCurrent));
	if(r < 0) return r;
	r = scriptengine->RegisterObjectProperty("DROID_STATE", "float fRotInterp", asOFFSET(DROID_STATE,fRotInterp));
	if(r < 0) return r;
	r = scriptengine->RegisterObjectProperty("DROID_STATE", "float fTaskTimer", asOFFSET(DROID_STATE,fTaskTimer));
	if(r < 0) return r;
	r = scriptengine->RegisterObjectProperty("DROID_STATE", "int nHitPoints", asOFFSET(DROID_STATE,nHitPoints));
	if(r < 0) return r;
	r = scriptengine->RegisterObjectProperty("DROID_STATE", "float fDeathAnimation", asOFFSET(DROID_STATE,fDeathAnimation));
	if(r < 0) return r;
	r = scriptengine->RegisterObjectProperty("DROID_STATE", "float fAlpha", asOFFSET(DROID_STATE,fAlpha));
	if(r < 0) return r;
	r = scriptengine->RegisterObjectProperty("DROID_STATE", "D3DXCOLOR Specular", asOFFSET(DROID_STATE,Specular));

	return r;
}

I'll handle the AMMO_STATE structure the same way. To see how this is coded, see "scriptdroidammostates.cpp" in the source code for this article.

Arrays - Getting The Droid State


Now there's one big thing to consider. XACTGame sample apps's DROID_STATE and AMMO_STATE information are stored in arrays inside of the GAME_STATE struct. C/C++ static arrays cannot be registered directly with AngelScript because AngelScript can't garuntee the lifetime of the array. Applications that want to use arrays need to register an array type. The AngelScript SDK comes with an add-on that can be used to register arrays, the class CScriptArray. This is a very useful class, but because it's for generic arrays, elements can't be accessed without converting back and forth between the type and void pointers. This means I can't just plug it into my C++ code. To get around this, I've written a STL-style wrapper for the CScriptArray class that behaves much like std::vector. Now all I have to do is change the array declaration to this:

CScriptArraySTL<AMMO_STATE> AmmoQ;
CScriptArraySTL<DROID_STATE>  DroidQ;

You can download the CScriptArraySTL class, by checking out one of the following blog entries:
Blogspot post: C++ STL-Style Array That's Compatible with AngelScript
GameDev post: C++ STL-Style Array That's Compatible with AngelScript

I'll also add a Reset() method to the GAME_STATE class and register it with AngelScript so the script will be able to reset the state. The Reset() method will also be important because the XACTGame sample previously just used ZeroMemory(), a macro that calls memset(), to clear the data. This will no longer work because of the CScriptArraySTL array. I've also added clear to the droid state.

// Reset in GAME_STATE
void Reset()
{
	// Reset here so we don't have to use ZeroMemory which messes up the CScriptArraySTL<> arrays
	gameMode		= GAME_RUNNING;

	nAmmoCount		= 0;
	fAmmoColorLerp	= 0.0f;
	BlendToColor	= (DWORD)0;
	BlendFromColor	= (DWORD)0;

	nDroidCount	= 0;
	nMaxDroids	= 0;

	bDroidCreate			= false;
	bMassDroidKill			= false;
	fDroidCreateCountdown	= 0.0f;

	bDroidMove		= false;
	bAutoAddDroids	= false;

	// clear the static arrays
	ZeroMemory(gamePad, sizeof(DXUT_GAMEPAD) * DXUT_MAX_CONTROLLERS);
    
	// reset Ammo array
	AmmoQ.resize(MAX_AMMO);
	for(auto it = AmmoQ.begin(); it < AmmoQ.end(); it++)
	{
		(*it).clear();
	}

	// reset the Droid array
	DroidQ.resize(MAX_DROID);
	for(auto it = DroidQ.begin(); it < DroidQ.end(); it++)
	{
		(*it).clear();
	}
}

// changes to RegisterGameStateInterface
...
result = scriptengine->RegisterGlobalFunction("void Reset()", asMETHOD(GAME_STATE, Reset), asCALL_THISCALL_ASGLOBAL, &g_GameState);
if(result < 0) return result;

// initialize the droid state and ammo state arrays
result = g_GameState.DroidQ.InitArray(scriptengine, "array<DROID_STATE>");
if(result < 0) return result;

result = g_GameState.AmmoQ.InitArray(scriptengine, "array<AMMO_STATE>");
if(result < 0) return result;
	
// register the droid state and ammo state arrays
result = scriptengine->RegisterGlobalProperty("array<DROID_STATE> DroidQ", g_GameState.DroidQ.GetRef());
if(result < 0) return result;

result = scriptengine->RegisterGlobalProperty("array<AMMO_STATE> AmmoQ", g_GameState.AmmoQ.GetRef());
if(result < 0) return result;
...

Registering the Remaining Math Functions


While keeping each type in its own source file, I've decided to clean up the DirectX math bindings by making one header and function that will add all of the types and functions. I had to make wrappers for most of the functions because the DirectX math functions use pointers but I want to use references with my AngelScript value type objects, and the wrapped functions will just return void and not a pointer to the out parameter.

Also, even though it's not a DirectX math function, I've decided to add my rand binding here as well with one change. Instead of just using the normal rand(), I'm going to change it so it takes the min and max values as a parameter.

static int rand(int min, int max)
{
	int diff = max - min;
	return (rand() % diff) + min;
}

Now when I register the function, I'll have to do it a little differently because I just overloaded rand() so it now has two versions with different params. So AngelScript knows which version to use, we need to register it like this:

result = scriptengine->RegisterGlobalFunction("int rand(int, int)", asFUNCTIONPR(rand, (int, int), int), asCALL_CDECL);

The asFUNCTIONPR macro takes 3 parameters: the function name, the parameter list, and the return type.

This function registers all of the needed math functions.

int RegisterD3DXMathFunctions(asIScriptEngine *scriptengine)
{
	int result;

	// register our types first
	result = RegisterD3DXVECTOR3(scriptengine);
	if(result < 0) return result;
	result = RegisterD3DXVECTOR4(scriptengine);
	if(result < 0) return result;
	result = RegisterD3DXQUATERNION(scriptengine);
	if(result < 0) return result;
	result = RegisterD3DXMATRIXA16(scriptengine);
	if(result < 0) return result;

	// register the global functions
	result = scriptengine->RegisterGlobalFunction("void D3DXMatrixInverse(D3DXMATRIXA16 &out, float &out, const D3DXMATRIXA16 &in)", asFUNCTION(MatrixInverse), asCALL_CDECL);
	if(result < 0) return result;
	result = scriptengine->RegisterGlobalFunction("void D3DXVec4Transform(D3DXVECTOR4 &out, const D3DXVECTOR4 &in, const D3DXMATRIXA16 &in)", asFUNCTION(Vec4Transform), asCALL_CDECL);
	if(result < 0) return result;
	result = scriptengine->RegisterGlobalFunction("void D3DXQuaternionRotationYawPitchRoll(D3DXQUATERNION &out, float, float, float)", asFUNCTION(QuaternionRotationYawPitchRoll), asCALL_CDECL);
	if(result < 0) return result;
	result = scriptengine->RegisterGlobalFunction("void D3DXMatrixRotationQuaternion(D3DXMATRIXA16 &out, const D3DXQUATERNION &in)", asFUNCTION(MatrixRotationQuaternion), asCALL_CDECL);
	if(result < 0) return result;
	result = scriptengine->RegisterGlobalFunction("void D3DXQuaternionSlerp(D3DXQUATERNION &out, const D3DXQUATERNION &in, const D3DXQUATERNION &in, float t)", asFUNCTION(QuaternionSlerp), asCALL_CDECL);
	if(result < 0) return result;

	// add rand() here
	result = scriptengine->RegisterGlobalFunction("int rand(int, int)", asFUNCTIONPR(rand, (int, int), int), asCALL_CDECL);

	return result;
}

Handling Audio


I won't script any of the audio functions, but some of the other functions that I will script need access to some of the audio functionality. I'll need to give access to the following functions:

	HRESULT PlayAudioCue( XACTINDEX iCueIndex );
	HRESULT Play3DAudioCue( XACTINDEX iCueIndex, D3DXVECTOR3* pvPosition );
	void SetNumDroidsForAudio( int nDroidCount );

Adding these should be easy enough, but I'll need to wrap PlayAudioCue() and Play3DAudioCue() because they take the cue index as a parameter. I don't want to provide access to those variables so instead I'll make an enum that's a list of all of the cue indices and the wrapped functions will take the enum as a parameter.

enum AudioCues
{
    Cue_iAmmoBounce = 0,
    Cue_iAmmoFire,
    Cue_iDroidDestroyed,
    Cue_iDroidScan,
    Cue_iBackgroundMusic,
    Cue_iRoomRumble
};

XACTINDEX GetCueIndex(AudioCues cue)
{
	switch(cue)
	{
		case Cue_iAmmoBounce:
			return g_audioState.iAmmoBounce;
		case Cue_iAmmoFire:
			return g_audioState.iAmmoFire;
		... // some code omitted
	}

	return 0;
}

void PlayAudioCueWrapper( AudioCues cue )
{
	PlayAudioCue(GetCueIndex(cue));
}

void Play3DAudioCueWrapper( AudioCues cue, const D3DXVECTOR3 &vPosition )
{
	Play3DAudioCue(GetCueIndex(cue), (D3DXVECTOR3 *)&vPosition);
}

int RegisterAudioInterface(asIScriptEngine *scriptengine)
{
	int result;

	// set the namespace
	result = scriptengine->SetDefaultNamespace("AUDIO"); 
	if(result < 0) return result;

	// first register our enum
	result = scriptengine->RegisterEnum("AudioCues");
	if(result < 0) return result;

	result = scriptengine->RegisterEnumValue("AudioCues", "Cue_iAmmoBounce", (int)Cue_iAmmoBounce);
	if(result < 0) return result;

	result = scriptengine->RegisterEnumValue("AudioCues", "Cue_iAmmoFire", (int)Cue_iAmmoFire);
	if(result < 0) return result;

	... // some code omitted

	result = scriptengine->RegisterGlobalFunction("void PlayAudioCue(AudioCues)", asFUNCTION(PlayAudioCueWrapper), asCALL_CDECL);
	if(result < 0) return result;

	result = scriptengine->RegisterGlobalFunction("void Play3DAudioCue(AudioCues, const D3DXVECTOR3 &in)", asFUNCTION(Play3DAudioCueWrapper), asCALL_CDECL);
	if(result < 0) return result;

	result = scriptengine->RegisterGlobalFunction("void SetNumDroidsForAudio(int)", asFUNCTION(SetNumDroidsForAudio), asCALL_CDECL);
	if(result < 0) return result;

	// reset back to global namespace
	result = scriptengine->SetDefaultNamespace(""); 

	return result;
}

Conclusion


It's taken some time, but now I have finished all of the needed bindings between AngelScript and C++ that I'll need. Now that all of the bindings are complete, I need to work on adding the scripts. AngelScript is a very powerful scripting language and I hope to show some of its features in part 3 of this article. In part 3, I'll write all of the scripts and also show how to restrict bindings to some scripts.

Coding style in this article


Listed in the best practices for AngelScript is to always check the return value for every function. In most of the AngelCode examples and in the manual an assert is used to check for errors. I don't use the assert, instead I've been using if(result < 0) return result;. This can easily be replaced by assert(r >= 0); as is used in the AngelScript documentation.

Also, my goal with this project was to change the XACTGame sample as little as possible. The XACTGame sample was designed to show certain techniques such as adding graphics and audio, and it uses a simple framework.

Getting AngelScript


You can download the latest version of the AngelScript SDK from the AngelCode website.
http://www.angelcode.com/
You'll find an excellent manual that explains the API in detail.

Note on Microsoft Source Code


Because Microsoft code was used in this program, I want to state some terms from the Direct X SDK EULA. The XACTGame sample was created by Microsoft and Microsoft owns the copyright. Changes made by Dominque Douglas have been clearly marked. Use of the source code provided does not change the license agreement for using Microsoft code. Microsoft code cannot be modified to work on non-Microsoft operating systems and Microsoft is not responsible for any claims related to the distribution of this program. Refer to the license agreement in the Direct X SDK for details.

Downloading This Project


The source code can be downloaded here: Attached File  XACTGameAngelScript-Part2.zip   514.04KB   38 downloads


Download note: Because of the size, this does not include the media files needed by the project such as the audio files and graphics files. You'll need to copy the "media" folder from the XACTGame sample in the Direct X SDK. You may need to alter the project's include and library directories to match your system. For simplicity, the AngelScript add-ons that were used in this project have been included. The project is a Visual Studio 2010 solution.


Article Update Log


9 Jan 2014: Initial release

This article was orignally posted on th Squared Programming Blog at http://squaredprogramming.blogspot.com/2014/01/programming-by-example-adding.html with some modifications.

Memory Usage Optimization Using Cache

$
0
0
When the processes running on your machine attempt to allocate more memory than your system has available, the kernel begins to swap memory pages to and from the disk. This is done in order to free up sufficient physical memory to meet the RAM allocation requirements of the requestor.

Excessive use of swapping is called thrashing and is undesirable because it lowers overall system performance, mainly because hard drives are far slower than RAM.

If your application needs to use a large amount of data, you will be exposed to thrashing and your application could slow dramatically. Two solutions exist: either optimize your applications to use memory more efficiently, or add more physical RAM to the system.

Using a cache is a popular way to optimize your memory usage. The idea is to store in a cache the data loaded from the disk; this cache will contains many slots, each one containing a specific piece of data, and some slots will be released if the cache size exceeds a certain value.

The performance of the cache depends on:
  • The container: it could be a queue, an array, a list or maybe a custom container. The choice of one of these containers could impact your cache performance a lot.
  • The max size of the cache.
  • The algorithm used to free entries from the cache: When the cache reaches its maximum, you have to decide which entries to release, for example you could:
    • Release the first slots loaded.
    • Release the last slots loaded.
    • Release the less-used slots.
Let's discover the advantages of using the cache by studying these two scenarios:

Scenario 1: The application uses a big amount of data stored in disk (Irrlicht engine)


This scenario occurs when you use a large amount of data stored in the disk, and when you load this data into the memory, your application becomes slow. When you search for the cause, you discover that your application consumes a lot of memory. You decide to free the data as soon as you finish using it, but the application is still slow, and this time it's due to thrashing. Indeed you need many times to reload the data released.

Let's take a look inside the Irrlicht engine using CppDepend and discover some facts about the cache used.

The Irrlicht Engine is an open-source high performance realtime 3D engine written in C++. And as every game engine it manages mesh classes.

A mesh is a collection of polygons. Each polygon is stored in memory as an ordered list of 3D points. In order to create a 3D cube mesh we would need to specify the eight corner points of the cube in 3D space. Each polygon could then be defined using four of eight points.

Meshes are stored in disk, and they must be loaded into memory to work with. Irrlicht provides the IMeshCache interface to define the cache contract, and the CMeshCache implement it.


Attached Image: cache2.png


This interface provides many useful methods to deal with the cache, and to discover which container is used let's search for methods invoked by the AddMesh method:


Attached Image: cache3.png


Irrlicht use the templated custom container irr::core::array<t,talloc>, this container is designed to be performent to store and search for meshs by index and name.

Here are for example the methods invoked by getMeshByName


Attached Image: cache4.png


The cache use the binary search algorithm, to improve the performence of the cache when searching mesh by name.

What about the algorithm used to free meshes?

No algorithm is provided from Irrelicht engine to release the cache, indeed it depends on the game logic using the engine, and it's the reponsibility of the Irrlicht user to decide when to free a mesh.

Scenario 2: The application uses a big ammount of calculated data in memory (Doxygen)


In this case the data used is not stored in the disk, but it's calculated after some process treatments.

Let's take as example the Doxygen tool, which is a useful tool for generating documentation from annotated C++ sources.

Doxygen take as entry source files to parse, and after a lexical analysis, many datas are extracted from these files to be stored in the memory to treat them and generate the documentation.

If you parse a big project, the data extracted from the sources become very big, and it would consume a lot of memory, and to resolve this issue Doxygen uses a cache to store these data.

Doxygen gets the cache max size from the configuration file:

int cacheSize = Config_getInt("SYMBOL_CACHE_SIZE");

It's a good idea to let this parameter be configurable, so you can increase the cache if you have a machine with a big physical memory size to increase the cache performence. However for the newer Doxygen releases this parameter is removed from the config file, and a default value is used.

And here's the container used by Doxygen:

Doxygen::symbolCache   = new ObjCache(16+cacheSize); // 16 -> room for 65536 elements, 

What about the algorithm to free entries from cache?

The Doxygen developers could decide the optimized algo to release the cache, it depends on how they implemented the parser; here's the code snippet from Doxygen code source responsible for releasing the cache when it reaches its maximum:

void MemberDef::makeResident() const
{ 
  if (m_cacheHandle==-1) // not yet in cache
  { 
    MemberDef *victim = 0;
    MemberDef *that = (MemberDef*)this; // fake method constness
    that->m_cacheHandle = Doxygen::symbolCache->add(that,(void **)&victim);
    //printf("adding %s to cache, handle=%d\n",m_impl->name.data(),that->m_cacheHandle);
    if (victim)  // cache was full, victim was the least recently used item and has to go
    {
      victim->m_cacheHandle=-1; // invalidate cache handle
      victim->saveToDisk();     // store the item on disk
    }
    else // cache not yet full
    {
      //printf("Adding %s to cache, handle=%d\n",m_impl->name.data(),m_cacheHandle);
    }
    if (m_storagePos!=-1) // already been written to disk
    {
      if (isLocked()) // locked in memory
      {
        assert(m_impl!=0);
        that->m_flushPending=FALSE; // no need to flush anymore
      }
      else // not locked in memory
      {
        assert(m_impl==0);
        loadFromDisk();
      }
    }
  }
  else // already cached, make this object the most recently used.
  {
    assert(m_impl!=0);
    //printf("Touching symbol %s\n",m_impl->name.data());
    Doxygen::symbolCache->use(m_cacheHandle);
  }
}

As specified in the makeResident method code, the least recently used item is removed if the cache is full.

Let's discover where this method is invoked:


Attached Image: cache.png


This method is invoked for almost all MemberDef methods, it's called each time you have to access the MemberDef state to check if this member is loaded or not. Load it if it's not the case and remove the least recently used member from the cache.

The impact of using the cache


Using the cache could increase the performence of your application, but did the cache bring a big optimisation or just a micro optimisation and it's not worth to add it in your application?

Let's take as example the Doxygen project, I modified its code to not use the cache and I parsed some C++ projects with this modified version. And surprisingly the parsing time has much increased, sometimes from 5 min to 25 min.

Conclusion


Using the cache is a powerful technique to increase the performance if you use a large amount of data. Discovering how open-source projects implement the cache could be very useful to understand how to implement it in your applications.

Introduction to Octrees

$
0
0
What exactly is an Octree? If you're completely unfamiliar with them, I recommend reading the wikipedia article (read time: ~5 minutes). This is a sufficient description of what it is but is barely enough to give any ideas on what it's used for and how to actually implement one. In this article, I will do my best to take you through the steps necessary to create an octree data structure through conceptual explanations, pictures, and code, and show you the considerations to be made at each step along the way. I don't expect this article to be the authoritative way to do octrees, but it should give you a really good start and act as a good reference.

Assumptions


Before we dive in, I'm going to be making a few assumptions about you as a reader:

  1. You are very comfortable with programming in a C-syntax-style language (I will be using C# with XNA).
  2. You have programmed some sort of tree-like data structure in the past, such as a binary search tree and are familiar with recursion and its strengths and pitfalls.
  3. You know how to do collision detection with bounding rectangles, bounding spheres, and bounding frustums.
  4. You have a good grasp of common data structures (arrays, lists, etc) and understand Big-O notation (you can also learn about Big-O in this GDnet article).
  5. You have a development environment project which contains spatial objects which need collision tests.

Setting the stage


Let's suppose that we are building a very large game world which can contain thousands of physical objects of various types, shapes and sizes, some of which must collide with each other. Each frame we need to find out which objects are intersecting with each other and have some way to handle that intersection. How do we do it without killing performance?


Attached Image: Octree_1a.png


Brute force collision detection


The simplest method is to just compare each object against every other object in the world. Typically, you can do this with two for loops. The code would look something like this:

foreach(gameObject myObject in ObjList)
{
	foreach(gameObject otherObject in ObjList)
	{
		if(myObject == otherObject) continue;	//avoid self collision check
		if(myObject.CollidesWith(otherObject))
		{
			//code to handle the collision
		}
	}
}

Conceptually, this is what we're doing in our picture:


Attached Image: Octree_1c.png


Each red line is an expensive CPU test for intersection. Naturally, you should feel horrified by this code because it is going to run in O(N^2) time. If you have 10,000 objects, then you're going to be doing 100,000,000 collision checks (hundred million). I don't care how fast your CPU is or how well you've tuned your math code, this code would reduce your computer to a sluggish crawl. If you're running your game at 60 frames per second, you're looking at 60 * 100 million calculations per second! It's nuts. It's insane. It's crazy. Let's not do this if we can avoid it, at least not with a large set of objects. This would only be acceptable if we're only checking, say, 10 items against each other (100 checks is palatable). If you know in advance that your game is only going to have a very small number of objects (ie, asteriods), you can probably get away with using this brute force method for collision detection and ignore octrees altogether. If/when you start noticing performance problems due to too many collision checks per frame, consider some simple targeted optimizations:

1. How much computation does your current collision routine take? Do you have a square root hidden away in there (ie, a distance check)? Are you doing a granular collision check (pixel vs pixel, triangle vs triangle, etc)? One common technique is to perform a rough, coarse check for collision before testing for a granular collision check. You can give your objects an enclosing bounding rectangle or bounding sphere and test for intersection with these before testing against a granular check which may involve a lot more math and computation time.

Note:  Use a "distance squared" check for comparing distance between objects to avoid using the square root method. Square root calculation typically uses the newtonian method of approximation and can be computationally expensive.


2. Can you get away with calculating fewer collision checks? If your game runs at 60 frames per second, could you skip a few frames? If you know certain objects behave deterministically, can you "solve" for when they will collide ahead of time (ie, pool ball vs. side of pool table). Can you reduce the number of objects which need to be checked for collisions? A technique for this would be to separate objects into several lists. One list could be your "stationary" objects list. They never have to test for collision against each other. The other list could be your "moving" objects, which need to be tested against all other moving objects and against all stationary objects. This could reduce the number of necessary collision tests to reach an acceptable performance level.

3. Can you get away with removing some object collision tests when performance becomes an issue? For example, a smoke particle could interact with a surface object and follow its contours to create a nice aesthetic effect, but it wouldn't break game play if you hit a predefined limit for collision checks and decided to stop ignoring smoke particles for collision. Ignoring essential game object movement would certainly break game play though (ei, player bullets stop intersecting with monsters). So, perhaps maintaining a priority list of collision checks to compute would help. First you handle the high priority collision tests, and if you're not at your threshold, you can handle lower priority collision tests. When the threshold is reached, you dump the rest of the items in the priority list or defer them for testing at a later time.

4. Can you use a faster but still simplistic method for collision detection to get away from a O(N^2) runtime? If you eliminate the objects you've already checked for collisions against, you can reduce the runtime to O(N(N+1)/2), which is much faster and still easy to implement. (technically, it's still O(N^2))

In terms of software engineering, you may end up spending more time than it's worth fine-tuning a bad algorithm & data structure choice to squeeze out a few more ounces of performance. The cost vs. benefit ratio becomes increasingly unfavorable and it becomes time to choose a better data structure to handle collision detection. Spatial partioning algorithms are the proverbial nuke to solving the runtime problem for collision detection. At a small upfront cost to performance, they'll reduce your collision detection tests to logarithmic runtime. The upfront costs of development time and CPU overhead are easily outweighed by the scalability benefits and performance gains.

Conceptual background on spatial partioning


Let's take a step back and look at spatial partitioning and trees in general before diving into Octrees. If we don't understand the conceptual idea, we have no hope of implementing it by sweating over code.

Looking at the brute force implementation above, we're essentially taking every object in the game and comparing their positions against all other objects in the game to see if any are touching. All of these objects are contained spatially within our game world. Well, if we create an enclosing box around our game world and figure out which objects are contained within this enclosing box, then we've got a region of space with a list of contained objects within it. In this case, it would contain every object in the game. We can notice that if we have an object on one corner of the world and another object way on the other side, we don't really need to, or want to, calculate a collision check against them every frame. It'd be a waste of precious CPU time. So, let's try something interesting! If we divide our world exactly in half, we can create three seperate lists of objects. The first list of objects, List A, contains all objects on the left half of the world. The second list, List B, contains objects on the right half of the world. Some objects may touch the dividing line such that they're on each side of the line, so we'll create a third list, List C, for these objects.


Attached Image: Octree_2a.png


We can notice that with each subdivision, we're spatially reducing the world in half and collecting a list of objects in that resulting half. We can elegantly create a binary search tree to contain these lists. Conceptually, this tree should look something like so:


Attached Image: Octree_2b.png


In terms of pseudo code, the tree data structure would look something like this:

public class BinaryTree
{
    //This is a list of all of the objects contained within this node of the tree
   	private List<GameObject> m_objectList;
    
    //These are pointers to the left and right child nodes in the tree
    private BinaryTree m_left, m_right;
    
    //This is a pointer to the parent object (for upward tree traversal).
    private BinaryTree m_parent;
}

We know that all objects in List A will never intersect with any objects in List B, so we can almost eliminate half of the number of collision checks. We've still got the objects in List C which could touch objects in either list A or B, so we'll have to check all objects in List C against all objects in Lists A, B & C. If we continue to sub-divide the world into smaller and smaller parts, we can further reduce the number of necessary collision checks by half each time.

This is the general idea behind spatial partitioning. There are many ways to subdivide a world into a tree-like data structure (BSP trees, Quad Trees, K-D trees, OctTrees, etc). Now, by default, we're just assuming that the best division is a cut in half, right down the middle, since we're assuming that all of our objects will be somewhat uniformally distributed throughout the world. It's not a bad assumption to make, but some spatial division algorithms may decide to make a cut such that each side has an equal amount of objects (a weighted cut) so that the resulting tree is more balanced. However, what happens if all of these objects move around? In order to maintain a near even division, you'd have to either shift the splitting plane or completely rebuild the tree each frame. It'd be a bit of a mess with a lot of complexity. So, for my implementation of a spatial partioning tree I decided to cut right down the middle every time. As a result, some trees may end up being a bit more sparse than others, but that's okay -- it doesn't cost much.

To subdivide or not to subdivide? That is the question.


Let's assume that we have a somewhat sparse region with only a few objects. We could continue subdividing our space until we've found the smallest possible enclosing area for that object. But is that really necessary? Let's remember that the whole reason we're creating a tree is to reduce the number of collision checks we need to perform each frame -- not to create a perfectly enclosing region of space for every object. Here are the rules I use for deciding whether to subdivide or not:
  • If we create a subdivision which only contains one object, we can stop subdividing even though we could keep dividing further. This rule will become an important part of the criteria for what defines a "leaf node" in our octree.
  • The other important criteria is to set a minimum size for a region. If you have an extremely small object which is nanometers in size (or, god forbid, you have a bug and forgot to initialize an object size!), you're going to keep subdividing to the point where you potentially overflow your call stack. For my own implementation, I defined the smallest containing region to be a 1x1x1 cube. Any objects in this teeny cube will just have to be run with the O(N^2) brute force collision test (I don't anticipate many objects anyways!).
  • If a containing region doesn't contain any objects, we shouldn't try to include it in the tree.
We can take our subdivision by half one step further and divide the 2D world space into quadrants. The logic is essentially the same, but now we're testing for collision with four squares instead of two rectangles. We can continue subdividing each square until our rules for termination are met. The representation of the world space and corresponding data structure for a quad tree would look something like this:


Attached Image: Quadtree_1a.png


If the quad tree subdivision and data structure makes sense, then an octree should be pretty straight forward as well. We're just adding a third dimension, using bounding cubes instead of bounding squares, and have eight possible child nodes instead of four. Some of you might wonder what should happen if you have a game world with non-cubic dimensions, say 200x300x400. You can still use an octree with cubic dimensions -- some child nodes will just end up empty if the game world doesn't have anything there. Obviously, you'll want to set the dimensions of your octree to at least the largest dimension of your game world.

Octree Construction


So, as you've read, an octree is a special type of subdividing tree commonly used for objects in 3D space (or anything with 3 dimensions). Our enclosing region is going to be a three dimensionsal rectangle (commonly a cube). We will then apply our subdivision logic above, and cut our enclosing region into eight smaller rectangles. If a game object completely fits within one of these subdivided regions, we'll push it down the tree into that node's containing region. We'll then recursively continue subdividing each resulting region until one of our breaking conditions is met. At the end, we should expect to have a nice tree-like data structure.

Note:  My implementation of the octree can contain objects which have either a bounding sphere and/or a bounding rectangle. You'll see a lot of code I use to determine which is being used.


In terms of our Octree class data structure, I decided to do the following for each tree:
  • Each node has a bounding region which defines the enclosing region
  • Each node has a reference to the parent node
  • Contains an array of eight child nodes (use arrays for code simplicity and cache performance)
  • Contains a list of objects contained within the current enclosing region
  • I use a byte-sized bitmask for figuring out which child nodes are actively being used (the optimization benefits at the cost of additional complexity is somewhat debatable)
  • I use a few static variables to indicate the state of the tree
Here is the code for my Octree class outline:

public class OctTree
{
	BoundingBox m_region;

	List<Physical> m_objects;

	/// <summary>
	/// These are items which we're waiting to insert into the data structure. 
	/// We want to accrue as many objects in here as possible before we inject them into the tree. This is slightly more cache friendly.
	/// </summary>
	static Queue<Physical> m_pendingInsertion = new Queue<Physical>();

	/// <summary>
	/// These are all of the possible child octants for this node in the tree.
	/// </summary>
	OctTree[] m_childNode = new OctTree[8];

	/// <summary>
	/// This is a bitmask indicating which child nodes are actively being used.
	/// It adds slightly more complexity, but is faster for performance since there is only one comparison instead of 8.
	/// </summary>
	byte m_activeNodes = 0;

	/// <summary>
	/// The minumum size for enclosing region is a 1x1x1 cube.
	/// </summary>
	const int MIN_SIZE = 1;

	/// <summary>
	/// this is how many frames we'll wait before deleting an empty tree branch. Note that this is not a constant. The maximum lifespan doubles
	/// every time a node is reused, until it hits a hard coded constant of 64
	/// </summary>
	int m_maxLifespan = 8;          //
	int m_curLife = -1;             //this is a countdown time showing how much time we have left to live

	/// <summary>
	/// A reference to the parent node is nice to have when we're trying to do a tree update.
	/// </summary>
	OctTree _parent;
	
	static bool m_treeReady = false;       //the tree has a few objects which need to be inserted before it is complete
	static bool m_treeBuilt = false;       //there is no pre-existing tree yet.
	
}

Initializing the enclosing region


The first step in building an octree is to define the enclosing region for the entire tree. This will be the bounding box for the root node of the tree which initially contains all objects in the game world. Before we go about initializing this bounding volume, we have a few design decisions we need to make:

1. What should happen if an object moves outside of the bounding volume of the root node? Do we want to resize the entire octree so that all objects are enclosed? If we do, we'll have to completely rebuild the octree from scratch. If we don't, we'll need to have some way to either handle out of bounds objects, or ensure that objects never go out of bounds.

2. How do we want to create the enclosing region for our octree? Do we want to use a preset dimension, such as a 200x400x200 (X,Y,Z) rectangle? Or do we want to use a cubic dimension which is a power of 2? What should be the smallest allowable enclosing region which cannot be subdivided?

Personally, I decided that I would use a cubic enclosing region with dimensions which are a power of 2, and sufficiently large to completely enclose my world. The smallest allowable cube is a 1x1x1 unit region. With this, I know that I can always cleanly subdivide my world and get integer numbers (even though the Vector3 uses floats). I also decided that my enclosing region would enclose the entire game world, so if an object leaves this region, it should be quietly destroyed. At the smallest octant, I will have to run a brute force collision check against all other objects, but I don't realistically expect more than 3 objects to occupy that small of an area at a time, so the performance costs of O(N^2) are completely acceptable.

So, I normally just initialize my octree with a constructor which takes a region size and a list of items to insert into the tree. I feel it's barely worth showing this part of the code since it's so elementary, but I'll include it for completeness. Here are my constructors:

/*Note: we want to avoid allocating memory for as long as possible since there can be lots of nodes.*/
/// <summary>
/// Creates an oct tree which encloses the given region and contains the provided objects.
/// </summary>
/// <param name="region">The bounding region for the oct tree.</param>
/// <param name="objList">The list of objects contained within the bounding region</param>
private OctTree(BoundingBox region, List<Physical> objList)
{
	m_region = region;
	m_objects = objList;
	m_curLife = -1;
}

public OctTree()
{
	m_objects = new List<Physical>();
	m_region = new BoundingBox(Vector3.Zero, Vector3.Zero);
	m_curLife = -1;
}

/// <summary>
/// Creates an octTree with a suggestion for the bounding region containing the items.
/// </summary>
/// <param name="region">The suggested dimensions for the bounding region. 
/// Note: if items are outside this region, the region will be automatically resized.</param>
public OctTree(BoundingBox region)
{
	m_region = region;
	m_objects = new List<Physical>();
	m_curLife = -1;
}

Building an initial octree


I'm a big fan of lazy initialization. I try to avoid allocating memory or doing work until I absolutely have to. In the case of my octree, I avoid building the data structure as long as possible. We'll accept a user's request to insert an object into the data structure, but we don't actually have to build the tree until someone runs a query against it. What does this do for us? Well, let's assume that the process of constructing and traversing our tree is somewhat computationally expensive. If a user wants to give us 1,000 objects to insert into the tree, does it make sense to recompute every subsequent enclosing area a thousand times? Or, can we save some time and do a bulk blast? I created a "pending" queue of items and a few flags to indicate the build state of the tree. All of the inserted items get put into the pending queue and when a query is made, those pending requests get flushed and injected into the tree. This is especially handy during a game loading sequence since you'll most likely be inserting thousands of objects at once. After the game world has been loaded, the number of objects injected into the tree is orders of magnitude fewer.

My lazy initialization routine is contained within my UpdateTree() method. It checks to see if the tree has been built, and builds the data structure if it doesn't exist and has pending objects.

/// <summary>
/// Processes all pending insertions by inserting them into the tree.
/// </summary>
/// <remarks>Consider deprecating this?</remarks>
private void UpdateTree()   //complete & tested
{
	if (!m_treeBuilt)
	{
		while (m_pendingInsertion.Count != 0)
			m_objects.Add(m_pendingInsertion.Dequeue());

		BuildTree();
	}
	else
	{
		while (m_pendingInsertion.Count != 0)
			Insert(m_pendingInsertion.Dequeue());
	}

	m_treeReady = true;
}

As for building the tree itself, this can be done recursively. So for each recurisive iteration, I start off with a list of objects contained within the bounding region. I check my termination rules, and if we pass, we create eight subdivided bounding areas which are perfectly contained within our enclosed region. Then, I go through every object in my given list and test to see if any of them will fit perfectly within any of my octants. If they do fit, I insert them into a corresponding list for that octant. At the very end, I check the counts on my corresponding octant lists and create new octrees and attach them to our current node, and mark my bitmask to indicate that those child octants are actively being used. All of the left over objects have been pushed down to us from our parent, but can't be pushed down to any children, so logically, this must be the smallest octant which can contain the object.

/// <summary>
/// Naively builds an oct tree from scratch.
/// </summary>
private void BuildTree()    //complete & tested
{
	//terminate the recursion if we're a leaf node
	if (m_objects.Count <= 1)
		return;

	Vector3 dimensions = m_region.Max - m_region.Min;

	if (dimensions == Vector3.Zero)
	{
		FindEnclosingCube();
		dimensions = m_region.Max - m_region.Min;
	}

	//Check to see if the dimensions of the box are greater than the minimum dimensions
	if (dimensions.X <= MIN_SIZE && dimensions.Y <= MIN_SIZE && dimensions.Z <= MIN_SIZE)
	{
		return;
	}

	Vector3 half = dimensions / 2.0f;
	Vector3 center = m_region.Min + half;

	//Create subdivided regions for each octant
	BoundingBox[] octant = new BoundingBox[8];
	octant[0] = new BoundingBox(m_region.Min, center);
	octant[1] = new BoundingBox(new Vector3(center.X, m_region.Min.Y, m_region.Min.Z), new Vector3(m_region.Max.X, center.Y, center.Z));
	octant[2] = new BoundingBox(new Vector3(center.X, m_region.Min.Y, center.Z), new Vector3(m_region.Max.X, center.Y, m_region.Max.Z));
	octant[3] = new BoundingBox(new Vector3(m_region.Min.X, m_region.Min.Y, center.Z), new Vector3(center.X, center.Y, m_region.Max.Z));
	octant[4] = new BoundingBox(new Vector3(m_region.Min.X, center.Y, m_region.Min.Z), new Vector3(center.X, m_region.Max.Y, center.Z));
	octant[5] = new BoundingBox(new Vector3(center.X, center.Y, m_region.Min.Z), new Vector3(m_region.Max.X, m_region.Max.Y, center.Z));
	octant[6] = new BoundingBox(center, m_region.Max);
	octant[7] = new BoundingBox(new Vector3(m_region.Min.X, center.Y, center.Z), new Vector3(center.X, m_region.Max.Y, m_region.Max.Z));

	//This will contain all of our objects which fit within each respective octant.
	List<Physical>[] octList = new List<Physical>[8];
	for (int i = 0; i < 8; i++) octList[i] = new List<Physical>();

	//this list contains all of the objects which got moved down the tree and can be delisted from this node.
	List<Physical> delist = new List<Physical>();

	foreach (Physical obj in m_objects)
	{
		if (obj.BoundingBox.Min != obj.BoundingBox.Max)
		{
			for (int a = 0; a < 8; a++)
			{
				if (octant[a].Contains(obj.BoundingBox) == ContainmentType.Contains)
				{
					octList[a].Add(obj);
					delist.Add(obj);
					break;
				}
			}
		}
		else if (obj.BoundingSphere.Radius != 0)
		{
			for (int a = 0; a < 8; a++)
			{
				if (octant[a].Contains(obj.BoundingSphere) == ContainmentType.Contains)
				{
					octList[a].Add(obj);
					delist.Add(obj);
					break;
				}
			}
		}
	}

	//delist every moved object from this node.
	foreach (Physical obj in delist)
		m_objects.Remove(obj);

	//Create child nodes where there are items contained in the bounding region
	for (int a = 0; a < 8; a++)
	{
		if (octList[a].Count != 0)
		{
			m_childNode[a] = CreateNode(octant[a], octList[a]);
			m_activeNodes |= (byte)(1 << a);
			m_childNode[a].BuildTree();
		}
	}

	m_treeBuilt = true;
	m_treeReady = true;
}

private OctTree CreateNode(BoundingBox region, List<Physical> objList)  //complete & tested
{
	if (objList.Count == 0)
		return null;

	OctTree ret = new OctTree(region, objList);
	ret._parent = this;

	return ret;
}

private OctTree CreateNode(BoundingBox region, Physical Item)
{
	List<Physical> objList = new List<Physical>(1); //sacrifice potential CPU time for a smaller memory footprint
	objList.Add(Item);
	OctTree ret = new OctTree(region, objList);
	ret._parent = this;
	return ret;
}

Updating a tree


Let's imagine that our tree has a lot of moving objects in it. If any object moves, there is a good chance that the object has moved outside of its enclosing octant. How do we handle changes in object position while maintaining the integrity of our tree structure?

Technique 1: Keep it super simple, trash & rebuild everything.

Some implementations of an Octree will completely rebuild the entire tree every frame and discard the old one. This is super simple and it works, and if this is all you need, then prefer the simple technique. The general concensus is that the upfront CPU cost of rebuilding the tree every frame is much cheaper than running a brute force collision check, and programmer time is too valuable to be spent on an unnecessary optimization.

For those of us who like challenges and to over-engineer things, the "trash & rebuild" technique comes with a few small problems:

  1. You're constantly allocating and deallocating memory each time you rebuild your tree. Allocating new memory comes with a small cost. If possible, you want to minimize the amount of memory being allocated and reallocated over time by reusing memory you've already got.
  2. Most of the tree is unchanging, so it's a waste of CPU time to rebuild the same branches over and over again.

Technique 2: Keep the existing tree, update the changed branches

I noticed that most branches of a tree don't need to be updated. They just contain stationary objects. Wouldn't it be nice if, instead of rebuilding the entire tree every frame, we just updated the parts of the tree which needed an update? This technique keeps the existing tree and updates only the branches which had an object which moved. It's a bit more complex to implement, but it's a lot more fun too, so let's really get into that!

During my first attempt at this, I mistakenly thought that an object in a child node could only go up or down one traversal of the tree. This is wrong. If an object in a child node reaches the edge of that node, and that edge also happens to be an edge for the enclosing parent node, then that object needs to be inserted above its parent, and possibly up even further. So, the bottom line is that we don't know how far up an object needs to be pushed up the tree. Just as well, an object can move such that it can be neatly enclosed in a child node, or that child's child node. We don't know how far down the tree we can go. Fortunately since we include a reference to each node's parent, we can easily solve this problem recursively with minimal computation!

The general idea behind the update algorithm is to first let all objects in the tree update themselves. Some may move or change in size. We want to get a list of every object which moved, so the object update method should return to us a boolean value indicating if its bounding area changed. Once we've got a list of all of our moved objects, we want to start at our current node and try to traverse up the tree until we find a node which completely encloses the moved object (most of the time, the current node still encloses the object). If the object isn't completely enclosed by the current node, we keep moving it up to its next parent node. In the worst case, our root node will be guaranteed to contain the object. After we've moved our object as far up the tree as possible, we'll try to move it as far down the tree as we can. Most of the time, if we moved the object up, we won't be able to move it back down. But, if the object moved so that a child node of the current node could contain it, we have the chance to push it back down the tree. It's important to be able to move objects down the tree as well, or else all moving objects would eventually migrate to the top and we'd start getting some performance problems during collision detection routines.

Branch Removal

In some cases, an object will move out of a node and that node will no longer have any objects contained within it, nor have any children which contain objects. If this happens, we have an empty branch and we need to mark it as such and prune this dead branch off the tree. There is an interesting question hiding here: When do you want to prune the dead branches off a tree? Allocating new memory costs time, so if we're just going to reuse this same region in a few cycles, why not keep it around for a bit? How long can we keep it around before it becomes more expensive to maintain the dead branch? I decided to give each of my nodes a count down timer which activates when the branch is dead. If an object moves into this nodes octant while the death timer is active, I double the lifespan and reset the death timer. This ensures that octants which are frequently used are hot and stick around, and nodes which are infrequently used are removed before they start to cost more than they're worth. A practical example of this usefulness would be apparent when you have a machine gun shooting a stream of bullets. Those bullets follow in close succession of each other, so it'd be a shame to immediately delete a node as soon as the first bullet leaves it, only to recreate it a fraction of a second later as the second bullet re-enters it. And if there's a lot of bullets, we can probably keep these octants around for a little while. If a child branch is empty and hasn't been used in a while, it's safe to prune it out of our tree.

Anyways, let's look at the code which does all of this magic. First up, we have the Update() method. This is a method which is recursively called on all child trees. It moves all objects around, does some house keeping work for the data structure, and then moves each moved object into its correct node (parent or child).

public void Update(GameTime gameTime)
{
	if (m_treeBuilt == true)
	{
		//Start a count down death timer for any leaf nodes which don't have objects or children.
		//when the timer reaches zero, we delete the leaf. If the node is reused before death, we double its lifespan.
		//this gives us a "frequency" usage score and lets us avoid allocating and deallocating memory unnecessarily
		if (m_objects.Count == 0)
		{
			if (HasChildren == false)
			{
				if (m_curLife == -1)
					m_curLife = m_maxLifespan;
				else if (m_curLife > 0)
				{
					m_curLife--;
				}
			}
		}
		else
		{
			if (m_curLife != -1)
			{
				if(m_maxLifespan <= 64)
					m_maxLifespan *= 2;
				m_curLife = -1;
			}
		}
		List<Physical> movedObjects = new List<Physical>(m_objects.Count);

		//go through and update every object in the current tree node
		foreach (Physical gameObj in m_objects)
		{
			//we should figure out if an object actually moved so that we know whether we need to update this node in the tree.
			if (gameObj.Update(gameTime))
			{
				movedObjects.Add(gameObj);
			}
		}

		//prune any dead objects from the tree.
		int listSize = m_objects.Count;
		for (int a = 0; a < listSize; a++)
		{
			if (!m_objects[a].Alive)
			{
				if (movedObjects.Contains(m_objects[a]))
					movedObjects.Remove(m_objects[a]);
				m_objects.RemoveAt(a--);
				listSize--;
			}
		}

		//recursively update any child nodes.
		for( int flags = m_activeNodes, index = 0; flags > 0; flags >>=1, index++)
			if ((flags & 1) == 1) m_childNode[index].Update(gameTime);


		//If an object moved, we can insert it into the parent and that will insert it into the correct tree node.
		//note that we have to do this last so that we don't accidentally update the same object more than once per frame.
		foreach (Physical movedObj in movedObjects)
		{
			OctTree current = this;

			//figure out how far up the tree we need to go to reinsert our moved object
			//we are either using a bounding rect or a bounding sphere
			//try to move the object into an enclosing parent node until we've got full containment
			if (movedObj.BoundingBox.Max != movedObj.BoundingBox.Min)
			{
				while (current.m_region.Contains(movedObj.BoundingBox) != ContainmentType.Contains)
					if (current._parent != null) current = current._parent;
					else break; //prevent infinite loops when we go out of bounds of the root node region
			}
			else
			{
				while (current.m_region.Contains(movedObj.BoundingSphere) != ContainmentType.Contains)//we must be using a bounding sphere, so check for its containment.
					if (current._parent != null) current = current._parent;
					else break;
			}

			//now, remove the object from the current node and insert it into the current containing node.
			m_objects.Remove(movedObj);
			current.Insert(movedObj);   //this will try to insert the object as deep into the tree as we can go.
		}

		//prune out any dead branches in the tree
		for (int flags = m_activeNodes, index = 0; flags > 0; flags >>= 1, index++)
			if ((flags & 1) == 1 && m_childNode[index].m_curLife == 0) 
			{
				m_childNode[index] = null;
				m_activeNodes ^= (byte)(1 << index);       //remove the node from the active nodes flag list
			}

		//now that all objects have moved and they've been placed into their correct nodes in the octree, we can look for collisions.
		if (IsRoot == true)
		{
			//This will recursively gather up all collisions and create a list of them.
			//this is simply a matter of comparing all objects in the current root node with all objects in all child nodes.
			//note: we can assume that every collision will only be between objects which have moved.
			//note 2: An explosion can be centered on a point but grow in size over time. In this case, you'll have to override the update method for the explosion.
			List<IntersectionRecord> irList = GetIntersection(new List<Physical>());

			foreach (IntersectionRecord ir in irList)
			{
				if (ir.PhysicalObject != null)
					ir.PhysicalObject.HandleIntersection(ir);
				if (ir.OtherPhysicalObject != null)
					ir.OtherPhysicalObject.HandleIntersection(ir);
			}
		}
		
	}
	else
	{

	}
}

Note that we call an Insert() method for moved objects. The insertion of objects into the tree is very similar to the method used to build the initial tree. Insert() will try to push objects as far down the tree as possible. Notice that I also try to avoid creating new bounding areas if I can use an existing one from a child node.

/// <summary>
/// A tree has already been created, so we're going to try to insert an item into the tree without rebuilding the whole thing
/// </summary>
/// <typeparam name="T">A physical object</typeparam>
/// <param name="Item">The physical object to insert into the tree</param>
private void Insert<T>(T Item) where T : Physical
{
	/*make sure we're not inserting an object any deeper into the tree than we have to.
		-if the current node is an empty leaf node, just insert and leave it.*/
	if (m_objects.Count <= 1 && m_activeNodes == 0)
	{
		m_objects.Add(Item);
		return;
	}

	Vector3 dimensions = m_region.Max - m_region.Min;
	//Check to see if the dimensions of the box are greater than the minimum dimensions
	if (dimensions.X <= MIN_SIZE && dimensions.Y <= MIN_SIZE && dimensions.Z <= MIN_SIZE)
	{
		m_objects.Add(Item);
		return;
	}
	Vector3 half = dimensions / 2.0f;
	Vector3 center = m_region.Min + half;

	//Find or create subdivided regions for each octant in the current region
	BoundingBox[] childOctant = new BoundingBox[8];
	childOctant[0] = (m_childNode[0] != null) ? m_childNode[0].m_region : new BoundingBox(m_region.Min, center);
	childOctant[1] = (m_childNode[1] != null) ? m_childNode[1].m_region : new BoundingBox(new Vector3(center.X, m_region.Min.Y, m_region.Min.Z), new Vector3(m_region.Max.X, center.Y, center.Z));
	childOctant[2] = (m_childNode[2] != null) ? m_childNode[2].m_region : new BoundingBox(new Vector3(center.X, m_region.Min.Y, center.Z), new Vector3(m_region.Max.X, center.Y, m_region.Max.Z));
	childOctant[3] = (m_childNode[3] != null) ? m_childNode[3].m_region : new BoundingBox(new Vector3(m_region.Min.X, m_region.Min.Y, center.Z), new Vector3(center.X, center.Y, m_region.Max.Z));
	childOctant[4] = (m_childNode[4] != null) ? m_childNode[4].m_region : new BoundingBox(new Vector3(m_region.Min.X, center.Y, m_region.Min.Z), new Vector3(center.X, m_region.Max.Y, center.Z));
	childOctant[5] = (m_childNode[5] != null) ? m_childNode[5].m_region : new BoundingBox(new Vector3(center.X, center.Y, m_region.Min.Z), new Vector3(m_region.Max.X, m_region.Max.Y, center.Z));
	childOctant[6] = (m_childNode[6] != null) ? m_childNode[6].m_region : new BoundingBox(center, m_region.Max);
	childOctant[7] = (m_childNode[7] != null) ? m_childNode[7].m_region : new BoundingBox(new Vector3(m_region.Min.X, center.Y, center.Z), new Vector3(center.X, m_region.Max.Y, m_region.Max.Z));

	//First, is the item completely contained within the root bounding box?
	//note2: I shouldn't actually have to compensate for this. If an object is out of our predefined bounds, then we have a problem/error.
	//          Wrong. Our initial bounding box for the terrain is constricting its height to the highest peak. Flying units will be above that.
	//             Fix: I resized the enclosing box to 256x256x256. This should be sufficient.
	if (Item.BoundingBox.Max != Item.BoundingBox.Min && m_region.Contains(Item.BoundingBox) == ContainmentType.Contains)
	{
		bool found = false;
		//we will try to place the object into a child node. If we can't fit it in a child node, then we insert it into the current node object list.
		for(int a=0;a<8;a++)
		{
			//is the object fully contained within a quadrant?
			if (childOctant[a].Contains(Item.BoundingBox) == ContainmentType.Contains)
			{
				if (m_childNode[a] != null)
					m_childNode[a].Insert(Item);   //Add the item into that tree and let the child tree figure out what to do with it
				else
				{
					m_childNode[a] = CreateNode(childOctant[a], Item);   //create a new tree node with the item
					m_activeNodes |= (byte)(1 << a);
				}
				found = true;
			}
		}
		if(!found) m_objects.Add(Item);

	}
	else if (Item.BoundingSphere.Radius != 0 && m_region.Contains(Item.BoundingSphere) == ContainmentType.Contains)
	{
		bool found = false;
		//we will try to place the object into a child node. If we can't fit it in a child node, then we insert it into the current node object list.
		for (int a = 0; a < 8; a++)
		{
			//is the object contained within a child quadrant?
			if (childOctant[a].Contains(Item.BoundingSphere) == ContainmentType.Contains)
			{
				if (m_childNode[a] != null)
					m_childNode[a].Insert(Item);   //Add the item into that tree and let the child tree figure out what to do with it
				else
				{
					m_childNode[a] = CreateNode(childOctant[a], Item);   //create a new tree node with the item
					m_activeNodes |= (byte)(1 << a);
				}
				found = true;
			}
		}
		if (!found) m_objects.Add(Item);
	}
	else
	{
		//either the item lies outside of the enclosed bounding box or it is intersecting it. Either way, we need to rebuild
		//the entire tree by enlarging the containing bounding box
		//BoundingBox enclosingArea = FindBox();
		BuildTree();
	}
}

Collision Detection


Finally, our octree has been built and everything is as it should be. How do we perform collision detection against it?

First, let's list out the different ways we want to look for collisions:

  1. Frustum intersections. We may have a frustum which intersects with a region of the world. We only want the objects which intersect with the given frustum. This is particularly useful for culling regions outside of the camera view space, and for figuring out what objects are within a mouse selection area.
  2. Ray intersections. We may want to shoot a directional ray from any given point and want to know either the nearest intersecting object, or get a list of all objects which intersect that ray (like a rail gun). This is very useful for mouse picking. If the user clicks on the screen, we want to draw a ray into the world and figure out what they clicked on.
  3. Bounding Box intersections. We want to know which objects in the world are intersecting a given bounding box. This is most useful for "box" shaped game objects (houses, cars, etc).
  4. Bounding Sphere Intersections. We want to know which objects are intersecting with a given bounding sphere. Most objects will probably be using a bounding sphere for coarse collision detection since the mathematics is computationally the least expensive and somewhat easy.

The main idea behind recursive collision detection processing for an octree is that you start at the root/current node and test for intersection with all objects in that node against the intersector. Then, you do a bounding box intersection test against all active child nodes with the intersector. If a child node fails this intersection test, you can completely ignore the rest of that child's tree. If a child node passes the intersection test, you recursively traverse down the tree and repeat. Each node should pass a list of intersection records up to its caller, which appends those intersections to its own list of intersections. When the recursion finishes, the original caller will get a list of every intersection for the given intersector. The beauty of this is that it takes very little code to implement and performance is very fast.

In a lot of these collisions, we're probably going to be getting a lot of results. We're also going to want to have some way of responding to each collision, depending on what objects are colliding. For example, a player hero should pick up a floating bonus item (quad damage!), but a rocket shouldn't explode if it hits said bonus item. I created a new class to contain information about each intersection. This class contains references to the intersecting objects, the point of intersection, the normal at the point of intersection, etc. These intersection records become quite useful when you pass them to an object and tell them to handle it. For completeness and clarity, here is my intersection record class:

public class IntersectionRecord
{
	
	Vector3 m_position;
	/// <summary>
	/// This is the exact point in 3D space which has an intersection.
	/// </summary>
	public Vector3 Position { get { return m_position; } }

	
	Vector3 m_normal;
	/// <summary>
	/// This is the normal of the surface at the point of intersection
	/// </summary>
	public Vector3 Normal { get { return m_normal; } }

	Ray m_ray;
	/// <summary>
	/// This is the ray which caused the intersection
	/// </summary>
	public Ray Ray { get { return m_ray; } }

	Physical m_intersectedObject1;
	/// <summary>
	/// This is the object which is being intersected
	/// </summary>
	public Physical PhysicalObject 
	{ 
		get { return m_intersectedObject1; }
		set { m_intersectedObject1 = value; }
	}

	Physical m_intersectedObject2;
	/// <summary>
	/// This is the other object being intersected (may be null, as in the case of a ray-object intersection)
	/// </summary>
	public Physical OtherPhysicalObject
	{
		get { return m_intersectedObject2; }
		set { m_intersectedObject2 = value; }
	}

	/// <summary>
	/// this is a reference to the current node within the octree for where the collision occurred. In some cases, the collision handler
	/// will want to be able to spawn new objects and insert them into the tree. This node is a good starting place for inserting these objects
	/// since it is a very near approximation to where we want to be in the tree.
	/// </summary>
	OctTree m_treeNode;     

	/// <summary>
	/// check the object identities between the two intersection records. If they match in either order, we have a duplicate.
	/// </summary>
	/// <param name="otherRecord">the other record to compare against</param>
	/// <returns>true if the records are an intersection for the same pair of objects, false otherwise.</returns>
	public override bool Equals(object otherRecord)
	{
		IntersectionRecord o = (IntersectionRecord)otherRecord;
		//
		//return (m_intersectedObject1 != null && m_intersectedObject2 != null && m_intersectedObject1.ID == m_intersectedObject2.ID);
		if (otherRecord == null)
			return false;
		if (o.m_intersectedObject1.ID == m_intersectedObject1.ID && o.m_intersectedObject2.ID == m_intersectedObject2.ID)
			return true;
		if (o.m_intersectedObject1.ID == m_intersectedObject2.ID && o.m_intersectedObject2.ID == m_intersectedObject1.ID)
			return true;
		return false;
	}

	double m_distance;
	/// <summary>
	/// This is the distance from the ray to the intersection point. 
	/// You'll usually want to use the nearest collision point if you get multiple intersections.
	/// </summary>
	public double Distance { get { return m_distance; } }

	private bool m_hasHit = false;
	public bool HasHit
	{
		get { return m_hasHit; }
	}

	public IntersectionRecord()
	{
		m_position = Vector3.Zero;
		m_normal = Vector3.Zero;
		m_ray = new Ray();
		m_distance = float.MaxValue;
		m_intersectedObject1 = null;
	}

	public IntersectionRecord(Vector3 hitPos, Vector3 hitNormal, Ray ray, double distance)
	{
		m_position = hitPos;
		m_normal = hitNormal;
		m_ray = ray;
		m_distance = distance;
		//m_hitObject = hitGeom;
		m_hasHit = true;
	}

	/// <summary>
	/// Creates a new intersection record indicating whether there was a hit or not and the object which was hit.
	/// </summary>
	/// <param name="hitObject">Optional: The object which was hit. Defaults to null.</param>
	public IntersectionRecord(Physical hitObject = null)
	{
		m_hasHit = hitObject != null;
		m_intersectedObject1 = hitObject;
		m_position = Vector3.Zero;
		m_normal = Vector3.Zero;
		m_ray = new Ray();
		m_distance = 0.0f;
	}
}

Intersection with a Bounding Frustum


/// <summary>
/// Gives you a list of all intersection records which intersect or are contained within the given frustum area
/// </summary>
/// <param name="frustum">The containing frustum to check for intersection/containment with</param>
/// <returns>A list of intersection records with collisions</returns>
private List<IntersectionRecord> GetIntersection(BoundingFrustum frustum, Physical.PhysicalType type = Physical.PhysicalType.ALL)
{
	if (m_objects.Count == 0 && HasChildren == false)   //terminator for any recursion
		return null;

	List<IntersectionRecord> ret = new List<IntersectionRecord>();

	//test each object in the list for intersection
	foreach (Physical obj in m_objects)
	{

		//skip any objects which don't meet our type criteria
		if ((int)((int)type & (int)obj.Type) == 0)
			continue;

		//test for intersection
		IntersectionRecord ir = obj.Intersects(frustum);
		if (ir != null) ret.Add(ir);
	}

	//test each object in the list for intersection
	for (int a = 0; a < 8; a++)
	{
		if (m_childNode[a] != null && (frustum.Contains(m_childNode[a].m_region) == ContainmentType.Intersects || frustum.Contains(m_childNode[a].m_region) == ContainmentType.Contains))
		{
			List<IntersectionRecord> hitList = m_childNode[a].GetIntersection(frustum);
			if (hitList != null)
			{
				foreach (IntersectionRecord ir in hitList)
					ret.Add(ir);
			}
		}
	}
	return ret;
}

The bounding frustum intersection list can be used to only render objects which are visible to the current camera view. I use a scene database to figure out how to render all objects in the game world. Here is a snippet of code from my rendering function which uses the bounding frustum of the active camera:

/// <summary>
/// This renders every active object in the scene database
/// </summary>
/// <param name="gameTime"></param>
public int Render()
{
	int triangles = 0;

	//Renders all visible objects by iterating through the oct tree recursively and testing for intersection 
    //with the current camera view frustum
	foreach (IntersectionRecord ir in m_octTree.AllIntersections(m_cameras[m_activeCamera].Frustum))
	{
		ir.PhysicalObject.SetDirectionalLight(m_globalLight[0].Direction, m_globalLight[0].Color);
		ir.PhysicalObject.View = m_cameras[m_activeCamera].View;
		ir.PhysicalObject.Projection = m_cameras[m_activeCamera].Projection;
		ir.PhysicalObject.UpdateLOD(m_cameras[m_activeCamera]);
		triangles += ir.PhysicalObject.Render(m_cameras[m_activeCamera]);
	}

	return triangles;
}

Intersection with a Ray


/// <summary>
/// Gives you a list of intersection records for all objects which intersect with the given ray
/// </summary>
/// <param name="intersectRay">The ray to intersect objects against</param>
/// <returns>A list of all intersections</returns>
private List<IntersectionRecord> GetIntersection(Ray intersectRay, Physical.PhysicalType type = Physical.PhysicalType.ALL)
{
	if (m_objects.Count == 0 && HasChildren == false)   //terminator for any recursion
		return null;

	List<IntersectionRecord> ret = new List<IntersectionRecord>();

	//the ray is intersecting this region, so we have to check for intersection with all of our contained objects and child regions.
	
	//test each object in the list for intersection
	foreach (Physical obj in m_objects)
	{
		//skip any objects which don't meet our type criteria
		if ((int)((int)type & (int)obj.Type) == 0)
			continue;

		if (obj.BoundingBox.Intersects(intersectRay) != null)
		{
			IntersectionRecord ir = obj.Intersects(intersectRay);
			if (ir.HasHit)
				ret.Add(ir);
		}
	}

	// test each child octant for intersection
	for (int a = 0; a < 8; a++)
	{
		if (m_childNode[a] != null && m_childNode[a].m_region.Intersects(intersectRay) != null)
		{
			List<IntersectionRecord> hits = m_childNode[a].GetIntersection(intersectRay, type);
			if (hits != null)
			{
				foreach (IntersectionRecord ir in hits)
					ret.Add(ir);
			}
		}
	}

	return ret;
}

Intersection with a list of objects


This is a particularly useful recursive method for determining if a list of objects in the current node intersect with any objects in any child nodes (See: Update() method for usage). It's the method which will be used most frequently, so it's good to get this right and efficient. What we want to do is start at the root node of the tree. We compare all objects in the current node against all other objects in the current node for collision. We gather up any of those collisions as intersection records, and insert them into a list. We then pass our list of tested objects down to our child nodes. The child nodes will then test their objects against themselves, then against the objects we passed down to them. The child nodes will capture any collisions in a list, and return that list to its parent. The parent then takes the collision list recieved from its child nodes and appends it to its own list of collisions, finally returning it to its caller.


Attached Image: Collision_1a.png
Attached Image: Collision_1b.png


If you count out the number of collision tests in the illustration above, you can see that we conducted 29 hit tests and recieved 4 hits. This is much better than [11*11 = 121] hit tests.

private List<IntersectionRecord> GetIntersection(List<Physical> parentObjs, Physical.PhysicalType type = Physical.PhysicalType.ALL)
{
	List<IntersectionRecord> intersections = new List<IntersectionRecord>();
	//assume all parent objects have already been processed for collisions against each other.
	//check all parent objects against all objects in our local node
	foreach (Physical pObj in parentObjs)
	{
		foreach (Physical lObj in m_objects)
		{
			//We let the two objects check for collision against each other. They can figure out how to do the coarse and granular checks.
			//all we're concerned about is whether or not a collision actually happened.
			IntersectionRecord ir = pObj.Intersects(lObj);
			if (ir != null)
			{
                intersections.Add(ir);
			}
		}
	}

	//now, check all our local objects against all other local objects in the node
	if (m_objects.Count > 1)
	{
		#region self-congratulation
		/*
		 * This is a rather brilliant section of code. Normally, you'd just have two foreach loops, like so:
		 * foreach(Physical lObj1 in m_objects)
		 * {
		 *      foreach(Physical lObj2 in m_objects)
		 *      {
		 *           //intersection check code
		 *      }
		 * }
		 * 
		 * The problem is that this runs in O(N*N) time and that we're checking for collisions with objects which have already been checked.
		 * Imagine you have a set of four items: {1,2,3,4}
		 * You'd first check: {1} vs {1,2,3,4}
		 * Next, you'd check {2} vs {1,2,3,4}
		 * but we already checked {1} vs {2}, so it's a waste to check {2} vs. {1}. What if we could skip this check by removing {1}?
		 * We'd have a total of 4+3+2+1 collision checks, which equates to O(N(N+1)/2) time. If N is 10, we are already doing half as many collision checks as necessary.
		 * Now, we can't just remove an item at the end of the 2nd for loop since that would break the iterator in the first foreach loop, so we'd have to use a
		 * regular for(int i=0;i<size;i++) style loop for the first loop and reduce size each iteration. This works...but look at the for loop: we're allocating memory for
		 * two additional variables: i and size. What if we could figure out some way to eliminate those variables?
		 * So, who says that we have to start from the front of a list? We can start from the back end and still get the same end results. With this in mind,
		 * we can completely get rid of a for loop and use a while loop which has a conditional on the capacity of a temporary list being greater than 0.
		 * since we can poll the list capacity for free, we can use the capacity as an indexer into the list items. Now we don't have to increment an indexer either!
		 * The result is below.
		 */
		#endregion

		List<Physical> tmp = new List<Physical>(m_objects.Count);
		tmp.AddRange(m_objects);
		while (tmp.Count > 0)
		{
			foreach (Physical lObj2 in tmp)
			{
				if (tmp[tmp.Count - 1] == lObj2 || (tmp[tmp.Count - 1].IsStatic && lObj2.IsStatic))
					continue;
				IntersectionRecord ir = tmp[tmp.Count - 1].Intersects(lObj2);
				if (ir != null)
					intersections.Add(ir);
			}

			//remove this object from the temp list so that we can run in O(N(N+1)/2) time instead of O(N*N)
			tmp.RemoveAt(tmp.Count-1);
		}
	}

	//now, merge our local objects list with the parent objects list, then pass it down to all children.
	foreach (Physical lObj in m_objects)
		if (lObj.IsStatic == false)
			parentObjs.Add(lObj);
	//parentObjs.AddRange(m_objects);

	//each child node will give us a list of intersection records, which we then merge with our own intersection records.
	for (int flags = m_activeNodes, index = 0; flags > 0; flags >>= 1, index++)
		if ((flags & 1) == 1) intersections.AddRange(m_childNode[index].GetIntersection(parentObjs, type));
	
	return intersections;
}

Screenshot Demos



Attached Image: Demo_1a.png
This is a view of the game world from a distance showing the outlines for each bounding volume for the octree.

Attached Image: Demo_1b.png
This view shows a bunch of successive projectiles moving through the game world with the frequently-used nodes being preserved instead of deleted.


Complete Code Sample


I've attached a complete code sample of the octree class, the intersection record class, and my generic physical object class. I don't guarantee that they're all bug free since it's all a work in progress and hasn't been rigorously tested yet.

Advanced Intersection Test Methods

$
0
0
When working with geometry, you'll need to do some intersection tests at some point. Sometimes it's directly related to graphics, but sometimes it helps determine other useful things, like optimum paths. This article is meant to give the up-and-coming game developer a few more tools in their computational toolbox.

For this article, I'm assuming you know all about vectors, points, dot and cross products. We'll cover some quick properties of polynomials, some things about some basic curves, and then go over intersection tests.

Polynomials


The Stone-Weierstrass theorem states that any continuous function defined on a closed interval can be uniformly approximated as closely as desired with a polynomial function. For graphics and game development, that means that most 2D objects we deal with can be expressed as polynomials. The key to these intersection test methods is using that to our advantage.

Bezout's Theorem


In order to find all our intersection points, we need to know how many intersections there can be between 2 polynomials. Bezout's theorem states that for a planar algebraic curve of degree n and a second planar algebraic curve m, they intersect in exactly mn points if we properly count complex intersections, intersections at infinity, and possible multiple intersections. If they intersect in more than that number, then they intersect at infinitely many points, which means that they are the same curve.

Bezout's theorem also extends to surfaces. A surface of degree m intersects a surface of degree n in a curve of degree mn. As well, a space curve of degree m intersects a surface of degree n in mn points.

The key here is to identify how to count intersections. For example, if 2 curves are tangent, they intersect twice. If they have the same curvature, then they intersect 3 times. Simple intersections (not tangent and not self-intersecting) are counted once. So how do we count intersections at infinity? Using homogeneous coordinates, of course!

Homogeneous Coordinates


Counting intersections at infinity sounds hard, but we can use homogeneous coordinates to do this. Here, we define a point in 3D homogeneous space \((X,Y,W)\) to correspond to a 2D point \((x,y)\) whose coordinates are \((X/W,Y/W)\). This means a 3D homogeneous point \((4,2,2)\) corresponds to a the 2D point \((4/2,2/2) = (2,1)\). Going the other way, the 2D point \((3,1)\) becomes the 3D point \((3,1,1)\), since the transformation back to 2D is simply \((3/1,1/1) = (3,1)\). This creates some weird equalities, but you can visualize this 3D-2D transformation as projecting the points (and curves) in 3D onto the plane \(z=1\).

This helps us with define infinities with finite numbers. Let's say we have the point \((2,3,1)\) in homogeneous coordinates. If we changed the weight coordinate W so that we had \((2,3,0.5)\), the 2D point would be \((4,6)\). Smaller weights mean larger projected coordinates. As the weight approaches zero, the 2D point gets larger and larger, until at W = 0, the projected point is at infinity. Thus, any homogeneous point \((X,Y,0)\) is a point at infinity.

Aside: Equations in Homogeneous Form

Usually, polynomials have terms that differ in their algebraic degrees. Some are quadratic, some cubic, some constant, etc. The polynomial takes the following form:
\[f(x,y) = \sum_{i+j\le n} a_{ij}x^iy^j = 0 \]
However, the same curve can be expressed in homogeneous form by adding a homogenizing variable w:
\[f(X,Y,W) = \sum_{i+j+k = n} a_{ij}X^iY^jW^k = 0 \]
Here, every term in the polynomial is of degree n.

Equation Types


To use polynomials effectively, we need to be familiar with how they can be expressed. There are basically 3 types of equations that can be used to describe planar curves: parametric, implicit, and explicit. If you've only had high-school math, you've probably dealt with explicit curves a lot and not so much with the others.

Parametric: The curve is specified by a real number \(t\) and each coordinate is given by a function of \(t\), like \(x = x(t)\) and \(y = y(t)\). Rational polynomials are defined the same way, but each coordinate divided by a different function of \(t\), like \(x = x(t) / w(t)\) and \(y = y(t)/w(t)\).

Implicit: This curve takes the form \(f(x,y) = 0\), where the point \((x,y)\) is on the curve.

Explicit: This is really a special case of both parametric and implicit forms. The explicit form of a curve is the classic \(y=f(x)\) form.

Lines


Let's start with a very basic curve: the line. It's a degree-1 polynomial. There are many definitions of this kind of curve. Some are helpful for games and others...not so much. Some you may have seen in algebra class and others you may be seeing for the first time.

Common Forms

Slope-intercept: \( y = mx+b \)

Point-slope: \( y - y_0 = m(x-x_0) \)

Affine: \( P(t) = [(t_1-t)P_0+(t-t_0)P_1] / (t_1-t_0) \)

Vector Implicit: \((P-P_0)\cdot n = 0\)

Parametric: \( P(x(t),y(t)) = (x_0+at,y_0+bt) \)

Algebraic Implicit: \( ax+by+c=0 \)

In secondary schools, they use the first 2 methods almost exclusively, but these turn out to be the least helpful for computing. Here, I've tried to order the definitions from least helpful to most helpful for our needs.

Implicit Form, Line-Point, and Line-Line Intersection


Probably one of the most useful forms, the implicit form is nice for a few reasons. First, we can express it in homogeneous coordinates: \(aX+bY+cW=0\). From here, we can define the coordinates as an ordered triple \((a,b,c)\) and then modify the equation to use the dot product so we can use a simple test if a point lies on a given line in implicit form:
\[L(a,b,c) \cdot P(X,Y,W) = 0\]
Signed Distance From a Line

Although this isn't strictly an intersection test, this method is valuable enough to mention here. Although points that lie on a line satisfy the equation \(L(a,b,c) \cdot P(X,Y,W) = 0\), if the point is not on the line, the scalar value can let us know on which side of the line the point lies by the sign. If \(L(a,b,c) \cdot P(X,Y,W) > 0\), the point lies on one side of the line; if \(L(a,b,c) \cdot P(X,Y,W) < 0\) it lies on the other. This is a convenient way of dividing a plane into 2 separate regions.

This scalar value is also a scaled distance from the line. If only the relative position of a point from the closest point on a line is desired, then this scalar can suffice. However, if true distance is required, then the full signed distance formula for a point \((x,y)\) from an implicit line defined by the ordered triple \((a,b,c)\) is given by the following:
\[D = \frac{ax+by+c}{\sqrt{a^2+b^2}}\]
Implicit Line from 2 Points

This ordered triple form a convenient way to define coordinate axes. For example, the x-axis can be defined as \((0,1,0)\) and the y-axis can be defined as \((1,0,0)\). The ordered triple is also really nice because we can compute it really easily if we have 2 points. Let's go back to our vector math to see how this might work.

Attached Image: crossP.png


In the above picture, two vectors \(\vec{a}\) and \(\vec{b}\) are crossed to produce a 3rd vector \(\vec{c} = \vec{a}\times\vec{b}\). This vector \(\vec{c}\) is orthogonal to the vectors \(\vec{a}\) and \(\vec{b}\), meaning that \(\vec{c}\cdot\vec{a} = 0\) and \(\vec{c}\cdot\vec{b} = 0\). This is important for this next neat trick.

Imagine we have points \(P_1\) and \(P_2\). To tie back into the example above, let's let \(\vec{a} = P_1 = (x_1,y_1,w_1)\) and \(\vec{b} = P_2 = (x_2,y_2,w_2)\). If we cross these vectors, we get a vector \(\vec{c} = \vec{a} \times\vec{b}\) for which \(\vec{c}\cdot\vec{a} = 0\) and \(\vec{c}\cdot\vec{b} = 0\). Since \(\vec{a}\) and \(\vec{b}\) are points, we could say that \(\vec{c} = (a,b,c)\), an implicit ordered triple that passes through both points. We can check that the line given by \((a,b,c)\) passes through both points by the dot product method.
\[L(a,b,c) = P(X_1,Y_1,W_1) \times P(X_2,Y_2,W_2)\]
Line-Line Intersection

Using the same kind of logic, we can get the point at which 2 lines intersect. To use the cross product example again, let's let the vectors be the ordered triples of 2 implicit lines, \(\vec{a} = (a_1,b_1,c_1)\) and \(\vec{b} = (a_2,b_2,c_2)\). Again, crossing these vectors yields a vector \(\vec{c} = \vec{a} \times\vec{b}\) for which \(\vec{c}\cdot\vec{a} = 0\) and \(\vec{c}\cdot\vec{b} = 0\). Since \(\vec{a}\) and \(\vec{b}\) are lines, we could say that \(\vec{c} = (X,Y,W)\), the intersection point of both lines. This point has to be on both lines, and we can verify that using the dot-product method.
\[P(X,Y,W) = L(a_1,b_1,c_1) \times L(a_2,b_2,c_2)\]
Connection to Bezout's Theorem

Lines are degree-1 algebraic curves. Bezout's theorem states that 2 lines must intersect at exactly 1 point. So what happens with parallel lines? Well, if we use homogeneous coordinates, the intersection point will take the form \((X,Y,0)\), a point at infinity. Bezout's theorem still holds for that case.

Parametric-Implicit Curve Intersection


Sometimes it's advantageous to define some curves as parametric and some as implicit to solve for intersections. Most times, it's better to define the simpler curve as parametric and the more complex curve as implicit is possible. This method solves for all algebraic intersections, which may or may not be "real" intersections. As well, multiple roots might come up, in which case we have to reconcile this with our intersection counting methods above.

Line-Circle Intersection


Let's take a fairly common case: line-circle intersection. Bezout's theorem says we will get 2 intersections since a circle is a degree-2 algebraic curve. The parametric line at point \((x_0,y_0)\) with direction \((c,d)\) and implicit circle centered at \((a,b)\) with radius \(r\) are defined as follows:
\[ \begin{aligned} C(x,y) &= (x-a)^2+(y-b)^2-r^2 = 0 \\
L(x(t),y(t)) &= \begin{cases} x &= x_0+ct \\ y&=y_0+dt \end{cases} \end{aligned}\]

We can substitute the parametric equations into the implicit equation to get a polynomial in terms of the parameter \(t\). The roots of the polynomial are the parameter values at which the line intersects the circle. Substituting, we get:
\[
\begin{aligned}
0 &= (x_0+ct-a)^2+(y_0+dt-b)^2-r^2 \\
&= [c^2t^2+2c(x_0-a)t+(x_0-a)^2]+[d^2t^2+2d(y_0-b)t+(y_0-b)^2]-r^2 \\
&= (c^2+d^2)t^2+2[c(x_0-a)+d(y_0-b)]t + [(x_0-a)^2+(y_0-b)^2-r^2] \\
&= At^2+Bt+C \\
\end{aligned}
\]
This looks promising. The quadratic formula can solve really quickly for the roots of the polynomial:
\[t=\frac{-B\pm\sqrt{B^2-4AC}}{2A}\]
The result of this will either be 2 complex roots, 1 real root, or 2 real roots, depending on the discriminant \(B^2-4AC\). The 1 real root case means the line is tangent to the circle, which according to our intersection counting rules above, we count twice. Bezout's theorem still holds. (Note: technically we have 1 real root with multiplicity 2 since we have the roots \(t = (-B+0)/2A\) and \(t = (-B-0)/2A\).)

That might be nice mathematically, but what does that mean for us? Well, the parameter \(t\) has to be a real number, so the real roots are values of \(t\) that the line intersects, so plugging the roots back into the parametric line gives us the Cartesian points of the intersection. What about the complex roots? Well, the restriction on \(t\) is that it has to be a real number, so in the case of 2 complex roots, we say the line doesn't intersect the circle.

Line-Plane Intersection


This is a simpler case than the line-circle intersection, although it involves a surface and a curve. A plane is algebraically a degree-1 polynomial in implicit form, so according to Bezout's theorem, they should intersect at exactly 1 point. We take the plane in implicit form and the line in parametric form and apply our method as above:
\[
\begin{aligned}
P(x,y) &= ax+by+cz+d=0 \\
L(x(t),y(t),z(t)) &= \begin{cases} x &= x_0+ut \\ y&=y_0+vt \\ z &= z_0+wt \\ \end{cases} \end{aligned}\]
We substitute the parametric equations into the implicit form and solve for \(t\) as before:
\[
\begin{aligned}
P(x(t),y(t),z(t)) = 0 &= a(x_0+ut)+b(y_0+vt)+c(z_0+wt)+d \\
&= (ax_0+by_0+cy_0+d) + (au+bv+cw)t \\
t &= -\frac{ax_0+by_0+cy_0+d}{au+bv+cw} \\
&= -\frac{(a,b,c,d)\cdot(x_0,y_0,z_0,1)}{(a,b,c,d)\cdot(u,v,w,0)}
\end{aligned}
\]
By substituting the value for \(t\) into the parametric line, we get the intersection point of the line and the plane.

Ray-Triangle Intersection

One method of ray-triangle intersection is basically a two-step process, the first step being computing the point on the plane that intersects the ray (line). The next is to determine if the point lies inside the triangle. Barycentric coordinates is a common method of doing this, but another method would be to create lines in the plane with directions such that a point inside the triangle would be on the same side of each line (i.e. the point would be on the left or right sides of each line). The side of the line can be computed using the scaled signed distance as detailed above.

Higher-Degree Curve Intersection


Just to illustrate how general this method is, let's take a more advanced example:
\[ \begin{aligned} C(x,y) &= x^2+y^2-1 = 0 \\
L(x(t),y(t)) &= \begin{cases} x &= 2t-1 \\ y&=8t^2-9t+1 \end{cases} \end{aligned}\]

Attached Image: curveIntx.PNG


The picture above shows the curves. The red curve is the parametric curve and the black curve is the implicit curve. As you can see, there are 4 real intersections, and since both curves are degree-2 we should end up with all real roots.

Inserting definitions of the parametric equations into the implicit form, we get:
\[
\begin{aligned}
f(x(t),y(t)) &= (2t-1)^2+(8t^2-9t+1)^2-1 \\
&= 64t^4-144t^3+101t^2-22t+1 \\
&= 0
\end{aligned}
\]
This is a quartic polynomial, so a more advanced numeric root finding method needs to be used, like bisection, regula falsi, or Newton's method. The roots of the polynomial are t = 0.06118, 0.28147, 0.90735, and 1.0. We do have all real roots, so Bezout's theorem is satisfied.

Higher-Degree Rational Curve Intersection


If the parametric curves are rational, then this method needs to be slightly modified. Rational parametric curves are usually of the form:
\[x = \frac{a(t)}{c(t)}, \, y = \frac{b(t)}{c(t)}\]
We can use homogeneous coordinates here really well. Since we have the mapping \((x,y) = (X/W,Y/W)\), we can define each homogeneous coordinate as \(X = a(t),\,Y = b(t),\,W=c(t)\). We also need to modify the implicit curve to handle homogeneous coordinates too, but this isn't very hard. Let's illustrate with an example:
\[
\begin{aligned}
S(x,y) &= x^2+2x+y^2+1 = 0 \\
P(x(t),y(t)) &= \begin{cases}
x &= \frac{t^2+t}{t^2+1} \\
y &= \frac{2t}{t^2+1} \\
\end{cases}
\end{aligned}\]
Our parametric curve in homogeneous coordinates is \(X = t^2+t\),\(Y=2t\),and \(W={t^2+1}\). Our implicit curve can be changed to use homogeneous coordinates by substituting in the mapping \((x,y) = (X/W,Y/W)\):
\[
\begin{aligned}
S &= \left(\frac{X}{W}\right)^2+2\left(\frac{X}{W}\right)+\left(\frac{Y}{W}\right)^2+1 \\
&= X^2+2XW+Y^2+W^2 \\
\end{aligned}
\]
We can substitute our homogeneous coordinate equation into our modified implicit function:
\[
\begin{aligned}
S &= (t^2+t)^2 + 2(t^2+t)(t^2+1) + (2t)^2 + (t^2+1)^2 \\
&= 4t^4+6t^3+5t^2+4t+1 \\
&= 0 \\
\end{aligned}
\]
We get 2 real roots at t = 0.3576 and t = 1, and 2 complex roots. Both curves are degree-2, so Bezout's theorem is satisfied as well.

Parametric-Parametric Curve Intersection


The way to solve intersections of 2 parametric curves is via implicitization of one of the curves. This can be a very fast method, but it suffers from numeric instability for high degree polynomials. We won't cover implicitization in this article, but there is a lot of literature on it out there.

Bezier Curve Intersection


A Bezier curve is just a degree-n Bernstein polynomial, which means it's just a regular polynomial of a different form. The above methods work well for Bezier curves, but they are more efficient and more numerically stable if modified slightly to take advantage of the Bernstein form. We won't cover this here, but there is literature out there on this as well.

Conclusion


These common methods of intersection testing can greatly aid any game programmer, whether purely for graphics or other game-specific logic. Hopefully you can make some use of these simple, yet powerful techniques.

References


A lot of the information here was taught to me by Dr. Thomas Sederberg (associate dean at Brigham Young University, inventor of the T-splines technology, and recipient of the 2006 ACM SIGGRAPH Computer Graphics Achievement Award). The rest came from my own study of relevant literature. The article image is from his self-published book, Computer-Aided Geometric Design.

Article Update Log


15 Jan 2014: Initial release

Efficient Art Production: Theory and Practice

$
0
0
Title image: Making sure your assets don't stink :) (An update of my 2009 article).

Video Game Artists work is all about efficiently producing an incredible looking asset. In this article we won't speak about what makes an asset look good, but rather about what makes it technically efficient.

From here this article splits into two parts. The first one will be about things you need to know to produce an efficient asset. The second one will be about things you need to do to make sure that the asset you've produced is efficient. Lets go!

Part 1: Theory


Attached Image: Things-You'dWant-To-Know_Web.jpg


Brain Cells Do Not Recover


Even though most of the information to follow is about making your assets more engine friendly, please don't forget that this is just an insight into how things work, a guideline to know which side to approach your work from. Saving you or your teammates time is also extremely important. An extra hundred of tris won't make FPS drop through the floor. Feeling work go smooth would make for a happier team that is able to produce a lot more a lot faster. Don't turn work into struggle for anyone including yourself. Always take concern in needs of people working with the assets you produce.

Dimension 1: Vertex


Remember geometry for a second. When we have a dot we, well, we have a dot. A dot is a unit of 1-dimensional space. If we move up to a 2-dimensional space, we'd be able to operate with dots in it too. But if we take two of them, then we'd be able to define a line. A line is a building block of 2-dimensional space. But if you take a closer look, a line is simply an endless number of dots put alongside each other according to a certain rule (linear function). Now lets move a level up again. In 3-dimensional space we can operate with both dots (or vertices) and lines. But, if we add one more dot to the previous two, that defined a line, we'd be able to define a face. And that face would be a building block in 3-dimensional space, that forms shapes which we are able to look at from different angles.

I'm pretty sure most of you are used to receiving a triangle count as a main guideline for creating a 3D model. And I think the fact of it being a building block of a 3-dimensional space has something to do with it. :)

But that's the human way of thinking. We, humans, also operate in a decimal numeral system, but hardware processors don't. It's just 0 and 1 - binary - the most basic number representation system. In order for a processor to execute anything at all, you have to break it into the smallest and simplest operations that it can solve consecutively. So in order to display 3D graphics you also have to get down to the basics. Even though a triangle is a building block of 3-dimensional space, it is still composed of 3 lines, which in their turn are defined by 3 vertices. So basically, it's not the tris that you are saving, but vertices.

Though, the less the tri count the less vertices there are, right? Totally. But unfortunately, the number of tris is not the only thing affecting your vert count. There's also some underlying process that are less obvious.

A 3D model is stored in memory as a number of vertex-structure-based objects. "Structures", speaking an object oriented programming language (figuratively), are predefined groups of different types of data and functions composed together to present a single entity. There could be thouthands of instances of such entities which all share the same variable types and functions, just different values stored in them. Such entities are called "objects". Here's a simplified example of how a vertex structure could look:

Vertex structure
{
Vertex Coordinates;
Vertex Color;
Vertex Normals;
UV1 Coordinates;
UV2 Coordinates;...
};

If you think of it, it's really obvious that vertex structures should only contain necessary data. Anything redundant could become a great waste of memory when your scenes hit a couple dozen million tris.

That's why a single vertex structure only holds one set of the same types of data. What does it mean for artists? It means that a vertex can't have 2 different UV positions in the same UV set, or 2 normals, or two material ID's. But we've all have seen how smoothing groups work, or applied multiple materials to objects and that didn't seem to increase the vert count. That's only in your modeling package. Your engine would treat the vertex data differently:

Attached Image: SgSplits.jpg


The easiest and most rational way to add that extra attribute to a vertex is to simply create another vertex in the exact same position.

Simply put, every time you set another smoothing group for a selection of polys or make a hard edge in maya, invisibly to you, the number of border vertices doubles. The same goes for every UV seam you create. And for every additional material you apply to your model.

UDK used to automatically compare the number of imported vertices versus generated upon assets import and warn you if the numbers differ for more than 25 percent. Now be it 25, 50 or a gazzilion percent - doesn't really matter that much if your game runs at frame. But knowing this stuff might help you get there. Just don't be surprised if your actual vert count is 3 times what you thought it was if you set all the edges to hard and break/detach all your UV verts.

Attached Image: Generated_Imported.jpg


Connecting the dots


This small chapter here concerns the stuff that keeps those vertices together – the edges. The way they form triangles is important for an artist, who wants to produce efficient assets. And not only because they define shape, but because they also define how fast your triangles are rendered in a pretty non-trivial way.

How would you render a pixel if it's right on the edge that 2 triangles share? You would render the pixel twice, for both triangles and then blend the results. And that leads us to a pretty interesting concept, that the tighter edge density, the more rerendered pixels you'll get and that means bigger render time. This issue should hardly affect the way you model, but knowing about it could come in handy in some other specific cases.

Triangulation would be a perfect example of such a case. It's a pretty known issue, that thin tris aren't all that good to render. But talking about triangulation, if you've made one triangle thinner - you made another one wider. Imagine if we zoom out from a uniformly triangulated model: the smaller the object becomes on screen, the tighter the edge density and the bigger the chance of rerendering the same pixels.

Attached Image: Triangulation.jpg


But, if you neglect uniform triangulation and worry about making every triangle have the largest area possible (thus making it incapacitate more pixels), in the end you'd get triangles with consecutively decreasing area sizes. Then once you zoom out again the amount of areas with higher edge density would be limited to a much smaller number of on-screen pixels. And the smaller the object becomes on screen, the smaller amount of potentially redrawn pixels it'll have. You could also try to work this the other way around, and start with making the triangle edges as short as possible. Now although trivial it's an interesting way to inform some of your decisions while modeling. Make sure to check out some statistics on the subject - pretty fascinating

Eating in portions is better for your health


Exactly the way your engine draws your object triangle by triangle, it draws the whole scene object by object. In order for your object to be rendered a draw call must be issued. While CPU gathers information and prepares batches to be sent to GPU, GPU renders stuff. What's important for us here, is that, if CPU is unable to supply GPU with the next batch by the time it's finished with the current, the GPU has nothing to do. This means that rendering an object with a small amount of tris actually isn't all that efficient. You'll spend more time preparing for the render, then on the render itself and waste the precious milliseconds your graphics card could be working its magic.

Attached Image: BatchBatchBatch.jpg
A frame from NVidia's 2005(?) GDC presentation


The number of tris GPU can render until the next batch is ready to be submitted varies per engine and GPU. But I'd say objects up to 700 tris probably wouldn't benefit that much from further optimization.

Defining such a number for your project would be a great help for your art team. It could save a lot of production and even some render time. Plus it'll serve as a guideline for artists to go by in production situations.

You'd want to have a less detailed model only when there's really no point in making it more complex, and you'd have to spent some extra time on things no one will ever notice. And that luckily works the other way around – you wouldn't want to make your model lowpolier than this, unless you have your specific reasons. Plus the reserve of tris you have could be well spent on shaving off those invisible vertices mentioned earlier. For example you can add some chamfered edges and fearlessly assign one smoothing group to the whole object (make all the edges soft). It may sound weird but having smoother, more tessellated models sometime could actually help the performance.

If you'd like your game to be more efficient, try to avoid making very low-polygonal objects a single independent asset. If you are making a tavern scene, you really don't want to have every fork, knife and dishes hand placed in the game editor. You'd rather combine them into sets or even combine them with a table. Yeah, you would have less variety, but when done right no one will notice.

But this in no case means that you should run around applying turbosmooth to everything. There are some things to watch out for, like stencil shadows for example. Plus some engines combine multiple objects into a single batch, so it's alway best to talk to talk your programmers first.

After batching, another very important thing to make or break your performance is the culling system your engine uses. If your engine doesn't cull the objects out of your frustum you're doing a lot of unnecessary and invisible rendering. If you doubled your Field Of View you've most likely doubled the amount of objects you'll have to render in your frame. Finally if your engine doesn't cull objects that are obstructed by other objects then technically you're rendering a lot more then you could've. So it's not only about the tri-counts but about being selective in what to render.

Vertex VS Pixel


Attached Image: VertVSPix_Web.jpg


If you ask an artist what is the main change in game art production we've seen in the last 10 years, I'm pretty sure that the most common answer would be the introduction of per texel shading and use of multiple textures to simulate different optical qualities of a single surface. Sure polycounts have grown, animation rigs now have more bones and procedurally generated physical movement is more widespread. But normal and spec maps are the ones contributing the most visual difference. And this difference comes at a price: in modern day engines, most of the render time is spent processing and applying all those endless maps based on the direction of the incoming lights and the cameras position.

Attached Image: Shaders_Web.jpg
Complex/Simple Shaders in UDK


From a viewpoint of an artist, who strives to produce effective art, this means following things: Optimizing your materials is much more fruitful than optimizing vertex counts. Adding an extra 10, 20 or even 500 tris ain't nearly as stressing for performance as applying another material on an object. Shaving hundreds of tris off your model would hardly ever bring a bigger bang than deciding that your object could do without an opacity map, or glow map, or bump offset or even a secular map. You can shave 2-3 millions triangles off a level just to gain around 2-3 fps. It's not the raw tri count that affects performance the most, but more the number of draw calls and shader and lighting complexity. Then there are vertex transformation costs when you have some really complex rigs or a lot of physically controlled objects.

Shader blending and lighting modes also have a lot to do with performance. Alpha blended materials cause a lot more stress than opaque ones. Vertex lit is faster and cheaper because you're going to have set vertex colors on your vertexes anyway. And if you're deffered you don't even have to worry about that, 'cause your engine is going to calculate lighting on the final frame (that has a constant amount of pixels) rather then for every object.

And finally: post processing. Doing too many opeartions on your final rendered pixels could also slow your game down significantly.

Things Differ (Communication is King)


As with everything in life, there's no universal recipe - things differ. And the best thing you can do is figure out what your specific case looks like. Get all the information you can from the people responsible. No one knows your engine better than the programmers. They know a lot of stuff that could be useful for artists but sometimes, due to lack of dialogue, this information remains with them. Miscommunication may lead to problems that could've been easily avoided, or be the reason you've done a lot of unnecessary work or wasted a truckload of time that could've been spent much wiser. Speak, you're all making one game after all and your success depends on how well you're able to cooperate. Asking has never hurt anyone and it's actually the best way to get an answer ;)

Dalai Lama once said:

“Learn your rules diligently, so you would now where to break them.”

Attached Image: Dalai-Lama_Web.jpg


And I can do nothing, but agree with him. Obeying rules all the time is the best way to not ever do anything original. All rules or restrictions have some solid arguments to back them up, and fit some general conditions. But conditions vary. If you take a closer look, every other asset could be an exception to some extent. And, ideally, having faced some tricky situation artists should be able to make decisions on their own, sometimes even break the rules if they know that the project will benefit from it, and them breaking the rules wouldn't hurt anything. But, If you don't know the facts behind the rules I doubt you would ever go breaking them. So I seriously encourage you take interest in your work. You're making games and not just art.


Part 2: Practice


Attached Image: Things-You'dWant-To-Do_Web.jpg


I hope this wall of text up here made some sense for you guys. All this information on how stuff works is really nice to know, but it's not exactly what you would use on a day-to-day basis. As an artist I'd love to have a place where all the “hows” are clearly stacked, without any other distracting information. And the “whys” section would serve as a reference you can turn to, in case something becomes unclear.

Now lets imagine you're finally done with an asset. You'd want to make sure things are clean and engine friendly. Here's the list of things to check upon consecutively:

- Deleted History, Frozen Transformations/Reset XForm, Collapsed Stack


Transformation information stored in a model could prevent it from being displayed correctly, making all further checks useless. Plus it's simply unacceptable for import into some engines. And even if it does import, the object's orientation and normal direction could be messed up.

Attached Image: Max_Collapse-XForm.jpg


In Maya, don't forget to select your object.

Attached Image: Maya_Freeze-History.jpg


- Inverted Normals


While mirroring (scaling by a negative number) or performing a ton of other operations actually, your vertex normals could get turned inside out. You should have the right settings set in your modeling application in order to spot such problems. In 3Ds Max you can go to object properties and turn “Backface cull” on. Then examine your mesh.

Attached Image: Max_BackFaceCull.jpg


In Maya you could just disable “Double Sided lighting” in the lighting tab (if it's missing hit “shift+m”), then make sure that in the Shading tab “Backface Culling” is disabled. Then if you'll check out your model with shading all the places with inverted normals will be black.

Attached Image: Maya_BackFaceCull.jpg


- Mesh splits/ Open Edges


It sometimes happens that while working we forget to weld some vertices or accidentally break/split some. Not only could this cause some lighting and smoothing issues, but it's also a waste of memory and pretty much a sign of a sloppy work. You wouldn't want that.

Open edges are an issue you want to think twice about. And not only because in some cases they could be an additional stress for computing dynamic lighting, but because it seriously reduces reusability of your asset. If you simply close the gap and find a place on your texture you can throw this new shell on, that would still be more preferable.

To detect both those issues in 3Ds Max simply choose border selection mode (“3” by default) and hit select all (“ctrl + a” by default).

Attached Image: Max_MeshSplits.jpg


In Maya you could use a handy tool called “Custom Polygon Display”. Choose “Highlight: Borders” radio button and apply to your object.

Attached Image: Maya_BorderEdges.jpg


- Multiple edges/ Double faces


This double stuff is a nasty bugger since it's almost impossible to spot unless you know how. And sometimes when you modify stuff you can get very surprised with things behaving not the way they should.

I could hardly remember having them in Max, but just to be sure I always apply a STL Check modifier. Tick the appropriate radio button and check the “Check” checkbox.)

Attached Image: Max_STLCheck.jpg


In Maya, the “Cleanup” tool is very useful. Just check “Nonmanifold geometry” and “Lamina faces” and hit apply.

Attached Image: Maya_Cleanup.jpg


- Smoothing groups/soft-hard edges


For this one you'd want to have as few smoothing groups/hard edges as possible. You might consider making it all smooth and just adding some extra chamfers, where bad lighting issues start to appear.

Plus there's one more issue to watch out for, more in Maya then in 3Ds Max though, since Max utilizes the Smoothing Group concept: edges on planar surfaces would appear smooth even if they are not. To see which edges are actually unsmoothed “Custom Polygon Display” tool comes in handy again. Just click “Soft/Hard” round button right alongside the “Edges:”

Attached Image: Maya_SoftHard.jpg


- UV splits


You would like your UVs to have the least number of seams possible, but as long as it is nice to work with. No need to go over the top with distortion here, just keep it clean and logical.

Broken/split vertices are a thing to watch out for too. 3Ds Max would indicate them with different color inside the “Edit UVWs” window.

While in Maya's “UV Texture Editor” window you have Highlight Edges button, that simply checks “Highlight: Texture Borders” checkbox in the “Custom Polygon Display” tool for you.

Attached Image: Maya_UVSplits.jpg


- Triangulation


While checking triangulation, first of all make sure that all the triangles accentuate the shape you're trying to convey, rather than contradict it. Then a quick glance, to check if triangulation is efficient.

Plus: some engines have their own triangulation algorithms and will triangulate a model on import themselves, with no concern about how you thought your triangulation looked. In trickier places this could lead to a messy result, so please take caution and investigate how your engine works and connect the vertices by hand if necessary. Btw, Maya more or less helps you find such places if you check “Concave faces” in “Cleanup Options”. In 3Ds Max you'll just have to keep an eye out for yourself.

Attached Image: Maya_Concave.jpg


- Grid Alignment/ Modularity/Pivot point placement


Since the last generation of videogames, graphics production costs have increased significantly, so modularity and extensive reusability are now a very common thing. Ease of implementation and combination with different assets could save a lot of time, maybe not even yours, so don't make your level designers hate you – think about it.

- Material Optimization


Evaluate your textures and materials again, since it's probably the biggest source of optimization. Maybe the gloss map doesn't deliver at all and specular is doing great on its own? Maybe if you used a twice smaller specular the asset would still hold up? Or maybe you can go with the diffuse for specular, since it's a just a small background asset? Maybe that additional tileable normal isn't necessary at all? Or maybe you could go with a grayscale spec, and use the spare channels for something else?

- Lightmapping possibility


If your engine supports lightmapping make sure you have a spare set of uniquely unwrapped UVs, which totally met all your engine's requirements.


Afterword


Please remember no matter how technical and optimized your model is, it's meant to look beautiful first of all. No optimization could be an excuse for an ugly model. Optimization is not what artists do. Artists do art. And that's what you should concentrate on. The beauty of technicalities is that they can be precise to a pretty big extent. This means you can write them down, memorize them and don't bother thinking about them again for quite a while, just remembering instead. But it's art where you have to evaluate and make millions of decisions every single second. All this text is not important and that is exactly why it is written down. Really important things aren't all that easy to be expressed with words. And I hoped, that maybe, if you didn't have to bother thinking about all the tech stuff at least for some time, you'd concentrate on stuff much more important, and prettier I hope.

Cheers,
Andrew

Math for Game Developers: Geometry Testing

$
0
0
Math for Game Developers is exactly what it sounds like - a weekly instructional YouTube series wherein I show you how to use math to make your games. Every Thursday we'll learn how to implement one game design, starting from the underlying mathematical concept and ending with its C++ implementation. The videos will teach you everything you need to know, all you need is a basic understanding of algebra and trigonometry. If you want to follow along with the code sections, it will help to know a bit of programming already, but it's not necessary. You can download the source code that I'm using from GitHub, from the description of each video. If you have questions about the topics covered or requests for future topics, I would love to hear them! Leave a comment, or ask me on my Twitter, @VinoBS

Note:  
The video below contains the playlist for all the videos in this series, which can be accessed via the playlist icon in the bottom-right corner of the embedded video frame once the video is playing. The first video in the series is loaded automatically


Geometry Testing



Programming By Example - Adding AngelScript to a Game Part 3

$
0
0
For Part 1, please click here: Programming By Example - Adding AngelScript to a Game Part 1
For Part 2, please click here: Programming By Example - Adding AngelScript to a Game Part 2

Introduction


This is part 3 of the article "Adding AngelScript to an Existing Game". In this series, I've been adding AngelScript to the XACTGame sample that can be found in the DirectX SDK. I hope to explain many of the needed concepts so others will be able to add scripting to their games.

AngelScript Concepts Covered
  • Using Script Header Files
  • Getting the Value of Script Global Variables in C++
  • Exposing Different Interfaces
  • Calling Script Functions with Parameters From C++
What will be Scripted

In the final part of this series, I'll use AngelScript to script much of the game logic inside of the XACTGame sample application. Here's a list of the functions that I'll script:
  • void FireAmmo()
  • void HandleAmmoAI( float fElapsedTime )
  • void HandleDroidAI( float fElapsedTime )
  • void CreateDroid()
  • float GetDistFromWall( D3DXVECTOR3 P1, D3DXVECTOR3 P2, D3DXVECTOR3 P3, D3DXVECTOR3 N )
  • void DroidPickNewDirection( int A )
  • void DroidChooseNewTask( int A )
  • void CheckForAmmoToDroidCollision( int A )
  • void CheckForInterAmmoCollision( float fElapsedTime )
  • void CheckForAmmoToWallCollision( int A )
  • void CreateAmmo( int nIndex, D3DXVECTOR4 Pos, D3DXVECTOR4 Vel )
Of all of these functions, only the first four need to be accessed by C++. The others will only be called by other script functions. The code here all runs while the game is being played. It's different from the InitApp() function that I scripted in part one. The InitApp function also needed access to the camera and menu settings while these functions don't, so instead of putting all of the script functions in one file, I'll divide it into two files--"initapp.as" and "gamelogic.as".

Using Script Header Files


Eventhough I use separate script files, there are some constants defined in "game.h" that I'd like to use in both files. AngelScript doesn't have built-in support for includes and can only parse scripts and doesn't have any functions to load files. However, if you remember from part 1, AngelScript has an optional add-on called Script Builder that can load files, handle includes and some C-sytle preprocessor directives, and removes meta data tags from the script before compilation.

Now I will add the a new file "constants.as"

// constants.as
// This is an AngelScript File
//--------------------------------------------------------------------------------------
// Consts
//--------------------------------------------------------------------------------------

// Will carry over to C++ code if modified in script ----------------------------------------
const uint MAXANISOTROPY            = 8;	// MAXANISOTROPY is the maximum anisotropy state value used when anisotropic filtering is enabled.
const float GROUND_ABSORBANCE       = 0.2f; // GROUND_ABSORBANCE is the percentage of the velocity absorbed by ground and walls when an ammo hits.
const float AMMO_ABSORBANCE         = 0.1f; // AMMO_ABSORBANCE is the percentage of the velocity absorbed by ammos when two collide.
const int MAX_AMMO                  = 10;  // MAX_AMMO is the maximum number of ammo that can exist in the world.
const int MAX_DROID                 = 50;
const uint DROID_HITPOINTS          = 20;
const float AMMO_SIZE               = 0.10f; // AMMO_SIZE is the diameter of the ball mesh in the world.
const float DROID_SIZE              = 0.5f;
const float DROID_MIN_HEIGHT        = 0.5f;
const float DROID_HEIGHT_FLUX       = 0.5f;
const uint DROID_TURN_AI_PERCENT    = 40;
const uint DROID_MOVE_AI_PERCENT    = 40;
const uint DROID_MOVE_TIME_MIN      = 2000;
const uint DROID_MOVE_TIME_FLUX     = 3000;
const uint DROID_CREATE_DELAY_FLUX  = 2500;
const float DROID_DEATH_SPEED       = 3.0f;
const float AUTOFIRE_DELAY          = 0.15f; // AUTOFIRE_DELAY is the period between two successive ammo firing.
const float CAMERA_SIZE             = 0.2f;	// CAMERA_SIZE is used for clipping camera movement
const float GRAVITY                 = 3.0f; // GRAVITY defines the magnitude of the downward force applied to ammos.
const float DROID_VELOCITY          = 2.0f; // MIN_VOL_ADJUST is the minimum volume adjustment based on contact velocity.
const float BOUNCE_TRANSFER         = 0.8f; // BOUNCE_TRANSFER is the proportion of velocity transferred during a collision between 2 ammos.
const float BOUNCE_LOST             = 0.1f; // BOUNCE_LOST is the proportion of velocity lost during a collision between 2 ammos.
const float REST_THRESHOLD          = 0.005f; // REST_THRESHOLD is the energy below which the ball is flagged as laying on ground.  // It is defined as Gravity * Height_above_ground + 0.5 * Velocity * Velocity
const float PHYSICS_FRAMELENGTH     = 0.003f; // PHYSICS_FRAMELENGTH is the duration of a frame for physics handling when the graphics frame length is too long.

// Will not carry over to C++ code if modified in script ------------------------------------
const float PI                      = 3.14159f;

// MinBound and MaxBound are the bounding box representing the cell mesh.
const float GROUND_Y                = 3.0f; // -GROUND_Y is the Y coordinate of the ground.
const D3DXVECTOR3           g_MinBound( -6.0f, -GROUND_Y, -6.0f );
const D3DXVECTOR3           g_MaxBound( 6.0f, GROUND_Y, 6.0f );

Even though this file will be used as an AngelScript "header file", it doesn't need header guards. The Script Builder add-on will check for that automatically. Later when I want to include it inside my other AngelScript files, I can include it just as I would in C/C++.

// include common constant variable definitions
#include "constants.as"

Getting the Value of Script Global Variables in C++


You may have noticed in the 'constants.as' file two comments one of which says, "Will carry over to C++ code if modified in script." The constants defined in 'constants.as' are the same as the ones defined in 'game.h'. It would be nice if I can change the values in 'constants.as' and have them carry over into the C++ code. This is possible in AngelScript. The asIScriptModule class contains the compiled script and it has methods that can be used to get the value stored in its global variables. To get a global variable from AngelScript, first we need to get the variable's index. We can do this by calling one of the following methods: GetGlobalVarIndexByName() or GetGlobalVarIndexByDecl(). Once you have the index, you can retrieve a pointer to the variable's memory location. I've written this short template function for retrieving a global variable from AngelScript. The function doesn't do any type checking; that's the responsibility of the programmer.

template <class T>
// returns -1 if not found
int GetGlobal(asIScriptModule *module, char *declaration, T &out_val)
{
	int index = module->GetGlobalVarIndexByDecl(declaration);
	if(index < 0) return -1;

	// get the variable from AngelScript and cast to our type
	T *var_ptr = (T *)module->GetAddressOfGlobalVar(index);

	// set the value
	out_val = *var_ptr;

	return 1;
}

From this, I wrote a class that will retrieve the values from AngelScript and provide functions to get the data. I'll store the class in the ScriptContextData struct and pass it to the functions that need it.

class CXACTGameScriptConstants
{
    public:
        void SetContantsFromScript(asIScriptModule *module)
        {
            int result;
            result = GetGlobal(module, "const uint MAXANISOTROPY", maxanisotropy); assert(result >= 0);
            result = GetGlobal(module, "const float GROUND_ABSORBANCE", ground_absorbance); assert(result >= 0);
            result = GetGlobal(module, "const float AMMO_ABSORBANCE", ammo_absorbance); assert(result >= 0);
            result = GetGlobal(module, "const int MAX_AMMO", max_ammo); assert(result >= 0);
            result = GetGlobal(module, "const int MAX_DROID", max_droid); assert(result >= 0);
            result = GetGlobal(module, "const uint DROID_HITPOINTS", droid_hitpoints); assert(result >= 0);
            result = GetGlobal(module, "const float AMMO_SIZE", ammo_size); assert(result >= 0);
            result = GetGlobal(module, "const float DROID_SIZE", droid_size); assert(result >= 0);
            result = GetGlobal(module, "const float DROID_MIN_HEIGHT", droid_min_height); assert(result >= 0);
            result = GetGlobal(module, "const float DROID_HEIGHT_FLUX", droid_height_flux); assert(result >= 0);
            result = GetGlobal(module, "const uint DROID_TURN_AI_PERCENT", droid_turn_ai_percent); assert(result >= 0);
            
            // Some parts ommitted because of length
            ....
        }

        unsigned int get_maxanisotropy() const { return maxanisotropy; }
        float get_ground_absorbance() const { return ground_absorbance; }
        float get_ammo_absorbance() const { return ammo_absorbance; }
        int get_max_ammo() const { return max_ammo; }
        int get_max_droid() const { return max_droid; }
        unsigned int get_droid_hitpoints() const { return droid_hitpoints; }
        float get_ammo_size() const { return ammo_size; }
        float get_droid_size() const { return droid_size; }
        float get_droid_min_height() const { return droid_min_height; }
        float get_droid_height_flux() const { return droid_height_flux; }
        unsigned int get_droid_turn_ai_percent() const { return droid_turn_ai_percent; }

        // Some parts ommitted because of length
        ....

    private:
        // helper function for getting global data from AngelScript
        template <class T>
        // returns -1 if not found
        int GetGlobal(asIScriptModule *module, char *declaration, T &out_val)
        {
            int index = module->GetGlobalVarIndexByDecl(declaration);
            if(index < 0) return -1;

            // get the variable from AngelScript and cast to our type
            T *var_ptr = (T *)module->GetAddressOfGlobalVar(index);

            // set the value
            out_val = *var_ptr;

            return 1;
        }

        unsigned int maxanisotropy;
        float ground_absorbance;
        float ammo_absorbance;
        int max_ammo;
        int max_droid;
        unsigned int droid_hitpoints;
        float ammo_size;
        float droid_size;
        float droid_min_height;
        float droid_height_flux;
        unsigned int droid_turn_ai_percent;
    
        // Some parts ommitted because of length
        ....
};

Exposing Different Interfaces


Now again, "initapp.as" and "gamelogic.as" don't need access to all of the same parts of the C++ interface. To limit access, AngelScript uses access masks. These are bit flags that we set when registering our interface and also when loading our scripts. To simplify things, I'll use only two types.

const unsigned int ScriptInterfaceMask_SetupOnly = 0x01; // interface can only be called by modules with this mask
const unsigned int ScriptInterfaceMask_Gameplay  = 0x02; // interface can only be called by modules with this mask
const unsigned int ScriptInterfaceMask_All       = ScriptInterfaceMask_SetupOnly | ScriptInterfaceMask_Gameplay; // interface can only be called both modules types

To set the access mask for our interface, we use the SetDefaultAccessMask() method inside asIScriptEngine. To add this support, the following changes are needed to the RegisterGameInterface() function inside "as_scripting.cpp":

int RegisterGameInterface(asIScriptEngine *scriptengine)
{
	int result;

	// Set Acces Mask ---------------------------------------------------------------------
	scriptengine->SetDefaultAccessMask(ScriptInterfaceMask_All); 
	// Bindings made in this section are accessible to everything -------------------------

	// Register STD String (Needed to help me test my implementation)
	RegisterStdString(scriptengine);

	// Register Arrays
	RegisterScriptArray(scriptengine, true);

	// Register print function (Needed to help me test my implementation)
	result = scriptengine->RegisterGlobalFunction("void print(const string &in)", asFUNCTION(print), asCALL_CDECL);
	if(result < 0) return result;

	// Set Acces Mask ---------------------------------------------------------------------
	scriptengine->SetDefaultAccessMask(ScriptInterfaceMask_Gameplay); 
	// Bindings made in this section are accessible to game play related scripts ----------

	// Register enum GAME_MODE
	result = RegisterEnumGAME_MODE(scriptengine);
	if(result < 0) return result;

	result = RegisterD3DXMathFunctions(scriptengine);
	if(result < 0) return result;

	result = RegisterD3DXCOLOR(scriptengine);
	if(result < 0) return result;

	result = RegisterDROID_STATE(scriptengine);
	if(result < 0) return result;

	result = RegisterAMMO_STATE(scriptengine);
	if(result < 0) return result;

	result = RegisterGameStateInterface(scriptengine);
	if(result < 0) return result;

	result = RegisterAudioInterface(scriptengine);
	if(result < 0) return result;

	result = RegisterCameraInterface(scriptengine); // FireAmmo() needs to get the view
	if(result < 0) return result;

	// Set Acces Mask ---------------------------------------------------------------------
	scriptengine->SetDefaultAccessMask(ScriptInterfaceMask_SetupOnly); 
	// Bindings made in this section are accessible to setup related scripts --------------

	result = RegisterDialogInterface(scriptengine);

	return result;
}

The Script Builder add-on can only load one module at a time. A module can be made up of a collection of script files, but the functions in the module have the same access mask. Since we want our modules to have different access masks we'll need to change LoadScript() from part 1.

int LoadScript(asIScriptEngine *scriptengine, ScriptContextData &contextdata)
// Note in earlier articles I passed the Script Builder object as a reference, but this isn't
// needed as once the module has been created, the script builder object no longer has any use.
{
	int result;

	// The CScriptBuilder helper is an add-on that loads the file,
	// performs a pre-processing pass if necessary, and then tells
	// the engine to build a script module.
	CScriptBuilder builder;

	// load initapp.as into a module -----------------------------------------------------------------------------
	result = builder.StartNewModule(scriptengine, "InitAppModule"); 
	if( result < 0 ) 
	{
		// If the code fails here it is usually because there
		// is no more memory to allocate the module
		MessageBoxA(NULL, "Unrecoverable error while starting a new module.", "AngelScript Message", MB_OK);
		return result;
	}

	// set the modules access mask
	builder.GetModule()->SetAccessMask(ScriptInterfaceMask_All); // give init app full access

	// load the script
	result = builder.AddSectionFromFile("initapp.as");
	if( result < 0 ) 
	{
		// The builder wasn't able to load the file. Maybe the file
		// has been removed, or the wrong name was given, or some
		// preprocessing commands are incorrectly written.
		MessageBoxA(NULL,"Please correct the errors in the script and try again.", "AngelScript Message", MB_OK);
		return result;
	}
	result = builder.BuildModule();
	if( result < 0 ) 
	{
		// An error occurred. Instruct the script writer to fix the 
		// compilation errors that were listed in the output stream.
		MessageBoxA(NULL,"Please correct the errors in the script and try again.", "AngelScript Message", MB_OK);
		return result;
	}

	// load gamelogic.as into a module ----------------------------------------------------------------------
	result = builder.StartNewModule(scriptengine, "GameModule"); 
	if( result < 0 ) 
	{
		// If the code fails here it is usually because there
		// is no more memory to allocate the module
		MessageBoxA(NULL, "Unrecoverable error while starting a new module.", "AngelScript Message", MB_OK);
		return result;
	}

	// set the modules access mask
	builder.GetModule()->SetAccessMask(ScriptInterfaceMask_Gameplay); // only give gameplay access

	// load the script
	result = builder.AddSectionFromFile("gamelogic.as");
	if( result < 0 ) 
	{
		// The builder wasn't able to load the file. Maybe the file
		// has been removed, or the wrong name was given, or some
		// preprocessing commands are incorrectly written.
		MessageBoxA(NULL,"Please correct the errors in the script and try again.", "AngelScript Message", MB_OK);
		return result;
	}
	result = builder.BuildModule();
	if( result < 0 ) 
	{
		// An error occurred. Instruct the script writer to fix the 
		// compilation errors that were listed in the output stream.
		MessageBoxA(NULL,"Please correct the errors in the script and try again.", "AngelScript Message", MB_OK);
		return result;
	}

	...
}

Calling Script Functions with Parameters From C++


Remember in part 1, I stored the AngelScript script function pointer for InitApp. I'll do the same with the new script function

// enumerations -------------------------------------------------------------
enum ScriptFunctionIDs
{
	Function_InitApp = 0,
	Function_FireAmmo,
	Function_HandleAmmoAI,
	Function_HandleDroidAI,
	Function_CreateDroid
};

const unsigned int max_script_functions = 5;

struct ScriptContextData
{
	asIScriptContext *ctx;
	asIScriptFunction *script_functions[max_script_functions];

	void ExecuteFunction(ScriptFunctionIDs func_id);
};

I've created a helper function for finding the script function and displaying a message if the script can't be found.

asIScriptFunction *GetScriptFunction(asIScriptModule *mod, char *declaration)
{
	asIScriptFunction *func = mod->GetFunctionByDecl(declaration);
	if(func == NULL)
	{
		MessageBoxA(NULL,"AngelScript Message - The script function missing. Please add it and try again.", declaration, MB_OK);
		return NULL;
	}
	return func;
}

Now with that function, I just need to add the following to the end of the LoadScript function.

// Find the function that is to be called. 
asIScriptModule *modInitApp = scriptengine->GetModule("InitAppModule");
contextdata.script_functions[Function_InitApp] = GetScriptFunction(modInitApp, "void InitApp()");
if( contextdata.script_functions[Function_InitApp] == 0 ) return -1;

asIScriptModule *modlogic = scriptengine->GetModule("GameModule");
contextdata.script_functions[Function_FireAmmo] = GetScriptFunction(modlogic, "void FireAmmo()");
if( contextdata.script_functions[Function_FireAmmo] == 0 ) return -1;

contextdata.script_functions[Function_HandleAmmoAI] = GetScriptFunction(modlogic, "void HandleAmmoAI( float fElapsedTime )");
if( contextdata.script_functions[Function_HandleAmmoAI] == 0 ) return -1;

contextdata.script_functions[Function_HandleDroidAI] = GetScriptFunction(modlogic, "void HandleDroidAI( float fElapsedTime )");
if( contextdata.script_functions[Function_HandleDroidAI] == 0 ) return -1;

contextdata.script_functions[Function_CreateDroid] = GetScriptFunction(modlogic, "void CreateDroid()");
if( contextdata.script_functions[Function_CreateDroid] == 0 ) return -1;

To call the scripts, I made an ExecuteFunction() which I created and put into the ScriptContextData structure. It takes a value from the ScriptFunctionIDs enum as a parameter. This worked well as the InitApp() function doesn't take any parameters, but now I want to support calling script functions from C++ that have parameters. Executing a script's functions from C++ is a 4-step process. First, you should call Prepare() which will allow the script context to prepare the stack. Next, if there are parameters, the paremeters should be set. One of the following asIScriptContext methods can be used for primitive types:

	int SetArgDWord(int arg, asDWORD value);
	int SetArgQWord(int arg, asQWORD value);
	int SetArgFloat(int arg, float value);
	int SetArgDouble(int arg, double value);
	int SetArgByte(int arg, asBYTE value);
	int SetArgWord(int arg, asWORD value);

The arg parameter is the index of the parameter in the function's paramter list. After the parameters have been set, you should call Execute(). Finally, to get the return value, use one of the following functions:

	asDWORD GetReturnDWord();
	asQWORD GetReturnQWord();
	float   GetReturnFloat();
	double  GetReturnDouble();
	asBYTE  GetReturnByte();
	asWORD  GetReturnWord();

To handle these changes in code, I'll divide the ExecuteFunction() into two separate methods--PrepareFunction() and ExecuteFunction(). If the function has parameters, one of the SetArg methods can be called between the prepare and execute calls.

int PrepareFunction(ScriptFunctionIDs func_id)
{
	// I'm no longer checking for a valid context here. It's up to the application writer to ensure 
	// that the context is valid before calling this
	return ctx->Prepare(script_functions[func_id]);
}

int ExecuteFunction()
{
	// I'm no longer checking for a valid context here. It's up to the application writer to ensure 
	// that the context is valid before calling this
	int result = ctx->Execute();
	if( result != asEXECUTION_FINISHED )
	{
		// The execution didn't complete as expected. Determine what happened.
		if( result == asEXECUTION_EXCEPTION )
		{
			// An exception occurred, let the script writer know what happened so it can be corrected.
			MessageBoxA(NULL, ctx->GetExceptionString(), "An exception occurred.", MB_OK);
			return -1;
		}
	}
	return result;
}

Converting the C++ code to AngelScript


All that's left to do is to convert the code from C++ to AngelScript. AngelScript and C++ have almost identical syntax so this isn't too much of a problem. When registering the C++ objects and bindings with AngelScript, I did change things. For example, to access the game state object, the C++ code directly accesses the g_GameState variable whereas in my bindings, I give limited access through a namespace GAME_STATE. Also, the DirectX math functions use pointers, but in my bindings, I use references. Here's an example of the FireAmmo() function in AngelScript:

void FireAmmo()
{
    // Check to see if there are already MAX_AMMO balls in the world.
    // Remove the oldest ammo to make room for the newest if necessary.
    double fOldest = GAME_STATE::AmmoQ[0].fTimeCreated;
    int nOldestIndex = 0;
    int nInactiveIndex = -1;
    for( int iAmmo = 0; iAmmo < MAX_AMMO; iAmmo++ )
    {
        if( !GAME_STATE::AmmoQ[iAmmo].bActive )
        {
            nInactiveIndex = iAmmo;
            break;
        }
        if( GAME_STATE::AmmoQ[iAmmo].fTimeCreated < fOldest )
        {
            fOldest = GAME_STATE::AmmoQ[iAmmo].fTimeCreated;
            nOldestIndex = iAmmo;
        }
    }

    if( nInactiveIndex < 0 )
    {
        GAME_STATE::AmmoQ[nOldestIndex].bActive = false;
        GAME_STATE::nAmmoCount--;
        nInactiveIndex = nOldestIndex;
    }

    int nNewAmmoIndex = nInactiveIndex;

    // Get inverse view matrix
    D3DXMATRIXA16 mInvView;
	float det = 0.0;
	D3DXMatrixInverse(mInvView, det, FirstPersonCamera::CameraGetViewMatrix());
    //D3DXMatrixInverse( &mInvView, NULL, g_Camera.GetViewMatrix() );

    // Compute initial velocity in world space from camera space
    D3DXVECTOR4 InitialVelocity( 0.0f, 0.0f, 6.0f, 0.0f );
	D3DXVec4Transform(InitialVelocity, InitialVelocity, mInvView);
    //D3DXVec4Transform( &InitialVelocity, &InitialVelocity, &mInvView );
    D3DXVECTOR4 InitialPosition( 0.0f, -0.15f, 0.0f, 1.0f );
	//D3DXVec4Transform(InitialPosition, InitialPosition, mInvView);
    D3DXVec4Transform( InitialPosition, InitialPosition, mInvView );

    AUDIO::PlayAudioCue(AUDIO::Cue_iAmmoFire);
	//PlayAudioCue( g_audioState.iAmmoFire );

    CreateAmmo( nNewAmmoIndex, InitialPosition, InitialVelocity );
}

AngelScript is almost like an extension to C++. This code is almost exactly like its C++ counterpart and because of the array add-on introduced in part 2 of this series, the arrays can be easily shared.

Results and Conclusion


The XACTGame sample runs almost exactly the same when using AngelScript to write some parts of it as opposed to coding the entire project in C++. The goal of this article was to show how to add AngelScript to a project and also to test out the languages capabilites. For the most part, the app runs without any slowdowns. When the project runs in "Release Mode", it runs exactly like it did when built 100% in C++, but there is a slowdown when the player fires too many projectiles in "Debug Mode". I traced this to the collision detection code which I also decided to do in AngelScript. By reducing the number of active projectiles, I was able to get the performance back in the range of the C++ levels. This is to be expected because of the usual debugging overhead. I was surprised at how nicely it worked in "Release Mode". Changing the constants in 'constants.as' successfully changes the project without recompiling.

AngelScript is a language that should be considered if you're thinking about adding scripting to your C++ project. It's easy to learn if you have a C++ background and it binds with C++ very well. I hope that I was able to cover the major points for adding AngelScript to your game.

Coding style in this article


Listed in the best practices for AngelScript is to always check the return value for every function. In most of the AngelCode examples and in the manual an assert is used to check for errors. I don't use the assert, instead I've been using if(result < 0) return result;. This can easily be replaced by assert(r >= 0); as is used in the AngelScript documentation.

Also, my goal with this project was to change the XACTGame sample as little as possible. The XACTGame sample was designed to show certain techniques such as adding graphics and audio, and it uses a simple framework.

Getting AngelScript


You can download the latest version of the AngelScript SDK from the AngelCode website.
http://www.angelcode.com/
You'll find an excellent manual that explains the API in detail.

Note on Microsoft Source Code


Because Microsoft code was used in this program, I want to state some terms from the Direct X SDK EULA. The XACTGame sample was created by Microsoft and Microsoft owns the copyright. Changes made by Dominque Douglas have been clearly marked. Use of the source code provided does not change the license agreement for using Microsoft code. Microsoft code cannot be modified to work on non-Microsoft operating systems and Microsoft is not responsible for any claims related to the distribution of this program. Refer to the license agreement in the Direct X SDK for details.

Downloading This Project


The source code can be downloaded here: Attached File  XACTGameAngelScript-Part3.zip   526.13KB   0 downloads


Download note: Because of the size, this does not include the media files needed by the project such as the audio files and graphics files. You'll need to copy the "media" folder from the XACTGame sample in the DirectX SDK. You may need to alter the project's include and library directories to match your system. For simplicity, the AngelScript add-ons that were used in this project have been included. The project is a Visual Studio 2010 solution.

With this final part of the series, I've decided to also include the binary version of the project.

Attached File  XACTGameAngelScript-Binaries.zip   451.59KB   0 downloads


If you would like the media files as well, you can download them from my blog at: Squared Programming Blog

Article Update Log


23 Jan 2014: Initial release

Stretching Your Game To Fit The Screen Without Letterboxing - SDL2

$
0
0
If you're making a game that you want to be used on a wide variety of systems, your game has to be scalable, especially if it is in a very small resolution. This is usually pretty straightforward. Most graphics libraries and the like have things built in for doing the scaling for you. SDL2 does, but only to a certain extent. If you want your game to be scaled without letter-boxing, you have to write this yourself. It took me a while to figure out, so I though I would spread the knowledge.

The code included is written in C++ and uses SDL2, which can be found here: http://libsdl.org/. The documentation which describes the functions that I use is located here: http://wiki.libsdl.org/CategoryRender. And to set up SDL2, some easy instructions can be found at the awesome site LazyFoo

Scaling On Up


The theory here is this: You don't want letterboxing. You want the game to be taken from its original size and stretched to fit the window no matter what. So you just render everything to a back buffer by setting the target to it, instead of to the window's buffer, then at the end of your rendering cycle, switch the target back to the screen and RenderCopy the back buffer onto the screen, rescaling if neccessary, then set the target back to your back buffer and repeat. It's slightly trickier than it sounds.

Enter The Code


So in order to accomplish scaling without letter-boxing, we will need some SDL rectangles variables.

SDL_Rect nativeSize;
SDL_Rect newWindowSize;

One is for storing the original size of the game, nativeSize, and one is for storing the size of the window when it changes size, newWindowSize.

float scaleRatioW;
float scaleRatioH;

SDL_Window * window;
SDL_Renderer * renderer;
SDL_Texture * backBuffer;

SDL_Texture * ballImage;

bool resize;

We also will need an SDL_Window, an SDL_Renderer for that window, and last but not least a backBuffer for rendering everything to before copying it, properly scaled, to the window's buffer. Also there is ballImage which will be used to demonstrate the actual scaling. The two floats, scaleRatioW and scaleRatioH are optional. They are used if you need to scale the values of certain things that rely on the coordinates of the mouse, e.g. a button. You would multiply the x coord and width of its bounding-box by scaleRatioW and the y coord and height by scaleRatioH. resize is used to tell the render function to call the resize function. It is neccessary to do it this way because there is a stall in the program after resizing the window that can sometimes cause problems if you call the resize functions immediately upon receiving a resize event.

So I have set up a fairly simple main function to run the program.

int main(int argc, char * argv[])
{
    InitValues();
    InitSDL();

    bool quit = false;

    while(!quit)
    {
        quit = HandleEvents();
        Render();
    }

    return 0;
}

InitValues does what you might expect it to do, gives all the variables their starting values. InitSDL does most of the importnat work, setting up the window and renderer as well as set up the back buffer and load the ball image. HandleEvents returns false if the user clicks the x button on the window, however it also captures the window resize event and resizes the screen using a function called Resize. Render handles a very important part of the process, changing from the back buffer to the window buffer. I'll explain each of the functions in turn.

void InitValues()
{ 
    nativeSize.x = 0;
    nativeSize.y = 0;
    nativeSize.w = 256;
    nativeSize.h = 224;

    newWindowSize.x = 0;
    newWindowSize.y = 0;
    newWindowSize.w = nativeSize.w;
    newWindowSize.h = nativeSize.h;
    
    scaleRatioW = 1.0f;
    scaleRatioH = 1.0f;

    window = NULL;
    renderer = NULL;
    backBuffer = NULL;
    ballImage = NULL;
}

I set the native size to that of a GameBoy, 256 by 224.

void InitSDL()
{
    if(SDL_Init(SDL_INIT_EVERYTHING) < 0)
    {
        cout << "Failed to initialize SDL" << endl;
    }

    //Set the scaling quality to nearest-pixel
    if(SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "0") < 0)
    {
        cout << "Failed to set Render Scale Quality" << endl;
    }

    //Window needs to be resizable
    window = SDL_CreateWindow("Rescaling Windows!",
                              SDL_WINDOWPOS_CENTERED,
                              SDL_WINDOWPOS_CENTERED,
                              nativeSize.w,
                              nativeSize.h,
                              SDL_WINDOW_RESIZABLE);

    //You must use the SDL_RENDERER_TARGETTEXTURE flag in order to target the backbuffer
    renderer = SDL_CreateRenderer(window,
                                  -1,
                                  SDL_RENDERER_ACCELERATED |
                                  SDL_RENDERER_TARGETTEXTURE);

    //Set to blue so it's noticeable if it doesn't do right.
    SDL_SetRenderDrawColor(renderer, 0, 0, 200, 255);

    //Similarly, you must use SDL_TEXTUREACCESS_TARGET when you create the texture
    backBuffer = SDL_CreateTexture(renderer,
                                   SDL_GetWindowPixelFormat(window),
                                   SDL_TEXTUREACCESS_TARGET,
                                   nativeSize.w,
                                   nativeSize.h);

    //IMPORTANT Set the back buffer as the target
    SDL_SetRenderTarget(renderer, backBuffer);

    //Load an image yay
    SDL_Surface * image = SDL_LoadBMP("Ball.bmp");

    ballImage = SDL_CreateTextureFromSurface(renderer, image);

    SDL_FreeSurface(image);
}

This is a fairly large chunk of code. First SDL_Init is called to set up all the SDL subsystems. Next, something that one might find important, SDL_SetHint is called to set the render scale quality to nearest-pixel. Linear quality makes scaling very small images to very large images hazy, and this is unwanted behavior.

Next the window is created with the SDL_WINDOW_RESIZABLE flag to allow it to be resized. Then, very important, the renderer is created with the SDL_RENDERER_TARGETTEXTURE flag which allows a texture to be targeted, and backBuffer must be created using the SDL_TEXTUREACCESS_TARGET flag for it to be used as a back buffer. The render target is then set to backBuffer so that all drawing will happen on it, not on the window.

bool HandleEvents()
{
    while(SDL_PollEvent(&event) )
    {
        if(event.type == SDL_QUIT)
        {
            return true;
        }
        else if(event.type == SDL_WINDOWEVENT)
        {
            if(event.window.event == SDL_WINDOWEVENT_SIZE_CHANGED)
            {
                resize = true;     
            }
        }
    }
    
    return false;
}

The HandleEvents function for clarification. Resize is not called all the time, just when the window's size changes.

void Resize()
{
    int w, h;

    SDL_GetWindowSize(window, &w, &h);

    scaleRatioW = w / nativeSize.w;
    scaleRatioH = h / nativeSize.h;  //The ratio from the native size to the new size

    newWindowSize.w = w;
    newWindowSize.h = h;

    //In order to do a resize, you must destroy the back buffer. Try without it, it doesn't work
    SDL_DestroyTexture(backBuffer);
    backBuffer = SDL_CreateTexture(renderer,
                                   SDL_GetWindowPixelFormat(window), 
                                   SDL_TEXTUREACCESS_TARGET, //Again, must be created using this
                                   nativeSize.w, 
                                   nativeSize.h);

    SDL_Rect viewPort;
    SDL_RenderGetViewport(renderer, &viewPort);

    if(viewPort.w != newWindowSize.w || viewPort.h != newWindowSize.h)
    {
        //VERY IMPORTANT - Change the viewport over to the new size. It doesn't do this for you.
        SDL_RenderSetViewport(renderer, &newWindowSize);
    }
}

So here is the most important part of scaling and where it became tricky. There are quite a few things that happen here. First, get the new window size (using GetWindowSize for clarity). Then calculate the new ratio based on the new size and the native size. Then, Very Important you must destory and recreate your back buffer. I honestly don't know why, but if you try without doing this it will not work. Then, also Very Important, you must set the view port to the new window size because it will not do it for you, or do it right anyways.

void Render()
{
    SDL_RenderCopy(renderer, ballImage, NULL, NULL); //Render the entire ballImage to the backBuffer at (0, 0)

    SDL_SetRenderTarget(renderer, NULL); //Set the target back to the window
    
    	if(resize) //If a resize is neccessary, do so.
        {
            Resize();
        }

        SDL_RenderCopy(renderer, backBuffer, &nativeSize, &newWindowSize); //Render the backBuffer onto the
                                                                           //screen at (0,0)    
        SDL_RenderPresent(renderer);
        SDL_RenderClear(renderer); //Clear the window buffer

    SDL_SetRenderTarget(renderer, backBuffer); //Set the target back to the back buffer
    SDL_RenderClear(renderer); //Clear the back buffer
}

And last but not least the Render function. The comments explain this one pretty well.

Note:  For some reason, whenever you first resize the window, there is about a three-second delay and after that it resizes without a hitch. Not sure why on that one.


Conclusion


SDL2 is a pretty straight-forward API for doing just about anything. However, getting around letter-boxing was slightly tricky. Some of the things that occur in the background of SDL aren't obvious which led to a sort of trial and error process to figure out the way around. I hope this helps if anyone has the same problem with letter-boxing that I did.

Here is the source. You can run it from inside the bin folder: Attached File  SDL2StretchToWindow.zip   965.39KB   15 downloads

Composing Music For Video Games - Key & Tempo

$
0
0
When composing music for visual media, especially films, TV shows, adverts and video games, it is key to be able to reinforce musically what is happening on the screen. Two areas that establish the mood of a composition is that of the key and the tempo. Further to the video tutorial that covers these areas briefly, this article was produced to delve into a bit more in depth about the choice of key and feel of the tempo.

The Youtube video which accompanies this article to help demonstrate what is discussed can be viewed below:




Know Thy Character & Environment


When considering what to compose it is important to grasp the character's personality, morals and attitude in the visual, as well as the environment they are in. We need to learn and get to know the person the actor is portraying. When it comes to putting music to the visual, it needs to suit every aspect of the scene down to the last detail like “What is the weather like?” or “What season does the scene look like its been shot in? Does it have an essence of summer about it or does it look cold like winter?” We can then take these details and enhance the emotions that need to be accented.

We're massive Star Wars fans and love the work of John Williams. The music he composed enhances the scenes. Think of the‘Imperial March' when Darth Vader walks in. It wouldn't have worked so well if he would have marched in to the sound of ‘Waterloo' by Abba!

So how is this acquiring the right tone and ideas for a scene achieved?

One of the techniques we use is to find key words that associate with the characters and the environment in the visuals. Here are some of the examples we came up with when watching the Grazia Fetish advert:

Environment: Shady, Noir, Erotic, Gloomy, Street lit

Character: Devious, Flirtatious, Enticing, Seductive, Silky (model's movements)

Starting with the environment, we can establish immediately that it's very dark, subtly lit with hints of red, purple and blue. This makes the scene feel like it is set outside at night time and the only light available is from neon shop lights and street lights. The elements of shade and rain portray a 'dark' feel, there's nothing really happy and joyous about the environment.

Looking at the model's personality in the scene, she gives off this mysterious, flirtatious persona. The way in which she speaks with her body language, the way she opens her eyes slowly, the way she turns to look at the camera, the way in which see runs her hand up and down her leg, they are all very silky smooth movements.

What is she trying to say with all these looks and movements?

Every smile she gives off has a hint of seductiveness and 'up to no good' intent about them. The look in her eyes gives off a mischievous and luring impression. We may even go as far as to say she has the “Hey, big boy” look! As the scene unfolds the “Erotic” meter gets turned up the more it goes on. That straight, serious face that we see at the start becomes a raunchy, playful smile.

So how can this translate musically? Through the choice of key, utilising a minor one for example to put emphasis on the 'dark' feel, and also through tempo. Let's discuss key first.

Major Or Minor?


The reason why we try to pick major and minor keys for compositions is for the emotional feel. Essentially the harmony of the chords and the choices of notes in the melody portrays the feel and emotion of the piece. We associate 'major' as a happy feel and 'minor' as a sad vibe. Before we go any further into this, we need to remember for every major key, there is a relative minor and vice versa. The relative minor is found 3 semi-tones below the route note of the major scale.

I have chosen C major for this example because there are no sharps or flats, I have also added the names for each chord to show how the harmonies are the same.

C Major Scale: C, Dm, Em, F, G, Am, Bdim, C.

A Minor Scale: Am, Bdim, C, Dm, Em, F, G, Am.

As you can see, both scales share the same notes and chords. The only difference you can see from this example is that one scale starts on a different note to the other.

So what's the difference if they are both the same?

The difference is in the 3rd and 6th notes of the scale. For example, in the major scale, the gap between notes 2 and 3 (D & E) are a tone apart. Whereas it is only a semi-tone apart in the minor scale (B & C). This difference in the scales are what make the major and the minor scale sound the way they do.

So to conclude, for us to achieve the mood we wanted to give in the clip, it was about choosing a minor key and minor chords that set a solid foundation to build a melody on. One that will enhance the visuals.

Tempo


This particular composition is set to 91 BPM. This tempo works so well with the visual not just because of how it links with the cuts of the shots, but also because of how the groove falls into place. If you watch the full advert for instance there are two points (00:46 & 01:18) which the tempo of the track plays a key role in emphasising what happens visually. Let's discuss one of these points, at 00:46, when the snare hits on the beat as she lifts her head. The drop of the music before the snare hitting adds to the rise in the composition that follows by allowing it to breath. If you look at her face, she looks as if she is about to overcome a situation and has a certain 'driven' look. It was this point that was key to emphasise and the tempo allowed for this to happen perfectly. If the track was faster it would lose the groove and feel rushed, if it was slower it would lose the pulse and lack the edge it has.

There are a few things that I want to mention that make up the composition that the tempo directly corresponds to. Let's start with the rhythm section, the foundation and groove of the piece. The drums are very straight which allows the bass to slot nicely into place with that 'pulsing' feel. To add extra attitude and rawness to that pulsing bass line is 2 distorted guitar parts, the first one being a palm muted 8th note rhythm to reinforce that rhythm section and the other was a “this one goes up to 11!” guitar tone that rings the chord out on each chord change. All this gives that raw edge to the composition that it needs. Lastly, notice how simple the rhythm section is. Its primary objective is to create that pulsing rhythm that many would consider “the groove” allowing space for the lead part to shine on top of it.

Another element in this composition to briefly look at is the string part and the end section with the guitar solo. These add depth into the emotion of the song. A good place to start with when writing a lead part is to emphasise on chord tones, notice again how simple the string parts are, highlighting certain notes of the chord that add flavour and draw attention to a particular element of the chord. Very simple, but effective. Lastly, credit to a great guitarist, Ben Monaghan, for the guitar solo at the end. Personally, I've played with many guitarists who judge a good solo with how much smoke they can create from the fretboard. Ben came up with clever little phrases sitting slightly in the back of the mix interweaved with a synth line bringing a little extra to the final progression of the song.

These are just some of the things that went into constructing the composition for this advert. I hope this article has inspired you and given you fresh ideas to try out for your own work.

Joe Gilliver & Dan Harris

www.ocularaudio.com


Contact - joe@ocularaudio.com

Indie Device Anxiety Syndrome (IDAS)

$
0
0
If there was an Indie Anonymous Meeting where I could stand up and tell my story, this would be it.

I have the same fear every morning when the post arrives on my doorstep. The fear that Microsoft, Sony or Nintendo will deliver me the hardware SDK for their current consoles, just because I applied for a license to develop for their platform. Adding to that is the fact that I already have multiple mobile, tablet, VR and desktop devices, all readily accessible to me using a plethora of programming languages and content development tools, a good example being Lightwave 3D and Unity which I use daily.

This combines to what I will try to formulate as my own personal IDAS.

Symptoms Of and Suffering With IDAS


Accessibility To High-power Hardware


So the hardware maker gods have gone and removed the barriers I, as well as many other Indies, have been running up against for all these years. It’s become like Christmas with the feeling of surprise a child gets on the first morning after Santa has visited, when all presents are there, stacked under the tree.

This, though, is not only a good thing for me.

That is not quite as easy as it sounds. I don’t mean that part of the creative process where I suffer to find a compelling game design, a good idea for a game mechanic or a faster way to draw my sprites. These are admittedly severe problems faced during the creation of any specific game at any time since the invention of the console and home computer.

The angst I feel now is that I have to produce something for the various types of hardware and software kits which have suddenly dropped into existence in the last 2 years or so. I feel a pressure to utilise them all in a relatively short period of time, to make use of the potential lying around and to justify the deliveries from the hardware makers in some form of low-budget, high-yield game.

The documentation available online is generally superb and, for my requirements as a tiny Indie, effectively limitless computing power and hardware accessories combine to put a tangible pressure on me. This pressure is clearly felt by me during each second I’m not creating something on and for these machines.

The second problem, which has changed my design and development habits, is that I can’t develop something without thinking of all the potential devices it may potentially need to run on. As most gamers and developers know, various types of hardware have their own unique access control schemes, with their idiosyncrasies, so this is not a small decision, regardless of the size of a company.

Creative Roots


Today, I can trace back the experiences I had as a child which led me on this path of being an Indie at heart and having und unerring desire to create something, specifically games.

Getting my first computer, a C64, on the 25th of March, 1984, was for me the beginning of a lifelong passion. In that same period, I had seen the films Tron (1982) as well as Blade Runner (1982) which implanted in me a desire to create worlds. But I also felt it needed to be a world which was unknown to me, even though I created it. A world which would surprise me with unpredictability and allow me to live out my yearning to explore the far reaches of the universe from the comfort of my desk.

The 80s and the early 90s were a phase where I could go to the arcades to get an idea where my future direction can take me. This is something that was somewhat lost in the times since then. Even film effects can’t bring back that sense of wonder, so this level of thinking has gone forever, I fear.

The Reality 2014 And Onwards


Where I have always looked straight up to move forward, I now have to look around 360 degrees to see where I need to go creatively.

I can now understand why previously large companies restricted themselves to making products for just a few selected platforms. The possibilities are just mind-blowing, in a scary sort of way. In earlier hardware generations, the main worry was how to allocate time to resources per frame, how much of the game enemy's Artificial Intelligence (A.I.) to sacrifice to give the game more graphical bling. And today most larger game companies still fight in this direction, but for a tiny Indie this can’t be the forward edge of the combat area.

I don’t believe restricting myself to a single device would be the solution though, as I already know they are all out there. So I had previously applied for everything I could, safe in the knowledge that the biggest and best companies in the world were ignoring me. I can't stop myself today in my desire to apply for everything I can get, but maybe I can put a sort of system in place to stem the tide somehow.

I think the correct way forward is to embrace an unlinking from the actual hardware, controllers and software, and mould the result from my inner desires, regardless of where and how these experiences will be seen by other people. In the end, I need to consider that all I have been doing, and will do, will be to create and see something I will explore as the first and only person. That memory I will probably keep even when the next generations have a hard time figuring out how to go back to touching a device to make it do something.

Evidently, there are more small Indies rising up every day, all making the most varied games which continue to surprise the gamers and industry. While this remains unpredictable and chaotic, I would like to know if those people show any of the IDAS symptoms I’ve tried to describe here. If so, there may be a need for us Indies to deal with this problem openly and not try to suffer through this each behind closed doors, alone.

Conclusion


I need to continue on doing what I do, but I would like to leave this nagging feeling of fear and pressure behind or learn to deal with it. The feeling is that there is never enough time to make any of that I dream about, and that I may be overwhelmed by too much choice where to realise my dreams and aspirations.
It is absolutely necessary for me to adapt to the changing circumstances and realities of being an Indie in 2014, and I assume this should be easier to accomplish for my tiny company than in a large Outfit.

And it would be great to sell enough to continue doing what I love too, but that I can still look up to today.

GameDev.net Soapbox logo design by Mark "Prinz Eugn" Simpson


Article Update Log


19 Jan 2014: Initial release

Beginners Guide to User Testing With Children

$
0
0
With the emergence of tablet computers, smartphones and gaming devices, more and more children use modern technology from a very early age. You can almost say that children get sucked into the technological world straight from the cradle itself, creating new challenges for both their parents and developers worldwide.

It is also known that children have a strong influence on their parents and today they are starting to recognize their role as consumers earlier than ever before!

But what does this mean for us?

The need to user test our apps, websites and games has never been bigger!


This however brings along a problem. As UX designers we have tons of experience under our belt. We have done usability tests with teens and adults, but young children have often been neglected as a general user group. What's even more concerning: some of us are not so sure about the methods themselves.

So how can we do it correctly? Here are six tips on how to get user testing right, the first time!

#1 Find a Relaxed Environment

A typical lab setting is often too intimidating for children unless you have a very child-friendly office. If you have a traditional testing lab then you need to make your office as relaxed as possible, but the best place would still be the child's natural environment.

At our office we have multiple walls which can be drawn on with special markers and it's not surprising that they are a real hit with children and adults alike. We also reward the children for their efforts. So far the reaction from both the children and their parents have been nothing but positive.


children-playing-2-560x420.jpg


Make sure the environment is as comfortable as possible for both yourself and the child. If the child feels slightly nervous or anxious then the quality of your test results will likely suffer.

#2 Give Them a Tour

When you invite children and their parents to your lab or office then do give them a grand tour of the surroundings. Letting children see the environment and goof around will help to build trust in both you and your company.

The worst thing you can do is start the testing session the very moment they set their foot in your office. This applies to regular testing sessions as well.

#3 Make Sure You Have Child-Friendly Lab Equipment

Normal sized devices can be difficult to work with for children who have small hands and a slightly smaller range of motion. If you are testing websites then make sure you have the equipment best suited for the task.

For example a smaller mouse could make the experience much more pleasant than using a regular sized mouse.

The table and chair must also be adjustable to smaller sizes.

#4 Take the Time

Children need the extra time to learn about the environment they are in. This means that the length of the sessions will likely be longer, while the actual tests take less time than usual. This is due to the short attention span of children. Keep this in mind and the end results will be that much better.


children-playing-3-560x372.jpg


Try to connect with the child before handing him or her tasks. Some children can be more closed-off than others and require more time to fully open up and feel free in the new environment. It is also advisable to do the testing sessions in pairs of two.

#5 Talk With the Parents First

It is adamant that the parents understand their role. Very young children will likely want their mother or father to accompany them in the sessions. If this is the case, allow the parents to stay in the room, but ensure that they are fully aware that they should not help the child complete the tasks required.

You also need to make sure that you understand the legal aspects of perfoming these tests. Even small things such as touching a child's arm can backfire. If you do need a more hands-on approach then always ask permission from the parents first.

#6 Pay Attention to Non-Verbal Cues

Children can often tell you more with non-verbal cues and verbal, so pay close attention to their body language and reactions. They might sometimes be too shy or feel the need to give out answers the moderator wants to hear – the so-called “school teacher effect”.

Ensure them that they are not in a classroom and that there are no right and wrong answers here.

Conclusion


Usability tests with children can be very fun and leave both parties feeling energized. Remember the tips talked about here, combine them with traditional testing methods and you are bound to get good results fast!

PS: If you find this post helpful then it would mean the world to us if you would share it with others as well! :)

IMAGE CREDITS
First image: http://bit.ly/11h0mzA
Second image: http://bit.ly/1b3fmXj
Third image: http://bit.ly/11XA0Zk

Math for Game Developers: Triangle Meshes

$
0
0
Math for Game Developers is exactly what it sounds like - a weekly instructional YouTube series wherein I show you how to use math to make your games. Every Thursday we'll learn how to implement one game design, starting from the underlying mathematical concept and ending with its C++ implementation. The videos will teach you everything you need to know, all you need is a basic understanding of algebra and trigonometry. If you want to follow along with the code sections, it will help to know a bit of programming already, but it's not necessary. You can download the source code that I'm using from GitHub, from the description of each video. If you have questions about the topics covered or requests for future topics, I would love to hear them! Leave a comment, or ask me on my Twitter, @VinoBS

Note:  
The video below contains the playlist for all the videos in this series, which can be accessed via the playlist icon in the bottom-right corner of the embedded video frame once the video is playing. The first video in the series is loaded automatically


Triangle Meshes




Ditching Diffuse Maps

$
0
0
One of the most amazing things for me as an artist that current generation brought with it was the introduction of the concept of shaders. No longer did we describe surfaces merely by flat images that already incorporated the majority of the lighting. Our materials became vastly richer with much more dynamic per-pixel lighting as well, and hands down had the biggest impact on the visual definition of the current generation of real-time graphics.

But the programmable pixel and vertex pipelines were capable of much more than just calculating normal map and specular contributions. Also that wide array of things you could do with a pixel was now at the fingertips of a much broader audience, including the visual content creators themselves, who finally got a chance to become the architects of their own tech. From animated wet surfaces in a Modern Warfare ship level to heightmap-based vertex and up-aligned blending popularized by Uncharted 2, shaders further drove the visual splendor that is this generation of games.

But such advancements came at a price:

Technological: the amount of textures needed to define a single surface grew exponentially, as well as the amount of video memory needed for their allocation.

Productional: The amount and sophistication of content skyrocketed. Budgets are through the roof in this generation of games and team sizes in some cases are plain ridiculous coming close to a thousand people. Of course it's not completely the fault of shaders, but art assets production is still one of the most expensive parts of your budget.

Now as an industry and as a consumerist society, I guess, we are always about more stuff. Number 1 thing your average Joe Gamer seems to want from every new game is better graphics. Most people naturally see it as more polygons and crisper textures, since they seem like things that drive the visual quality of games. And they certainly do, but unfortunately they have a pretty remote impact on the visual pleasure that players experience with our games.

When I come back to the old games that I used to enjoy 10 or even 5 years ago I'm amazed at just how crude the technology was back then: the resolutions, the polycounts, the effects. Yet somehow I and millions of people who played that game as well, managed to enjoy it on all levels. Including the visual.

How so? Some might say that people are always dazzled by the top technology of their time. But I reckon you this: People are dazzled by Beauty.

As an artist I've always been fascinated and interested in the concept of Beauty and I study it always with everything I do. I've been kindly invited by the top universities and art academies of the country, to share my findings on that particular subject, so you could say some of my ideas about Beauty were “ok”. Now in this paper I'm not going to discuss what Beauty is, but I can definitely tell you what it isn't. Beauty never lies in the details.


Attached Image: ZhuExample.jpg
Still frame from my “Analyzing Beauty” Lecture, image by Zhu Haibo


I so love this image, since it illustrates the point so vividly. It is a quick sketch that has very little in terms of detail, yet it makes it so painstakingly obvious why it is a sight worth seeing. The amazing lights and colors take your breath away, completely disregarding the fact there isn't a remotely detailed object in the whole image.

The visual world around us is infinitely complex. All visual representative art is merely an approximation, comprised with the limited resources we have at our disposal. We can never have enough resources to recreate every single process that shapes the world around us, so trying to distill it to what makes reality feel real and feel beautiful is key in balancing the quality of your art with the amount time required to produce it. Another amazing example would be Robh Ruppel with the way he breaks down images to simple shapes and gradients to eventually come out with an almost photorealistic image that actually reeks with “simplicity” when you just take a closer look.


Attached Image: colorado-blvd.jpg
Attached Image: colorado blvd-process.jpg
Painting by Robh Ruppel


Now with this logic in mind and a little fetish for smart tech I was always fascinated with the concept of procedural materials. Also, in my own work I kept noticing little things here and there, like people ignoring that wood, plastic and metal could have the same base diffuse texture as long as you sell their specular properties or wear and tear correctly. Or that my tileable textures diffuse could at times be just a kind of surface noise if it wasn't for the AO on top.

Somewhere along that time I stumbled upon another interesting piece of shader tech: Gradient Mapping. All it does is take a grayscale heightmap you feed it and paint it with colors you assign to different pixel heights (brightness values).


Attached Image: GradientMapping_Explanation.jpg


For example, here all pixels with brightness from 0.0 to 0.33 will gradually transition from red to green, .33-.66 will transition from green to blue and blue eventually to yellow. You've seen that technology used in Left for Dead 2 by Valve, where it allows them to pack numbers of blood splatter and detail onto a single zombie texture, as well as to variably color them at runtime.


Attached Image: Left4Dead2.jpg
Screenshot from Valves “Left for Dead 2”


Procedural Environment


So with another personal project I set out to do I really wanted to push the concept and see how far you can go with “procedural” materials even on current-day tech. After months of hard work, here it is (be sure to HD):




Not a single surface here uses a dedicated RGB diffuse texture. In fact I wanted to take it as far as inputting only one texture per one type of surface. Now of course there were some other little textures buried in the shader functions but they were no more than small grayscale masks stacked together. But let's take it from the top. The main idea in this procedural approach is this:


Attached Image: Separation.jpg


Separating Surface Volume from Surface Detail. As simple as this. Generally rocks in the same area will have a similar geological origin thus requiring a similar diffuse rock texture component. All objects in the same environment usually accumulate identical types of dirt. Objects made from the same materials will generally wear, tear and decay similarly. Objects in a damp environment will start growing a similar type of moss, etc.,etc. What differs is how this dirt, moss, wear, tear accumulate on the surface, as well as the diffuse texture pattern.

Now it may look like a lot of textures, but don't forget that all those grayscale ones are always stacked into R, G and B channels of a single DXT1 texture and are reused everywhere. Let's take a look at the memory usage in the worst case scenario, when we just have 2 materials to compare:


Attached Image: Memory_Worst.jpg


Optimizing Storage of Heightmaps


Now whether we import our heightmaps as an alpha channel in a DXT5 texture or a separate grayscale texture we’ll still end up using another 128kb for 512x map. We can stack 3 different heightmaps into a single DXT1 texture and save 1/3 of 128kb but there is a better way.

A normal map's blue channel hardly stores any vital information, so why don’t we put it to meaningful use? You don’t even need to recreate the blue channel in-shader – merely replacing it with a neutral normal color works fine in most situations. All and all you’ll spend 2 additional pixel shader instructions, which is a puny price to pay for cutting down the memory footprint by half.

And this is exactly why further on you’re going to see Normal Maps and Height Maps listed as one single entity.

Damage


Now imagine you wanted to vertex blend damage to your material as a lot of games do this day. Here's how it goes:


Attached Image: Memory_Damage.jpg


With this tech you get your damage smartly blended only where it could exist in real life – on the most protruding parts of your surface volume. Now on top of getting a 2 times memory gain you get an opportunity to dynamically tile, tweak intensity and available range of your damage, which by the way will always be taken into account in every kind of heightmap-based blending further on. And you can change it per every single material instance, creating exactly the type of damage you need.


Attached Image: ProceduralDamage.jpg


Multitude


Now imagine that we, as it usually happens, need a bunch of similar surfaces for our level:


Attached Image: Memory_Multitude.jpg


Almost 2 times difference. Want to count what the difference would be if we also did a damage pass for each of those textures? I took that liberty and did that for you: 488kb to 1576kb. Now how do you like that?

A reasonable question now would be just how much different surfaces could be considered similar and produce acceptable results if we just keep swapping the combo normal and height map. Turns out: almost all of them.


Attached Image: GM_Variety.jpg


How is that possible? In the beginning we said that the surface volume and diffuse texture patterns were an integral part of any material. But the big thing is, they do not have to be separate entities. You don't have to strictly feed heightmaps to gradient mapping. In fact feel free to forget the term heightmap for now, because from now on it'll be a part of a broader term:

Gradient Map


Gradient Map is your main diffuse component so you make it work as such. Blend your depth info with AO and every single texture pattern you might need. Paint in all the details and accents or tweak surface values just like you would with a regular texture. If you're working with heavily photo sourced textures, it's even easier because you can generate a normal map and a heightmap from your diffuse. And then you blend your grayscale diffuse with your heightmap to create a Gradient Map, thus keeping both your surface depth and surface detail info. If you think about surface detail, it is still depth just on a much smaller scale, so Gradient Mapping function processes it greatly.


Attached Image: GradeintMap_Example.jpg


Think of it as evaporating water from juice or soup to create a concentrate. Colors are water that we can spruce back in at runtime.

The Gradient Map works as good as the Height Map for blending, so no worries here. Also an amazing unexpected use is as an Opacity Mask, for foliage for example. You just have to make sure that your background is completely black and your gradient has its level pushed up so no pixel there is black also. Then you just clip this opacity mask from zero brightness and that's it.

Another amazing fact is that gradient mapping works perfectly well even if you use just your usual diffuse turned grayscale. Here's a comparison of a material from Epic and the same material only with the diffuse map desaturated and put through the GM function.


Attached Image: GM_vs_DiffuseGreyscale.jpg


Now does this difference really warrant a whole diffuse texture for this and every similar object?

The silver lining here is that you can actually use your diffuse textures as Gradient Maps without any modification, right here right now, significantly trimming your texture footprint. Extremely comfy and easy for new tech penetration.

Now's the time for a little pros and cons, which should cover the questions I'd be having right now if I were you:

- Procedural Colors in Tileable Textures vs. Procedural Colors in Uniquely Mapped Textures

The point that I was most worried about as an artist was color. How much do we need? To my surprise 4 colors to color a Gradient Map are more than enough! I even had to create a lighter version of the Gradient Mapping function that just blends between two colors and has no texture overlay. Now I've obviously used it mostly for environment textures, which are generally tiled thus requiring certain uniformity from their colors to make tiling seem unapparent, and that actually works greatly to the advantage of gradient mapping. If you're working with tiled textures a lot – you definitely want to try this out. And if you're not working with tiling textures a lot, how the hell do your games even work?


Attached Image: Coloring_Examples.jpg


Also important to note, from an artistic standpoint, good lighting, fog and post processing greatly influence colors usually creating the broadest and most important strokes. Your scene hardly ever is supposed to be about every little piece screaming for attention with a different color. Uniformity is good in a lot of ways and it is definitely not something to be fighting with a lot of the time.

Now you don't have to take my word for what the textures should look like. To make it fair, let's analyze a real-life example and see what makes a high-quality modern day texture:


Attached Image: UC_Example.jpg
Textures from Uncharted 3 from Naughty Dog by Melissa Altobello


These textures are made in the good old RGB-diffuse-map fashion, yet you can see that all that grunge and damage is really just an additional layer on top of the base textures. And if you strip that away, the textures have a of lot shades of pretty similar colors – it's the overall color tone and the brightness of each pixel that describes them. I hope that previous examples have convinced you that “Procedural” materials could do all of that – red brick/white bricks, dirty/clean, damaged – in a matter of a few button clicks, saving you a lot of production time and memory as a bonus.

By now you probably think “Ok, what about uniquely mapped stuff?!”. Do not despair. Valve has used it to create vast varieties of undead hordes for Left for Dead 2 and so can you.


Attached Image: Left4Dead2_Variation.jpg
Viewport screenshot of a “Left For Dead 2” character by Valve


You'll hardly want to Gradient Map your main characters but there's still a lot of mileage you can get out of it. Read all about it here

The main hurdle is of course: color variety. Whenever you need a lot of color in a single texture Gradient Mapping probably won't suit your needs. But that's a good thing - you have a choice of what technology to apply to get the biggest bang for your buck. Gradient Mapping and Diffuse Mapping were never meant to be mutually exclusive.

- Relinquishing control vs. Unexpected Variety:

Another tricky thing for me as an artist was fear of losing complete and utter control. Now I'm the first guy to be plain anal about every little piece of my artwork, I'll be carefully planting old pieces of chewing gum and cigarette buds on my textures in places where most people won't even bother. Yet throughout my career I constantly had to teach myself to choose my battles wisely. I don't believe in things existing for their own sake. Anything is only as good as it performs its purpose – no more, no less. And the purpose of details is to be sufficient enough not to break the illusion of the imposed reality, as much as beginning artists think detail is all there is. I was and probably still am a guy who loves his details, yet I had to admit that most players will never notice a difference between Gradient Mapping and Diffuse mapping, just like all the professional artists who I've shown this environment to.

Only further down the road I noticed just how much flexibility I could get from this system. From plants to rocks to bark it's a matter of swapping the only texture and tweaking the colors, diffuse patterns, damage and specular values. One second you use this texture as an orange canyon wall, next one it's already blue and is a part of a cave. Your bark is brown, but just turn it green, tile it more and there you have vines texture. Need more cold color in your shadows? Just make them appear in the cavities of your surfaces to amplify the effect. Want it dirty – just push a button and determine how much. Mossy/sandy - all just a matter of a button push. Procedural damage was another unexpectedly awesome thing since I could reuse a single asset with different damage tiling and intensity and create a whole lot more variety then I could ever imagine doing it the old way. And then I could paint my cracks green and invert their intensity, to create an illusion of vines overgrowing assets further in the distance.

As an artist I've got to grips with this workflow. I can't imagine not being able to tweak any object's color, diffuse noise scale or dirt at my smallest whim. I love it now. It's like working with bigger Lego pieces. We no longer model every part of our level individually, but rather create a set of modular meshes to work with. Then why on earth shouldn't we do it with our materials and textures?!

- Extra Processing Power vs. Freed up VRam

While “procedural” texturing technology frees up a whole lot of VRam it also requires additional processing power. And as always with software optimization - it's a question of what you've got to spare.


Attached Image: LoU Texeleration.jpg
Screenshot from the “Last of Us” by Naughty Dog


If we look at something like PS3 we'll find just 256mb of VRam and it shows. Even the most gorgeous games sometimes put blurry textures right in your face. Notice the dramatic texeleration difference in characters and the background truck in the screenshot above. Yet PS3 has 8 SPUs that technically could be used to alleviate the issue by implementing Gradient Mapping Shaders, cutting the diffuse texture footprint in half or even by 3/4, allowing to selectively increase texeleration on some surfaces. Xbox 360 has 2 SPUs and a combined Ram/VRam module yet it's still half the amount of memory of an iPad. With the Desert environment I didn't use diffuse textures at all, just a couple of masks that are insignificant in terms of the whole level's memory footprint. So it would be fairly accurate to say that I cut my texture memory expenses in half by relying on this technology. It's also worth noting that to create damage I use in shader normal map generation from a grayscale mask which is somewhat of a pricey operation (though there could be plenty of workarounds). Yet on a modern-day PC the environment you've seen has no trouble doing 60+ FPS. If we can trust UDK's custom nodes instructions calculations then it would take us just 14 instructions to replace a diffuse map with a gradient map. I have fully functional shaders that with 73 instructions provide you full diffuse, specular, masked opacity and normal functionality with only One texture sampled. The most complex version of the shader is 153 instructions and features vertex paintable heightmap-based sand blending as well as 2 types of procedural “smart” damage generation that are also both vertex paintable. These instruction counts are at the very least comparable to the instruction counts of Unreal Engine Games Materials with similar functionality.

Yet I’m not a graphics programmer by any means and UDK's instruction counter does lie from time to time. Right now some proper tests are being conducted and so far I’m afraid I can’t tell you much more. Though I promise to update this with more info the second it appears.

- Production Costs

This one has no cons. Procedural Materials save a lot of production time and subsequently a ton of money. If I had to create every single diffuse map by hand I can assure you that the environment you've seen would've been much smaller or taken more time to produce.

It's funny how explaining this to a more business savvy person the first question I got was: “So how many people can you replace?”. And this is definitely not about replacing people. It's about how much more and how much faster you can produce. There is always lack of time in our industry and having a chance to free some of it up for more important things is an amazing opportunity to have that yields better games and subsequently more profit.

Outro


Now there's no question left for me, the notion of diffuse textures being indispensable the way they are, couldn't be further from the truth. Gradient Map carries enough information to make your brain perceive surfaces as completely believable. And that's all there is to it.

So if there's somewhere we can trim fat, it's in the diffuse textures. Both in terms workload and technical constraints. Without sacrificing visual quality.

Now I know our industry is all about more stuff. The next generation is just around the corner and it will have to blow something like Battlefield 3 out of the water if Sony and Microsoft are going to convince their audience to make the pricey upgrade. As much as I look forward to quadrupling texture sizes and polygon budgets, the industry could well collapse under its own weight. Nowadays, in the world of big games, good-yet-not great-70%-metacritic games just do not pay off any more, and companies are uber reluctant to invest money in something that, in their eyes, doesn't absolutely guarantee return. Now consider investing in big next gen projects, which could very well be double the cost of today's AAA game, for a platform that has zero market penetration? When can you really expect at least 3 million units shipped there, to at least make it worth your while?

The risks could just keep rising proportionally with graphical fidelity. Now there is a cushion in the form of assets being downsized for today's games so going higher-rez could merely be a matter of not resizing, but higher resolutions are still going to require more work.

If we as industry want to stay competitive we should not only advance the quality of the product we produce, but the quality of production itself as well. The industry should slowly move towards things becoming more “procedural”. In fact it does: we switched from animating dudes being blown away by a shotgun to simulating it; we have specific tools to build trees, roads, terrain, or LoDs; we no longer model every single piece of our levels, but rather create a set of highly modular Lego pieces to build our levels with; gameplay scripting in UDK is now visual node-based editing – no more writing code! All of this saved our industry years of man-hours and millions of dollars in production costs. And I believe that this is something we should be doing with materials. Give our artists bigger Lego pieces so they can dedicate themselves to the bigger picture with no real loss in detail. I would love to see next-gen engine creators take that into account.

Normal + Gradient map RGB combo, built-in gradient mapping functionality or even high-level procedural material editor with built in variety of diffuse patterns, specular presets, dirt, damage and wear types where users just input their Gradient Map, tweak a few handles and: voila! Procedural wet effect or water rolling down oblique surfaces, overgrown with moss, dusty, sandy, burned; procedural polished metal, car paint, glass, water etc. – all with one button push. And with deferred lighting becoming commonplace imagine using Screen Space AO to mask tiled dirt to make it appear only in cavities, corners and intersections of flagged static objects. Or use vertex normals to procedurally create a mask for sharp corners that you multiply with a wear map and use it to reveal metal underneath worn painted surfaces, or even use pixel normals from you scene's Normal Pass, combine it with highly contrasted depth pass to try to figure out where those sharp edges are.

There are millions of ideas and some of them could save you months of work and hundreds megabytes of VRam. Technology is meant to be a tool to achieve artistic results, so instead making us try to keep up with the crazy amount of fidelity we have to put in, lets make it help us concentrate on adding details and creating real beauty where it would matter most.

Thank you very much and keep it pretty.

Andrew Maximov, 2012

P.s. There one last thing I wanted to share with you guys...but I can't seem to remember what it is....

...oh yeah, THE MATERIALS!

Click here to grab them! :)


Attached Image: GM_Demo.jpg


These are all kinds of "procedural" materials for you to check out as well as a couple of example textures and meshes. Also there's a .PSD that makes for very smooth gradient map production as it allows you to preview your gradient mapping, normal, specular, damage and diffuse pattern influence right in Photoshop! It's like you're painting your gradient map directly in UDK and can immediately see the end result! I've made a little video that will hopefully make things more visual for you:



When a Game Teaches You

$
0
0

Press Command to Start


Grab your beer, your cup of coffee, or whatever… The tale begins here.

A year ago, we met some lovely people from a training center. They had this training course in programming and were looking for a game to help their participants learn programming more efficiently. They didn’t have a ton of money but the idea of making a game about coding was enticing.

There is no need to tell you how coding is important in video games development, and at Fishing Cactus, we really do love making games. After hours of reflection, we called those guys back. Challenge accepted!

Then we thought about it. How are we going to teach programming without being dull and boring? That was our first challenge, and also the beginning of the A team. As part of this team, Laurent Grumiaux and Guillaume Bouckaert thought about the game’s concept, and we can truly tell you that this game was mind-tingling before even being a real game.

The first good idea that came out from this collaboration was to invite programmers to join the team, which allowed them to act early in the development process. It turned out that many of them used to play Logo and Robot Rally as kids. That point helped to redefine the concept.

Step 1: Redefine the Concept


Making a game about programming is not that easy, and we didn’t want to create another “OkILearnedSomethingAndSoWhat?” game. Then we were reminded of a famous quote from Steve Jobs: “I think everyone should learn how to program a computer, because it teaches you how to think.” The solution was so obvious that no one even noticed it until then: we wouldn’t create a game that teaches you to code, but a game that teaches you how to improve the way you think.

But how were we going to do that? With logic, of course! Nowadays, coding is one of the more desirable skills there is, and coding is nothing without logic. With this in mind, we set out to create a game that would help people grasp the essential skills of logic that the programming craft requires. Good! We have got a nice concept. The question now is how will we manage to do that?

The first idea was to create a character that has a job to perform in a futuristic power plant full of leaking toxic containers, industrial crates, and stubborn little robots. By carrying around those toxic containers, sorting them out and re-arranging them, the player will need to be smart and use programming basics such as functions, variables, the set theory, conditions, and loops.


Algo-Bot_old_prototype_wip-600x450.png


The player won’t control the character directly. He won’t make him jump on mushrooms by pressing a single button either. Instead, he sets up a sequence of orders for him: go straight, turn left, go straight again, turn right, etc. When the player is done creating his little sequence, he passes it on to the character, who will follow the given orders to move around the power plants. In a nutshell, the player manipulates sequential commands to order the character around in an attempt to reach the given goal of the level.

Step 2: Select Your team


We had a concept and a gameplay. All we needed was love, time, and money. We had to make this concept into a game. Joined by Jef Stuyck, Silouane Jeanneteau, and Christophe Clementi, our team 1.1 was now made up of a project manager, a game designer and three programmers. The programmers would tell you that they are all you need to create a game, but they are wrong. We needed a complete world. More than anything, we needed a hero. Then, we needed artists.

In game development, that is the moment when everything turns into chaos. Not because artists join the development, but because now you have a complete functional team with motivated people full of ideas. In that moment, nobody is a programmer, an artist, or a game designer, but everyone is the guy with an idea. And because it’s a game about coding, guess who think he knows best? Well, it’s like bombarding atoms with neutrons. It’s up to the project manager to make good use of this energy.

Step 3: Create a World


Even if you want to, you definitely can’t sell a game about coding starring a unicorn in a cotton candy world. Obviously, the robot was the perfect hero for our game. Inspired by Wall-E, Antoine Petit, our 2D artist, mixed futuristic shapes and retro styling into a robot with a real personality. Since there was no reason for Algo-Bot to be a 2D game, Cédric Stourme started to 3D model the robot and its world.


49e5a51e0f91b94e2577f433e0d51271_large.p


Speaking about the world, we imagined it as a huge power plant with a level of toxicity so high that humans can’t enter without dying instantly. The first mockups of this power plant were way too bright. You really don’t need that much light in a place where humans can’t stay. We had to stay realistic. Oh yeah, I hear you from here: “Realistic? With a robot in a futuristic toxic power plant, huh?” But yes, we had to stay as realistic as we could be within that setting. We now have a version of the power plant close to our idea: darker and more appropriate to the gameplay.

Step 4: Make a Game


“Make a game” sounds simple enough. Everyone can make a game, but we wanted to make a good game, and making a good game requires a lot of self-investment.

First of all, a game needs time and includes hours of research and development. Focusing on this uncommon gameplay and writing this clean code that would bring the game to life became our daily routine. The deadline was approaching, and Algo-Bot seemed like nothing more than a seed, no matter how much time and love we gave it.


2f398e2c54197314b6f9e146dfac2c80_large.p


In most developments, being too self-invested in your project happens to be a huge thorn in the team’s foot. There is that moment you are truly living for your game, rejecting other ideas because you think that you are the only person who knows where this project is going. When you waste hours modifying the concept, trying to do the work of others… let me tell you this: you’ve never been so blind! So have a Kit-Kat and stop trying to be the man of the situation. Game development is a sports team for geeks. That’s the lesson we learned from this development. If Algo-Bot was made to teach others programming, it taught us how to communicate within our team.

And then we experienced the moment when you believe the game you dreamt of will never be developed for the simple reason that the client is out of money. Sure, the client can perfectly use this version. It’s playable, the learning process is quite effective, and the graphics are not so bad. But for you, it would never be more than an alpha version of your dream. So, like any good parent, you open your wallet until you run out of money yourself and then you cry… AND you launch it on Kickstarter.

Now you know the topic of my future article: How our Kickstarter failed :D Because obviously we won't meet our goal. One says that failure is the best lesson to learn :)

Article Update Log


27 Jan 2014: Initial release

Composing Music For Video Games - Tempo

$
0
0
The first part of this composing music for video games series focused mainly on musical key and only slightly touched upon tempo. All of the articles in this series are adapted from blog posts about composing for film, however the same musical principles apply to games. This article covers tempo and whilst not going into masses of detail it does provide an overview of why it is important to pay attention to tempo. This article will be updated with a video that will display more examples.

Syncing Up


In order for the music to fit the visuals it is intended for, careful consideration needs to be shown to the tempo. A piece that is too fast or slow for the visual will not help tell the story correctly. It is vital to keep in mind that the feel or 'groove' of the piece has to flow; too fast and the composition could feel clumsy and undefined as to what is happening, too slow and it will feel sluggish and drag on. You should be thinking about how you want the consumer to feel when listening to the piece of music. For instance in a cut scene if the music is rushing along but the visual isn't, you are confusing the gamer, sending them different signals.

When composing for video games it's important to note the pace of what is happening not just in game play, but also in cut scenes. During game play the pace of the game may shift quickly and the audio can adjust to accentuate this. For instance if you were playing a FPS, walking through the jungle and all seems calm, the music could become slow, withdrawn and almost non-existent - allowing the sound effects of the environment to take centre stage. But as the character approaches a key point, say an enemy stronghold, the music could start to build. The tempo could shift up, note durations could get shorter, all adding to the tension. As the player moves quicker extra instrumentation could join to further reflect the excitement and anticipation of what is happening on screen. So paying attention to what happens visually can have a bearing of what tempo/s your music needs to be composed at.

Stay On Target!


If you didn't get the message in the last article that we love Star Wars, you might get it this time! Looking at a scene from ‘Star Wars, A New Hope' where Luke Skywalker and the Rebel Alliance go on a suicide mission to blow up the Death Star, we can see the visuals changing and flowing together and that the music matches up with it perfectly. For example, take a look at the short shots in and out of the cockpit, the abrasive maneuvers of the fighter ships and masses of explosions. We can see that it's all very visually choppy, yet it is all glued together with the high energy music that draws us into the scene and maintains the 'on the edge of our seat' atmosphere. It is due to John Williams' choice of tempo and time signatures that allows this to happen. The music slots into what is happening visually and reinforces it.

All of the special effects take on life with the integration of the superb sound effects, whilst the music joins each visual clip together seamlessly. Right at the very end when we see the Death Star explode, the resolve is reinforced by a decrease in tempo. This gives a feeling where we can finally release tension and breathe normally. What's really amazing about this is that you probably didn't even think about this until now… amazing stuff right?! This is the joy of when music and sound design are composed and produced in such an expert manner. Many individuals may not realise the effect of audio on the visual experience, but it is the combination of music, sound design and the visuals which gives us the intense emotions we can experience when watching a film, watching TV or playing a video game.

Creating Suspense and Tension


When composing music for film, or any sort of visual media for that matter, increasing the tempo of a piece can be a great way to create suspense and tempo in a composition. If you listen to this Suspense Scene Cue you will note how towards the end of the composition the piece becomes more uneasy. This is due to the rise in tempo which tells the mind to expect something to happen. If you picture this cue being part of a cutscene this point can be further illustrated. Set the scene in your mind of a dark corridor, no light at all, and you're following the character's movements from the first person perspective. As you listen to the piece and picture this notice how at the end of the piece the composition makes you increasingly uneasy, to the point where you expect something to happen.

By increasing the BPM, the tension is enhanced, making the viewer anticipate that something is going to happen soon. This can also make the viewer's heart rate go up, releasing more adrenaline which triggers a 'fight or flight' response - a survival instinct that is hardwired into our subconscious that gives us 2 choices, to fight or to flee. By this point the consumer is captured in the moment and we can call that mission accomplished.

It's Not All About Speed


Before we get all carried away with BPM, let's take a look at another element linked to time: the time signature. Time signatures are wondrous tools which often get overlooked. Nowadays in the charts it is hard to find songs that are not in 4/4 (which means 4 crochet beats to a bar). Maybe on the odd occasion you will come across a song in 6/8, or what we like to call the other 4/4. However using different time signatures within a piece can open up your compositional palette. Shorter or longer bars can allow for an easy transition into different sections. And changing the feel of a piece completely, if required, for instance to a 3/4 waltz can introduce a different flavour.

One great way to create tension with time signatures is to make the listener unsure where beat 1 lies. It can lead people into a false sense of security or make them feel that they are no longer in control. Time signatures like 5/4 and 7/4, which are commonly known as irregular time signatures, can sometimes make it difficult to determine where beat 1 is. Take a listen to the Michael Myers theme in ‘Halloween’ - even though the ostinato piano part that continues throughout the piece makes it very easy to find the first beat, without it, finding beat 1 might be more of a challenge.

Conclusion


In conclusion, tempo is a key fundamental that makes up a firm foundation on which a song is built on. This element, if not given enough attention, can lead to the whole piece feeling rushed or lethargic; used effectively, you'll have your audience hook, line and sinker!

Dan Harris and Joe Gilliver
www.ocularaudio.com

Contact: joe@ocularaudio.com

Making a Game Engine: Transform Hierarchy

$
0
0
See Also:
Making a Game Engine: Core Design Principles

Moving objects around in a scene is not hard to do. You simply draw objects at different locations each frame. Adding rotation, and resizing on top of that makes the code a little trickier but still not hard to manage. But then start trying to move objects relative to another such as putting a sword in a character's hand and things can get really tricky. The sword should stay in the character's hand regardless of what direction they rotate or where they move. This is where using matrices to represent these transformations is extremely valuable.

Coordinate Systems


In order for a coordinate to have any meaning there needs to be a point of reference to base that coordinate off of. Imagine you have a dot on a piece of paper. The dot does not have a coordinate unless you come up with a way to measure its location. One way would be to determine the distance the point is from the left side of the page and its distance from the bottom. These two measurements give the coordinate meaning since it has a point of reference, the bottom left corner of the page. The point of reference is known as a basis and any point measured relative to the same basis are said to be in the same coordinate system. If a worm were on a ball you could devise a way to measure the location of the worm on the ball. This coordinate would be the worm's location in the ball's coordinate space.


Attached Image: WormOnBall.png


The ball itself also has a location but its location is measured in world coordinates. Let's assume that the location of the ball is measured from the bottom left corner of the image to the center of the ball. If you were to throw that ball the worm would move with the ball. The position of the worm in world coordinates would move as the ball moves, even though its position in ball coordinates stay the same. Transformations give us a way to convert between these coordinate systems relatively easy using matrices allowing us to easily keep the worm on the ball as it travels through the air.


Attached Image: WormOnBallThrown.png


Matrices have a lot of useful properties that lend themselves to solving many complex problems. We will be using them to transform objects on the screen. This article will focus on three simple transformations: translation, rotation, and scaling. I will be using 2D transformations for the sake of simplicity. The principles in the article also apply to 3D transformations. If you want to do 3D transformations, search around on the web for 3D transformation matrices. The jump from 2D to 3D is trivial with the exception of rotations. I am assuming you already know what matrices are and how to multiply them, what the inverse and transpose of a matrix are, and other basic matrix operations. (For help with matrices, try this video series)

So far this talk of coordinate spaces and matrices may seem to be complicated things when all you need to do is move or rotate objects. How exactly do these things help move objects on the screen anyway? Well, imagine you define a character sprite with a width of 50 and a height of 100. You could define four points that represent the corners of the sprite image (-25, 0), (-25, 0), (25, 100), (25, 0). These points represent the corners of the sprite relative to the character. So what whappens when the character moves? The transform of the player converts the points of the sprite to world space. Then the camera transform converts from world space to screen space to be drawn. If the player clicks on the screen you can use the inverse of these transforms to determine where on the character they clicked.

Basic Transformations


Translation

A translation simply changes an object's location. The matrix used to represent this is pretty simple too.

Attached Image: TranslationMatrix.png

Tx and Ty is the number of units to move in the x and y directions respectively.

Rotation

Rotates points around the origin.

Attached Image: RotationMatrix.png

The above matrix will rotate counter clockwise by θ radians.

Scaling

Scaling an object changes its size. Scalar values in the range (0, 1) make objects smaller. A scale of 1 keeps it the same size, a scale greater than 1 makes it bigger. Really all the scale is doing is taking the current size and multiplying by the scale value.

Attached Image: ScaleMatrix.png

Scales along the x and y directions by Sx and Sy respectively. When Sx = Sy the size will change uniformly. If you only modify a single value then the object will stretch meaning you can make an object wider by setting Sx to a value greater than 1 and keeping y the same. You can even make a value negative which will flip the object.

Using Matrices as Transforms


Transforming Points

Now that we have a few different transformation matrices we need a way to transform points using them. To do this you simply treat the point as a 1x3 matrix padding the extra slots with a 1 and multiplying it with the transform matrix.

Attached Image: TransformPoint.png

The result of that multiplication is a another 1x3 matrix. Just taking the top two elements of the resulting matrix gives a new x, y pair of the point in the new coordinate system.

Transforming Directions

Transforming a direction is slightly different than transforming a point. Take a look at the example below.


Attached Image: TransformDirection.png


The ball has been squashed. This is achieved by scaling the y direction down while keeping the x the same. If directions were transformed using the same matrix that were used to transform points, then you would get the red arrow as a result, but notice how the red arrow is no longer perpendicular to the ball. We want the green arrow that still is. To get that result, use the following equation.

Attached Image: TransformDirectionEquation.png

As shown above, to get the proper matrix for transforming direction you need the inverse transpose of the transform matrix. You can avoid transposing a 3x3 matrix if you simply do a row vector multiplication in front of the matrix. Also take note that the third element is a 0 instead of a 1. This makes it so transforming a direction is uneffected by any translations. The is proper behavior for transforming a direction since directions still point the same way regaurdless of where they are moved to.

Concatenating transforms

The three basic tranformations listed above aren't that useful on their own. To make things interesting we want to be able to join them together allowing objects to be translated, rotated, and scaled simultaneously. To do this we simply multiply the matrices together.

Attached Image: MatrixConcat.png

The resulting matrix, M, is a combination of scaling an object, rotating it, then translating it. Keep in mind that the order of T, R and S does matter. Since S is the right-most matrix its transformation will be applied first when transforming a point followed by R, followed by T. To see this grab some object nearby, such as a pencil, and hold it so it is pointing to the right. Now imagine that the pencil is first scaled by half in the x direction making the pencil shorter but not changing its thickness. Then the pencil is rotated 90 upward. The result is a short pencil with the same width pointing upward. Now switch the transformation order. First rotate it upward then scale. The resulting trasnform is a pencil of the same length but different width. So pay attention to the transform order.


Attached Image: TransformOrder.png


One thing to note is that roatations pivot around the origin. This means if you move an object up ten units then rotate it clockwise 90 degrees it will end up 10 units to the right. If you want the object to pivot around its own center, rotate it first then move it.

Since matrices can be multiplied to concatenate transforms you can easily keep an object at the same position relative to another, like the example of the worm on a ball described above. You first create a transform using the position and orientation of the worm on the ball. This transform represents the conversion of the worm's local coordinates to the ball coordinates. You then create the transform of the ball relative to world coordinates. Since the worm is on the ball, to find the transform from worm coordinates to world coordinates you simply multiply them together.

Matrix insectToWorldTransform = ballToWorldTransform * insectToBallTransform;

Transform Class


Now that we have covered some basics of how transforms work let's build a transform class. Since we don't want to deal with matrices at a high level, we will allow the transform to be manipulated using a position, rotation, and scale. The transform class will also have children to allow for a heirarchy. When the parent transform moves all of the children move with it and the children's position, rotation, and scale will be defined relative to its parent. Finally the transform will extend the component class that is attached to a game object. Game objects and components are touched upon in this article.

class Transform extends Component
{
    // the parent transform of this transform
    // if it is null then the parent transform
    // is the world coordinate system
	private Transform parent;
    // all of the transforms that have this
    // transform set as their parent
    private Transform[] children;
    
    // the position relative to the parent transform
    private Vector2 localPosition = new Vector2(0.0f, 0.0f);
    // rotation relative to the parent
    private float localRotation = 0.0f;
    // scale relative to the parent
    private Vector2 localScale = new Vector2(1.0f, 1.0f);
    
    // specifies if the localToWorldTransform
    // needs to be recalulated
    private bool isDirty = false;
    // the transform that converts local coordinates
    // to world coordinates
    private Matrix localToWorldMatrix = Matrix.identity;
    
    // specifies if the worldToLocalMatrix
    // needs to be recalculated
    private bool isInverseDirty = false;
    // the transform that converts world cooridnates
    // to local coordinates
    private Matrix worldToLocalMatrix = Matrix.identity;
    
    /*
     * Whenever any change happens that changes the localToWorldMatrix
     * this should be called. That way the next time localToWorldMatrix
     * is requested it will be recalculated
     */
    private void setDirty()
    {
    	// only update dirty boolean if it isn't already dirty
        if (!isDirty)
        {
     		isDirty = true;
            isInverseDirty = true;
            
            // set all children to be dirty since any modification
            // of a parent transform also effects its children's
            // localToWorldTransform
            foreach (Transform child in children)
            {
                child.setDirty();
            }
        }
    }
    
    // change the parent transform.
    // setting it to null makes the
    // transform a child of world coordinates
    public void setParent(Transform value)
    {
        // remove this from the previous parent
        if (parent != null)
        {
        	parent.children.remove(this);
        }
        
        // assign new parent
        parent = value;
        
        // add this to new parent
        if (parent)
        {
            parent.children.add(this);   
        }
        
        // changes parents effects 
        // the world position
        setDirty();
    }
    
    public Transform getParent()
    {
    	return parent;   
    }
    
    // calculates the transform matrix that converts
    // from local coordinates to the coordinate space
    // of the parent transform
    public Matrix calculateLocalToParentMatrix()
    {
        // Matrix.translate creates a translation matrix
        // that shifts by (localPosition.x, localPosition.y)
        // Matrix.rotate rotates by localRotation radians
        // Matrix.scale scales by a factor of (localScale.x, localScale.y)
        // These are the basic transforms that are described previously
        // in this article
        return Matrix.translate(localPosition) * 
                Matrix.rotate(localRotation) * 
                Matrix.scale(localScale);
    }
    
    // gets the matrix that converts from local
    // coordinates to world coordinates
    public Matrix getLocalToWorldMatrix()
    {
        // if the dirty flag is set, the the
        // localToWorldMatrix is out of date
        // and needs to be reclaculated
    	if (isDirty)
        {
            if (parent == null)
            {
           		// if the parent is null then the parent is
           		// the world so the localToWorldMatrix
           	 	// is the same as local to parent matrix
                localToWorldMatrix = calculateLocalToParentMatrix();
            }
           	else
            {
                // if there is a parent, then the localToWorldMatrix
                // is calcualted recursively using the parent's localToWorldMatrix
                // concatenated with the local to parent matrix
                localToWorldMatrix = parent.getLocalToWorldMatrix() * calculateLocalToParentMatrix();
            }
            
            // clear the dirty flag since the 
            // matrix is now up to date
            isDirty = false;
        }
        
        return localToWorldMatrix;
    }
    
    public Matrix getWorldToLocalMatrix()
    {
        if (isInverseDirty)
        {
            // the inverse is out of date
            // so it needs to be updated
            
            // the worldToLocalMatrix is the inverse of
            // the localToWorldMatrix
         	worldToLocalMatrix = getLocalToWorldMatrix().inverse(); 
            
            // clear the dirty flag since the 
            // matrix is now up to date
            isInverseDirty = false;
        }
        
        return worldToLocalMatrix;
    }
    
    // transforms a point from local coordinates to world coordinates
    public Vector2 transformPoint(Vector2 point)
    {
        // matrix multiply padding the extra element with a 1
     	Matrix1x3 transformResult = getLocalToWorldMatrix() * Matrix1x3(point.x, point.y, 1);
        return new Vector2(transformResult[1,1], transformResult[1,2], transformResult[1,3]);
    }
    
    // transforms a direction from local coordinates to world coordinates
    public Vector2 transformDirection(Vector2 point)
    {
        // matrix multiply padding the extra element with a 0
        // notice that the worldToLocalMatrix is used here
        // and the point is multiplied as a row matrix before the
        // transform matrix. This is the proper way to transform
        // directions as described before in this article
     	Matrix3x1 transformResult = Matrix3x1(point.x, point.y, 0) * getWorldToLocalMatrix();
        return new Vector2(transformResult[1,1], transformResult[2,1], transformResult[3,1]);
    }
    
    // transforms a point from world coordinates to local coordinates
    public Vector2 inverseTransformPoint(Vector2 point)
    {
        // same logic as transformPoint only with the inverse matrix
     	Matrix1x3 transformResult = getWorldToLocalMatrix() * Matrix1x3(point.x, point.y, 1);
        return new Vector2(transformResult[1,1], transformResult[1,2], transformResult[1,3]);
    }
    
    // transforms a direction from world coordinates to local coordinates
    public Vector2 inverseTransformDirection(Vector2 point)
    {
        // same logic as transformDirection only with the inverse of the 
        // inverse localToWorldMatrix which is just the localToWorldMatrix
     	Matrix3x1 transformResult = Matrix3x1(point.x, point.y, 0) * getLocalToWorldMatrix();
        return new Vector2(transformResult[1,1], transformResult[2,1], transformResult[3,1]);
    }
    
    public Vector2 getLocalPosition()
    {
    	return localPosition;
    }
    
    // sets the position relative to the parent
    // and marks the transform as dirty
    public void setLocalPosition(Vector2 value)
    {
        localPosition = value;
        // set the dirty flag since the localToWorldMatrix needs to be updated
        setDirty();
    }
    
    /* localRoation and localScale should also have getters and setters
     * like the local position does. Be sure to call setDirty in the 
     * setters for each of them */
}

There is a lot of code there so let me explain the key points. First of all, I am using a dirty flag to indicate when the transform matrices are out of date. That allows the position, rotation, and scale to be changed multiple times and the matrix is only actually recalculated once it is requested, reducing the amount of uneeded recalculations. Also take note that the dirty flag is only set if the transform is not already dirty. This is to keep the setDirty call from propagating to all of the children every time the transform is modified and only setting the dirty flag when necessary.

The inverse matrix is also stored in the transform to keep from having to calculate it everytime it is needed. It has its own dirty flag so it isn't calculated if it isn't needed.

Using the Transform Class


Now that we have a transform class, let's see how it can be used. First of all, the localToWorldMatrix can be used in draw calls. Most drawing libraries will allow you to specify a matrix to position objects on the screen.

class Renderer extends Component
{
    void render(graphics)
    {
    	graphics.setTransformMatrix(this.gameObject.transform.getLocalToWorldMatrix());
        graphics.drawSprite(this.frame);
    }
    
    // whatever else the renderer does
    // would go here
    ...
}

Keep in mind the above code doesn't account for the view transform. Think of the view transform as the camera for the scene. If you wanted to be able to scroll the view of the scene you should specify a camera object and multiply all transforms by the worldToLocalMatrix from the transform of the camera.

    	graphics.setTransformMatrix(
            camera.transform.getWorldToLocalMatrix() *
            gameObject.transform.getLocalToWorldMatrix());

The transform could be used in game logic code

class Mine extends Behavoir
{
    void update(float deltaTime, InputState input)
    {
        // transforming the zero vector gets that 
        // transform's origin in world coordinates
    	Vector2 playerWorldPosition = player.transform.transformPoint(new Vector2(0.0, 0.0));
        
        // take the players world position and convert it the mine's
        // local coordinates
        Vector2 playerLocalPosition = this.transform.inverseTransformPoint(playerWorldPosition);
        
        // since playerLocalPosition is the players position relative to the mine
        // the magnitude of the position is the distance from the mine to the player
        if (playerLocalPosition.magnitudeSquared() < sensorRange * sensorRange)
        {
            this.detonate();
        }
    }
}

Conclusion


Transforms may take a while to fully grasp but once understood help simplify problems that would otherwise be a huge challenge to tackle. Hopefully this article helped you understand how to use them in your games better.

Article Update Log


5 Feb 2014: Initial release

Getting Games Done

$
0
0

Get Games Done


This article is aimed at hobbyists/indie developers with limited spare time to dedicate to their projects. It seeks to help them increase their motivation (and finish their projects) all the while improving their working environment setup and productivity.

From past experiences, I have learned that the single most important factor to get games done lies with the ability of the developer to deliver increments of functionality at a steady pace.

Essentially, a project will only get done if an external eye to the project sees consistent progress throughout longer periods of time. The odds of succeeding are also generally tied with the ability to deliver these increments at more or less regular intervals.

There are many reasons why a developer that sets out with the best of intentions will end up being unable to deliver functionality over time, and these reasons will end up killing the project. Rather than discussing the symptoms here, I’ll explain my way of overcoming them.


Implementing a Development Routine (5,1,1 Routine)


To establish sustainable development over long stretches of time, a developer needs to have a Routine. This mental construct will initially be forced, and eventually become a habit, so that the majority of threats endangering the project will be met with fierce and effortless resistance.

Different developers will prefer different routines, obviously, so bear in mind this works for me and may not work for you. However, the idea of a Routine remains universal.

I call mine the “5,1,1 Routine”...

I accidentally created the 5,1,1 Routine during one of my hobby projects, and decided to stick with this method as it allowed me to meet three very important objectives:

  1. Remain motivated with the project by working on fun features most of the time.
  2. Deliver shippable increments of functionality regularly (sustained velocity).
  3. Perform a constant health check of my code with limited effort.

What is the 5,1,1 Routine?


The 5,1,1 Routine is a representation of the workload in a given week in regards to three axis.


5 Dev Days... (I deliver a new feature or improvement)

1 Refactor Day... (I cleanup and optimize the code)

1 Rest Day... (I don’t look at the project)


What are Dev Days?


This methodology assumes that my codebase is healthy enough to allow me to actually develop new aspects of the game 5 days per week, which is what will keep me motivated to continue my work on the project.

During these 5 days, I will develop new features with the quickest possible route in mind, regardless of how “unclean” it will look. The reason for this is twofold:


a - We’re developing an entertainment product (game) and fun is hard to catch. Rarely will the first implementation feel right, therefore it’s better to be able to try several messy ways in a single day than to try a single clean way.

b - These dev days are meant to keep my motivation high. The fun comes from seeing new things working in my game, not from clean code. The more new stuff I get to try, the more excited I get about the project as a whole. I don’t feel wrong because I know that 90% of the things I’ll try will end up being discarded, thus won’t harm my codebase much at the end of the day.


Normally, I'll want to deliver something new EVERY day. To achieve this, I like to keep the JIT / DWYN attitude.


JIT (Just-In-Time): While generally employed to relate to inventory, one can correlate this with information flow in regards to decision-making. Essentially, post-pone any technical decision until later. Code your new feature quickly and don't have any foresight about how it can be reused (you can always use your refactor days for that anyway).

Do what you need (Lean): A good way to prevent over-engineering, this tells you to code exactly what you need and nothing more. When you refactor later, or need to build a more complex mechanic that leverages the same logic, you can always revisit, but assuming that you will and over-engineering will result in tremendous amounts of time loss.


What is a Refactor Day?


If you’ve been developing for a while, chances are you’ve quit on many projects solely because they reached a point where it would take too long to implement any new idea. This can be explained as technical debt.

Though I spend 5 days per week adding stuff without caring much about how clean the code is, I discipline myself into adding an extra day per week where I am not allowed to develop anything new, and rather, I seek only to cleanup my codebase, refactor, etc. In other words, I deal with my technical debt.

Note that I use the term discipline to represent that I not only force myself to refactor, but that I allow refactoring a very specific amount of time. This allows me to ensure I do enough refactoring, so that I can maintain my development velocity, but not too much refactoring, which would pointlessly halt development altogether. This is used as a means to prevent me from falling into either the prototyping mood (which can never amount to shipped large scope projects) or the coder’s mood (where the technological thrill outweighs the goal of delivering a game).

In the methodology known as Test-Driven Development (TDD), the goal is to reach a functional state as quickly as possible, and then refactor. This is usually done within a few minutes. In 5,1,1, this Routine is stretched across a development cycle of a week.

The other advantage of having a 6th day dedicated to refactoring is to keep a general understanding of the project. Since I spend 5 days looking at new code, I don’t want to lose sight of what the rest of the code base looks like, and how I can find ways to optimize / refactor. It also ensures that I’m still familiar with the codebase when I get back to development on the next Dev day the week after.

I used to be the kind of developer that tried to get things done through weekend development sprees believing that having longer stretches of time would be more beneficial as my concentration would be easier to muster. It turns out that the amount of concentration required to undergo these sprees is largely due to forgetting a lot about the codebase in-between sprees. As a result, I find a lot more value in spacing that development time across a week and retain a constant understanding of the codebase.

For more information on refactoring (and clean code), see this video. I went to college with this guy and he’s grown into a very successful indie, so I was amazed to watch this video after completing this article.

What if 5,1,1 breaks?


It’s entirely possible that the 5,1,1 will break, and though this is not desirable per se, it exposes any threat so that it becomes actionable.

Determining that the 5,1,1 is broken is fairly simple and can be answered with a very simple question:


Did I deliver an increment of functionality on each dev day?


The key to 5,1,1 is to understand why the method broke, and how to fix it. One of the most common reasons for the cycle to break will be that a specific class, function or segment of code is currently in a state that makes it impossible to deliver new increments of functionality on each dev day. This could be due to code readability, unnecessary complexity resulting from multiple iterations, or any other logical issue that could be dealt with on a refactor day.

When this occurs, I willingly choose to sacrifice one of my dev days and turn it in an extra refactor day (turning that specific week in a 4,2,1), knowing there’s a very specific goal to be attained in order to restore my cycle.

I generally refrain from using the 7th day (rest day). The rest day should, most of the time, be sacred. It is a day that keeps everything in equilibrium and allows for this routine to last. No matter how asocial one might be, there’s always a time when other things need to get done, and having a routine that leaves room for this is essential. It’s also a good way to stay clear from burn-outs.

Ways to make refactoring fun


Some developers refactor too much and over-engineer. If you are one such developer, I recommend reading this article.

For the more gameplay-oriented programmers however, refactoring is often perceived as a pain, a necessary evil, and is often cause for grief.

We’ve already addressed a way to build up momentum and motivation by having 5 dev days, but unless you come to your refactoring day with a plan in mind, you’re still likely to loathe it. There are various ways you can approach this, but the important thing here is to prioritize the work that makes the most sense all the while keeping your sanity above ground.

My personal approach is to come with a specific goal in mind. It could be that I have identified throughout the week a very painful bit of code that I need to deal with recurrently that could be refactored into something more intuitive and easier to use. It could also be that I happen to think that I’ve broken enough encapsulation rules to try and fix this before it becomes spaghetti code. The key here is that your recent development cycle is still fresh in memory, so you know where you’ve struggled, and what you have stained with poor (new) code.

Whatever the problem you may have identified, make sure that it will reduce your development time for new features. That way, you won’t feel like you’re shooting in the dark. Also, make sure to vary your method and focus from week to week to keep things interesting. It’s a good way to make sure it does not feel redundant over longer stretches of time.

Ways to make each dev day more efficient


Here is a shortlist of tools and techniques that can really help you stay focused and gain velocity. These are software that I have used in various projects. Please note that while I suggest specific software for each category, it may not be the most suitable for you, but it doesn't hurt to know where to start looking.

Source Control (SVN / GIT / Perforce)

I'm always surprised by developers (generally "lone" wolves) that don't use source versioning. These allow you to do many things at once with relatively little effort:
  • Share your source code with somebody and allow more than one person to work on the project at once. Though you may not plan on having anybody else contributing to the project at this stage, it's not a given that it will never happen.
  • Prevent data loss when your development setup unmistakenly breaks. A simple checkout will restore your project a few minutes away from where you were at.
  • Help you establish a "system". By establishing the rules of "when to commit", you have a more healthy relationship with your development cycle.
Also, when looking for free hosting for source versioning, I recommend: Assembla.

Task / Bug Tracking (Trello, Assembla)

As an indie, it's easy to fall prey to the misconception that it's possible for one person to "know-it-all" because of the scope of the project. Even if it was accurate, it would take such a toll on a person's memory that the impact in everyday life is not desirable.

Rather, the use of tools that allow you to track tasks and ideas is preferable. I personally use Trello for my projects. The key here is that a simple tool allows you to do more. Trello has the advantage of being flexible (create your own workflow / categories, ability to add checklists, etc.). Furthermore it works on mobile, making it also good at taking notes in context of a project (rather than in a standalone app).

What I really like about Trello is that it allows you to create one item (say, a feature) and break it down in many sub-checklists. This way, you can contain within a single feature the necessary logical components, the assets you might need and other requirements, the polish ideas you have along with any bug or optimization requirements you might have. Thus, your Trello board can be a collection of high level features that display something along the lines of "Feature name - 10/93 items".

Continuous Integration (Jenkins)

A bit more recent perhaps than other items listed here, the concept of continuous integration is really something to delve into. It allows you to minimize the amount of time lost "building" the project and makes it simple to screen a build whenever you want. By default, it will build a version after every commit you make. Versioning might prove catastrophic at first, but you'll get the hang of it.

I'll admit that I've never looked at alternatives to Jenkins. The gain I've experienced when first using Jenkins was so great that I did not bother to compare, but there may be better ways to do this out there.


How to get Games Done


You may follow all of the above and still not get games done. That's because while you may understand how to "get there", it's possible that you have no idea where "there" actually is. Say you want to go to Newark's airport and I tell you it's somewhere along the New Jersey Turnpike, it's quite possible you'll end up god-knows-where along the turnpike because you have no idea what an airport looks like (arguably they've done a pretty good job at putting signs up, and well, there are planes, but without prior knowledge about what planes are it won't help you much).

Making games is no exception. If you know your way around development tools and have a development cycle, it's possible you'll miss out on completing a project, despite being highly motivated. If this is your case, here are a few things to bear in mind as you define your overall development "strategy" (something you should think about before starting, and iterate on as time passes by).

Have a clear idea of what you want to demonstrate


What you should have at this stage is a "game vision". In simple terms, you should be able to describe the end-user experience before the software is built, knowing that some things will need to shift along the way. You should not know everything in finer détails, but you need to have this sort of high-level interpretation of how it will all come together.

A good way to confirm that you have a clear idea of what you want to demonstrate is to try to explain your game to a friend using as few words as possible, and yet hype them. If your friend starts saying "oh and you should do this and that..." you should probably ignore the specifics, but you'll at least know you've come up with an idea that is fertile ground. Your job then will only be to squeeze out the fun where it really is and exploit the fun-vein to a maximum.

How to scope & iterate (MVP)


As a game developer and designer, your role is initially to demonstrate fun gameplay. The core of your job should be to build a skeleton of the game you want to build, by focusing on its most important aspects (what makes it unique, fun, etc). If you're not going for original gameplay, and rather focus on execution, then there should be less unknowns in regards to what's fun early on, but timing will still be critical. Don't assume that just because you are doing a final fantasy ripoff, the same pace should be used in exploration and combat just because it worked for these games.

The next logical step is to build an MVP: Minimum-Viable Product. In essence the MVP represents the minimum your game can ever be. It's a condensed game that contains very few mechanics, however each mechanic will be brought to complete fruition.

The process to get there is to build the skeleton first. Prog-art your prototype, check what works, what doesn't. Then iterate until this part makes sense. Then, proceed to the next core gameplay mechanic. Once each CORE gameplay mechanic feels right, hone them. Develop each of these mechanics until you feel like your game is a polished gem, with much untapped potential. Play this build over and over again and ask for feedback. THIS is your MVP. If anything happens tomorrow, you'll have at least achieved that (the minimum your game can ever be to deserve to be called a game). Instead of being a shady prototype halfway through completion of the next uber MMORPG, you'll have achieved a shippable game that may just lack depth.

Why is this critical? Because from now on, you can release this game anytime you want, and you know the quality you are delivering. In fact, you knew the quality you were delivering every step of the way, never worried about whether other mechanics would feel right or not.

Knowing that you can release anytime alleviates the pressure of the unfinished project and helps you keep a high motivation. It's much easier to think in terms of polishing a final product than to get everything missing for release. While you may spend the same amount of time on the game, you've had a lot more control on overall quality, always working on that which has more value, and always judging of new feature's value based on their coherence with what is established.

This is a mindset that takes some getting used to, but over time, it's something I've grown incapable to avoid based on the sheer benefits of this approach.

The drawback to this approach is feature-creep. Having an MVP in hand puts you in a state of mind where you need to consider the features you feel you need to add to complete the greater experience. The problem is that you might get excited about way more features than the game would normally support. There's no real rule to determine when you've entered feature-creep lane, but if you find yourself months beyond your MVP and still have a lengthy list of things you'd like to add, consider releasing first and adding whatever you need based on user reviews instead. If the game allows it, consider metric-driven development if at all relevant.

Set Deadlines


Let's face it, there are many reasons not to set deadlines. This is likely a hobby project, and the truth is that you might enjoy the indefinite nature of it. I won't argue here as I have plenty of side-projects of my own that don't have a set deadline. The key difference is that I did not choose to make these a priority and I did not determine I wanted to get them Done.

The premise of this article is that you want to get this game done. If this specific project does not fit this criteria, you are more than welcome resuming work on it without actually applying any system or deadline. Just know that it is likely to fail / fall in an indefinite hiatus at some point. There's nothing wrong with that. As a matter of fact, it's better to toy with many ideas at once and choose to only pursue the most fun.

Once you've browsed over your projects and have identified one you really want to complete, then it's time to trade the safety you had knowing there were no "rules" for a system and actual deadlines.

Deadlines shouldn't be daunting. You are not liable to a contract here as you would in the corporate world. Rather, think of deadline as goals, ways to keep a macro idea of where you are headed. I personally like to think of my own deadlines as extremely lax and vague, but they allow me to place tangible milestones in time and give me an idea of the commitment I need to put towards achieving what I want.

For example, your first milestone could be just a prototype, and it may take as many milestones as you want just to get to the MVP. Don't try to be too micro however, you're only likely to make your milestone definition irrelevant as time passes by.

Also, don't put an exact date on a milestone. Depending on the scope, a specific week or month may be enough. I personally use quarters of a year and calendar events as references ("I'll get this done before Christmas, and this somewhere in Q1 2014).

You don't have to stick to your milestones if the circumstances change (if a core gameplay element isn't as fun as it should and you've chosen to take the time to fix it or find alternatives) but you should always have a broad plan of where you are headed days, weeks, months from now (task tracking helps, especially in the form of notes for "later").

The mere effort to project your project in time is what matters, not the end result. Pat yourself on the back if you meet a deadline, don't focus on the deadlines you miss and simply keep in check the reasons why you have failed. Are you overthinking things? Are you still working on the MVP? It's possible that you'll find out you've been feature-creeping. If you are, learn to let go, push these ideas through Trello instead, and focus on what needs to be done first. Perhaps you'll discover your game vision has grown blurry? Take a pause, reassess what you think the game should be, how it should feel, then resume work. It's pointless to work on fun bits of the game if they can't become part of the greater whole and feel coherent with the experience.

Random Thoughts & Rants


Here's a short list of practices you might want to set up. These are mostly aimed at team work environments (2+ people).

Weekly Reviews

One good practice is to do team weekly review. If you can afford it (even over skype), this is extremely helpful in setting everybody's expectations straight and keeping the vision clear. It's also a good opportunity to discuss potential features, solutions to some problems, and more importantly, evaluate the level of fun resulting from the current implementation.

If using the 5,1,1 you can probably do these every other Rest day.

Internal Playtesting

If you're working on something on your end while others work on something else, it's always good to send them something partial as you are progressing with minimal information. If possible, seeing them manipulating it is ideal, but comments can also work. It will give you an idea how intuitive what you're doing is and will grant you impressively useful feedbacks.

The often forgotten effect of doing this is keeping the motivation across the team. This article does not discuss team work much, but it is critical to leverage each other's motivation to keep the boat afloat in larger teams (or risk chain reaction of demotivation). Experiencing what others are doing and receiving a stream of comments on what you are doing keeps the dialogue open. You need to use each other's mind as much as possible to make this the best game it can be.

External Playtesting

Often discarded... sadly.

You need to get out there and let people play your build. Ideally, you want to see them in action (moreso than team members). Be aware of who these players are compared to your target audience (this will let you know which part of the notes you are taking will be truly relevant). There are various ways to do this which doesn't fit within the scope of this article however. I recommend reading further on the subject of defining your target audience through the big five.

Leadership (who owns what)

As the team becomes larger, it's important for roles to be clear. By "who owns what", I'm not referring to the final product and legal ownership of the end-result. Rather, I'm discussing about who should have final say on what matters.

The best approach I've used this far is to allow everyone to pitch into everything, but give the final say to the one most proficient with that discipline. For example, I do some programming on my projects, but it's not uncommon for my partner to have superior programming skills, and I tend to grant him the final say in everything technical. Likewise, I tend to have a clearer vision of the end product (especially when I'm responsible for initially creating the high-level concept) and like to keep final say in design matters, but not all.

To keep other team members motivated on the team, I also break down design into various features / components. For example, if making a tactical game, though the game may be my original idea, I may end up being teamed with a Tactics Ogre // Final Fantasy Tactics freak. Why not share design ownership?

There may be reasons not to share this ownership though. The important part is to make sure that ownership is clear for everyone to avoid people feeling like they are in control until denied later. Confusion on who owns what can be a real team killer as it turns out, and it's better to gauge team member's assumptions early on (and tell them outright where they might not have authority) than to lead them on until you need to squash their dreams by imposing your vision.

***


If you do all of the above, you may not make a great game, but you'll at least complete it, knowing that you've completed something that is as good as you could make it. The difference if you lose hope is that you'll have something to show for instead of nothing. Putting that "decent project that could've been so much more" on your website might draw people to you, either in the form of potential players, or collaborators. Never under-estimate the value of completed projects when gauging for partners. It gives people faith in your ability to get things done, and with the amount of indie developers lurking about, this is key to forming bigger teams or relationships.

What you'll learn from this experience will also help you on the next project, and the next... until ultimately you release games in the state you want them to be. It can take 4-5+ projects before you get there, but hopefully this method will make the journey less painful, and leave you with tangible proofs of your efforts.

If you have your own system, let me know! I'm always curious about how other minds work, and how people manage to motivate themselves. Though my system allows me to complete games, it's not perfect and I'm always looking for ways to further improve it.

Cheers,

- M

Article Update Log


28 Jan 2014: Created Draft, modified templating from original file.
Viewing all 17825 articles
Browse latest View live


<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>