Advertisement

Frame independent scripting

Started by November 09, 2010 08:46 AM
8 comments, last by _orm_ 14 years ago
Hello,

Ok here it goes. I've recently integrated Angelscript to our team's game engine and
I've got some questions.

The way i've made it now:

I have a seperate module for each type of job, e.g gui, entity control etc.
Let's concentrate on Entity Control for the moment. Each Entity has a Script Component which's job is to cache the function ID provided by the asIScriptEngine of the script function that the parent Entity must execute (defined in a file specifying the function's module and the function's prototype).

On each update (every frame) the Script Component prepares a context with the cached ID, passes a handle to it's parent Entity as a paremeter to the Script Function and executes it.

The problem arises here, say that a script is like this:

void main(Entity@ currentEntity){   if(currentEntity.isHit(player))   {       Spawn_Particle("Fire_Burst", ...)               currentEntity.Echo("THOU ART DOOMED!");       currentEntity.Kill();   }}


As you can understand, this script's operations will all be executed in the same frame (something which is not good), because angelscript executes the Main script function first and then returns control to the host application, which in turn updates the Entities, the Player, the Screen etc.

Is it possible to script the engine in a way that's independent of how it is updated? I thought about executing the script function in a separate thread but it seems a little bit overwhelming since the engine doesn't support multithreading currently.
my game development site:http://sites.google.com/site/billgamedevelopment/
It's possible to register a function that automatically suspends script execution. Ex:
void Suspend() {  asIScriptContext *ctx = asGetActiveContext();  if (ctx) ctx->Suspend();}

This will allow the function to proceed partway through execution and then pause. You can later resume execution to get the rest of the script processed.
Advertisement
The following topics in the manual may be of interest:

Concurrent scripts

Timeout long running scripts

Sample: Events

Add-on: ContextMgr


The one problem with having scripts that run over multiple frames, is that it is difficult to save the state at any time of the execution. There is currently no way of serializing a script context (though it is a feature I would like to add in the future), so it is a bit complicated to design the script so that it is possible to restart it where it ended the last time the game was running.

That's why I usually recommend event based scripting. The state is then kept outside of the script context and can be easily saved and restored. Each event handler will execute and return immediately so there are no active scripts between frames.

AngelCode.com - game development and more - Reference DB - game developer references
AngelScript - free scripting library - BMFont - free bitmap font generator - Tower - free puzzle game

Hello again,

First of all, thank you both for your time.


Quote: Original post by WitchLord

That's why I usually recommend event based scripting. The state is then kept outside of the script context and can be easily saved and restored. Each event handler will execute and return immediately so there are no active scripts between frames.



If I understand correctly, you recommend executing a script not in a frame-per-frame basis but having game events trigger script execution.
Let's say I did implement scripting this way, If we take the script in my original post as an example, the script would execute these operations 'simultaneously' given that all these operations will be executed before the engine updates the world.

So, I think a solution would be to execute the activated scripts concurrently
and when a context reaches its' timeout and execution is suspended, update the game engine.

I would like to hear how you implement scripting in you own engine, if you have the time of course :)

I also realized that you are the creator of AngelScript. Kudos for your great work, it's an excellent tool for game developers.

Thank you again,
Vassilis
my game development site:http://sites.google.com/site/billgamedevelopment/
In my engine I use event based scripting. (of course)

At the beginning of each frame each entity has it's 'onThink' event handler called. This is where the entity decides what it wants to do, for example start an animation, shoot at the player, etc.

Then the engine updates the game world, where all the movements is performed, collisions are detected, and so on. If any collisions happend, the entities' 'onCollide' event handler is called. This is where the entity updates the health status, sets 'angry' state, etc.

If any script function times out, then it is misbehaving and I kill the script.

All the entity AIs are implemented as script classes, so the scripts can have their own properties and methods, without being limited to whatever the game engine is providing. The script class is then linked to the actual game entity that the engine knows of. The properties of the script class can be easily serialized for saving and loading.

There are also other event handlers, for example 'onTimer' where the script can request a timer event, to perform more complex animations without having to execute 'onThink' each frame. There is also event handlers that can be set up to be called when a situation occurs, e.g. player comes within x meters, etc.


However, my game engine is still very raw. The script library probably has more code than the entire game engine so far. I'm mostly building the game engine to get some real world experience out of the scripting library, instead of just artificial samples.

AngelCode.com - game development and more - Reference DB - game developer references
AngelScript - free scripting library - BMFont - free bitmap font generator - Tower - free puzzle game

Quote: Original post by WitchLord
In my engine I use event based scripting. (of course)

At the beginning of each frame each entity has it's 'onThink' event handler called. This is where the entity decides what it wants to do, for example start an animation, shoot at the player, etc.

Then the engine updates the game world, where all the movements is performed, collisions are detected, and so on. If any collisions happend, the entities' 'onCollide' event handler is called. This is where the entity updates the health status, sets 'angry' state, etc.

If any script function times out, then it is misbehaving and I kill the script.

All the entity AIs are implemented as script classes, so the scripts can have their own properties and methods, without being limited to whatever the game engine is providing. The script class is then linked to the actual game entity that the engine knows of. The properties of the script class can be easily serialized for saving and loading.

There are also other event handlers, for example 'onTimer' where the script can request a timer event, to perform more complex animations without having to execute 'onThink' each frame. There is also event handlers that can be set up to be called when a situation occurs, e.g. player comes within x meters, etc.


