This guide is based on the Ilios Scripting Guide by Chris Tikkos (Dionous Games) and is published here with his permission. Example projects and images are by Dionous Games.

Before you get started

Visionaire Studio was originally designed for 2D point and click adventures. Since version 5, Box2D physics and the Ilios scripting language make other genres possible too, for example physics based platformers. This guide walks you through your first Ilios scripts and the physics setup of a small platformer prototype.

Useful references:

Prerequisites

You do not need deep programming knowledge, but the basics of object oriented programming (classes, functions, variables) will help a lot. This guide does not teach programming in general, it shows how these concepts work in Visionaire.

Code vs. visual scripting

Visionaire offers two ways to write Ilios scripts:

  • Code: classic typed code
  • Blocks: visual scripting

Open Visual Scripting in the top bar, create a new script and choose the editor you prefer:

Creating a new Ilios script and choosing between code and blocks
Creating a new Ilios script and choosing between code and blocks

Both kinds can be mixed in one project, so some scripts can be written as code and others with blocks.

Your first Ilios script

Ilios brings a component based approach to Visionaire. You write behaviours, which are classes, and attach them to objects, characters, scenes, interfaces, buttons or the game itself via the Components tab.

1. Create a script block

Go to the Visual Scripting tab and create a new entry. It will hold your behaviour class. Code and blocks offer the same functionality, so it is a matter of taste. This example uses blocks first, the code version follows at the end of the section.

New entry in the Visual Scripting tab
New entry in the Visual Scripting tab

2. Create a behaviour class

Click on the canvas to open the block list, choose New Class and confirm with a double click.

Block list with the entry New Class
Block list with the entry New Class

Name your class and assign a behaviour superclass. A superclass already contains the basic behaviour of a Visionaire entity. Your class inherits it, you can override its functions, and you can attach your class to the matching entity as a component.

Available behaviour superclasses:

  • GameBehaviour
  • ObjectBehaviour
  • CharacterBehaviour
  • SceneBehaviour
  • ButtonBehaviour
  • InterfaceBehaviour

For this example, create an object behaviour, so choose ObjectBehaviour:

Class dialog with ObjectBehaviour as superclass
Class dialog with ObjectBehaviour as superclass

Click OK. The class node now appears on the canvas:

Class node on the canvas
Class node on the canvas

3. Create a function

The + sign adds connection points (yellow dots) for functions, variables, states and more. Drag a connection point onto the canvas and release it to see the list of nodes you can connect.

List of nodes that can be connected to the class
List of nodes that can be connected to the class

Here you can also override functions of the ObjectBehaviour superclass, for example FixedUpdate, which runs at a fixed rate of about 50 times per second:

Overriding FixedUpdate
Overriding FixedUpdate

The FixedUpdate block is yellow, which marks it as a function:

The FixedUpdate function block
The FixedUpdate function block

Now call a method that prints text: Debug.Print(). Click on the canvas, search for debug and double click Print(string) of the Debug class:

Searching for Debug.Print in the block list
Searching for Debug.Print in the block list

The block looks like this:

The Debug.Print block
The Debug.Print block

Inputs are on the left side of a block, outputs on the right. The purple dot is a string input. You can either connect a string to it or type the text directly into the small box next to the dot:

Typing text into the string input
Typing text into the string input

4. Connect the execution flow

The print block does nothing until it is part of the execution flow, the white line between blocks. Execution starts at a function, follows the line and runs all blocks in order.

The white execution flow line
The white execution flow line

Drag the small white arrow of FixedUpdate onto the Debug.Print block. The hint add exec appears. Release it, and your first script is ready:

FixedUpdate connected to Debug.Print
FixedUpdate connected to Debug.Print

5. Attach the behaviour to an object

The new behaviour is now available in the Components tab of your object (here test-player):

Adding the behaviour in the Components tab
Adding the behaviour in the Components tab

Run the game and open the console with Tab. Under Lua Execute you see the output of Debug.Print, about 50 times per second, because it sits inside FixedUpdate.

Console output of the first script
Console output of the first script

6. Using function arguments

Now print the name of the object as well. The key is the this argument of the update function:

The this argument of FixedUpdate
The this argument of FixedUpdate

this refers to the object the behaviour is attached to. Drag this onto the canvas:

