GC-Szenario-Tutorial/2. Szenarioscript

Aus Clonk Wiki
Wechseln zu: Navigation, Suche

Im vorigen Abschnitt wurde bereits ein komplettes Szenario mit Landschaft und Ausstattung erzeugt. Allerdings fehlt noch jede weiter gehende Funktionalität und damit Spielsinn. In diesem Kapitel soll ein Szenarioscript entwickelt werden, das das Szenario zumindest mit den Grundfunktionen ausstattet.

Der Beginn[Bearbeiten]

In dem Szenario befindet sich bereits ein Script (Script.c), der bisher folgendermaßen aussieht:

C4Script
/*-- Neues Szenario --*/

#strict

func
 
Initialize()
 
{
  

  
return(1);
}

Von diesem haben wir bisher nicht viel bemerkt, weil er auch nichts tut. Die erste Zeile ist ein Kommentar und wird von der Engine ignoriert (es sollte gleich mal auf "Demonstrations-Szenario" oder etwas ähnlich passenderes geändert werden), und die einzige Funktion, Initialize, ist leer.

Wir wollen erstmal langsam anfangen, und fügen einen einzigen Befehl zu Initialize hinzu:

C4Script
func
 
Initialize()
 
{
  
Message("Willkommen im Demonstrationsszenario!");
    

  
return(1);
}

Starten wir jetzt das Szenario, so wird ebendiese Meldung angezeigt. Wir können sie auch immer wieder erzeugen, indem wir Initialize() in der Konsole eingeben (dadurch wird unsere Funktion ausgeführt).


Nun wollten wir dem Clonk eine Startposition zuweisen. Da wir uns die nicht ausdenken können, schalten wir in der gestarteten Engine in den Objektbearbeitungsmodus (weißer Pfeil) und suchen mit der Maus die Position, an der wir ihn haben wollen. Während wir die Maus über das Spielfeld bewegen, erscheinen unten im Enginefenster die Koordinaten, an denen sich die Maus gerade befindet. Da wir den Clonk links im See starten wollen, lesen wir diese Position ab (z.B. "40/410").

Als nächstes müssen wir das konkrete Script schreiben, das den Clonk des Spielers an diese Stelle versetzt. Das geht (vielleicht überraschenderweise) nicht in Initialize, da die Spieler dem Spiel erst danach beitreten.

Statt dessen müssen wir eine neue Funktion definieren, die sich speziell um den Spieler kümmert: InitializePlayer. Wir fügen also die Funktion ein:

C4Script
func
 
InitializePlayer(iPlr)
 
{
  
GetCrew(iPlr)->SetPosition(40,
 
410);
}

(du wirst entsprechend deine abgelesene Position einsetzen müssen)

Dieser Script sucht sich mittels GetCrew den Clonk des Spielers und ruft SetPosition für ihn auf. Dies ändert die Position des Clonks entsprechend, wirft den Clonk also sofort in den See. Speichere das neue Script. Beachte: iPlr gibt die Spieler Nummer des beigetretenem Spielers. Also wenn Spieler 1 Beitreten würde, gibt iPlr 0 an.

Falls du in den Optionen "Bearbeitete Objekte im Hintergrund nachladen" aktiviert hast, sollte die Engine deine Änderungen sofort realisieren (erkennbar an einer Zeile wie "C4AulScriptEngine linked" in der Konsole). Ansonsten musst du das Szenario neu starten, damit dein neues Script geladen wird.

Lässt du jetzt einen Spieler beitreten, so startet sein Clonk auch wirklich an angegebener Position (hattest du bereits einen Spieler im Spiel und hast die Scriptänderungen nachgeladen, so kannst du durch Ausführen von "InitializePlayer(iPlr)" in der Konsole die Initialisierung nachholen).

Zur Perfektionierung des Starts fügen wir jetzt noch eine Zeile in InitializePlayer ein:

C4Script
  
GetCrew(iPlr)->SetComDir(COMD_UpRight);

