initial commit
This commit is contained in:
195
samples/game/source/gamemgr.cpp
Normal file
195
samples/game/source/gamemgr.cpp
Normal file
@@ -0,0 +1,195 @@
|
||||
#include "gamemgr.h"
|
||||
#include "gameobj.h"
|
||||
#include "scriptmgr.h"
|
||||
#include <string.h> // strcpy
|
||||
#include <stdio.h> // rand
|
||||
#include <stdlib.h> // rand
|
||||
#include <iostream> // cout
|
||||
|
||||
using namespace std;
|
||||
|
||||
CGameMgr::CGameMgr()
|
||||
{
|
||||
gameOn = false;
|
||||
}
|
||||
|
||||
CGameMgr::~CGameMgr()
|
||||
{
|
||||
for( unsigned int n = 0; n < gameObjects.size(); n++ )
|
||||
gameObjects[n]->DestroyAndRelease();
|
||||
}
|
||||
|
||||
int CGameMgr::StartGame()
|
||||
{
|
||||
// Set up the game level
|
||||
|
||||
// In a real game this would probably be loaded from a file, that would define
|
||||
// the position of each object and other things that would be placed in the game world.
|
||||
|
||||
// The properties for each object type would probably also be loaded from a file.
|
||||
// The map file, would only have to specify the position and the type of the object.
|
||||
// Based on the type, the game manager would retrieve the graphics object and script
|
||||
// controller that should be used.
|
||||
|
||||
// Create some stones
|
||||
for( unsigned int n = 0; n < 10; n++ )
|
||||
SpawnObject("stone", '0', rand()%10, rand()%10);
|
||||
|
||||
// Create some zombies
|
||||
for( unsigned int n = 0; n < 3; n++ )
|
||||
SpawnObject("zombie", 'z', rand()%10, rand()%10);
|
||||
|
||||
// Create the player
|
||||
CGameObj *obj = SpawnObject("player", 'p', rand()%10, rand()%10);
|
||||
if( obj )
|
||||
obj->name = "player";
|
||||
|
||||
// Check if there were any compilation errors during the script loading
|
||||
if( scriptMgr->hasCompileErrors )
|
||||
return -1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
CGameObj *CGameMgr::SpawnObject(const std::string &type, char dispChar, int x, int y)
|
||||
{
|
||||
CGameObj *obj = new CGameObj(dispChar, x, y);
|
||||
gameObjects.push_back(obj);
|
||||
|
||||
// Set the controller based on type
|
||||
obj->controller = scriptMgr->CreateController(type, obj);
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
void CGameMgr::Run()
|
||||
{
|
||||
gameOn = true;
|
||||
while( gameOn )
|
||||
{
|
||||
// Render the frame
|
||||
Render();
|
||||
|
||||
// Get input from user
|
||||
GetInput();
|
||||
|
||||
// Call the onThink method on each game object
|
||||
for( unsigned int n = 0; n < gameObjects.size(); n++ )
|
||||
gameObjects[n]->OnThink();
|
||||
|
||||
// Kill the objects that have been queued for killing
|
||||
for( unsigned int n = 0; n < gameObjects.size(); n++ )
|
||||
{
|
||||
if( gameObjects[n]->isDead )
|
||||
{
|
||||
// We won't actually delete the memory here, as we do not know
|
||||
// exactly who might still be referencing the object, but we
|
||||
// make sure to destroy the internals in order to avoid
|
||||
// circular references.
|
||||
gameObjects[n]->DestroyAndRelease();
|
||||
gameObjects.erase(gameObjects.begin()+n);
|
||||
n--;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CGameMgr::EndGame(bool win)
|
||||
{
|
||||
gameOn = false;
|
||||
|
||||
if( win )
|
||||
cout << "Congratulations, you've defeated the zombies!" << endl;
|
||||
else
|
||||
cout << "Too bad, the zombies ate your brain!" << endl;
|
||||
|
||||
// Get something to let the player see the message before exiting
|
||||
char buf[2];
|
||||
cin.getline(buf, 1);
|
||||
}
|
||||
|
||||
void CGameMgr::Render()
|
||||
{
|
||||
// Clear the buffer
|
||||
char buf[10][11];
|
||||
for( int y = 0; y < 10; y++ )
|
||||
memcpy(buf[y], "..........\0", 11);
|
||||
|
||||
// Render each object into the buffer
|
||||
for( unsigned int n = 0; n < gameObjects.size(); n++ )
|
||||
buf[gameObjects[n]->y][gameObjects[n]->x] = gameObjects[n]->displayCharacter;
|
||||
|
||||
// Clear the screen
|
||||
#ifdef _WIN32
|
||||
system("cls");
|
||||
#else
|
||||
system("clear");
|
||||
#endif
|
||||
|
||||
// Print some useful information and start the input loop
|
||||
cout << "Sample game using AngelScript " << asGetLibraryVersion() << "." << endl;
|
||||
cout << "Type u(p), d(own), l(eft), r(ight) to move the player." << endl;
|
||||
cout << "Type q(uit) to exit the game." << endl;
|
||||
cout << "Try to avoid getting eaten by the zombies, hah hah." << endl;
|
||||
cout << endl;
|
||||
|
||||
// Present the buffer
|
||||
for( int y = 0; y < 10; y++ )
|
||||
cout << buf[y] << endl;
|
||||
}
|
||||
|
||||
void CGameMgr::GetInput()
|
||||
{
|
||||
cout << "> ";
|
||||
|
||||
char buf[10];
|
||||
cin.getline(buf, 10);
|
||||
|
||||
memset(actionStates, 0, sizeof(actionStates));
|
||||
|
||||
switch( buf[0] )
|
||||
{
|
||||
case 'u':
|
||||
actionStates[0] = true;
|
||||
break;
|
||||
case 'd':
|
||||
actionStates[1] = true;
|
||||
break;
|
||||
case 'l':
|
||||
actionStates[2] = true;
|
||||
break;
|
||||
case 'r':
|
||||
actionStates[3] = true;
|
||||
break;
|
||||
case 'q':
|
||||
gameOn = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
bool CGameMgr::GetActionState(int action)
|
||||
{
|
||||
if( action < 0 || action >= 4 ) return false;
|
||||
|
||||
return actionStates[action];
|
||||
}
|
||||
|
||||
CGameObj *CGameMgr::GetGameObjAt(int x, int y)
|
||||
{
|
||||
for( unsigned int n = 0; n < gameObjects.size(); n++ )
|
||||
if( gameObjects[n]->x == x && gameObjects[n]->y == y )
|
||||
return gameObjects[n];
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
CGameObj *CGameMgr::FindGameObjByName(const string &name)
|
||||
{
|
||||
for( unsigned int n = 0; n < gameObjects.size(); n++ )
|
||||
{
|
||||
if( gameObjects[n]->name == name )
|
||||
return gameObjects[n];
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
39
samples/game/source/gamemgr.h
Normal file
39
samples/game/source/gamemgr.h
Normal file
@@ -0,0 +1,39 @@
|
||||
#ifndef GAMEMGR_H
|
||||
#define GAMEMGR_H
|
||||
|
||||
#include <vector>
|
||||
#include <string>
|
||||
|
||||
class CGameObj;
|
||||
|
||||
class CGameMgr
|
||||
{
|
||||
public:
|
||||
CGameMgr();
|
||||
~CGameMgr();
|
||||
|
||||
int StartGame();
|
||||
void Run();
|
||||
void EndGame(bool win);
|
||||
|
||||
CGameObj *GetGameObjAt(int x, int y);
|
||||
|
||||
bool GetActionState(int action);
|
||||
|
||||
CGameObj *FindGameObjByName(const std::string &name);
|
||||
|
||||
protected:
|
||||
void Render();
|
||||
void GetInput();
|
||||
CGameObj *SpawnObject(const std::string &type, char dispChar, int x, int y);
|
||||
|
||||
std::vector<CGameObj*> gameObjects;
|
||||
|
||||
bool actionStates[4];
|
||||
|
||||
bool gameOn;
|
||||
};
|
||||
|
||||
extern CGameMgr *gameMgr;
|
||||
|
||||
#endif
|
119
samples/game/source/gameobj.cpp
Normal file
119
samples/game/source/gameobj.cpp
Normal file
@@ -0,0 +1,119 @@
|
||||
#include "gameobj.h"
|
||||
#include "scriptmgr.h"
|
||||
#include "gamemgr.h"
|
||||
|
||||
using namespace std;
|
||||
|
||||
CGameObj::CGameObj(char dispChar, int x, int y)
|
||||
{
|
||||
// The first reference is already counted when the object is created
|
||||
refCount = 1;
|
||||
|
||||
isDead = false;
|
||||
displayCharacter = dispChar;
|
||||
this->x = x;
|
||||
this->y = y;
|
||||
controller = 0;
|
||||
|
||||
weakRefFlag = 0;
|
||||
}
|
||||
|
||||
CGameObj::~CGameObj()
|
||||
{
|
||||
if( weakRefFlag )
|
||||
{
|
||||
// Tell the ones that hold weak references that the object is destroyed
|
||||
weakRefFlag->Set(true);
|
||||
weakRefFlag->Release();
|
||||
}
|
||||
|
||||
if( controller )
|
||||
controller->Release();
|
||||
}
|
||||
|
||||
asILockableSharedBool *CGameObj::GetWeakRefFlag()
|
||||
{
|
||||
if( !weakRefFlag )
|
||||
weakRefFlag = asCreateLockableSharedBool();
|
||||
|
||||
return weakRefFlag;
|
||||
}
|
||||
|
||||
int CGameObj::AddRef()
|
||||
{
|
||||
return ++refCount;
|
||||
}
|
||||
|
||||
int CGameObj::Release()
|
||||
{
|
||||
if( --refCount == 0 )
|
||||
{
|
||||
delete this;
|
||||
return 0;
|
||||
}
|
||||
return refCount;
|
||||
}
|
||||
|
||||
void CGameObj::DestroyAndRelease()
|
||||
{
|
||||
// Since there might be other object's still referencing this one, we
|
||||
// cannot just delete it. Here we will release all other references that
|
||||
// this object holds, so it doesn't end up holding circular references.
|
||||
if( controller )
|
||||
{
|
||||
controller->Release();
|
||||
controller = 0;
|
||||
}
|
||||
|
||||
Release();
|
||||
}
|
||||
|
||||
void CGameObj::OnThink()
|
||||
{
|
||||
// Call the script controller's OnThink method
|
||||
if( controller )
|
||||
scriptMgr->CallOnThink(controller);
|
||||
}
|
||||
|
||||
bool CGameObj::Move(int dx, int dy)
|
||||
{
|
||||
// Check if it is actually possible to move to the desired position
|
||||
int x2 = x + dx;
|
||||
if( x2 < 0 || x2 > 9 ) return false;
|
||||
|
||||
int y2 = y + dy;
|
||||
if( y2 < 0 || y2 > 9 ) return false;
|
||||
|
||||
// Check with the game manager if another object isn't occupying this spot
|
||||
CGameObj *obj = gameMgr->GetGameObjAt(x2, y2);
|
||||
if( obj ) return false;
|
||||
|
||||
// Now we can make the move
|
||||
x = x2;
|
||||
y = y2;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void CGameObj::Send(CScriptHandle msg, CGameObj *other)
|
||||
{
|
||||
if( other && other->controller )
|
||||
scriptMgr->CallOnMessage(other->controller, msg, this);
|
||||
}
|
||||
|
||||
void CGameObj::Kill()
|
||||
{
|
||||
// Just flag the object as dead. The game manager will
|
||||
// do the actual destroying at the end of the frame
|
||||
isDead = true;
|
||||
}
|
||||
|
||||
int CGameObj::GetX() const
|
||||
{
|
||||
return x;
|
||||
}
|
||||
|
||||
int CGameObj::GetY() const
|
||||
{
|
||||
return y;
|
||||
}
|
44
samples/game/source/gameobj.h
Normal file
44
samples/game/source/gameobj.h
Normal file
@@ -0,0 +1,44 @@
|
||||
#ifndef GAMEOBJ_H
|
||||
#define GAMEOBJ_H
|
||||
|
||||
#include <string>
|
||||
#include <angelscript.h>
|
||||
#include "../../../add_on/scripthandle/scripthandle.h"
|
||||
|
||||
class CGameObj
|
||||
{
|
||||
public:
|
||||
CGameObj(char dispChar, int x, int y);
|
||||
int AddRef();
|
||||
int Release();
|
||||
asILockableSharedBool *GetWeakRefFlag();
|
||||
|
||||
// This method is used by the application
|
||||
// when the object should be destroyed
|
||||
void DestroyAndRelease();
|
||||
|
||||
// This event handler is called by the game manager each frame
|
||||
void OnThink();
|
||||
|
||||
bool Move(int dx, int dy);
|
||||
void Send(CScriptHandle msg, CGameObj *other);
|
||||
void Kill();
|
||||
|
||||
// The script shouldn't be allowed to update the position directly
|
||||
// so we won't provide direct access to the position
|
||||
int GetX() const;
|
||||
int GetY() const;
|
||||
|
||||
std::string name;
|
||||
char displayCharacter;
|
||||
bool isDead;
|
||||
asIScriptObject *controller;
|
||||
int x, y;
|
||||
|
||||
protected:
|
||||
~CGameObj();
|
||||
int refCount;
|
||||
asILockableSharedBool *weakRefFlag;
|
||||
};
|
||||
|
||||
#endif
|
90
samples/game/source/main.cpp
Normal file
90
samples/game/source/main.cpp
Normal file
@@ -0,0 +1,90 @@
|
||||
#include <iostream> // cout
|
||||
#include <stdio.h>
|
||||
#include <angelscript.h>
|
||||
#ifdef _MSC_VER
|
||||
#include <crtdbg.h> // debugging routines
|
||||
#endif
|
||||
|
||||
#include "gamemgr.h"
|
||||
#include "scriptmgr.h"
|
||||
|
||||
using namespace std;
|
||||
|
||||
CScriptMgr *scriptMgr = 0;
|
||||
CGameMgr *gameMgr = 0;
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
#ifdef _MSC_VER
|
||||
// Detect memory leaks
|
||||
_CrtSetDbgFlag(_CRTDBG_LEAK_CHECK_DF|_CRTDBG_ALLOC_MEM_DF);
|
||||
_CrtSetReportMode(_CRT_ASSERT,_CRTDBG_MODE_FILE);
|
||||
_CrtSetReportFile(_CRT_ASSERT,_CRTDBG_FILE_STDERR);
|
||||
|
||||
// Use _CrtSetBreakAlloc(n) to find a specific memory leak
|
||||
#endif
|
||||
|
||||
int r;
|
||||
|
||||
// Make sure the game is being executed with the correct working directory
|
||||
// At the very least there should be a 'player.as' script for controlling the
|
||||
// player character.
|
||||
FILE *f = fopen("player.as", "r");
|
||||
if( f == 0 )
|
||||
{
|
||||
cout << "The game is not executed in the correct location. Make sure you set the working directory to the path where the 'player.as' script is located." << endl;
|
||||
cout << endl;
|
||||
cout << "Press enter to exit." << endl;
|
||||
char buf[10];
|
||||
cin.getline(buf, 10);
|
||||
return -1;
|
||||
}
|
||||
fclose(f);
|
||||
|
||||
// Initialize the game manager
|
||||
gameMgr = new CGameMgr();
|
||||
|
||||
// Initialize the script manager
|
||||
scriptMgr = new CScriptMgr();
|
||||
r = scriptMgr->Init();
|
||||
if( r < 0 )
|
||||
{
|
||||
delete scriptMgr;
|
||||
delete gameMgr;
|
||||
return r;
|
||||
}
|
||||
|
||||
// Start a new game
|
||||
r = gameMgr->StartGame();
|
||||
if( r < 0 )
|
||||
{
|
||||
cout << "Failed to initialize the game. Please verify the script errors." << endl;
|
||||
cout << endl;
|
||||
cout << "Press enter to exit." << endl;
|
||||
char buf[10];
|
||||
cin.getline(buf, 10);
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Let the game manager handle the game loop
|
||||
gameMgr->Run();
|
||||
|
||||
// Uninitialize the game manager
|
||||
if( gameMgr )
|
||||
{
|
||||
delete gameMgr;
|
||||
gameMgr = 0;
|
||||
}
|
||||
|
||||
// Uninitialize the script manager
|
||||
if( scriptMgr )
|
||||
{
|
||||
delete scriptMgr;
|
||||
scriptMgr = 0;
|
||||
}
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
|
||||
|
325
samples/game/source/scriptmgr.cpp
Normal file
325
samples/game/source/scriptmgr.cpp
Normal file
@@ -0,0 +1,325 @@
|
||||
#include "scriptmgr.h"
|
||||
#include "gamemgr.h"
|
||||
#include "gameobj.h"
|
||||
#include <iostream> // cout
|
||||
#include <stdio.h> // fopen, fclose
|
||||
#include <string.h> // strcmp
|
||||
#include <assert.h>
|
||||
#include "../../../add_on/scriptstdstring/scriptstdstring.h"
|
||||
#include "../../../add_on/scriptbuilder/scriptbuilder.h"
|
||||
#include "../../../add_on/weakref/weakref.h"
|
||||
|
||||
using namespace std;
|
||||
|
||||
CScriptMgr::CScriptMgr()
|
||||
{
|
||||
engine = 0;
|
||||
hasCompileErrors = false;
|
||||
}
|
||||
|
||||
CScriptMgr::~CScriptMgr()
|
||||
{
|
||||
for( unsigned int n = 0; n < controllers.size(); n++ )
|
||||
delete controllers[n];
|
||||
|
||||
for( unsigned int n = 0; n < contexts.size(); n++ )
|
||||
contexts[n]->Release();
|
||||
|
||||
if( engine )
|
||||
engine->ShutDownAndRelease();
|
||||
}
|
||||
|
||||
int CScriptMgr::Init()
|
||||
{
|
||||
int r;
|
||||
|
||||
engine = asCreateScriptEngine();
|
||||
|
||||
// Set the message callback to print the human readable messages that the engine gives in case of errors
|
||||
r = engine->SetMessageCallback(asMETHOD(CScriptMgr, MessageCallback), this, asCALL_THISCALL); assert( r >= 0 );
|
||||
|
||||
// Register the string type
|
||||
RegisterStdString(engine);
|
||||
|
||||
// Register the generic handle type, called 'ref' in the script
|
||||
RegisterScriptHandle(engine);
|
||||
|
||||
// Register the weak ref template type
|
||||
RegisterScriptWeakRef(engine);
|
||||
|
||||
// Register the game object. The scripts cannot create these directly, so there is no factory function.
|
||||
r = engine->RegisterObjectType("CGameObj", 0, asOBJ_REF); assert( r >= 0 );
|
||||
r = engine->RegisterObjectBehaviour("CGameObj", asBEHAVE_ADDREF, "void f()", asMETHOD(CGameObj, AddRef), asCALL_THISCALL); assert( r >= 0 );
|
||||
r = engine->RegisterObjectBehaviour("CGameObj", asBEHAVE_RELEASE, "void f()", asMETHOD(CGameObj, Release), asCALL_THISCALL); assert( r >= 0 );
|
||||
r = engine->RegisterObjectBehaviour("CGameObj", asBEHAVE_GET_WEAKREF_FLAG, "int &f()", asMETHOD(CGameObj, GetWeakRefFlag), asCALL_THISCALL); assert( r >= 0 );
|
||||
|
||||
// The object's position is read-only to the script. The position is updated with the Move method
|
||||
r = engine->RegisterObjectMethod("CGameObj", "int get_x() const property", asMETHOD(CGameObj, GetX), asCALL_THISCALL); assert( r >= 0 );
|
||||
r = engine->RegisterObjectMethod("CGameObj", "int get_y() const property", asMETHOD(CGameObj, GetY), asCALL_THISCALL); assert( r >= 0 );
|
||||
r = engine->RegisterObjectMethod("CGameObj", "bool Move(int dx, int dy)", asMETHOD(CGameObj, Move), asCALL_THISCALL); assert( r >= 0 );
|
||||
|
||||
// The script can kill the owning object
|
||||
r = engine->RegisterObjectMethod("CGameObj", "void Kill()", asMETHOD(CGameObj, Kill), asCALL_THISCALL); assert( r >= 0 );
|
||||
|
||||
// The script can send a message to the other object through this method
|
||||
// Observe the autohandle @+ to tell AngelScript to automatically release the handle after the call
|
||||
// The generic handle type is used to allow the script to pass any object to
|
||||
// the other script without the application having to know anything about it
|
||||
r = engine->RegisterObjectMethod("CGameObj", "void Send(ref msg, const CGameObj @+ to)", asMETHOD(CGameObj, Send), asCALL_THISCALL); assert( r >= 0 );
|
||||
|
||||
|
||||
|
||||
// The game engine will determine the class that represents the controller
|
||||
// by checking if the class implements the IController interface. No methods
|
||||
// are registered for this interface, as the script shouldn't be required to
|
||||
// implement the methods. This will allow the game engine to avoid calling
|
||||
// methods that doesn't do anything, thus improving performance.
|
||||
r = engine->RegisterInterface("IController"); assert( r >= 0 );
|
||||
|
||||
|
||||
|
||||
// Register the game manager as a singleton. The script will access it through the global property
|
||||
r = engine->RegisterObjectType("CGameMgr", 0, asOBJ_REF | asOBJ_NOHANDLE); assert( r >= 0 );
|
||||
|
||||
// Register the game manager's methods
|
||||
r = engine->RegisterGlobalProperty("CGameMgr game", gameMgr); assert( r >= 0 );
|
||||
|
||||
// The script can determine what the user wants to do through the actionStates
|
||||
r = engine->RegisterObjectMethod("CGameMgr", "bool get_actionState(int idx) property", asMETHOD(CGameMgr, GetActionState), asCALL_THISCALL); assert( r >= 0 );
|
||||
|
||||
// The script can call this method to end the game
|
||||
r = engine->RegisterObjectMethod("CGameMgr", "void EndGame(bool win)", asMETHOD(CGameMgr, EndGame), asCALL_THISCALL); assert( r >= 0 );
|
||||
|
||||
// Register a method that will allow the script to find an object by its name.
|
||||
// This returns the object as const handle, as the script should only be
|
||||
// allow to directly modify its owner object.
|
||||
// Observe the @+ that tells AngelScript to automatically increase the refcount
|
||||
r = engine->RegisterObjectMethod("CGameMgr", "const CGameObj @+ FindObjByName(const string &in name)", asMETHOD(CGameMgr, FindGameObjByName), asCALL_THISCALL); assert( r >= 0 );
|
||||
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void CScriptMgr::MessageCallback(const asSMessageInfo &msg)
|
||||
{
|
||||
const char *type = "ERR ";
|
||||
if( msg.type == asMSGTYPE_WARNING )
|
||||
type = "WARN";
|
||||
else if( msg.type == asMSGTYPE_INFORMATION )
|
||||
type = "INFO";
|
||||
|
||||
cout << msg.section << " (" << msg.row << ", " << msg.col << ") : " << type << " : " << msg.message << endl;
|
||||
|
||||
if( msg.type == asMSGTYPE_ERROR )
|
||||
hasCompileErrors = true;
|
||||
}
|
||||
|
||||
CScriptMgr::SController *CScriptMgr::GetControllerScript(const string &script)
|
||||
{
|
||||
int r;
|
||||
|
||||
// Find the cached controller
|
||||
for( unsigned int n = 0; n < controllers.size(); n++ )
|
||||
{
|
||||
if( controllers[n]->module == script )
|
||||
return controllers[n];
|
||||
}
|
||||
|
||||
// No controller, check if the script has already been loaded
|
||||
asIScriptModule *mod = engine->GetModule(script.c_str(), asGM_ONLY_IF_EXISTS);
|
||||
if( mod )
|
||||
{
|
||||
// We've already attempted loading the script before, but there is no controller
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Compile the script into the module
|
||||
CScriptBuilder builder;
|
||||
r = builder.StartNewModule(engine, script.c_str());
|
||||
if( r < 0 )
|
||||
return 0;
|
||||
|
||||
// If the script file doesn't exist, then there is no script controller for this type
|
||||
FILE *f;
|
||||
if( (f = fopen((script + ".as").c_str(), "r")) == 0 )
|
||||
return 0;
|
||||
fclose(f);
|
||||
|
||||
// Let the builder load the script, and do the necessary pre-processing (include files, etc)
|
||||
r = builder.AddSectionFromFile((script + ".as").c_str());
|
||||
if( r < 0 )
|
||||
return 0;
|
||||
|
||||
r = builder.BuildModule();
|
||||
if( r < 0 )
|
||||
return 0;
|
||||
|
||||
// Cache the functions and methods that will be used
|
||||
SController *ctrl = new SController;
|
||||
controllers.push_back(ctrl);
|
||||
ctrl->module = script;
|
||||
|
||||
// Find the class that implements the IController interface
|
||||
mod = engine->GetModule(script.c_str(), asGM_ONLY_IF_EXISTS);
|
||||
asITypeInfo *type = 0;
|
||||
int tc = mod->GetObjectTypeCount();
|
||||
for( int n = 0; n < tc; n++ )
|
||||
{
|
||||
bool found = false;
|
||||
type = mod->GetObjectTypeByIndex(n);
|
||||
int ic = type->GetInterfaceCount();
|
||||
for( int i = 0; i < ic; i++ )
|
||||
{
|
||||
if( strcmp(type->GetInterface(i)->GetName(), "IController") == 0 )
|
||||
{
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if( found == true )
|
||||
{
|
||||
ctrl->type = type;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if( ctrl->type == 0 )
|
||||
{
|
||||
cout << "Couldn't find the controller class for the type '" << script << "'" << endl;
|
||||
controllers.pop_back();
|
||||
delete ctrl;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Find the factory function
|
||||
// The game engine will pass in the owning CGameObj to the controller for storage
|
||||
string s = string(type->GetName()) + "@ " + string(type->GetName()) + "(CGameObj @)";
|
||||
ctrl->factoryFunc = type->GetFactoryByDecl(s.c_str());
|
||||
if( ctrl->factoryFunc == 0 )
|
||||
{
|
||||
cout << "Couldn't find the appropriate factory for the type '" << script << "'" << endl;
|
||||
controllers.pop_back();
|
||||
delete ctrl;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Find the optional event handlers
|
||||
ctrl->onThinkMethod = type->GetMethodByDecl("void OnThink()");
|
||||
ctrl->onMessageMethod = type->GetMethodByDecl("void OnMessage(ref @msg, const CGameObj @sender)");
|
||||
|
||||
// Add the cache as user data to the type for quick access
|
||||
type->SetUserData(ctrl);
|
||||
|
||||
return ctrl;
|
||||
}
|
||||
|
||||
asIScriptObject *CScriptMgr::CreateController(const string &script, CGameObj *gameObj)
|
||||
{
|
||||
int r;
|
||||
asIScriptObject *obj = 0;
|
||||
|
||||
SController *ctrl = GetControllerScript(script);
|
||||
if( ctrl == 0 ) return 0;
|
||||
|
||||
// Create the object using the factory function
|
||||
asIScriptContext *ctx = PrepareContextFromPool(ctrl->factoryFunc);
|
||||
|
||||
// Pass the object pointer to the script function. With this call the
|
||||
// context will automatically increase the reference count for the object.
|
||||
ctx->SetArgObject(0, gameObj);
|
||||
|
||||
// Make the call and take care of any errors that may happen
|
||||
r = ExecuteCall(ctx);
|
||||
if( r == asEXECUTION_FINISHED )
|
||||
{
|
||||
// Get the newly created object
|
||||
obj = *((asIScriptObject**)ctx->GetAddressOfReturnValue());
|
||||
|
||||
// Since a reference will be kept to this object
|
||||
// it is necessary to increase the ref count
|
||||
obj->AddRef();
|
||||
}
|
||||
|
||||
// Return the context to the pool so it can be reused
|
||||
ReturnContextToPool(ctx);
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
void CScriptMgr::CallOnThink(asIScriptObject *object)
|
||||
{
|
||||
// Find the cached onThink method id
|
||||
SController *ctrl = reinterpret_cast<SController*>(object->GetObjectType()->GetUserData());
|
||||
|
||||
// Call the method using the shared context
|
||||
if( ctrl->onThinkMethod != 0 )
|
||||
{
|
||||
asIScriptContext *ctx = PrepareContextFromPool(ctrl->onThinkMethod);
|
||||
ctx->SetObject(object);
|
||||
ExecuteCall(ctx);
|
||||
ReturnContextToPool(ctx);
|
||||
}
|
||||
}
|
||||
|
||||
void CScriptMgr::CallOnMessage(asIScriptObject *object, CScriptHandle &msg, CGameObj *caller)
|
||||
{
|
||||
// Find the cached onMessage method id
|
||||
SController *ctrl = reinterpret_cast<SController*>(object->GetObjectType()->GetUserData());
|
||||
|
||||
// Call the method using the shared context
|
||||
if( ctrl->onMessageMethod != 0 )
|
||||
{
|
||||
asIScriptContext *ctx = PrepareContextFromPool(ctrl->onMessageMethod);
|
||||
ctx->SetObject(object);
|
||||
ctx->SetArgObject(0, &msg);
|
||||
ctx->SetArgObject(1, caller);
|
||||
ExecuteCall(ctx);
|
||||
ReturnContextToPool(ctx);
|
||||
}
|
||||
}
|
||||
|
||||
int CScriptMgr::ExecuteCall(asIScriptContext *ctx)
|
||||
{
|
||||
int r = ctx->Execute();
|
||||
if( r != asEXECUTION_FINISHED )
|
||||
{
|
||||
if( r == asEXECUTION_EXCEPTION )
|
||||
{
|
||||
cout << "Exception: " << ctx->GetExceptionString() << endl;
|
||||
cout << "Function: " << ctx->GetExceptionFunction()->GetDeclaration() << endl;
|
||||
cout << "Line: " << ctx->GetExceptionLineNumber() << endl;
|
||||
|
||||
// It is possible to print more information about the location of the
|
||||
// exception, for example the call stack, values of variables, etc if
|
||||
// that is of interest.
|
||||
}
|
||||
}
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
asIScriptContext *CScriptMgr::PrepareContextFromPool(asIScriptFunction *func)
|
||||
{
|
||||
asIScriptContext *ctx = 0;
|
||||
if( contexts.size() )
|
||||
{
|
||||
ctx = *contexts.rbegin();
|
||||
contexts.pop_back();
|
||||
}
|
||||
else
|
||||
ctx = engine->CreateContext();
|
||||
|
||||
int r = ctx->Prepare(func); assert( r >= 0 );
|
||||
|
||||
return ctx;
|
||||
}
|
||||
|
||||
void CScriptMgr::ReturnContextToPool(asIScriptContext *ctx)
|
||||
{
|
||||
contexts.push_back(ctx);
|
||||
|
||||
// Unprepare the context to free any objects that might be held
|
||||
// as we don't know when the context will be used again.
|
||||
ctx->Unprepare();
|
||||
}
|
58
samples/game/source/scriptmgr.h
Normal file
58
samples/game/source/scriptmgr.h
Normal file
@@ -0,0 +1,58 @@
|
||||
#ifndef SCRIPTMGR_H
|
||||
#define SCRIPTMGR_H
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <angelscript.h>
|
||||
#include "../../../add_on/scripthandle/scripthandle.h"
|
||||
|
||||
class CGameObj;
|
||||
|
||||
class CScriptMgr
|
||||
{
|
||||
public:
|
||||
CScriptMgr();
|
||||
~CScriptMgr();
|
||||
|
||||
int Init();
|
||||
|
||||
asIScriptObject *CreateController(const std::string &type, CGameObj *obj);
|
||||
void CallOnThink(asIScriptObject *object);
|
||||
void CallOnMessage(asIScriptObject *object, CScriptHandle &msg, CGameObj *caller);
|
||||
|
||||
bool hasCompileErrors;
|
||||
|
||||
protected:
|
||||
void MessageCallback(const asSMessageInfo &msg);
|
||||
asIScriptContext *PrepareContextFromPool(asIScriptFunction *func);
|
||||
void ReturnContextToPool(asIScriptContext *ctx);
|
||||
int ExecuteCall(asIScriptContext *ctx);
|
||||
|
||||
struct SController
|
||||
{
|
||||
SController() : type(0), factoryFunc(0), onThinkMethod(0), onMessageMethod(0) {}
|
||||
std::string module;
|
||||
asITypeInfo *type;
|
||||
asIScriptFunction *factoryFunc;
|
||||
asIScriptFunction *onThinkMethod;
|
||||
asIScriptFunction *onMessageMethod;
|
||||
};
|
||||
|
||||
SController *GetControllerScript(const std::string &type);
|
||||
|
||||
asIScriptEngine *engine;
|
||||
|
||||
// Our pool of script contexts. This is used to avoid allocating
|
||||
// the context objects all the time. The context objects are quite
|
||||
// heavy weight and should be shared between function calls.
|
||||
std::vector<asIScriptContext *> contexts;
|
||||
|
||||
// This is the cache of function ids etc that we use to avoid having
|
||||
// to search for the function ids everytime we need to call a function.
|
||||
// The search is quite time consuming and should only be done once.
|
||||
std::vector<SController *> controllers;
|
||||
};
|
||||
|
||||
extern CScriptMgr *scriptMgr;
|
||||
|
||||
#endif
|
Reference in New Issue
Block a user