this dragged onto the canvas
this dragged onto the canvas

You now have access to all properties of the object. Drag the blue output dot onto the canvas and choose Name from the list:

Choosing the Name property
Choosing the Name property

The Name node has a purple output, so it delivers a string:

The Name node
The Name node

Connect it to a second Debug.Print block:

Name connected to a second Debug.Print block
Name connected to a second Debug.Print block

Finally connect both print blocks with the execution line so they run one after the other:

The finished script
The finished script

The console shows both lines:

Console output with the object name
Console output with the object name

The same script as code

class TestClassCode : ObjectBehaviour
{
  override void FixedUpdate()
  {
    Debug.Print("Hello World! -Code Version-");
    Debug.Print(this.Name);
  }
}

The class has a different name, so you can switch between the block version and the code version in the components dropdown:

Both versions in the components dropdown
Both versions in the components dropdown

Example project: quickstart.veb (Dionous Games)

Three ways to use Ilios scripts

  • As a component that gives an object, scene, character, button, interface or the game a behaviour
  • As a static class with functions that every other script can use
  • As a data type, a template for data that other scripts use

Script components

To attach a script to an entity, its class must inherit from the matching superclass:

EntitySuperclass
ObjectObjectBehaviour
SceneSceneBehaviour
ButtonButtonBehaviour
InterfaceInterfaceBehaviour
CharacterCharacterBehaviour
GameGameBehaviour

Components are instances: you can add the same script several times, and each instance keeps its own data. To use one from another script, you need a reference to that instance.

Visionaire calls certain functions of a component automatically, for example Awake and FixedUpdate. That is how your code connects to what happens in the game:

class PlayerMoveC : ObjectBehaviour
{
  // called once before the first update
  override void Awake()
  {
    // initialization code
  }

  // called at a fixed rate
  override void FixedUpdate()
  {
    // code that updates the object
  }
}

Something only happens if a component reacts to a trigger: input, a collision, a call from another script, or a regular call like FixedUpdate or Update.

Static classes

Static classes are not attached to anything. They exist once in the project, and every script can use them directly by their class name, without a reference. That makes them useful for helper functions that many scripts need.

Example: a helper class that calculates the distance between two points:

class MathUtils
{
  // distance between two points
  static float CalculateDistance(vec2 pointA, vec2 pointB)
  {
    float distance = vec2.Distance(pointB, pointA);
    return distance;
  }
}

Call it from anywhere like this:

MathUtils.CalculateDistance(new vec2(0,0), new vec2(3,4))

To test it, use the action part Run ilios script and print the result in the console:

Debug.Print(MathUtils.CalculateDistance(new vec2(0,0), new vec2(3,4)).ToString());

Debug.Print() only accepts strings, so .ToString() converts the float result first.

Custom data types

Scripts can also define your own data types: structs, plain classes, enums or interfaces. You use them throughout the game as a type of data or as a template, see Constructors below.

Variables and outlets

A simple example with two variables:

class Player : ObjectBehaviour
{
  outlet float playerHealth;
  outlet bool isDead;

  void Damage(float dmg)
  {
    playerHealth -= dmg;
    if(playerHealth < 0)
    {
      // the player has died
      isDead = true;
    }
  }

  void AddHealth(float health)
  {
    playerHealth += health;
    if(playerHealth > 100)
    {
      // health is full
      playerHealth = 100;
    }
  }
}

The keyword outlet shows the variable in the editor, so you can adjust its value there. Add the Player component to an object to see it:

Outlet variables in the component settings
Outlet variables in the component settings

Setting up the player's physics

Preparing the player

  • Create the player object, for example test-player.
  • Create an idle animation, add its frames and set it to infinite loops:
Idle animation with infinite loops
Idle animation with infinite loops
  • Position the animation on the canvas, for now at (40,90):
Positioning the animation
Positioning the animation
  • Set the idle animation as the default animation of the player:
Idle animation as default animation
Idle animation as default animation

When you run the game, the player appears with its idle animation. Because the pixel art is very small, these settings improve the display:

  • Set the game resolution to 320 × 240 pixels.
  • Enlarge the window with a script in the start action:
Script in the start action that enlarges the window
Script in the start action that enlarges the window
  • Activate the pixel effect for crisp pixels:
