Planet
navi homePPSaboutscreenshotsdownloaddevelopmentforum

Changes between Initial Version and Version 1 of ~archive/ScriptingHowTo


Ignore:
Timestamp:
Nov 27, 2007, 10:52:27 PM (17 years ago)
Author:
landauf
Comment:

Legend:

Unmodified
Added
Removed
Modified
  • ~archive/ScriptingHowTo

    v1 v1  
     1= Using the scripting engine of Orxonox =
     2
     3== 1. Introduction ==
     4We 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]]
     5The 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]]
     6
     7=== 1.1. The script ===
     8Every *.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]]
     9Every '''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]]
     10
     11=== 1.2. Scripttrigger ===
     12Scripttriggers 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]]
     13
     14=== 1.3. Scriptmanager ===
     15The 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]]
     16[[br]]
     17
     18== 2. Conventions ==
     19
     20I tried to make the interface as simple as possible, however there are some rules you have to follow:[[br]][[br]]
     21
     22=== 2.1. From C++ to lua ===
     23'''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]]
     24
     25=== 2.2. From lua to C++ ===
     26Every '''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>")[[br]]
     27Every '''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]]
     28
     29NOTE: You have to call addObject and registerClass from global scope in the script, so they are executed on loadtime.  [[br]]
     30[[br]]
     31
     32== 3. Lua extensions ==
     33
     34The following classes and objects are available besides the lua standard functions:[[br]]
     35
     36=== 3.1. thisscript ===
     37You can access the wrapperobject of your lua script with "thisscript". The following methods are aviable:[[br]]
     38  '''addObject("Classname", "Name of an instance"):''' You have to call this method for every object that you want to use in the script.[[br]]
     39  '''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())
     40  '''registerClass("Classname"):''' If you want to instantiate an object in the script, you have to call this function.
     41
     42=== 3.2. !ScriptTrigger ===
     43Every 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]]
     44
     45 '''setAbsCoor(x,y,z):''' Set the absolute coordinate of the trigger.[[br]]
     46 '''getAbsCoorX():''' Get the x coordinate of the trigger.[[br]]
     47 '''getAbsCoorY():''' Get the y coordinate of the trigger.[[br]]
     48 '''getAbsCoorZ():''' Get the z coordinate of the trigger.[[br]]
     49 '''setName("Triggername"):''' Sets the name of the trigger.[[br]] 
     50 '''setScript("Scriptname"):''' Sets the script to be called by the trigger.[[br]]
     51 '''setFunction("Functionname"):''' Sets the function in the script to be called by the trigger.[[br]]
     52 '''setDebugDraw(bool):''' true: the trigger is drawn as a red cube.[[br]][[br]]
     53
     54==== 3.2.1 !SpaceTrigger ====
     55The !SpaceTrigger triggers a Script whenever a specified !WorldEntity is in range. It has the following methods (adidionally to the ones above):
     56 '''setRadius(float):''' Sets the radius of the trigger '''Default:'''10.0[[br]]
     57 '''setTarget("Targetname"):''' Sets the target the trigger reacts to.[[br]]
     58 '''setTriggerParent("Parentname"):''' Sets the parent of the trigger itself.[[br]]
     59 '''setInvert(bool):'''true: the trigger gets triggered if the distance between the target and the trigger is bigger than radius '''Default:'''false[[br]]
     60
     61==== 3.2.2. !TickTrigger ====
     62
     63This trigger triggers the Script every tick. So there are no aditional methods available. [[br]]
     64
     65==== 3.2.3. !TimeTrigger ====
     66
     67This trigger triggers the Script after a certain time.
     68
     69 '''start():''' Sets off the timer. [[br]]
     70 '''stop():''' Stops the timer.[[br]]
     71 '''reset():''' Resets the timer.[[br]]
     72 '''setDelay(float):''' Sets the amount of time after which the script will be called.[[br]]
     73
     74The 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]]
     75
     76==== 3.2.4 !ActionTrigger ====
     77The !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 :
     78 '''setRadius(float):''' Sets the radius of the trigger '''Default:'''10.0[[br]]
     79 '''setTarget("Targetname"):''' Sets the target the trigger reacts to.[[br]]
     80 '''setTriggerParent("Parentname"):''' Sets the parent of the trigger itself.[[br]]
     81 '''setInvert(bool):'''true: the trigger gets triggered if the distance between the target and the trigger is bigger than radius '''Default:'''false[[br]]
     82
     83[[br]]
     84
     85== 4. Handling Objects ==
     86
     87=== 4.1.Creating Objects ===
     88Example: [[br]]
     89{{{
     90trigger = SpaceTrigger() --create new trigger
     91}}}
     92In general:[[br]]
     93{{{
     94variable = ClassName()
     95}}}
     96=== 4.2. Deleting Objects ===
     97There 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.
     98
     99=== 4.3. Calling Methods ===
     100Suppose we have a !ScriptTrigger named "trigger" then we could do the following:[[br]]
     101{{{
     102trigger:setAbsCoor(1,2,3) --sets trigger to the coordinates "1,2,3"
     103}}}
     104In general:[[br]]
     105{{{
     106name:methodName(argument1,argument2,...)
     107}}}
     108[[br]]
     109
     110== 5. Example ==
     111
     112Here's a very basic script to show you the very basic stuff:
     113{{{
     114-- File: example.lua
     115
     116trigger = TickTrigger()                       -- Create a trigger which calls the script every tick.
     117trigger:setScript("example.lua")              -- Tell the trigger which script to call
     118trigger:setFunction("tick")                   -- Tell the trigger which scriptfunction to call
     119 
     120-- Get objects from orxonox
     121thisscript:addObject("FPSPlayer", "Player")
     122thisscript:addObject("SpaceShip", "UberShip")
     123
     124-- Global variables
     125testVariable = 1
     126
     127-- THE tick function
     128
     129function tick(timestep) -- Function with one parameter: the timestep
     130-- Do something cool...
     131
     132
     133return false -- We want the scriptrigger to call the script again
     134end
     135}}}
     136[[br]]
     137
     138== 6. How to add a script to Orxonox ==
     139
     140To make a script work with Orxonox there are only a few steps to take:
     141
     1421. [[br]]
     143a) Create a folder inside the scripts folder of the data branch named like the level your script is going to be used in. [[br]]
     144b) Create a script and copy it into the folder created in step 1.a). [[br]]
     145You 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"
     146
     1472. Make Orxonox load the script when a world is loaded by creating a tag with the following syntax in the .oxw file:[[br]][[br]]
     148{{{
     149<ScriptManager>
     150<Scripts>
     151...
     152<Script>
     153<file>worldname/filename.lua</file>
     154</Script>
     155...
     156</Scripts>
     157...
     158</ScriptManager>
     159}}}
     160NOTE: Maybe the Scriptmanager tag already exists, in which case you just have to add the Script tag.[[br]]
     161
     1623. Make Orxonox execute the script:
     163You 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]]
     164NOTE: 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''.
     165[[br]]
     166
     167== 7. How to make an Orxonox object (C++) scriptable ==
     168
     169You want to use a class or a method that is not scripable yet? To fix this is very easy.[[br]]
     170All you have to do is to create a "scriptable class" in the *.cc file:[[br]]
     171{{{
     172#!cpp
     173#include "script_class.h"
     174CREATE_SCRIPTABLE_CLASS(ClassName,
     175      addMethod("methName", ExecutorLua{Number of arguments}ret<ClassName,lua_State*,rettype,arg1type,arg2type,...>(&ClassName::methName))
     176      ->addMethod("anotherMethodName", ExecutorLua{Number of arguments}<ClassName,lua_State*,arg1type,arg2type,...>(&ClassName::anotherMethodName))
     177      ->addMethod(...)
     178      ...   
     179 );
     180}}}
     181NOTE: Only the first method gets added with addMethod(...) for the other methods use ->addMethod(...)[[br]][[br]]
     182So if you want to add the method [[br]]
     183{{{
     184void addObject(const std::string& className, const std::string& objectName)
     185}}}
     186of the class "Script":[[br]]
     187{{{
     188addMethod("addObject", Executor2<Script, lua_State*,const std::string&, const std::string& >(&Script::addObject))
     189}}}
     190
     191To add a method with no return value, use
     192{{{
     193Executor{Argcount}<ClassName, lua_State*, arg1type, arg2type, ...>.
     194}}}
     195(Replace "{Argcount}" with the number of arguments). [[br]][[br]]
     196To add a method with a return value use
     197{{{
     198Executor{Argcount}ret<ClassName, luaState*, rettype, arg1type, arg2type, ...>.
     199}}}
     200Note that the  first type doesn't stand for the first argument but for the type of the return value.[[br]]
     201
     202'''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]]
     203{{{
     204->addMethod("getAbsCoorX", Executor0ret<PNode, lua_State*, float>(&PNode::getAbsCoorX))
     205}}}
     206[[br]]
     207
     208== FAQ ==
     209'''I'm getting a "attempt to index global `some name' (a nil value)" error. What's wrong?''' [[br]]
     210This 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]]
     211
     212'''I want to make a method that takes a  std::string as a parameter. Why doesn't it work?'''[[br]]
     213The !ExecutorLua can only handle methods that take a const std::string& as parameter.[[br]][[br]]
     214
     215'''I can't make a method scriptable although I did everything as described?'''[[br]]
     216There is not an !ExecutorLua for every argument count.[[br]][[br]]
     217
     218'''Haven't found your question here?''' send me an email: snellen {at} ee"."ethz.ch