... damit der Clonk des Spielers am Anfang nach rechts schwimmt. Nun kann man bereits normal starten und bis an den Fuß unserer Burg laufen. Doch noch fehlt jemand, der uns sagt, was zu tun ist.

Der König[Bearbeiten]

Nur zur Erinnerung: Ein böser Magier hat dem König seine Krone - die Quelle aller seiner Macht - weggenommen und ihn aus seiner Burg ausgeschlossen. Jetzt steht er mehr oder weniger unschlüssig vor der Burg und wartet auf zufällig vorbeikommende clonkische Helden, die ihn aus seiner Situation befreien (eine typische Reaktion von Königen).

Also benötigen wir an dieser Stelle einen Clonk - keinen König, weil der trägt ja bereits seine Krone - der vor der Burg steht. Entsprechend ergänzen wir Initialize:

C4Script
func
 
Initialize()
 
{
  
// Spieler begrüßen
  
Message("Willkommen im Demonstrationsszenario!");
  
// König erzeugen (sieht noch wie ein Clonk aus)
  
CreateObject(CLNK,
 
570,
 
370,
 
-1);
  
return(1);
}

Beachte dabei, dass der Clonk mit Besitzer "-1", also ohne Besitzer erzeugt wird. Ansonsten würde der Clonk dem Spieler mit der Nummer 0 - also dem ersten Spieler - gehören! Außerdem wurden Kommentare in den Script eingefügt, die kurz beschreiben, was getan wird. Dies ist generell eine gute Idee, falls man später mal Zweifel hat, wofür ein bestimmtes Script gut war.

Führt man nach dem Nachladen des Scripts nun Initialize() in der Konsole aus, so wird auch wirklich ein Clonk vor der Burg erzeugt. Allerdings schaut er zufällig entweder nach rechts oder links, und ist in unroyales Standard-Blau gekleidet.

Das können wir selbstverständlich ändern. Dazu müssen wir allerdings bei Erzeugung einen Verweis auf das König-Objekt speichern, damit wir ihn später im Script direkt ansprechen können. Da wir diesen Verweis aller Wahrscheinlichkeit auch später im Szenario noch brauchen werden, speichern wir den Verweis in einer statischen (global gültigen) Variable.

Das komplette Script sieht dann ungefähr folgendermaßen aus:

C4Script
/*-- Demonstrations-Szenario --*/

#strict

// Verweis auf den König
static
 
pKing;

func
 
Initialize()
 
{
  
// Spieler begrüßen
  
Message("Willkommen im Demonstrationsszenario!");
  
// König erzeugen (sieht noch wie ein Clonk aus)
  
pKing
 
=
 
CreateObject(CLNK,
 
570,
 
370,
 
-1);
  
pKing->SetColor(1);
  
pKing->SetDir(DIR_Right);
 

  
return(1);
}

func
 
InitializePlayer(iPlr)
 
{
  
GetCrew(iPlr)->SetPosition(40,
 
410);
  
GetCrew(iPlr)->SetComDir(COMD_UpRight);
}

Damit trägt der König zu anfang rot (Farbe 1), und schaut in Richtung seiner Burg (also nach rechts). Das können wir entsprechend testen, indem wir ggf. den alten König löschen (anklicken und "Entf" auf der Tastatur) und dann wieder einmal in der Konsole "Initialize()" ausführen.

Jetzt steht der König da, wo er hingehört - aber im Szenario passiert immer noch nicht viel.

Interaktivität[Bearbeiten]

Nun wäre es schön, wenn der König auch reagieren würde, wenn der Clonk in seine Nähe kommt. Er sollte den Spieler bemerken und ihm erzählen, was zu tun ist. Dazu benötigen wir ein Mittel, solche Abläufe darzustellen.