Pixel effect setting
Pixel effect setting

Body and fixture

Visionaire uses Box2D for physics and adds it to objects through components. A working Box2D object needs a Body and a Fixture:

Body and Fixture components
Body and Fixture components

The body holds the properties you cannot see or touch:

  • mass: how heavy it is
  • velocity: how fast and in which direction it moves
  • rotational inertia: how much effort it takes to start or stop spinning
  • angular velocity: how fast and which way it rotates
  • location: where it is
  • angle: which way it faces

Add a body to the player:

Body settings of the player
Body settings of the player

Box2D knows three body types:

  • dynamic: affected by forces and reacts to collisions, like our player
  • static: not affected by forces and cannot be moved, like walls or the ground
  • kinematic: moves with a set velocity but ignores forces, like a moving platform. When the original guide was written, Visionaire did not support this type yet.

Body settings:

  • Static: leave it unticked, the player is a dynamic body.
  • Fixed Rotation: prevents rotation, for example after a collision. The player needs this.
  • Linear Damping: reduces the linear velocity.
  • Angular Damping: reduces the angular velocity.
  • Gravity Scale: weakens or strengthens gravity. 1 is normal gravity, -1 reverses it, 0 turns it off.

A body alone has no shape and cannot collide. Size, shape and material come from fixtures. A body can have several fixtures, and their arrangement affects its center of mass. When two bodies collide, their fixtures decide how they react. Fixture properties:

  • sensor: a sensor detects collisions but does not react to them. The player is not a sensor.
  • friction: how slippery it is
  • restitution: how bouncy it is
  • density: how heavy it is in relation to its area
  • shape: polygon or circle

Visionaire offers three fixture shapes: rectangle, circle and edge (free form). For the player, choose the box:

Box fixture of the player
Box fixture of the player

The red rectangle is the collision border of the player. Run the game: the player falls off the screen because of gravity. So add the background image and a ground object with a static body and its own fixture:

Ground object with static body and fixture
Ground object with static body and fixture

Run the game again. The player now stands on the ground: your first collision works.

Example project: player-setup.zip (Dionous Games)

Constructors

A constructor is a special function that runs automatically when a new instance is created. It initializes the data of the new instance.

Create a new block and, in the code tab, a class for weapons with a constructor:

class Weapon : object // inherits from the object superclass
{
  string wpnName;
  int dmg;
  float fRate;

  // constructor, must have the same name as the class
  Weapon(string weaponName, int damage, float fireRate)
  {
    this.wpnName = weaponName;
    this.dmg = damage;
    this.fRate = fireRate;
    Debug.Print("Created ${this.wpnName} with Damage ${this.dmg} and Fire Rate ${this.fRate}");
  }
}
  • Such classes inherit from the internal object superclass. They describe data, not behaviour, so they cannot be attached to Visionaire entities.
  • The constructor has the same name as its class, here Weapon.
  • this refers to the instance that is being created.

The equivalent script in visual scripting (click to enlarge):

The Weapon class with its constructor as blocks
The Weapon class with its constructor as blocks

To create a constructor in the block editor, create a normal function block and choose Constructor:

Choosing Constructor in the function block
Choosing Constructor in the function block

Using the constructor

In this example the weapons are created once when the script loads, so they are created in Awake(). The script is attached to the player object, so its superclass is ObjectBehaviour:

class MainPlayer : ObjectBehaviour
{
  override void Awake()
  {
    Weapon axe = new Weapon("Axe", 7, 3);
    Weapon sword = new Weapon("Sword", 5, 4);
  }
}

The same as blocks:

Creating two weapons in Awake with blocks
Creating two weapons in Awake with blocks

Run the project and open the console: both weapons are created.

Console output of the two constructors
Console output of the two constructors

Constructor overloading

A class can have several constructors with different signatures, that is, different parameters in the parentheses. Besides Weapon(string weaponName, int damage, float fireRate) you could add Weapon() without parameters and Weapon(string weaponName) with only a name. Then a weapon can be created in different ways, for example new Weapon("Spear") or new Weapon().

Example projects (Dionous Games): constructors.veb (blocks) and constructors-code.veb (code)

Next step: build a level for this player in the platformer tutorial with Tiled and Box2D.

More on constructors, destructors and static constructors is in the Ilios reference.