= Using the scripting engine of Orxonox = [[ArchivePage]] == 1. Introduction == We use [http://www.lua.org/ 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.[[br]] 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.[[br]] === 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. [[br]] 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.[[br]] === 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.[[br]] === 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.[[br]] [[br]] == 2. Conventions == I tried to make the interface as simple as possible, however there are some rules you have to follow:[[br]][[br]] === 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.[[br]][[br]] === 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>")[[br]] 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.[[br]] NOTE: You have to call addObject and registerClass from global scope in the script, so they are executed on loadtime. [[br]] [[br]] == 3. Lua extensions == The following classes and objects are available besides the lua standard functions:[[br]] === 3.1. thisscript === You can access the wrapperobject of your lua script with "thisscript". The following methods are aviable:[[br]] '''addObject("Classname", "Name of an instance"):''' You have to call this method for every object that you want to use in the script.[[br]] '''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:[[br]] '''setAbsCoor(x,y,z):''' Set the absolute coordinate of the trigger.[[br]] '''getAbsCoorX():''' Get the x coordinate of the trigger.[[br]] '''getAbsCoorY():''' Get the y coordinate of the trigger.[[br]] '''getAbsCoorZ():''' Get the z coordinate of the trigger.[[br]] '''setName("Triggername"):''' Sets the name of the trigger.[[br]] '''setScript("Scriptname"):''' Sets the script to be called by the trigger.[[br]] '''setFunction("Functionname"):''' Sets the function in the script to be called by the trigger.[[br]] '''setDebugDraw(bool):''' true: the trigger is drawn as a red cube.[[br]][[br]] ==== 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[[br]] '''setTarget("Targetname"):''' Sets the target the trigger reacts to.[[br]] '''setTriggerParent("Parentname"):''' Sets the parent of the trigger itself.[[br]] '''setInvert(bool):'''true: the trigger gets triggered if the distance between the target and the trigger is bigger than radius '''Default:'''false[[br]] ==== 3.2.2. !TickTrigger ==== This trigger triggers the Script every tick. So there are no aditional methods available. [[br]] ==== 3.2.3. !TimeTrigger ==== This trigger triggers the Script after a certain time. '''start():''' Sets off the timer. [[br]] '''stop():''' Stops the timer.[[br]] '''reset():''' Resets the timer.[[br]] '''setDelay(float):''' Sets the amount of time after which the script will be called.[[br]] 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() [[br]] ==== 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[[br]] '''setTarget("Targetname"):''' Sets the target the trigger reacts to.[[br]] '''setTriggerParent("Parentname"):''' Sets the parent of the trigger itself.[[br]] '''setInvert(bool):'''true: the trigger gets triggered if the distance between the target and the trigger is bigger than radius '''Default:'''false[[br]] [[br]] == 4. Handling Objects == === 4.1.Creating Objects === Example: [[br]] {{{ trigger = SpaceTrigger() --create new trigger }}} In general:[[br]] {{{ 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:[[br]] {{{ trigger:setAbsCoor(1,2,3) --sets trigger to the coordinates "1,2,3" }}} In general:[[br]] {{{ name:methodName(argument1,argument2,...) }}} [[br]] == 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 }}} [[br]] == 6. How to add a script to Orxonox == To make a script work with Orxonox there are only a few steps to take: 1. [[br]] a) Create a folder inside the scripts folder of the data branch named like the level your script is going to be used in. [[br]] b) Create a script and copy it into the folder created in step 1.a). [[br]] 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" 2. Make Orxonox load the script when a world is loaded by creating a tag with the following syntax in the .oxw file:[[br]][[br]] {{{ ... ... ... }}} NOTE: Maybe the Scriptmanager tag already exists, in which case you just have to add the Script tag.[[br]] 3. 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") [[br]] 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''. [[br]] == 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.[[br]] All you have to do is to create a "scriptable class" in the *.cc file:[[br]] {{{ #!cpp #include "script_class.h" CREATE_SCRIPTABLE_CLASS(ClassName, addMethod("methName", ExecutorLua{Number of arguments}ret(&ClassName::methName)) ->addMethod("anotherMethodName", ExecutorLua{Number of arguments}(&ClassName::anotherMethodName)) ->addMethod(...) ... ); }}} NOTE: Only the first method gets added with addMethod(...) for the other methods use ->addMethod(...)[[br]][[br]] So if you want to add the method [[br]] {{{ void addObject(const std::string& className, const std::string& objectName) }}} of the class "Script":[[br]] {{{ addMethod("addObject", Executor2(&Script::addObject)) }}} To add a method with no return value, use {{{ Executor{Argcount}. }}} (Replace "{Argcount}" with the number of arguments). [[br]][[br]] To add a method with a return value use {{{ Executor{Argcount}ret. }}} Note that the first type doesn't stand for the first argument but for the type of the return value.[[br]] '''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:[[br]] {{{ ->addMethod("getAbsCoorX", Executor0ret(&PNode::getAbsCoorX)) }}} [[br]] == FAQ == '''I'm getting a "attempt to index global `some name' (a nil value)" error. What's wrong?''' [[br]] 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")[[br]][[br]] '''I want to make a method that takes a std::string as a parameter. Why doesn't it work?'''[[br]] The !ExecutorLua can only handle methods that take a const std::string& as parameter.[[br]][[br]] '''I can't make a method scriptable although I did everything as described?'''[[br]] There is not an !ExecutorLua for every argument count.[[br]][[br]] '''Haven't found your question here?''' send me an email: snellen {at} ee"."ethz.ch