User Tools

Site Tools


hpl3:engine:script

Differences

This shows you the differences between two versions of the page.

Link to this comparison view

Both sides previous revision Previous revision
hpl3:engine:script [2014/02/18 07:24]
thomas [Comments]
hpl3:engine:script [2020/07/01 07:07] (current)
thomas
Line 8: Line 8:
  
 ==== Prefixes ==== ==== Prefixes ====
-System Hungarian ​ notation is to be used for all variable names. These are the notations used: + 
-||**Prefix**|| **Description**|| **Example** || +System Hungarian notation is to be used for all variable names. These are the notations used: 
-||c|| a class || class cMyClass {}|| + 
-||i|| an interface || interface iMyInterface {}|| +|**Prefix** ||**Description** ||**Example** || 
-||f|| a float or double value.|| float fX;|| +|c ||a class ||class cMyClass {} || 
-||l|| any integer (char, int, uint, etc)|| int lX;|| +|i ||an interface ||interface iMyInterface {} || 
-||p|| an angel script handle || iPhysicsBody@ pBody;|| +|f ||a float or double value. ||float fX; || 
-||s|| a string || cString sWord;|| +|l ||any integer (char, int, uint, etc) ||int lX; || 
-||v|| a vector or array || int[] vNumbers; cVector3f vPoint|| +|p ||an angel script handle ​or C++ pointer. ​||iPhysicsBody@ ​pBody; iPhysicsBody *pBody; || 
-||mtx|| a matrix|| cMatrixf mtxTransform;​|| +|s ||a string ||cString sWord; || 
-||q|| a quaternion|| cQuaternion qRotation;​|| +|v ||a vector or array ||int[] vNumbers; cVector3f vPoint || 
-||a|| an argument|| void Func(int alX) { || +|mtx ||a matrix ||cMatrixf mtxTransform;​ || 
-||m|| a member variable || class cMyClass {\\ int mlMember;\\ } || +|q ||a quaternion ||cQuaternion qRotation; || 
-||g|| a global variable, defined outside of a class or function.|| int glMyGlobal; \\ class cMyClass {} || +|a ||an argument ||void Func(int alX) { || 
-||id|| this is for tID type.|| tID m_idEntity ||+|m ||a member variable ||class cMyClass { \\ int mlMember; \\ } || 
 +|g ||a global variable, defined outside of a class or function. ||int glMyGlobal; \\ class cMyClass {} || 
 +|id ||this is for tID type. ||tID m_idEntity||
  
 a variable name always starts with a lower case letter, so anything of type not specified must start with a lower case word. Example: a variable name always starts with a lower case letter, so anything of type not specified must start with a lower case word. Example:
-<code c++>​cMyOwnClass myClass;</​code>​+ 
 +<code c++> 
 +cMyOwnClass myClass; 
 +</​code>​
  
 If combining two prefixes, and one consist of more than one letter, then have an underscore between them. Example: If combining two prefixes, and one consist of more than one letter, then have an underscore between them. Example:
-<code c++>​cMatrix m_mtxMemberMatrix;</​code>​+ 
 +<code c++> 
 +cMatrix m_mtxMemberMatrix;​ 
 +</​code>​
  
 ==== Comments ==== ==== Comments ====
  
 +Always comment the code you write; when scripting, it is always certain somebody else will work on it. Comment to make other people understand what it means and to make the code look nicer.
  
-Always comment the code you write; when scripting, it is always certain somebody else will work on it. Comment to make other people understand what it means and to make  the code look nicer. +**Function/​Class comment** \\
- +
- +
-**Function/​Class comment** \\ +
 Use comment blocks for the main comment for each class/​function,​ this so that the parser can show the correct info for code compleition/​hints. Only for helpers and similar type of base functions where the information is needed. Use comment blocks for the main comment for each class/​function,​ this so that the parser can show the correct info for code compleition/​hints. Only for helpers and similar type of base functions where the information is needed.
- 
  
 <code c++> <code c++>
-/**   ​+/**
  * Change the light radius to the specified value.  * Change the light radius to the specified value.
- ​* ​+ *
  * @param tString asLight, name of the light to change radius of.  * @param tString asLight, name of the light to change radius of.
  * @param float afRadius, the radius in meters to set for the light.  * @param float afRadius, the radius in meters to set for the light.
Line 53: Line 58:
 </​code>​ </​code>​
  
- +**Section comment** \\
-**Section comment** \\ +
 When starting a new section of some kind do a comment that explains what is going on. When starting a new section of some kind do a comment that explains what is going on.
  
- +<code c++> 
-<code c++>////////////////////////////////////////////​+////////////////////////////////////////////​
 // Update the monster damage // Update the monster damage
 cMonster@ pMonster = CurrentHit();​ cMonster@ pMonster = CurrentHit();​
Line 68: Line 72:
 </​code>​ </​code>​
  
- +**Single line comment** \\
-**Single line comment** \\ +
 When commenting a single line When commenting a single line
  
- +<code c++> 
-<code c++>//​The square of the distance will be enough+//The square of the distance will be enough
 float fDistSqr = vDiff.x*vDiff.x +vDiff.y*vDiff.y;​ float fDistSqr = vDiff.x*vDiff.x +vDiff.y*vDiff.y;​
 </​code>​ </​code>​
Line 79: Line 82:
 Only do this in rare cases when it is really hard to figure what the code means. Normally, simply using section comments is enough. If you split the code into nice sections and name them well, that should be more than enough for other people to figure out what it is about. Only do this in rare cases when it is really hard to figure what the code means. Normally, simply using section comments is enough. If you split the code into nice sections and name them well, that should be more than enough for other people to figure out what it is about.
  
- +**Function/​Class separation** \\
-**Function/​Class separation** \\ +
 Use this to make it easier to see functions and classes. Can also be used to encapsulate other data. Use this to make it easier to see functions and classes. Can also be used to encapsulate other data.
  
- +<code c++> 
-<code c++>void Foo()+void Foo()
 { {
- +
 } }
- +
 //​------------------------------------------- //​-------------------------------------------
- +
 void Bar() void Bar()
 { {
- +
 } }
 </​code>​ </​code>​
Line 99: Line 101:
 ===== Containers ===== ===== Containers =====
  
-There are some containers that are not part of the angelscript language, but added as extension, these will be described here. +There are some containers that are not part of the angelscript language, but added as extension, these will be described here.
  
 ==== Array ==== ==== Array ====
 +
 Arrays are simple linear storage containers. Arrays are simple linear storage containers.
  
 You can access elements using //​varname[index]//​. You can access elements using //​varname[index]//​.
  
-**Syntax:**\\ +**Syntax:​** 
-<code c++>​array<​T>​ or T[]</​code>​+<code c++> 
 +array<​T>​ or T[] 
 +</​code>​
  
-**Methods**\\ +**Methods** 
-<code c>void insertAt(uint,​ const T&in)+<code c> 
 +void insertAt(uint,​ const T&in)
 void removeAt(uint) void removeAt(uint)
 void insertLast(const T&in) void insertLast(const T&in)
Line 131: Line 137:
 void pop_back() void pop_back()
 void pop_front() void pop_front()
-uint size() const</​code>​+uint size() const 
 +</​code>​
  
 ===== Included files ===== ===== Included files =====
Line 138: Line 145:
  
 An important thing to think about is that any enums, classes, interfaces, etc that are meant to be shared, needs to be declared with the //shared// keyword. Example: An important thing to think about is that any enums, classes, interfaces, etc that are meant to be shared, needs to be declared with the //shared// keyword. Example:
-<code c++> shared enum eMyEnum { +<code c++> 
-shard interface iMyInterface { </​code>​ + shared enum eMyEnum { 
-If this is not done, then the data types will be declared as different types in all files that include the files, which will lead to exceptions, or worse crashes.+shard interface iMyInterface { 
 +</​code>​
  
 +  If this is not done, then the data types will be declared as different types in all files that include the files, which will lead to exceptions, or worse crashes.
  
 ===== User Classes ===== ===== User Classes =====
Line 147: Line 156:
 Script user classes are C++ classes that implement the iScriptUpdateableUserClassInterface interface. Script user classes are C++ classes that implement the iScriptUpdateableUserClassInterface interface.
  
-The script for these classes must contain the code:\\ +The script for these classes must contain the code:
-<code c>​cUserClass@ mBaseObj; +
-void SetupBaseInterface(cUserClass@ aObj){@mBaseObj = aObj;​}</​code>​ +
-Where "​cUserClass"​ is the name of the implemented user class. ​+
  
-These classes can choose to implement the method:  +<code c> 
-<code c>​void ​Init()</​code>​ +cUserClass@ mBaseObj; 
-This method is called upon creation of the class object, but __not__ on reload. ​+void SetupBaseInterface(cUserClass@ aObj){@mBaseObj = aObj;} 
 +</​code>​
  
-The constructor ​of the class is called on creation __and__ on reload. However, note that the handle //​mBaseObj//​ is not initialized when the constructor is called and hence may only be used in //Init()//.+  Where "​cUserClass"​ is the name of the implemented user class.
  
 +These classes can choose to implement the method:
 +
 +<code c>
 +void Init()
 +</​code>​
 +
 +  This method is called upon creation of the class object, but __not__ on reload.
 +
 +The constructor of the class is called on creation __and__ on reload. However, note that the handle //​mBaseObj//​ is not initialized when the constructor is called and hence may only be used in //Init()//.
 ===== Script Classes ===== ===== Script Classes =====
  
Line 163: Line 179:
  
 ==== Handles ==== ==== Handles ====
- 
  
 For any script that is to be saved (which means all scripts pretty much), handles to script classes can NOT be member variables. So a script file like: For any script that is to be saved (which means all scripts pretty much), handles to script classes can NOT be member variables. So a script file like:
- 
  
 <code c++> <code c++>
Line 177: Line 191:
 } }
 </​code>​ </​code>​
- 
  
 is NOT supported. The reason for this is that when a script is destroyed any handle to these classes are destroyed too and cannot be retrieved again! is NOT supported. The reason for this is that when a script is destroyed any handle to these classes are destroyed too and cannot be retrieved again!
- 
  
 It is however okay to to pass handles as arguments: It is however okay to to pass handles as arguments:
  
- +<code c++> 
-<code c++>void MyFunc(cMyClass@ apArg) {+void MyFunc(cMyClass@ apArg) {
 ... ...
 } }
 </​code>​ </​code>​
- 
  
 or use them as local or global variables. or use them as local or global variables.
- 
  
 <code c++> <code c++>
Line 199: Line 209:
 </​code>​ </​code>​
  
- +<code c++> 
-<code c++>void MyFunc() {+void MyFunc() {
  ​cMyClass@ pLocalVar  ​cMyClass@ pLocalVar
  ...  ...
 } }
 </​code>​ </​code>​
- 
  
 Its only when they are to be saved that problems arise. Its only when they are to be saved that problems arise.
- 
  
 ==== ID Handles ==== ==== ID Handles ====
- 
  
 When working with engine types it is recommended to use ID handles instead of class pointers. When working with engine types it is recommended to use ID handles instead of class pointers.
- 
  
 <code c++> <code c++>
Line 221: Line 227:
 } }
 </​code>​ </​code>​
- 
  
 Using class handler directly like this works but it is unsafe and does not support saving. Instead the handle can be saved like this. Using class handler directly like this works but it is unsafe and does not support saving. Instead the handle can be saved like this.
- 
  
 <​code>​ <​code>​
Line 231: Line 235:
 } }
 </​code>​ </​code>​
- 
  
 This saves a unqiue identifier to the body. The ID of a object can be retrived by calling GetID() function. There are script functions for converting the ID to a class handle. This saves a unqiue identifier to the body. The ID of a object can be retrived by calling GetID() function. There are script functions for converting the ID to a class handle.
- 
  
 <​code>​ <​code>​
Line 248: Line 250:
 } }
 </​code>​ </​code>​
- 
  
 This retrives the class handle from the engine so that it can be used. Retriving the class handle from the ID is very fast. This retrives the class handle from the engine so that it can be used. Retriving the class handle from the ID is very fast.
- 
  
 Using a ID instead of a class handle has two big advantages. Unlike class handles the ID can be saved. When loading the save file the ID will still work and retrive the same object. Using a ID instead of a class handle has two big advantages. Unlike class handles the ID can be saved. When loading the save file the ID will still work and retrive the same object.
- 
  
 If the object that the ID handle points to gets deleted anywhere else in the code the ID will still be safe. When trying to retrive a deleted object the function will return null. Accessing null pointers will never cause the game to crash. When accessing a class handle that has been deleted that leads to accessing deallocated memory and the game will crash. If the object that the ID handle points to gets deleted anywhere else in the code the ID will still be safe. When trying to retrive a deleted object the function will return null. Accessing null pointers will never cause the game to crash. When accessing a class handle that has been deleted that leads to accessing deallocated memory and the game will crash.
Line 261: Line 260:
  
 ==== Calling script functions from scripts ==== ==== Calling script functions from scripts ====
-If you want to run a script function/​method from script using the Prepare, Execute, SetArg, etc you need to make sure that string arguments are NOT refences from C++ code. For example ​ this is NOT allowed: 
  
-<code c++>​if(mObj.ScriptPrepare("​void DoSomething(string &in asStr)"​) ){+If you want to run a script function/​method from script using the Prepare, Execute, SetArg, etc you need to make sure that string arguments are NOT refences from C++ code. For example this is NOT allowed: 
 + 
 +<code c++> 
 +if(mObj.ScriptPrepare("​void DoSomething(string &in asStr)"​) ){
 SetArgString(0,​ mObj.GetName() ); SetArgString(0,​ mObj.GetName() );
 ScriptExecture();​ ScriptExecture();​
-}</​code>​+} 
 +</​code>​
  
 Where mObj is of class cMyObj implemented in C++: Where mObj is of class cMyObj implemented in C++:
-<code c++>​cMyObj {+ 
 +<code c++> 
 +cMyObj {
  ...  ...
  ​tString&​ GetName();  ​tString&​ GetName();
-}</​code>​+} 
 +</​code>​
  
 What is wrong here is that the argument is "​string &​in",​ an in reference. The reason why this is bad is because when the string sent to the callback function is a reference and the string returned from C++ is also this. What happens when a reference is returned from script is that the Script makes a copy of the class and then provides a reference to this. In the example above, the variable returned from GetName() only has a scope lasting the function call. So when we arrive at ScriptExecute,​ the reference is gone. What is wrong here is that the argument is "​string &​in",​ an in reference. The reason why this is bad is because when the string sent to the callback function is a reference and the string returned from C++ is also this. What happens when a reference is returned from script is that the Script makes a copy of the class and then provides a reference to this. In the example above, the variable returned from GetName() only has a scope lasting the function call. So when we arrive at ScriptExecute,​ the reference is gone.
Line 278: Line 283:
 To fix this you can do one of two things. One is to make sure that the argument for the called function is not a reference, like: To fix this you can do one of two things. One is to make sure that the argument for the called function is not a reference, like:
  
-<​code>​if(mObj.ScriptPrepare("​void DoSomething(string in asStr)"​) ){</​code>​+<​code>​ 
 +if(mObj.ScriptPrepare("​void DoSomething(string in asStr)"​) ){ 
 +</​code>​
  
 Or you can simply save the variable as a script one and then set that one, like: Or you can simply save the variable as a script one and then set that one, like:
  
-<code c++>​tString sName = mObj.GetName()+<code c++> 
 +tString sName = mObj.GetName()
 SetArgString(0,​ sName ); SetArgString(0,​ sName );
-ScriptExecture();</​code>​+ScriptExecture();​ 
 +</​code>​
  
 In the last example, the variable (sName) is in scope until after ScriptExecute is called, and hence for the as long as needed. In the last example, the variable (sName) is in scope until after ScriptExecute is called, and hence for the as long as needed.
 +
 ===== Global Functions ===== ===== Global Functions =====
  
Line 292: Line 302:
  
 **1)** Before running the Global function, all the arguments need to be set up. Example: **1)** Before running the Global function, all the arguments need to be set up. Example:
-<code c++>​cScript_SetGlobalArgBool(0,​ true);+<code c++> 
 +cScript_SetGlobalArgBool(0,​ true);
 cScript_SetGlobalArgVector3f(1,​ cVector3f(1,​2,​3));​ cScript_SetGlobalArgVector3f(1,​ cVector3f(1,​2,​3));​
-cScript_RunGlobalFunc("​MyClassObj",​ "​cMyClass",​ "​MyGlobalFunc"​);</​code>​+cScript_RunGlobalFunc("​MyClassObj",​ "​cMyClass",​ "​MyGlobalFunc"​);​ 
 +</​code>​
  
 **2)** The return value must be gotten before any other global function is called, else it goes away.\\ **2)** The return value must be gotten before any other global function is called, else it goes away.\\
 This is okay: This is okay:
-<code c++>...+ 
 +<code c++> 
 +...
 cScript_RunGlobalFunc("​MyClassObj",​ "​cMyClass",​ "​MyGlobalFunc"​);​ cScript_RunGlobalFunc("​MyClassObj",​ "​cMyClass",​ "​MyGlobalFunc"​);​
 bool bReturnFromMyGlobalFunc = cScript_GetGlobalReturnBool() bool bReturnFromMyGlobalFunc = cScript_GetGlobalReturnBool()
-cScript_RunGlobalFunc("​MyClassObj",​ "​cMyClass",​ "​SomeOtherFunc"​);</​code>​ 
-This is NOT okay: 
-<code c++>​cScript_RunGlobalFunc("​MyClassObj",​ "​cMyClass",​ "​MyGlobalFunc"​);​ 
 cScript_RunGlobalFunc("​MyClassObj",​ "​cMyClass",​ "​SomeOtherFunc"​);​ cScript_RunGlobalFunc("​MyClassObj",​ "​cMyClass",​ "​SomeOtherFunc"​);​
-bool bReturnFromMyGlobalFunc = cScript_GetGlobalReturnBool()</​code>​ +</​code>​
-(it would be getting the return value from //​SomeOtherFunc//​.)+
  
-**3)** When creating a function that is to be global, it must have void as return value and no arguments. Example:  +  This is NOT okay:
-<code c++>void MyGlobalFunc()</​code>​ +
-**4)** The implemented global function must get all arguments using  +
-<​code>​void MyGlobalFunc() { +
-bool bArg0 = cScript_GetGlobalArgBool(0);​ +
-cVector3f vArg1 = cScript_GetGlobalArgVector3f(1);</​code>​ +
-**5)** When calling a global function, it is okay to have one or more asterix in the object name and then several object will be called. For example //"​My*test"//​ would call: //"​MyNiceTest"//​ and //"​MyBadTest"//​. However, this will be a bit slower, so be careful when using it (eg not during things called every update). +
- +
-Asterix is also supported for the class name (eg "​cMyClass*"​). If you do not care about the class of the objects the class param can simply be empty (""​). The following will call MyGlobalFunc in all script modules named "​MyClassObj"​ no matter the class name:+
  
 <code c++> <code c++>
-cScript_RunGlobalFunc("​MyClassObj",​ "",​ "​MyGlobalFunc"​);​+cScript_RunGlobalFunc("​MyClassObj",​ "cMyClass", "​MyGlobalFunc"​);​ 
 +cScript_RunGlobalFunc("​MyClassObj",​ "​cMyClass",​ "​SomeOtherFunc"​);​ 
 +bool bReturnFromMyGlobalFunc = cScript_GetGlobalReturnBool()
 </​code>​ </​code>​
-===== Saving ===== 
- 
- 
-To skip saving a variable, you must use metadata keyword [nosave] in front of the the variable name. Examples: 
  
 +(it would be getting the return value from //​SomeOtherFunc//​.)
  
-<code c++>[nosave] float mfT; +**3)** When creating a function that is to be global, it must have void as return value and no arguments. Example: 
-[nosave] cMyClass@ mHandle;+<code c++> 
 +void MyGlobalFunc()
 </​code>​ </​code>​
  
- +**4)** The implemented global function must get all arguments using
-If you only want to save the actual pointer for a handle, but not any data, then use the prefix [nodatasave] Example: +
- +
- +
-<code c++>​[nodatasave] cMyClass@ mNoSaveHandle;​ +
-</​code>​ +
- +
-However it is almost always better to use the tID type for these situations! +
- +
- +
-Also, there is a the prefix [volatile], this will do the same as a [nosave] but will also make sure that variable is not even saved during script reload. This should be used on handles where you are never sure if saved variable can invalid, for instance cSoundEntity and cParticleSystem (that can be deleted by the engine)+
- +
- +
-<code c++>​[volatile] cSoundEnity@ mpCurrentSoundEntity;​ +
-</​code>​ +
- +
- +
-Instead of using class handle here it is better to store the ID to the object. ​The ID can always be saved and will work correctly on load even if the object no longer exists. Worst thing that can happen is that when getting the actual class NULL is returned. This leads to an excpection, stopping further execution of the current script file, but does not lead to a crash or similar major failure. +
- +
 <​code>​ <​code>​
-tID mCurrentSoundEntity;+void MyGlobalFunc() { 
 +bool bArg0 = cScript_GetGlobalArgBool(0);​ 
 +cVector3f vArg1 = cScript_GetGlobalArgVector3f(1);
 </​code>​ </​code>​
  
 +**5)** When calling a global function, it is okay to have one or more asterix in the object name and then several object will be called. For example //"​My*test"//​ would call: //"​MyNiceTest"//​ and //"​MyBadTest"//​. However, this will be a bit slower, so be careful when using it (eg not during things called every update).
  
-In case you have an array of handles where none of the properties should be saved, this is NOT possible. Instead save names of IDs of the objects in the array. +Asterix is also supported for the class name (eg "​cMyClass*"​). If you do not care about the class of the objects the class param can simply be empty (_ckgedit_QUOT__ckgedit____>​). The following will call MyGlobalFunc in all script modules named "​MyClassObj"​ no matter the class name: ''​ cScript_RunGlobalFunc(''​ __''​GESHI_QUOT__MyClassObj__GESHI_QUOT__,​ __ GESHI_QUOTGESHI_QUOT__,​ __ GESHI_QUOT__MyGlobalFunc__GESHI_QUOT__);​ __''​__ ​  ===== Saving ===== To skip saving a variable, you must use metadata keyword [nosave] in front of the the variable name. Examples: ''​[nosave] float mfT; [nosave] cMyClass@ mHandle; '' ​  If you only want to save the actual pointer for a handle, but not any data, then use the prefix [nodatasave] Example: ''​[nodatasave] cMyClass@ mNoSaveHandle;​ '' ​  ​However it is almost always better to use the tID type for these situations! Also, there is a the prefix [volatile], this will do the same as a [nosave] but will also make sure that variable is not even saved during script reload. This should be used on handles where you are never sure if saved variable can invalid, for instance cSoundEntity and cParticleSystem (that can be deleted by the engine). ''​[volatile] cSoundEnity@ mpCurrentSoundEntity;​ '' ​  ​Instead of using class handle here it is better to store the ID to the object. The ID can always be saved and will work correctly on load even if the object no longer exists. Worst thing that can happen is that when getting the actual class NULL is returned. This leads to an excpection, stopping further execution of the current script file, but does not lead to a crash or similar major failure. ''​ tID mCurrentSoundEntity;​ '' ​  In case you have an array of handles where none of the properties should be saved, this is NOT possible. Instead save names of IDs of the objects in the array. ===== Optimizing ===== All of the optimizations tips are meant to be used when speed is of essence. It is good to always use them when possible, but it is even more important to have the code readable, so take that into account too! **Declare Local Variables Outside Loops** \\ 
-===== Optimizing =====+Try and keep the local variables outside a loop. So instead of doing: ''​for(int i=0; i''​__''​GESHI_OPEN__1000;​ ++i) { float fX = GetSomeThing(i);​ … } __''​__ ​  Do it like this instead: ''​float fX; for(int i=0; i''​__''​GESHI_OPEN__1000;​ ++i) { fX = GetSomeThing(i);​ … } '' ​  That way they script does not need to create the local var for every loop and this can boost performance quite a bit!
  
-All of the optimizations tips are meant to be used when speed is of essenceIt is good to always use them when possible, but it is even more important to have the code readable, so take that into account too!+**Include only what is needed** \\ 
 +Make sure that you only include hps files that are really neededHaving includes ​that are not used can eat up a lot of extra loading time very easily!
  
-**Declare Local Variables Outside Loops**\\ 
-Try and keep the local variables outside a loop. So instead of doing: 
-<code c++>​for(int i=0; i<1000; ++i) { 
- float fX = GetSomeThing(i);​ 
- ... 
-}</​code>​ 
-Do it like this instead: 
-<code c++>​float fX; 
-for(int i=0; i<1000; ++i) { 
- fX = GetSomeThing(i);​ 
- ... 
-}</​code>​ 
-That way they script does not need to create the local var for every loop and this can boost performance quite  a bit! 
- 
-**Include only what is needed**\\ 
-Make sure that you only include hps files that are really needed. Having includes that are not used can eat up a lot of extra loading time very easily! 
 ===== Important notes ===== ===== Important notes =====
  
Line 386: Line 357:
  
   * Helper functions should use degrees, not radians   * Helper functions should use degrees, not radians
 +
 +\\
 +
hpl3/engine/script.txt · Last modified: 2020/07/01 07:07 by thomas