Diese Anleitung beruht auf dem Ilios Scripting Guide von Chris Tikkos (Dionous Games) und erscheint hier mit seiner Erlaubnis. Beispielprojekte und Abbildungen stammen von Dionous Games. Die Abbildungen zeigen die englische Oberfläche, deshalb stehen die Bezeichnungen der Bedienelemente hier ebenfalls auf Englisch.
Bevor du loslegst
Visionaire Studio wurde ursprünglich für 2D Point-and-Click-Adventures entwickelt. Seit Version 5 machen die Box2D-Physik und die Skriptsprache Ilios auch andere Genres möglich, zum Beispiel physikbasierte Plattformer. Diese Anleitung führt dich durch deine ersten Ilios-Skripte und die Physik-Einrichtung eines kleinen Plattformer-Prototyps.
Nützliche Verweise:
- Ilios Referenz für visuelles Skripting in diesem Wiki
- Offizielle Ilios-Dokumentation (Englisch)
- Box2D-Dokumentation (Englisch)
Voraussetzungen
- Visionaire Studio 5.1.9.2 oder neuer
- Das Grafikpaket Animated Pixel Adventurer von rvros. Es ist kostenlos, über eine Spende freut sich der Künstler aber sehr.
- Das Hintergrundbild für das Physik-Beispiel
Tiefe Programmierkenntnisse brauchst du nicht, aber die Grundlagen der objektorientierten Programmierung (Klassen, Funktionen, Variablen) helfen sehr. Die Anleitung erklärt nicht das Programmieren an sich, sondern zeigt, wie diese Konzepte in Visionaire funktionieren.
Code oder visuelles Skripting
Visionaire bietet zwei Wege, Ilios-Skripte zu schreiben:
- Code: klassisch getippter Programmcode
- Blocks: visuelles Skripting mit Bausteinen
Öffne in der oberen Leiste „Visual Scripting“, lege ein neues Skript an und wähle den gewünschten Editor:

Beide Arten lassen sich in einem Projekt mischen. Manche Skripte kannst du also als Code schreiben und andere mit Blöcken.
Dein erstes Ilios-Skript
Ilios bringt einen komponentenbasierten Ansatz nach Visionaire. Du schreibst Behaviours (Verhaltensklassen) und hängst sie über den Reiter „Components“ an Objekte, Charaktere, Szenen, Interfaces, Buttons oder an das Spiel selbst.
1. Skriptblock anlegen
Wechsle in den Reiter „Visual Scripting“ und lege einen neuen Eintrag an. Er nimmt deine Verhaltensklasse auf. Code und Blöcke können dasselbe, die Wahl ist Geschmackssache. Dieses Beispiel zeigt zuerst die Blöcke, die Code-Fassung folgt am Ende des Abschnitts.

2. Verhaltensklasse erstellen
Klicke auf die Arbeitsfläche, um die Blockliste zu öffnen, wähle „New Class“ und bestätige mit einem Doppelklick.

Gib deiner Klasse einen Namen und weise ihr eine Oberklasse (Superclass) zu. Eine Oberklasse enthält bereits das Grundverhalten eines Visionaire-Elements. Deine Klasse erbt es, du kannst ihre Funktionen überschreiben und deine Klasse als Komponente an das passende Element hängen.
Verfügbare Oberklassen:
GameBehaviourObjectBehaviourCharacterBehaviourSceneBehaviourButtonBehaviourInterfaceBehaviour
Für dieses Beispiel erstellst du ein Objektverhalten, wähle also ObjectBehaviour:

Klicke auf „OK“. Der Klassenknoten erscheint auf der Arbeitsfläche:

3. Funktion anlegen
Über das + fügst du Anschlusspunkte (gelbe Punkte) für Funktionen, Variablen, Zustände und mehr hinzu. Ziehe einen Anschlusspunkt auf die Arbeitsfläche und lass ihn los. Dann siehst du die Liste der Knoten, die du anschließen kannst.

Hier kannst du auch Funktionen der Oberklasse ObjectBehaviour überschreiben, zum Beispiel FixedUpdate. Diese Funktion läuft in einem festen Takt, etwa 50-mal pro Sekunde:

Der Block FixedUpdate ist gelb, das kennzeichnet eine Funktion:

Jetzt rufst du eine Methode auf, die Text ausgibt: Debug.Print(). Klicke auf die Arbeitsfläche, suche nach debug und wähle per Doppelklick „Print(string)“ aus der Klasse Debug:

Der Block sieht so aus:

Eingänge liegen links am Block, Ausgänge rechts. Der violette Punkt ist ein Eingang vom Typ string (Text). Du kannst einen Text anschließen oder ihn direkt in das kleine Feld neben dem Punkt schreiben:

4. Ausführungsfluss verbinden
Der Print-Block tut nichts, solange er nicht Teil des Ausführungsflusses ist, der weißen Linie zwischen den Blöcken. Die Ausführung beginnt bei einer Funktion, folgt der Linie und führt alle Blöcke der Reihe nach aus.

Ziehe den kleinen weißen Pfeil von FixedUpdate auf den Block Debug.Print. Der Hinweis add exec erscheint. Lass los, und dein erstes Skript ist fertig:

5. Verhalten an ein Objekt hängen
Das neue Verhalten steht jetzt im Reiter „Components“ deines Objekts zur Verfügung (hier test-player):

Starte das Spiel und öffne die Konsole mit der Tab-Taste. Unter „Lua Execute“ siehst du die Ausgabe von Debug.Print, etwa 50-mal pro Sekunde, weil der Block in FixedUpdate steckt.

6. Funktionsargumente nutzen
Jetzt soll zusätzlich der Name des Objekts erscheinen. Der Schlüssel dazu ist das Argument this der Update-Funktion:

this verweist auf das Objekt, an dem das Verhalten hängt. Ziehe this auf die Arbeitsfläche:

Damit hast du Zugriff auf alle Eigenschaften des Objekts. Ziehe den blauen Ausgang auf die Arbeitsfläche und wähle „Name“ aus der Liste:

Der Knoten „Name“ hat einen violetten Ausgang, liefert also einen Text:

Verbinde ihn mit einem zweiten Debug.Print-Block:

Verbinde zum Schluss beide Print-Blöcke mit der Ausführungslinie, damit sie nacheinander laufen:

Die Konsole zeigt beide Zeilen:

Dasselbe Skript als Code
class TestClassCode : ObjectBehaviour
{
override void FixedUpdate()
{
Debug.Print("Hello World! -Code Version-");
Debug.Print(this.Name);
}
}
Die Klasse hat einen anderen Namen, damit du in der Komponentenauswahl zwischen Block-Fassung und Code-Fassung wechseln kannst:

Beispielprojekt: quickstart.veb (Dionous Games)
Drei Arten, Ilios-Skripte zu nutzen
- Als Komponente, die einem Objekt, einer Szene, einem Charakter, einem Button, einem Interface oder dem Spiel ein Verhalten gibt
- Als statische Klasse mit Funktionen, die jedes andere Skript nutzen kann
- Als Datentyp, also als Vorlage für Daten, die andere Skripte verwenden
Skript-Komponenten
Damit du ein Skript an ein Element hängen kannst, muss seine Klasse von der passenden Oberklasse erben:
| Element | Oberklasse |
|---|---|
| Objekt | ObjectBehaviour |
| Szene | SceneBehaviour |
| Button | ButtonBehaviour |
| Interface | InterfaceBehaviour |
| Charakter | CharacterBehaviour |
| Spiel | GameBehaviour |
Komponenten sind Instanzen: Du kannst dasselbe Skript mehrfach hinzufügen, und jede Instanz behält ihre eigenen Daten. Um eine Instanz aus einem anderen Skript zu nutzen, brauchst du einen Verweis auf genau diese Instanz.
Bestimmte Funktionen einer Komponente ruft Visionaire automatisch auf, zum Beispiel Awake und FixedUpdate. So verbindet sich dein Code mit dem Geschehen im Spiel:
class PlayerMoveC : ObjectBehaviour
{
// wird einmal vor dem ersten Update aufgerufen
override void Awake()
{
// Code zur Initialisierung
}
// wird in einem festen Takt aufgerufen
override void FixedUpdate()
{
// Code, der das Objekt aktualisiert
}
}
Etwas passiert nur, wenn eine Komponente auf einen Auslöser reagiert: eine Eingabe, eine Kollision, einen Aufruf aus einem anderen Skript oder einen regelmäßigen Aufruf wie FixedUpdate oder Update.
Statische Klassen
Statische Klassen hängen an keinem Element. Sie existieren einmal im Projekt, und jedes Skript kann sie direkt über ihren Klassennamen nutzen, ohne Verweis. Das macht sie praktisch für Hilfsfunktionen, die viele Skripte brauchen.
Beispiel: eine Hilfsklasse, die den Abstand zwischen zwei Punkten berechnet:
class MathUtils
{
// Abstand zwischen zwei Punkten
static float CalculateDistance(vec2 pointA, vec2 pointB)
{
float distance = vec2.Distance(pointB, pointA);
return distance;
}
}
Aufrufen kannst du sie von überall so:
MathUtils.CalculateDistance(new vec2(0,0), new vec2(3,4))
Zum Testen nimmst du den Aktionsteil „Run ilios script“ und gibst das Ergebnis in der Konsole aus:
Debug.Print(MathUtils.CalculateDistance(new vec2(0,0), new vec2(3,4)).ToString());
Debug.Print() nimmt nur Text an, deshalb wandelt .ToString() das Ergebnis vom Typ float vorher um.
Eigene Datentypen
Mit Skripten kannst du auch eigene Datentypen festlegen: Structs, einfache Klassen, Enums oder Interfaces. Du nutzt sie im ganzen Spiel als Datentyp oder als Vorlage, siehe Konstruktoren weiter unten.
Variablen und Outlets
Ein einfaches Beispiel mit zwei Variablen:
class Player : ObjectBehaviour
{
outlet float playerHealth;
outlet bool isDead;
void Damage(float dmg)
{
playerHealth -= dmg;
if(playerHealth < 0)
{
// der Spieler ist besiegt
isDead = true;
}
}
void AddHealth(float health)
{
playerHealth += health;
if(playerHealth > 100)
{
// Gesundheit ist voll
playerHealth = 100;
}
}
}
Das Schlüsselwort outlet zeigt die Variable im Editor an, sodass du ihren Wert dort einstellen kannst. Füge die Komponente Player einem Objekt hinzu, um das zu sehen:

Physik des Spielers einrichten
Spieler vorbereiten
- Lege das Spielerobjekt an, zum Beispiel test-player.
- Erstelle eine Ruheanimation, füge ihre Einzelbilder hinzu und stelle sie auf unendliche Wiederholung:

- Platziere die Animation auf der Arbeitsfläche, vorerst bei (40,90):

- Lege die Ruheanimation als Standardanimation des Spielers fest:

Wenn du das Spiel startest, erscheint der Spieler mit seiner Ruheanimation. Weil die Pixelgrafik sehr klein ist, verbessern diese Einstellungen die Darstellung:
- Stelle die Spielauflösung auf 320 × 240 Pixel.
- Vergrößere das Fenster mit einem Skript in der Startaktion:

- Aktiviere den Pixel-Effekt für scharfe Pixel:

Body und Fixture
Visionaire nutzt Box2D für die Physik und fügt sie Objekten über Komponenten hinzu. Ein funktionierendes Box2D-Objekt braucht einen Body (Körper) und eine Fixture (Form):

Der Body enthält die Eigenschaften, die man weder sieht noch anfassen kann:
- Masse: wie schwer er ist
- Geschwindigkeit: wie schnell und in welche Richtung er sich bewegt
- Trägheitsmoment: wie viel Kraft nötig ist, um eine Drehung zu starten oder zu stoppen
- Winkelgeschwindigkeit: wie schnell und in welche Richtung er sich dreht
- Position: wo er sich befindet
- Winkel: in welche Richtung er zeigt
Füge dem Spieler einen Body hinzu:

Box2D kennt drei Arten von Bodies:
- dynamisch: wird von Kräften beeinflusst und reagiert auf Kollisionen, wie unser Spieler
- statisch: wird nicht von Kräften beeinflusst und lässt sich nicht bewegen, wie Wände oder der Boden
- kinematisch: bewegt sich mit vorgegebener Geschwindigkeit, ignoriert aber Kräfte, wie eine bewegliche Plattform. Als die Originalanleitung entstand, unterstützte Visionaire diese Art noch nicht.
Einstellungen des Body:
- „Static“: nicht ankreuzen, der Spieler ist ein dynamischer Body.
- „Fixed Rotation“: verhindert Drehungen, zum Beispiel nach einer Kollision. Der Spieler braucht das.
- „Linear Damping“: bremst die Bewegungsgeschwindigkeit.
- „Angular Damping“: bremst die Drehgeschwindigkeit.
- „Gravity Scale“: schwächt oder verstärkt die Schwerkraft. 1 ist normale Schwerkraft, -1 kehrt sie um, 0 schaltet sie ab.
Ein Body allein hat keine Form und kann nicht kollidieren. Größe, Form und Material kommen von den Fixtures. Ein Body kann mehrere Fixtures haben, und ihre Anordnung beeinflusst seinen Schwerpunkt. Wenn zwei Bodies zusammenstoßen, entscheiden ihre Fixtures, wie sie reagieren. Eigenschaften einer Fixture:
- Sensor: Ein Sensor erkennt Kollisionen, reagiert aber nicht darauf. Der Spieler ist kein Sensor.
- Reibung (friction): wie rutschig sie ist
- Rückprall (restitution): wie stark sie abprallt
- Dichte (density): wie schwer sie im Verhältnis zu ihrer Fläche ist
- Form (shape): Polygon oder Kreis
Visionaire bietet drei Formen: Rechteck, Kreis und Kante (freie Form). Für den Spieler wählst du das Rechteck:

Das rote Rechteck ist die Kollisionsgrenze des Spielers. Starte das Spiel: Der Spieler fällt wegen der Schwerkraft aus dem Bild. Füge also das Hintergrundbild und ein Bodenobjekt mit einem statischen Body und eigener Fixture hinzu:

Starte das Spiel erneut. Der Spieler steht jetzt auf dem Boden: Deine erste Kollision funktioniert.
Beispielprojekt: player-setup.zip (Dionous Games)
Konstruktoren
Ein Konstruktor ist eine besondere Funktion, die automatisch läuft, wenn eine neue Instanz entsteht. Er legt die Anfangswerte der neuen Instanz fest.
Lege einen neuen Block an und erstelle im Code-Reiter eine Klasse für Waffen mit Konstruktor:
class Weapon : object // erbt von der Oberklasse object
{
string wpnName;
int dmg;
float fRate;
// Konstruktor, muss genauso heißen wie die Klasse
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}");
}
}
- Solche Klassen erben von der internen Oberklasse
object. Sie beschreiben Daten, kein Verhalten, und lassen sich deshalb nicht an Visionaire-Elemente hängen. - Der Konstruktor heißt genauso wie seine Klasse, hier
Weapon. thisverweist auf die Instanz, die gerade entsteht.
Dasselbe Skript als Blöcke (zum Vergrößern anklicken):

Einen Konstruktor legst du im Block-Editor als normalen Funktionsblock an und wählst dort „Constructor“:

Den Konstruktor verwenden
In diesem Beispiel entstehen die Waffen einmal beim Laden des Skripts, also in Awake(). Das Skript hängt am Spielerobjekt, seine Oberklasse ist deshalb ObjectBehaviour:
class MainPlayer : ObjectBehaviour
{
override void Awake()
{
Weapon axe = new Weapon("Axe", 7, 3);
Weapon sword = new Weapon("Sword", 5, 4);
}
}
Dasselbe als Blöcke:

Starte das Projekt und öffne die Konsole: Beide Waffen werden erzeugt.

Konstruktoren überladen
Eine Klasse kann mehrere Konstruktoren mit unterschiedlicher Signatur haben, also mit unterschiedlichen Parametern in den Klammern. Neben Weapon(string weaponName, int damage, float fireRate) könntest du Weapon() ohne Parameter und Weapon(string weaponName) nur mit Namen anlegen. Dann lässt sich eine Waffe auf verschiedene Arten erzeugen, zum Beispiel mit new Weapon("Spear") oder new Weapon().
Beispielprojekte (Dionous Games): constructors.veb (Blöcke) und constructors-code.veb (Code)
Nächster Schritt: Baue für diesen Spieler ein Level im Plattformer-Tutorial mit Tiled und Box2D.
Mehr zu Konstruktoren, Destruktoren und statischen Konstruktoren findest du in der Ilios Referenz.