Glücklicherweise hat Clonk-Script einfache Bordmittel, um globale Abläufe zu beschreiben. Dazu gibt es nämlich den sogenannten Script-Counter. Ist dieser gestartet, so zählt er jedes mal eine Einheit hoch. Dabei wird jedes Mal im Szenarioscript eine Scriptfunktion "Script[x]" aufgerufen (wobei [x] = aktuelle Script-Counter).

Um den Counter zu starten, fügen wir folgenden Befehl in Initialize ein:

C4Script
  
// Script-Counter starten
  
ScriptGo(true);

Außerdem erzeugen wir eine neue Scriptfunktion:

C4Script
func
 
Script10()
 
{
  
Message("Script-Counter ist auf 10!");
}

Lade das Script in die Engine und gib "ScriptGo(true)" in die Konsole ein (Wir wollen ja nicht noch einen König erzeugen...). Du solltest sehen, wie der "Script"-Zähler in der Konsole startet. Ist er bei 10 angekommen, so wird die Meldung ausgegeben.

Nun bringt uns ein ewig fortlaufender Zähler noch relativ wenig. Um auf das Geschehen im Spiel reagieren zu können, brauchen wir eine Möglichkeit, den Scriptcounter zu setzen. Genau das leistet der goto-Befehl. Füge z.B. in die Funktion Script10 folgende Zeile ein:

C4Script
  
goto(0);

Lädst du das Script in die Engine und führst in der Konsole einmal "goto(0)" aus, so sollte jetzt ungefähr alle 3 Sekunden die Nachricht ausgegeben werden.

Jetzt wird es langsam Zeit, uns um unser eigentliches Ziel zu kümmern: den König zu einer Reaktion zu bewegen. Wir wollen den Scriptcounter solange unter 10 halten, bis der Spieler nah genug um entthronten König ist, damit dieser ihn bemerken kann. Dazu ersetzen wir Script10 durch folgende Version:

C4Script
func
 
Script10()
 
{
  
if(pKing->ObjectDistance(GetCrew())
 
>
 
70)
    
goto(10);
  
else
    
Message("Ah, endlich, ein Untertan!|Du musst mir helfen!",
 
pKing);
}

Wir prüfen mit ObjectDistance die Entfernung vom König zum Clonk des Spielers. Ist diese größer als ein bestimmter Wert (hier: 70), so setzen wir den ScriptCounter erneut auf 10, sodass Script10 nach weiteren 10 Frames erneut aufgerufen wird (der Script-Counter wird sozusagen "festgehalten").

Dies passiert nun so lange, bis der Clonk nahe genug an den König kommt. Ist das der Fall, so wird durch Message eine Nachricht über dem König angezeigt. Da in diesem Fall goto nicht ausgeführt wird, tickt der Script-Counter danach auch weiter hoch.

Ausprobieren kannst du dies, indem du wieder das Script nachlädst und "goto(0)" in der Konsole eingibst.


Nun sagt der König zwar etwas, doch schaut er dabei immer noch starr auf seine Burg. Könige sind üblicherweise höflich und kommen ihrem nahenden Retter vielleicht sogar ein paar Schritte entgegen. Also fügen wir eine entsprechende Anweisung ein:

C4Script
func
 
Script10()
 
{
  
if(pKing->ObjectDistance(GetCrew())
 
>
 
100)
    
goto(10);
  
else
  
{
    
SetCommand(pKing,
 
"MoveTo",
 
0,
 
pKing->GetX()
 
-
 
20);
    
Message("Ah, endlich, ein Untertan!|Du musst mir helfen!",
 
pKing);
  
}
}

Des weiteren sollte er fortfahren, und dem Spieler seinen Auftrag erklären. Das macht er in entsprechenden weiteren Script-Funktionen:

C4Script
func
 
Script20()
 
{
  
Message("Ich war mal ein mächtiger König, aber jetzt hat ein böser Magier meine Krone gestohlen!",
 
pKing);
}
func
 
Script30()
 
{
  
Message("Sie ist da drin! Doch ich komme nicht rein! Kannst du sie rausholen, bitte?",
 
pKing);
}
func
 
