Planet
navi homeaboutscreenshotsdownloaddevelopmentforum

Using the scripting engine of Orxonox

ArchivePage?

1. Introduction

We use lua as scripting language in orxonox. I will not explain the synthax of lua here so if you are not familiar with the language I suggest you check out their website (We use lua 5.0 in orxonox). Furthermore I assume that you already know the basics of the Orxonox framework. Basically the orxonox scriptengine allows you access orxonox classes from within a lua script. That means you can create new objects or call methods of already existing objects.
The scripting engine of Orxonox consist of three parts: the ScriptManager, the ScriptTriggers and the scripts. If you just want to use the scriptengine, you can skip 1.3 as you don't have to know about the scriptmanager to make your scripts work.

1.1. The script

Every *.lua file is wrapped in a script. A script in orxonox is an object of the class "Script" and this object contains all the data that the script needs. The script is the link between C++ and lua. Whenever a lua function is called from C++, it is the script that actually calls the function. On creation the script loads the file and then parses the loaded file. While parsing everything in the global scope of the script is executed. (If you write io.write("test") in the global scope, then "test" is printed when the file gets loaded). After parsing the script registers the standard classes "SpaceTrigger", "TimeTrigger" and "TickTrigger", so you don't have to register it if you want to use ScriptTriggers in the script.
Every Orxonox object has to be scriptable to be used in a script, that is a scriptable_class has to be created for the Orxonox class. Please read the Section "How to make an Orxonox object (C++) scriptable" for further information.

1.2. Scripttrigger

Scripttriggers are responsible for calling a function of a script on runtime. When the ScriptTrigger is triggered it executes the specified function of the Luascript. There are different types of scripttriggers: The SpaceTrigger calls a script whenever a specified WorldEntity is in range. The TickTrigger calls the Script in every tick. The TimeTrigger calls its Script when a certain amount of time has passed. The ActionTrigger triggers the script if the player hits the actionkey within its range.

1.3. Scriptmanager

The Scriptmanager is responsible for loading all scripts. Every world has it's own ScriptManager. It is created after creating all objects (Player, NPCs, etc.) of the world (The reason for this is that throughout loadtime the scripts have to store a pointer to every object they need). Then it goes through its xml node and loads every script; this means it creates a script object containing the lua script.

2. Conventions

I tried to make the interface as simple as possible, however there are some rules you have to follow:

2.1. From C++ to lua

Script functions to be called by a ScriptTrigger have to take one parameter: The time since the last frame in seconds (similar to tick in the Orxonox framework) They must return a boolean value (true/false): true means "the function has finished and doesn't have to be called again", that is the Scripttrigger won't call the function again even if the target is in range. false means that the function hasn't finished and needs to be called again.

2.2. From lua to C++

Every object to be used in the script has to be added to the script by calling thisscript:addObject("class","name") in the global scope of the script. (The "name" of an object is loaded from its xml-node: "<name>…<\name>")
Every class you want to instantiate must be registered to the script by calling thisscript:registerClass("class"). If you have already called the addObject method with an object of type "class" you don't have to call this function again.

NOTE: You have to call addObject and registerClass from global scope in the script, so they are executed on loadtime.

3. Lua extensions

The following classes and objects are available besides the lua standard functions:

3.1. thisscript

You can access the wrapperobject of your lua script with "thisscript". The following methods are aviable:

addObject("Classname", "Name of an instance"): You have to call this method for every object that you want to use in the script.
addObjectAsName("Classname", "Name of an instance", "Name of the object in the script"): Same as addObject, but you can specify the name that the object goes by in the script. E.g. if you call thisscript:addObject("FPSPlayer", "Player", "mySuperPlayer") you can access the orxonox object named Player through mySuperPlayer. (E.g. mySuperPlayer:getAbsCoor()) registerClass("Classname"): If you want to instantiate an object in the script, you have to call this function.

3.2. ScriptTrigger

Every script has to be called by a script trigger. There are three different kinds of triggers: the SpaceTrigger, the TickTrigger and the TimeTrigger. All these triggers have the following methods in common:

setAbsCoor(x,y,z): Set the absolute coordinate of the trigger.
getAbsCoorX(): Get the x coordinate of the trigger.
getAbsCoorY(): Get the y coordinate of the trigger.
getAbsCoorZ(): Get the z coordinate of the trigger.
setName("Triggername"): Sets the name of the trigger.
setScript("Scriptname"): Sets the script to be called by the trigger.
setFunction("Functionname"): Sets the function in the script to be called by the trigger.
setDebugDraw(bool): true: the trigger is drawn as a red cube.

3.2.1 SpaceTrigger

The SpaceTrigger triggers a Script whenever a specified WorldEntity is in range. It has the following methods (adidionally to the ones above):

setRadius(float): Sets the radius of the trigger Default:10.0
setTarget("Targetname"): Sets the target the trigger reacts to.
setTriggerParent("Parentname"): Sets the parent of the trigger itself.
setInvert(bool):true: the trigger gets triggered if the distance between the target and the trigger is bigger than radius Default:false

3.2.2. TickTrigger

This trigger triggers the Script every tick. So there are no aditional methods available.

3.2.3. TimeTrigger

This trigger triggers the Script after a certain time.

start(): Sets off the timer.
stop(): Stops the timer.
reset(): Resets the timer.
setDelay(float): Sets the amount of time after which the script will be called.

The stop() method only interrupts the timer. So if you det the delay to 5 seconds and call stop after 2 seconds, then the Script will be executed 3 seconds after you call the start() method for the second time. If you want to stop the timer you have to call stop() followed by reset()

3.2.4 ActionTrigger

The ActionTrigger triggers a Script whenever a specified WorldEntity is in range AND the the player hits the action key. It has the following aditional methods :

setRadius(float): Sets the radius of the trigger Default:10.0
setTarget("Targetname"): Sets the target the trigger reacts to.
setTriggerParent("Parentname"): Sets the parent of the trigger itself.
setInvert(bool):true: the trigger gets triggered if the distance between the target and the trigger is bigger than radius Default:false


4. Handling Objects

4.1.Creating Objects

Example:

trigger = SpaceTrigger() --create new trigger

In general:

variable = ClassName()

4.2. Deleting Objects

There is no way to delete an object manually. Objects created in a script are deleted automatically by the lua garbage collector when the script is deleted.

4.3. Calling Methods

Suppose we have a ScriptTrigger named "trigger" then we could do the following:

trigger:setAbsCoor(1,2,3) --sets trigger to the coordinates "1,2,3"

In general:

name:methodName(argument1,argument2,...)


5. Example

Here's a very basic script to show you the very basic stuff:

-- File: example.lua

trigger = TickTrigger()                       -- Create a trigger which calls the script every tick.
trigger:setScript("example.lua")              -- Tell the trigger which script to call
trigger:setFunction("tick")                   -- Tell the trigger which scriptfunction to call
 
-- Get objects from orxonox
thisscript:addObject("FPSPlayer", "Player")
thisscript:addObject("SpaceShip", "UberShip")

-- Global variables
testVariable = 1

-- THE tick function

function tick(timestep) -- Function with one parameter: the timestep
-- Do something cool...


return false -- We want the scriptrigger to call the script again
end


6. How to add a script to Orxonox

To make a script work with Orxonox there are only a few steps to take:


a) Create a folder inside the scripts folder of the data branch named like the level your script is going to be used in.
b) Create a script and copy it into the folder created in step 1.a).
You can look up the syntax of lua at www.lua.org (we use version 5.0 in orxonox). For Orxonox framework specific syntax please refer to the section "3. Lua extensions" and "4. Handling Objects"

  1. Make Orxonox load the script when a world is loaded by creating a tag with the following syntax in the .oxw file:

    <ScriptManager>
    <Scripts>
    ...
    <Script>
    <file>worldname/filename.lua</file>
    </Script>
    ...
    </Scripts>
    ...
    </ScriptManager>
    

NOTE: Maybe the Scriptmanager tag already exists, in which case you just have to add the Script tag.

  1. Make Orxonox execute the script:

You can generate a ScriptTrigger form within the script. You can look up an example in chapter 5 (Also see "3. Lua extensions" and "4. Handling Objects")
NOTE: You don't have to register SpaceTrigger, TimeTrigger and TickTrigger as these classes are registered automatically with every script. At least you have to set a file and function.

7. How to make an Orxonox object (C++) scriptable

You want to use a class or a method that is not scripable yet? To fix this is very easy.
All you have to do is to create a "scriptable class" in the *.cc file:

#include "script_class.h"
CREATE_SCRIPTABLE_CLASS(ClassName,
      addMethod("methName", ExecutorLua{Number of arguments}ret<ClassName,lua_State*,rettype,arg1type,arg2type,...>(&ClassName::methName))
      ->addMethod("anotherMethodName", ExecutorLua{Number of arguments}<ClassName,lua_State*,arg1type,arg2type,...>(&ClassName::anotherMethodName))
      ->addMethod(...)
      ...   
 );

NOTE: Only the first method gets added with addMethod(…) for the other methods use →addMethod(…)

So if you want to add the method

void addObject(const std::string& className, const std::string& objectName)

of the class "Script":

addMethod("addObject", Executor2<Script, lua_State*,const std::string&, const std::string& >(&Script::addObject))

To add a method with no return value, use

Executor{Argcount}<ClassName, lua_State*, arg1type, arg2type, ...>. 

(Replace "{Argcount}" with the number of arguments).

To add a method with a return value use

Executor{Argcount}ret<ClassName, luaState*, rettype, arg1type, arg2type, ...>. 

Note that the first type doesn't stand for the first argument but for the type of the return value.

IMPORTANT: Inheritance doesn't work with the scripts. For example if you want to call getAbsCoorX() (a method that is inherited from pnode) with an object of type SpaceShip you have to add the method in space_ship.cc:

->addMethod("getAbsCoorX", Executor0ret<PNode, lua_State*, float>(&PNode::getAbsCoorX))


FAQ

I'm getting a "attempt to index global `some name' (a nil value)" error. What's wrong?
This means that lua can't find anything (object, method) with that name. Check if you have added the object or registered the class (typo?). If that doesn't help you should check if the object/class is scriptable, e.g. that it has a scriptable class. (If it hasn't, refer to "How to make an Orxonox object (C++) scriptable")

I want to make a method that takes a std::string as a parameter. Why doesn't it work?
The ExecutorLua can only handle methods that take a const std::string& as parameter.

I can't make a method scriptable although I did everything as described?
There is not an ExecutorLua for every argument count.

Haven't found your question here? send me an email: snellen {at} ee"."ethz.ch

Last modified 9 years ago Last modified on Nov 28, 2007, 12:45:52 AM