However, my game engine is still very raw. The script library probably has more code than the entire game engine so far. I'm mostly building the game engine to get some real world experience out of the scripting library, instead of just artificial samples.


Interesting, I do something almost identical to your onThink and onCollide routine.
I'm interested on how you linked your custom Script AI classes to your actual C++ entities though?
-Jawshttp://uploading.com/files/eff2c24d/TGEpre.zip/
Advertisement
Here's a bit of code to illustrate it:

// C++class CGameObject{  // not reference counted  asIScriptObject *scriptController;  CGameObjectLink *link;};// C++class CGameObjectLink{  // reference counted  CGameObject *object;}// AngelScriptclass CController : IEntityController{  CController(CEntity @entity);  CEntity @entity;}


The CGameObject is where the game engine implements the logic. This class is not reference counted. Instead it has a weak link as a thin wrapper, CGameObjectLink. Anywhere where something references the game object the pointer is actually to the link, instead of the real object. Note, that I don't do this just for the scripting, it's done everywhere as it makes it much easier for the game engine to control the memory management. A game object for example can easily be destroyed without having to find all references to the object.

The CGameObjectLink is registered with the script engine as the CEntity type.

The IEntityController is a script interface registered with the scripting engine to identify the script controllers, but it doesn't have any methods so the scripts are not forced to implement any specific method. The onThink and onCollide methods are only implemented if desired.

Each script controller is implemented in its own script module. The object has to implement the IEntityController, and a constructor that takes the CEntity handle.

I use factories to create the game objects. The factories load a small text file where the game object's attributes are defined, such as the script controller, the physics attributes, the 3d model, etc.



AngelCode.com - game development and more - Reference DB - game developer references
AngelScript - free scripting library - BMFont - free bitmap font generator - Tower - free puzzle game

Quote: Original post by WitchLord
I use factories to create the game objects. The factories load a small text file where the game object's attributes are defined, such as the script controller, the physics attributes, the 3d model, etc.


Why not keep the solution uniform and use the scripts themselves to set the properties of the GameObjects (which in my solutions are called Actors).

For example, here is a sample object from a demo I made of my framework

class Bullet : object, IUpdatable // this is really just a crate that when created, gets flung in the look direction of the camera object that is passed to it.{	Bullet(Camera@ cam)	{		@bul = Actor(this,"models/crate/woodcrate.scene.xml");                // Actor expects an object of type object		                // prior to this, a rigid body resource was created that was hashed to the name "Crate"		bullet_rb = bul.AddRigidBody("Crate",1);                		bul.SetPos(cam.X,cam.Y,cam.Z);		bul.ApplyLinearImpulse(bullet_rb,-(sin(d2r(cam.rY))*cos(-d2r(cam.rX)))*50,-(sin(-d2r(cam.rX)))*50,-(cos(d2r(cam.rY))*cos(-d2r(cam.rX)))*50);		AddChild(bul); // add the Actor to the stage, deprecated / useless		AddUpdatable(this); // add the script object to the update pipeline, expects instance of IUpdatable	}	~Bullet()	{		Println("bullet deleted");		RemoveChild(bul); // deprecated / useless	}		void OnUpdate(float time)	{		bul.Syncronize(time); //Since this object is physically simulated, we have to call this method to set the actors internal scenegraph node to be synchronized with the internal rigid body. 	}		private Actor@ bul;     // our actor	private uint bullet_rb; // rigid body ID}


Hell, using this method of breaking update logic up, I'm even considering removing the constructors that take script objects for the Actor type, leaving the actors completely decoupled from the script engine. I'm also looking at the implementation again and wondering to myself "What purpose is AddChild and RemoveChild serving again?". Answer, none really. It also provides a convenient built-in content management solution if you were to compile game object scripts into bytecode.

[Edited by - _orm_ on November 21, 2010 1:56:37 PM]
Most things in the game world doesn't need scripting, and I don't want to have to write a script just to set a few properties.

Here's an example script for a grenade:

#include "common.as"class CGrenade : IEntityController{  CEntity @entity;  float lifeTime;  CGrenade(CEntity @_entity)  {    @entity = @_entity;     game.SpawnParticleEffect("gas_trail", vector3(0,0,0), entity);    lifeTime = 0;  }  void onThink()  {    lifeTime += game.GetFrameDeltaTime();    if( lifeTime > 2 )    {      game.SpawnEntity('explosion', entity.GetPosition(), vector3(0,0,0));      entity.Kill();    }  }  void onCollide(const CEntity @object, const vector3 &in position, const vector3 &in normal, float appliedImpulse)  {    if( object is null )      game.SpawnParticleEffect('red_marker', position, null);    else      game.SpawnParticleEffect('yellow_marker', position, null);  }}


The game object file used by the factory looks like this:

mesh=grenade.xmaterial=box.matcollisionShape=sphere,0.05mass=0.5script=grenade.as


Observe that my game engine is far from complete. There is likely a lot that will change before I complete it, but so far I'm pretty happy with how the scripting works.

AngelCode.com - game development and more - Reference DB - game developer references
AngelScript - free scripting library - BMFont - free bitmap font generator - Tower - free puzzle game

Ah, well, it makes sense in that regard. I personally just like minimizing the number of times I need to reinvent the wheel, and wrapping actors seems like the best way to do that in my case. I also have a set of methods that directly expose the framework's rendering interface, Horde3D, in case the Actor class is overkill for a certain application. For example, we don't need an actor to represent a skybox, which is currently the case. We could just make a scene node and spare us the overhead of all the other stuff the Actor provides.

This topic is closed to new replies.

Advertisement