Script40()
 
{
  
Message("Ohne meine Krone bin ich hilflos...",
 
pKing);
  
SetCommand(pKing,
 
"MoveTo",
 
0,
 
pKing->GetX()
 
+
 
20);
}

Wie gehabt kannst du dies wieder durch Nachladen des Scripts und "goto(0)" testen.

Damit hätten wir die erste Stelle, an der der Spieler etwas nicht triviales machen muss - wenn man das Ausgraben eines Lehmklumpens mal als nicht trivial ansieht. Er wird also die Burg stürmen - und drinnen selbstverständlich erwartet.

Der Magier[Bearbeiten]

Auch böse Magier haben nichts besseres zu tun, als den lieben langen Tag auf clonkische Helden zu warten, die ihnen ihre neuerlangte Macht wegnehmen wollen. Entsprechend schreiben wir eine weitere Script-Funktion, die einen Magier erzeugt, sobald der Clonk des Spielers die Burg betritt:

C4Script
static
 
pMage;

func
 
Script50()
{
  
if(GetCrew()->GetX()
 
>
 
700
 
&&
 
GetCrew()->GetY()
 
<
 
400)
  
{
    
pMage
 
=
 
CreateObject(SCLK,
 
880,
 
310,
 
-1);
    
pMage->SetDir(DIR_Left);
    
Message("Elender! Du wirst die Krone niemals bekommen!",
 
pMage);
  
}
  
else
    
goto(50);
}

Wir überprüfen anhand horizontaler (X) und vertikaler (Y) Position, ob sich der Spieler in der Burg befindet - damit wird sichergestellt, dass der Spieler nicht durch Untergraben der Burg das Script auslöst.

Nun ist der Auftritt des Magiers noch etwas effektlos. Dies lässt sich schnell beheben, indem wir folgende zwei Zeilen nach der Erzeugung des Magiers (CreateObject) einfügen:

C4Script
    
CastParticles("FSpark",
 
20,
 
40,
 
pMage->GetX(),
 
pMage->GetY(),
 
20,
 
30);
    
Sound("Magic*");

Außerdem sollte er auch etwas unternehmen, um unseren Spieler von seinem heldenhaften Unterfangen abzuhalten. Wir nehmen mal an, dass er die Krone im Kontor versteckt hat. Und um sie zu schützen, wirkt er einen Zauber:

C4Script
func
 
Script52()
{
  
pMage->SetAction("Magic");
  
FindObject(CPOF)->CastObjects(MFLM,
 
10,
 
10);
}

func
 
Script60()
{
  
Message("BWHAHAHA!|Pass auf König Arthur, du bist der nächste auf meiner Liste!",
 
pMage);
}

func
 
Script70()
{
  
CastParticles("FSpark",
 
20,
 
40,
 
pMage->GetX(),
 
pMage->GetY(),
 
20,
 
30);
  
Sound("Magic*");
  
RemoveObject(pMage);
}

CPOF ist die ID des Kontors, MFLM die ID der ewigen Flammen von dem gleichnamigen Zauber. Es werden also ewige Flammen vor dem erstbesten Kontor im Szenario verstreut. Danach verrät uns der Magier programmgemäß seine weiteren Pläne und widmet sich weiter seinem bösen Handwerk - woanders.


Wie geht es weiter? Unser Held wird sich vermutlich von den Flammen wenig abschrecken lassen. Hier könnten wir uns ggf. etwas wirkungsvolleres einfallen lassen.

Außerdem wird er bisher im Kontor noch nichts finden, da es im Szenario keine Krone gibt. Das Problem liegt sogar noch tiefer: Es existiert kein Objektdefinition "Krone" (vorausgesetzt, wir wollen nicht unnötigerweise das Fantasy-Pack einbinden).


Es führt also kein Weg drum herum: Wie benötigen neue Objektdefinitionen. Darum wird es im nächsten Abschnitt gehen.

to be continued...