ILIOS is a scripting language that you can approach either by Visual Scripting or classic Typed Code. Along with Box2D will allow you to create physics-based games, such as Platformers or even RPGs!
Have fun and be creative!
While Visionaire is a really powerful engine designed originally for 2D point ‘n click adventure games, it is possible since version 5 with the addition of Box2D and the Ilios scripting language to create different genre games, like physics-based platformers. So, welcome to the first Box2d/Ilios scripting tutorial for Visionaire! Let’s try to make a prototype platformer and see how this works 🙂
Some useful reference links:
https://box2d.org/documentation (Box2D)
https://www.visionaire-studio.com/luadocs/ilios.html (Ilios)
Feel free to contact us for your comments, ideas and any assistance to your projects! As always, the community’s assistance is also there to help:
https://www.visionaire-studio.net/forum (Forum)
https://discord.com/invite/g5zFejW (Discord)
For this tutorial, we will use:
Please note that while deep programming knowledge is not critical, it’s highly recommended to know the basics of object-oriented programming in order to fully understand the concepts set out in this tutorial. This guide’s primary purpose is not teach programming but to show implementation of these concepts on the Visionaire engine.
Visionaire offers 2 ways to create Ilios scripts:
You can access both of these through 1. Visual Scripting at the Top Bar -> 2. Create a new script -> 3. Choose your preferred editor for your script:
Code and Block scripting can be used together in a project. So you can have some scripts with code and some with visual scripting.Â
Ilios is offering Visionaire a component-based approach to game development. The basic principle is to create behaviours (which are basically classes) that can then be attached to objects, characters, scenes, interfaces, buttons, and the game itself through components. The basic steps can be summarized below:
1. Create a script block to hold your Behaviour ClassÂ
Go to the Visual Scripting tab and create a new block entry. You are now ready to create your class. You have 2 options: Visual Scripting (Blocks Tab) or classic Typed Code (Code Tab). Both offer basically the same functionality so it’s a matter of preference. Will show you first how to do it with the visual editor and at the end of this section you can see the code version.
2. Create a Behaviour Class
On the Visual Scripting Editor, Click on the canvas. This will bring up the Block List to create your block. Choose ‘New Class’ and double click to confirm.
You can now name your class and assign it to a behaviour superclass. A behaviour superclass is just a mother class which holds basic behaviours for Visionaire entities (objects, characters, scenes, interfaces, buttons, and the game itself). By assigning your class to a superclass, you inherit from it all these behaviours which you can now override and alter and you will be able to attach your new behaviour to the relevant Visionaire entity through components.
Â
The behaviour superclasses available are:
Â
For now let’s make an object behaviour so let’s attach it to ObjectBehaviour superclass:
Click OK when finished and your class node is now on the canvas:
3. Create your function
Â
By click on the + sign you can create connection points (yellow dot) for adding functions, variables, states etc to your class. Just click on a connection point and drag it anywhere on the canvas, then release it. A list of possible nodes to connect to will be shown.
More importantly, you will now be able to Override functions of the ObjectBehaviour superclass, like for example the Fixed Update function which runs at every frame (50 times per second). Let’s do this then:
The Fixed Update block is now visible. It’s colour is yellow, denoting that it is a function:
Now let’s call a method to print something on the screen. This method for this is Debug.Print(). Click anywhere on the canvas to open the block list and search for ‘debug’, double click to choose Print(string) from the Debug internal class:
The block looks like this:
You notice that it has an input node to the left (purple color dot means ‘string’ input). Similarly, output nodes are positioned to the right of the block, but here there is no any. To enter your string input here you have 2 ways: either connect a string, or enter the text directly. Let’s do the 2nd for now, click on the small box to the left of the purple dot and you can enter your text:
4. Create your execution flow
The Debug.Print block is ready but won’t run unless we connect it with our class and update function. We can connect blocks by dragging a white line between them, the ‘Execution Flow’ line. Â Execution starts at a function, follows the line, and executes all blocks in order.
Just drag from the small white arrow on the FixedUpdate over the Debug.Print block (you will see a hint msg ‘add exec’) and release. The blocks are now connected and our first script is ready!Â
5. Connect the behaviour to your object
Â
Our new script – behaviour is now available under the components tab to be connected to our object (test-player):
When you Run Game, open the console (TAB) and note that under Lua Execute you will see Debug.Print being executed 50 times/s as it is inside the Update function. Well done!
7. Using Function Arguments
Now let’s say that we also want to print out the object’s name (“test-player” in our case). How can we retrieve the linked object’s data and manipulate it? The secret is in the Update function’s “this” argument:
‘this’ holds a reference to the object currently linked to the behaviour and it will help us access and manipulate its properties. To do this, you need to click and drag ‘this’ and drop it on the canvas:
You now have an instance of the linked object and full access to its properties. For example, we want to retrieve and print the object’s name; click and drag the output node on the right (blue dot) and drop it on the canvas. A list of possible Visionaire Object properties are now available to select, so go ahead and choose Name:
You will now see the Name node appear and getting linked to this. You also see that it has a purple output node which means that it’s of string type:
And you guessed right; you can connect the purple output node to a block that accepts strings as input, and yes that is our purple Debug.Print block, so go ahead, make a new Debug Print Block and link it:
Finally, connect the two Debug.Print blocks using the execution line so that they run one after the other and your script is ready:
And you can confirm it in the console:
The code version of the above looks like:
class TestClassCode : ObjectBehaviour { override void FixedUpdate() { Debug.Print("Hello World! -Code Version-"); Debug.Print(this.Name); } }
(we changed a bit the Class name so that you can use it interchangeably with TestClass component in the dropdown):
Project Files
quickstart.veb | Visionaire file containing this section’s example. |
Â
Let’s create some actual functionality here with some scripts and analyze their structure. We will create 2 scripts that can be attached to a scene object:
class PlayerInput : ObjectBehaviour { Movement movement; void Update() { Vector3 moveDirection = new Vector3(Input.GetAxis("Horizontal"), 0, Input.GetAxis("Vertical")); movement.Move(movedirection); } }
class Movement : ObjectBehaviour { float moveSpeed = 1; void Move(Vector3 direction) { transform.position += direction * moveSpeed * Time.deltaTime; } }
Class Declaration
This defines the accessibility of the script, such as public, followed by optional modifiers, such as if the class is static or abstract which, in the case of scripts that are attached to objects, it can’t be, the script’s type, which will normally be class, followed by the identifier, which is the name of the script.
In most cases, the script’s name must be the same as the script’s file name, otherwise you’ll get an error.
After the script’s identifier is the script’s Base Class. For scripts that you can attach to an object, this will always be MonoBehaviour or a class that inherits from MonoBehaviour.
You’ll learn how inheritance works later on in this course but all you need to know for now is that this is what allows a script to be attached to an object as a component, and what allows event functions like Update and Start to be called on it automatically.
You’ll the see a set of brackets encapsulating the rest of the script. This is the body of the script, and anything placed within these brackets are within the class’s scope.
So what goes inside the script.
Body of the Script
Generally speaking there are three different types of code you can place inside a class:
Where you put your variables and functions doesn’t technically matter but, generally, you’d put class member variables at the top of your script.
This is information that everything in the script is going to use. Such as a movement speed float, for example, where a float is a type of number with a decimal point.
Other value types include bool, for a true or false value, int for an integer value, which is a whole number, or vec3, which is a set of 3 values that can be used to describe a position, a rotation or even a colour.
It’s also possible to reference other classes and objects using a variables. These are reference types, and typically point to something that exists elsewhere. You’ll find out more about reference types and value types in the lesson on variables later in this course.
On their own, however, variables don’t do anything. They just kind of exist.
To actually make something happen, you’ll need to write a function.
Functions, sometimes called methods, are blocks of code that are executed, line by line, when they’re called.
They are the working, moving parts of your script and are the building blocks of all of the logic in your game.
So how do you write a function?
A function starts with its return type, which is the type of information its going to give back to whatever calls it. In many cases this can be void, meaning that it doesn’t return any information.
Next, is the name of the function. Which needs to be different from the containing class and unique unless you’re overloading the function with different sets of parameters.
What’s a parameter? Parameters are the pieces of information that you pass into a function when you call it and are contained within a set of parentheses that follow the function’s name.
You don’t have to accept any data when writing a function, in which case you can leave the parentheses empty. However, in many cases, you will need to pass some kind of information to the method when you use it.
For example, a move function might accept a vec3 value that represents the movement direction for that frame.
That vector can then be scaled by the movement speed value that’s kept in the class to create movement.
But why keep the two values, movement direction and speed, in separate places?
Why not put the movement vector with the speed value, or make the speed value a parameter of the function?
While you could technically do either of those things, the reason why you might not want to is how the two pieces of information are going to be used.
For example, serializing the speed value, or making it public, allows you to control it in the inspector, meaning that you can change how fast this object can move in an appropriate place, on the object in the component that’s doing the moving.
While the movement vector that’s passed into the function is likely going to be controlled by a different script. For example, if the function is made public, meaning that other scripts can call it, a different script, such as an Input script that reads the player’s input axes and generates a vec3 direction, can then pass that information into the function when it calls it, creating player-controlled movement.
But why two scripts?
Why not just write one script with input and movement combined?
Generally speaking it’s good practice for each script to only do one thing, since it makes problems easier to find and fix, it limits what each script can do and makes it much easier to add, remove or change how the different parts of your game work.
You might be familiar with the basic concepts of the engine, such as scenes, objects and components as well as LUA scripts
But how are Ilios scripts different and what are they for? And how should you be using them?
3 basic ways to use an Ilios script in Visionaire:
Script Components  can be added to the following entities to make them do something:
In order to add these scripts to the entities above, they need to inherit from specific superclasses which are built into Visionaire:
This means that they exist as instances, where you can add multiple scripts of the same type and each one holds its own data. Because they are instances, however, when you want to use one, you’ll need to have a reference to that instance.
When you add a script to an entity above, certain event messages, like FixedUpdate and Awake will be called on that script. These functions, and others like it, are ultimately how you will connect the behaviour that you write in the script to the actual events of your game.
Here’s an example of a script that can be attached on a Scene Object:
class PlayerMoveC : ObjectBehaviour { // Awake is called once before the first execution of Update override void Awake() { // Initialization code here } // FixedUpdate is called once per frame override void FixedUpdate() { // Code to update object behavior each frame here } }
For something to actually happen in your game, it needs to happen on such entity and be triggered by something that happens, such as input, a collision, an event that another script calls, or constant triggers such as FixedUpdate or Update.
Global classes, or static classes, don’t exist on objects. They exist in your project as a whole and can be used by other scripts.
When something is static, such as a class, it means that there’s only one of that type in your project. This means that, unlike script components, static classes cannot exist as multiple instances.
The benefit of that is, that when you want to use one, you can do it without needing a reference. You just type out the name of the class to access it.
As a result, static classes can be useful when you want to create one-of-a-kind code that everything might need to use. An example of this might be a utility class that contains commonly used functions.
Here’s an example of a static class that does a calculation, more specifically it calculates the distance between two points:
class MathUtils { // A static function to calculate the distance between two points static float CalculateDistance(vec2 pointA, vec2 pointB) { // Use the Pythagorean theorem to calculate the distance float distance = vec2.Distance(pointB, pointA); return distance; } }
We can now call this function easily from anywhere just using the following syntax:
MathUtils.CalculateDistance(new vec2(0,0), new vec2(3,4))
You can easily check this, by using the relevant “Run ilios script” actionpart and printing the result of our calculation on the console:
Debug.Print(MathUtils.CalculateDistance(new vec2(0,0), new vec2(3,4)).ToString());
Notes:
Resources
static functions.veb | Demonstration of static functions in Visionaire Studio |
Lastly, scripts can be used to define types of data in your game. Custom data types such as structs, plain classes, enums, interfaces are all examples of data types that can be created, using scripts, and then used throughout your game as a type of data or as a template for something else.
Setting the Assets
The basic steps to setup our player are:
Â
Now if you run the game you will see the player on the screen with his idle animation. A few notes though due to the small size of the pixel art player assets to improve the display:
Â
Apply Physics Properties
Visionaire uses Box2D engine to apply physics behaviours to objects through components so let’s add some to our player. To have a fully working Box2D object, you need to define 1. a Body and 2. a Fixture component:
Think of a body as the properties of an object that you cannot see (draw) or touch (collide with). These invisible properties are:
Â
Â
So let’s add a body to our player:
Note that there are 3 types of Box2D bodies:Â
Â
Â
Â
Even if you know all of these characteristics of an object, you still don’t know what it looks like or how it will react when it collides with another object. To define the size and shape of an object we need to use fixtures; fixtures are used to describe the size, shape, and material properties of an object. One body can have multiple fixtures attached to it, and the center of mass of the body will be affected by the arrangement of its fixtures. When two bodies collide, their fixtures are used to decide how they will react. The main properties of fixtures are:
Â
So let’s add a fixture to our body. Note that you can make 3 fixture shapes: 1. Rectangle, 2. Circle, 3. Edge (free form). All work similarly but for our example let’s choose the box one:
The red rectangle we created is actually the bounding box that defines the collision borders of our player.
Try to run the game now. The player will fall off the screen due to gravity, nice! (well, sort of). So let’s make some ground. Add the background image provided above and setup a new ground object and its relevant (static!) body and fixture:
Try to run the game and now the player will stand on the ground, our first collision is a fact!
Project Files
player-setup.zip | Zip file containing this section’s example. |
A Constructor is a special method which is invoked automatically at the time of object creation and is used to initialize the data members of the new object fast.
Creating a Constructor (Code and Visual Scripting)
Create a new block, and in the code tab let’s create a class for Weapon objects which will also hold the Weapon constructor:
class Weapon : object // we inherit from the object Superclass { string wpnName; int dmg; float fRate; Weapon(string weaponName, int damage, float fireRate) // constructor, needs to have the same name with its class { this.wpnName = weaponName; this.dmg = damage; this.fRate = fireRate; Debug.Print("Created ${this.wpnName} with Damage ${this.dmg} and Fire Rate ${this.fRate}"); } }
A few notes:
The equivalent script in visual scripting looks like this: