Inhalt:
- Einführung
- Beschreibung der Syntax
- Kommentare
- Anweisungen
- Ausdrücke
- Typen, Variablen und Konstanten
- Elementare Typen
- C++ und DAEDALUS
- Variablen und Konstanten
- Bezugsrahmen: global, lokal
- Zuweisungen
- Funktionen
- Definition
- Parameterübergabe
- Funktionsaufrufe
- Klassen, Schablonen und Instanzen
- Klassen
- Prototypen
- Instanzen
- Kontrollstrukturen
- Dynamische Variablen
- Wichtige Unterschiede zu C++
- Schleifen
1. Einführung
Die von uns entworfene Skript-Sprache wird im folgenden Text mit DAEDALUS bezeichnet. Das eigentliche Spielprogramm, das den Skript-Code interpretiert wird in der Regel mit "C++"-Code bezeichnet.
Dieses Kapitel beschreibt die Skriptsprache DAEDALUS. Beim Design inspirierten uns vor allem die Programmiersprache "C", die Skript-Sprache "Quake-C" und zum Teil Pascal. DAEDALUS weicht jedoch in manchen Bereichen weit von diesen ab.
Einsatzgebiete von DAEDALUS
- Dialoge / Informationen
- NSC - AI
- Aufträge
- Text-Pool
- NPCs (Prototypen und Instanzen)
- Gegenstände (Prototypen und Instanzen)
- Implementation von Konzept-Regeln
- "Event-Programme" (Realisierung komplexer logischer Rätsel)
- Deklaration von Sound-Resourcen
- Deklaration von Grafik-Resourcen (auch mit Ani-Information)
2. Beschreibung der Syntax
Es gibt 5 Arten von sogenannten Token: Bezeichner (identifier), Schlüsselwörter (keywords), Literale (literals),
Operatoren (operators) und andere Trennzeichen. Aus diesen Token ist ein Skript zusammengesetzt.
Leerzeichen, Zeilenumbrüche, Kommentare etc. werden ignoriert. Die Länge von Bezeichnern ist nicht beschränkt.
Bezeichner sind Namen für Variablen, Konstanten, Instanzen, Prototypen, Klassen und Funktionen. Ein Bezeichner ist eine Folge von Buchstaben und Ziffern. Das erste Zeichen muß ein Buchstabe sein. Danach sind
Buchstaben, die Ziffern 0 bis 9, sowie der Unterstrich erlaubt.
Schlüsselwörter sind:
class const else float func if
instance int other prototype return self
string this var void while
Literale sind Zeichenketten ("Hallo") und konstante Werte (453). Operatoren werden weiter unten eingeführt.
In dieser Referenz beschreibe ich die Skriptsprache formal in Anlehnung an das Buch "Die C++ Programmiersprache" von Bjarne Stroustrup. Syntaktische Bezeichner werden in Kursivschrift dargestellt.
Die Skriptsprache ist nicht case-sensitive (anders als in C).
Wir empfehlen aber folgende Konventionen für die Namensgebung von Bezeichnern:
(Sei "dng" die Kurzkennung für ein Modul "dungeon.D")
- Funktion: Dng_MoveLift();
- Variable: dng_buttonsPressed;
- Konstante: DNG_NUM_TRIES;
3. Kommentare
Die Zeichenfolge /* beginnt einen Kommentar, der mit der Zeichenfolge */ beendet wird. Die Zeichenfolge // beginnt einen Kommentar, der bis zum Ende der Zeile geht.
Innerhalb eines Kommentars haben die Zeichenfolgen // und /*, sowie */ nach einem Zeilenkommentar keine weitere Bedeutung und werden wie andere Zeichen behandelt.
Das Kommentarkonzept wurde aus C++ übernommen
4. Anweisungen
Anweisungen (statements) sind Deklarationen, Befehle oder auch ein Block von Anweisungen:
statement
vardecl
constdecl
assignment
if-statement
return-statement
statement-block
Beispiel:
statement: door_open();
statement-list: door_open(); opened = TRUE;
statement-block: { door_open(); opened = TRUE; }
5. Ausdrücke
Operatoren
Ein Operator "berechnet" aus einem oder zwei Werten den Ergebnis-Wert. Ein Vergleichsoperator sowie ein boolscher Operator liefert den Integer-Wert 1, falls der Ausdruck wahr ist, sonst 0. Bit-Operatoren manipulieren Variablen auf Bit-Ebene.
Die Operatorenpriorität wurde von C++ übernommen.
operator:
calc-op
cmp-op
bool-op
bit-op
a) Operatoren
calc-op:
+ Addition
- Subtraktion
* Multiplikation
/ Division
% Restdivision (Modulo)
c) Vergleichsoperatoren
cmp-op:
< kleiner
<= kleiner gleich
> größer
>= größer gleich
== Gleichheit
!= Ungleichheit
d) Boolesche Operatoren
bool-op:
! nicht
&& und
|| oder
e) Bitweise Operatoren
bit-op:
& and
| or
f) Vorzeichen
vorzeichen:
+ positiv
- negativ
Ausdruck
Ausdrücke werden mit den oben dargestellten Operatoren wie in C üblich gebildet.
Hier werden nur Beispiele von Ausdrücken gezeigt.
expression:
literal
calc-expressoin
cmp-expression
bool-expression
bit-expression
a) Ausdruck
expression:
-x1 + x2
x1 * (x2 + x3)
(x2 % 2) * x3
b) Vergleiche (compares)
cmp- expression:
x1 < x2
x1 == x2
c) Boolesche Ausdrücke
bool- expression:
x1 && x2
x1 || x2
(x0)
Ein numerischer Wert gilt als wahr, falls er nicht gleich Null ist.
d) Bitweise Manipulationen
bit- expression:
x1 | 5;
x1 & 4;
6. Typen, Variablen und Konstanten
Es existieren zwei Arten von Typen: elementare Typen und Klassen. Es können keine weiteren Typen definiert werden, wie in C/C++ üblich. Klassen haben eine direkte Entsprechung im C-Code der Engine. Variablen einer Klasse sind die sogenannten Instanzen.
Elementare Typen
float
int
string
Entsprechen den Typen in C/C++
Weiterhin können int- und string-arrays gebildet werden:
VAR INT attribute[MAX_ATTRIBUTES];
VAR STRING names[MAX_NAMES];
Es können nur eindimensionale Arrays erstellt werden. Die einzelnen Elemente der Felder werden wie in C++ gewohnt angesprochen, es können dafür aber nur Konstanten als Index benutzt werden:
attribute[1] = 0;
Das erste Element beginnt mit dem Index Null.
C++ und DAEDALUS
Funktionen, Variablen und Konstanten, auf die sowohl im C++-Code als auch in DAEDALUS zugegriffen werden muß, werden mindestens in DAEDALUS deklariert. Dazu dient das Schlüsselwort extern. Variablen und Konstanten werden zusätzlich auch definiert, d.h. ihnen werden Werte zugewiesen.
Variablen und Konstanten
Eine Variablendeklaration muß durch das Schlüsselwort var eingeleitet werden. Dies gilt für jede einzelne Deklaration, nicht, wie in PASCAL, für einen ganzen Block. Auflistungen von Variablen aber (wie in C) möglich:
vardecl:
var type identifier [,identifier]opt [...]opt;
Beispiel:
korrekt:
var int wert1, wert2, wert3;
var string frage, antwort;
var int wert;
falsch:
int value;
Eine Konstantendefinition muß durch das Schlüsselwort const eingeleitet werden:
constsdef:
const type identifier = expression;
Beispiel:
const type identifier[x] = { expression, expression, expression };
Bezugsrahmen: global, lokal
Es existieren zwei verschiedene Bezugsrahmen für Variablen und Konstanten:
- Eine außerhalb jedes Blockes deklarierte Variable oder Konstante ist global verfügbar: Sie ist nach ihrer Deklaration im gesamten folgenden Skriptteil gültig.
- Eine innerhalb eines Blockes deklarierte Variable/Konstante ist lokal im Bezug auf den äußersten Block.
Beispiele:
var int count;
func void Test()
{ var int x; var int y; }
Die variable count ist im Skript global verfügbar. Die Variablen x und y haben wie in C/C++ - den gleichen lokalen Bezugsrahmen: die Funktion Test().
7. Zuweisungen
assignment:
identifier = expression; // einfache Zuweisung
Beispiel:
var int x1;
x1 = 40;
x1 = x1 / 2;
x1 = x1* 3;
8. Funktionen
Definition
Funktionsdefinitionen werden mit dem Schlüsselwort func eingeleitet.
func-def:
func type identifier ( vardecl1opt , ... , vardecl8opt ) statement-block
Beispiel:
func int UsingSchild(var int x1, var string s1)
{
[...]
};
Parameterübergabe
Die Länge der Parameterliste ist unbegrenzt, sollte allerdings aus Speicherplatzgründen möglichst gering gehalten werden. Parameter werden call-by-value übergeben, liefern also keinen Wert zurück. Arrays sind als Übergabeparameter nicht erlaubt.
Funktionsaufrufe
Funktionen werden wie in C++ üblich aufgerufen. Also mit ihrem Bezeichner sowie einer zwingend notwendigen Parameterklammer.
9. Klassen, Schablonen und Instanzen
Klassen
Die Klassendeklarationen beschreiben exakt die Datenstrukturen der Engine. Sie sind also nicht beliebig im Skript erweiterbar, sondern direkt mit der Engine verknüpft.
classdecl:
class classname (base-classname)opt declaration-block
Beispiel:
class Creature (Vob)
{
// attributes
var string name;
var int hitpoints;
var int hp_max;
// actions
var funcref birth;
var funcref death;
};
Die Attribute erhalten Standardwerte:
- Variablen vom Typ int enthalten den Wert 0
- Strings den leeren String ""
- Pointertypen referenzieren NULL.
Prototypen
Mit dem Schlüsselwort prototype ist es möglich, sogenannte Prototypen zu erzeugen, die andere Standardwerte haben:
prototype-def:
prototype class-identifier identifier statement-block
Ein Prototyp kann man als eine "abgeleitete" Klasse ansehen, bei der NUR die Standardwerte geändert wurden. Oder als eine Instanz der Klasse, die nur als Vorlage für weitere Instanzen dient. Die Definition der Standardwerte findet im statement-block satt.
Es findet eine Trennung zwischen Klassendeklaration (class), welche die exakte Struktur der Engine-internen Klasse widerspiegelt, und der Klassendefinition (prototype)statt.
Instanzen
Instanzen von Klassen oder Prototypen stellen deren konkrete Repräsentationen dar. Die Instanz einer Klasse Material ist ein bestimmtes Material mit all seinen Eigenschaften.
instance-def:
instance class-identifier identifier statement-block
instance instance-identifier prototype-identifier statement-block
Der statement-block einer Instanz-Definition dient zur Definition der Variablen, die zu der Klasse gehören. Dabei behalten nicht definierte Attribute oder Aktionen ihren Standardwert. Es sind aber auch alle weiteren Anweisungen erlaubt, soweit sie in irgendeiner Weise Sinn machen.
Kleines (fiktives) Beispiel für Item/Schild/Holzschild
class Item(Vob)
{
// attributes
var int damage;
var int attack;
var string description;
// actions
var funcref use;
};
prototype SchildProtoType (Item)
{
damage = 0;
attack = 0;
descriptions = "";
// actions
use = UsingSchild();
};
instance HolzSchild1 (SchildProtoType)
{
// attributes
description = "Ein recht erbärmliches Holzschild";
};
10. Kontrollstrukturen
Verzweigung: if-then-else
Die if-Abfrage wurde aus C++ übernommen. Zu beachten sind nur die eingeschränkten Möglichkeiten von Ausdrücken. Außerdem wird immer ein statement-block erwartet.
if-statement:
if ( expression ) statement-block
if ( expression ) statement-block else statement-block
falsch: if (x<4) SoundPlay(ID_sound_roar);
richtig: if (x<4) { SoundPlay(ID_sound_roar); };
Funktionswert-Rückgabe: return
In Funktionen, die einen Wert zurückliefern, wird wie in C++ die return-Anweisung verwendet:
return-statement:
return ( expression );
11. Dynamische Variablen
Einige Variablen werden beim Aufruf einer Funktion dynamisch gesetzt und verweisen dann z.B. in einem Dialog auf die Instanz des NPCs (self) und seinen Gesprächspartner(other). Weiterhin wird es Build-In-Funktionen geben, die einen Zugriff auf andere VOBs ermöglicht.
Zur Unterstützung dieses Konzeptes werden einige globale Variablen deklariert und im Spielverlauf dynamisch gesetzt, so daß diese in Funktionen abgefragt werden können. Die Variablen sind momentan folgende:
VAR C_NPC self;
VAR C_NPC other;
VAR C_NPC victim;
VAR C_NPC hero;
VAR C_NPC item;
12. Wichtige Unterschiede zu C++
Hier sollen nur in Stichworten einige Fallstricke aufgezeigt werden, die aus den Unterschieden zu C++ entstehen. Wo C++ auch einzelne Anweisungen erlaubt, muß in DAEDALUS ein Anweisungsblock stehen. Dies betrifft if-Statement.
Beispiel:
if (x<4) { SoundPlay(); };
13. Schleifen
Daedalus kennt keine Schleifen-Konstrukte, aber man kann mittels Rekursion dennoch eine Iteration durchführen.
FOR-Schleife
Beispiel:
var int Zaehler;
func void TestFunc()
{
// ... Beliebiger Code
// Aufruf der Schleife
Zaehler = 0;
ForLoop();
// ... Beliebiger Code
};
func void ForLoop()
{
// ... Code der Schleife
// Um eins erhöhen
Zaehler = Zaehler + 1;
// Schleife läuft von 0 bis 19
if (Zaehler >= 20)
{
ForLoop();
};
};
WHILE-Schleife
Beispiel:
func void TestFunc2()
{
// ... Beliebiger Code
// Aufruf der Schleife
WhileLoop();
// ... Beliebiger Code
};
func void WhileLoop()
{
// Kontrolle, ob While-Bedingung noch erfüllt
if (...)
{
// ... Beliebiger Code
// Und noch einmal
WhileLoop();
};
};
|