Script - The Basics of Script Writing

In this tutorial the basics of scripting in GameStart will be explained.

Glossary


The following glossary outlines the important terms used in this tutorial:

  • Class: A class is a programming language object that encapsulate variables and functions.
  • Instance: An instance is a space in memory used to store the content of a class such as a separate copy of the class variables.
  • Item: Please refer to the Item documentation page.

You can find additional informations on the script objects and conventions in the Squirrel Online Documentation.

Some Theory


The Application Main Loop

GameStart scripting is event based. What this mean is that the 'main loop' of your application is handled by the engine and your scripts are only called to handle specific events.

You do not provide a main loop. The engine already provides one and takes care of handling physics, collisions and display of all your scene items. As events happen during the loop execution the engine calls the scripts you assigned to your items so that you may specialize their behavior and reactions.

Event-based Scripting

As an illustration, say we want to script a 3d item (a ship) and execute some code with each frame drawn (an event handled by the OnUpdate callback) by the engine. Say we'd also like to execute some code when the item collides (OnCollision callback) with another item. A typical script for this might look like this:


                          
  1. class PlayerShip
  2. {
  3. function OnUpdate(item, dt_clock)
  4. {
  5. // ... Do something to update the ship.
  6. }
  7.  
  8. function OnCollision(item, with_item)
  9. {
  10. // ... Update ship energy, explode if energy reaches zero.
  11. }
  12. }

                        

This script must be linked to an item so that the engine may call it to handle item events. Let's skip over how to link a script to an item as we will see that later on. For now, let's focus on the script itself.

This class defines two functions: OnUpdate() is called on each frame update, OnCollision() is called when two items collide. Notice how the first parameter of both of these functions is an item? This is done so that you may use this class for more than one item and still know for which item the engine is calling your script.

The item parameter is filled by the engine when it calls your function. Without this parameter this script could not be applied to another item to provide the same behavior.

These callback functions are the backbone of your application so knowing which callbacks are available is essential in getting the most out of the facilities the engine provides.

Note: A complete reference of the available callback functions is available here.

The Script Entry Point

You can see the script VM (virtual machine) as a big blob of data made of global variables that may hold references to tables, instances, functions, class definitions, etc... Whatever you put in it is there. Contrary to statically linked langages like C/C++ the script VM does not need to know about the global structure of a program to compile a script. An unresolved reference will only trigger an exception at runtime, not at compile time. You can also compile/execute the same script several time and it will only update already existing definitions.

So, knowing that, what is the first line of script executed? If you are executing a project then the first script compiled and executed is the project one. If you are executing a scripted scene then its script will be the first to be compiled and executed on the engine script VM.

The project and scene objects are the highest-level objects that can be scripted. As they can both react to a frame update event (OnUpdate callback) they can both provide a global control over the project or scene execution and act very much like a main loop would. You only provide the core code of the loop, not the loop itself.

Storing States in Script

Of course you may store variables in your class to track your scripted object state. In the previous ship example we need to store the ship's energy. The updated script then becomes:


                          
  1. class PlayerShip
  2. {
  3. energy = 0
  4.  
  5. function OnUpdate(item, dt_clock)
  6. {
  7. // ... Do something to update the ship.
  8. }
  9.  
  10. function OnCollision(item, with_item)
  11. {
  12. // ... Update ship energy, explode if energy reaches zero.
  13. }
  14.  
  15. function OnSetup(item)
  16. {
  17. energy = 50
  18. }
  19. }

                        

A new callback was introduced: OnSetup() is called by the engine during the item setup (only once). This callback is used tio set the default shp energy to 50.

It is important to note that an item allocates its own instance of a class. So, while the class code may be shared by two different items (by assigning the same class to both items) they will use a distinct chunk of memory to store the class variables.

To stay on the ship example: If we assign the PlayerShip class to two different items, reducing the energy of one item won't reduce the energy of the other even if they both execute the same code.

Hopefully you are now getting a clearer view of how item's script work.
But most likely you scripts will need to communicate with the scene environment, so let's see how to do that.

Script Communication

GameStart provides you with several way to retrieve an item (or any scriptable object) script instance. For items you may use the ItemGetScriptInstanceFromClass(item, string class_name) function.

Once you have retrieved an item instance you can access it just like any other instance. Let's look at a concrete example when two ships collide:


                          
  1. function OnCollision(item, with_item)
  2. {
  3. local other_item_script = ItemGetScriptInstanceFromClass(with_item, "PlayerShip")
  4. energy -= other_item_script.energy / 2
  5. }

                        

What is happening now is that when two ships collide the OnCollision() callback retrieves the other item PlayerShip script instance in order to subtract half the other ship's energy to this ship.

Note: a -= b is equivalent to a = a - b. This notation shortcut is available for all operators (a *= b, a &= b, etc...).

But wait a second! There's an obvious problem with this sample code: What if the colliding item does not use the 'PlayerShip' instance? Simple, the script will trigger an exception. And it is up to you to act upon it. Let's see how to gracefully handle such a case:


                          
  1. function OnCollision(item, with_item)
  2. {
  3. try
  4. {
  5. local other_item_script = ItemGetScriptInstanceFromClass(with_item, "PlayerShip")
  6. energy -= other_item_script.energy
  7. }
  8. catch (e)
  9. {
  10. // Not a PlayerShip
  11. energy -= 5
  12. }
  13. }

                        

Good. So now, when the ItemGetScriptInstanceFromClass() call will trigger an exception our script will execute the catch block connected to the try block which triggered the exception. In this case we know that the item the ship is colliding with is not another ship.

Another way to avoid that would be to use the ItemHasScript(item, string class_name) function to check beforehand if a given script instance can be found on the item.

The choice is yours to make. You might want to use exceptions and have a base script class common to all your game objects. Or you could use a dispatcher (if (has_script) ... else ... etc...) to query and react to specific script classes.

In Practice


Assigning Scripts

Assigning a script to any scriptable item in GameStart is done from the Script pane in the Property View window. Script components can be added by clicking the Add... button.


The Viper Scout body object script pane.

Several builtins script components are provided for basic behaviors and common usage cases.

Note: A script can also be added to an item by drag'n'dropping it from an explorer over an item in the 3d view.

Comments

davidgoodis's picture

Wow that's exactly what I was struggling with and ended up putting all variables as globals...
I can clean up my code now