Scripting Engine – Teil 2

Nachdem ich im ersten Teil ein wenig über die Grundlagen und die Philosophie einer Scripting Engine geschrieben habe, möchte ich in diesem zweiten, lange erwarteten Teil mehr in die Details gehen und weitere Grundlagen schaffen, die im weiteren Verlauf sehr wichtig sein werden.

Ein kurzer Rückblick: Es geht um Scripting, welches wir im ersten Teil als das Fernsteuern von Systemen definiert haben. In dieser mehrteiligen Artikel-Serie möchte ich gemeinsam mit euch eine Scripting Engine implementieren und alle notwendigen Hintergrundinformationen liefern. Trotzdem müsst ihr später keine eigene Engine entwickeln, sondern könnt die in die starLiGHT.Engine integrierte verwenden. Diese werde ich nämlich im Laufe dieser Artikel-Serie entwickeln.

Die bisherigen Teile

Hier eine kleine Übersicht über die bisherigen Teile, falls jemand diese nochmal lesen möchte.

Teil 1 – Die Grundlagen
Teil 2 – Verträge und Interfaces

Los geht’s…

Die Planung ist beim Scripting für ein Spiel der wichtigste Bestandteil, denn wenn dabei Fehler gemacht werden, dann ist die Chance sehr groß, daß das Scripting später nicht korrekt funktioniert oder eher ein Hindernis für den Scripter oder den Modder darstellt. Das Problem ist dabei wie immer, daß diese Planung maßgeblich davon abhängt, wieviel Erfahrung der Scripter hat und wie genau das Spiel an sich geplant ist. Selbstverständlich kann diese Planung evolutionär wachsen, aber spätestens zum Zeitpunkt der Veröffentlichung, sei es intern an den Scripter oder extern an den Spieler und Modder sollte diese Planung abgeschlossen und weitestgehend wasserdicht sein. Das schöne dabei ist jedenfalls, daß diese Planung eine wunderbare Grundlage für eine Dokumentation darstellt. Denn wenn es keine Dokumentation gibt, kann weder Scripter, noch Modder etwas mit der Scripting Engine anfangen. Woher soll er auch wissen, wie diese zu verwenden ist, ist er doch kein Entwickler…

Der Vertrag

Da wir mit dem Scripting ja im Grunde genommen die Programmlogik (also das Script) vom ausführenden Code (ein Teil des Spiels) trennen wollen, müssen wir irgendwo festlegen, wie die Schnittstellen aussehen. Schnittstelle hat der ein oder andere sicherlich schon einmal gehört und denkt dabei jetzt an das C#-Interface. Sehr gut und auch sehr richtig, denn genau darauf wollte ich hinaus. Ein Interface ist unser Vertrag. Er regelt, wie ein richtiger Vertrag aus dem täglichen Leben auch, wie das Script und das Spiel miteinander umgehen müssen, um den Vertragsgegenstand Scripting in einem Spiel mit einer Scripting-Engine sicherzustellen.

Dieses Konzept, daß wir hier anwenden möchten ist ein Konzept der Softwareentwicklung und ist eng verbunden mit dem Design by contract.

Dies klingt vielleicht im ersten Moment vielleicht etwas verwirrend und auch umständlich, aber es wird uns eine große Hilfe sein.

Der erste Schritt, den wir vornehmen müssen, ist also unseren Vertrag erarbeiten auf den wir später aufbauen können. Ein Vorteil dieses Vertrages ist jedenfalls, daß wir bei dem hier eingesetzten Verfahren später eine strenge Typisierung haben werden und auch IntelliSense verwenden können. Aber was wollen wir nun mit dem Vertrag genau erreichen? Der Vertrag, der ja eigentlich ein Interface ist, bietet eine definierte Schnittstelle, die es ermöglicht, bestimmte Gegebenheiten in unserem Programm zu steuern.

Ein kleines Beispiel um diesen Sachverhalt etwas zu verdeutlichen. Wir haben in unserem Spiel eine Methode, die es uns ermöglicht ein Sprite von Position A nach Position B zu bewegen. Dies ist nicht sehr schwer, wird aber häufig benötigt:

Zunächst definieren wir ein paar Variablen.

bool running;
Vector2 start;
Vector2 end;
Vector2 pos;
double timeAccu = 0.0;
double time = 10.0;

Und eine passende Methode, die diese Variablen befüllt und initialisiert.

public void SpriteMove(Vector2 startPoint, Vector2 destinationPoint, double timeInSeconds)
{
  this.start = startPoint;
  this.end = destinationPoint;
  this.time = timeInSeconds;
  this.timeAccu = 0.0;
  this.running = true;
}

Bisher kein Hexenwerk und auch die Änderungen in Update und Draw sind nichts besonderes.

public void Update(GameTime gameTime)
{
  if (this.running == true)
  {
    pos = Vector2.Lerp(start, end, (float)(timeAccu / time));
    timeAccu += gameTime.ElapsedGameTime.TotalSeconds;
    timeAccu = timeAccu > time ? time : timeAccu;
  }
}

public void Draw(GameTime gameTime)
{
  if (this.running == true)
  {
    spriteBatch.Begin();
    spriteBatch.Draw(texture, this.pos, Color.White);
    spriteBatch.End();
  }
}

Diese paar Zeilen in einem frischen Game-Projekt, garniert mit dem Laden einer Textur und dem geeigneten Methodenaufruf führen dazu, daß sich eine Textur von Punkt A (startPoint) nach Punkt B (destinationPoint) innerhalb von einer bestimmten Anzahl von Sekunden (timeInSeconds) bewegt. Dinge, die viele von uns sicherlich schon sehr, sehr häufig gemacht haben, aber auch der Einsteiger sollte keine großen Schwierigkeiten haben, dies zu verstehen.

Das Script

Um nun dem Scripter oder Modder zu ermöglichen, z.B. eine Cutscene zu erstellen, müssen wir ihn in die Lage versetzen, daß er diese Methode ansprechen kann und damit von außen Kontrolle auf unser Spiel hat. Dies könnte z.B. so aussehen:

<script name="cutscene01">
<code>
script.MoveSprite(new Vector2(100,10), new Vector2(700,10), 10);
</code>
</script>

Sieht aus wie C#, schreibt sich wie C#, ist C#. Unser erstes Script. Die Herausforderung ist nun, daß dieses Script extern erstellt wird, und im passenden Moment von unserem Spiel geladen und ausgeführt wird und die passenden Methoden aufruft.

Dazu werden wir in den nächsten Teilen die folgenden Dinge entwickeln und erarbeiten:

1. Einen Script-Loader, der aus dem XML/C# Gemisch unseres Scriptes eine richtige und gültige Klasse erzeugt
2. Einen FileSystem-Watcher, der unter Windows Änderungen an unserem Script erkennt und diese sofort übernimmt
3. Eine ContentPipeline-Extension, die ein Script für die Verwendung auf der XBox, dem Windows Phone und dem Zune vorkompiliert (da dort dynamisches Kompilieren nicht möglich ist)

Dazu aber in späteren Teilen mehr.

Das Interface

Aber warum benötigen wir nun dieses angesprochene Interface? Immer langsam mit den jungen Pferden. Erstmal definieren wir unser noch kleines Interface.

public interface ISimpleScriptInterface
{
  MoveSprite(Vector2 startPoint, Vector2 destinationPoint, double TimeInSeconds);
}

Dieses Interface versetzt uns in die Lage, eine externe Komponente (ich nenne diese jetzt mal Script_CutScene01.dll) mit unserem ScriptHost zu verbinden. Gleichzeitig ist es eine Auflistungen aller Methoden und Eigenschaften, sowie Variablen, die der Scripter bzw. Modder zur Verfügung hat.

Ich versteh nur Bahnhof, was ist der ScriptHost?

Der ScriptHost ist der Einstiegspunkt für unser Script. Dieser implementiert das zuvor erstellte Interface und kümmert sich dann darum, daß die aufgerufenen Methoden ihr Ziel finden. In diesem Beispiel würde er die Methode MoveSprite aus unserer Game-Klasse aufrufen.

Und um den Kreis zu schließen und eine Typisierung zu erreichen, ist script aus unserem Beispielscript („script.MoveSprite(…“) vom Typ ISimpleScriptInterface. So sind wir in der Lage unser Script in Visual-Studio zu erzeugen und haben volles Intellisense und eine anständige Syntax-Prüfung.

Fazit

In diesem Teil der Artikelserie Scripting Engine habe ich erklärt, was ein Vertrag ist und das dieser als Interface zwischen dem eigentlichen Script und dem ScriptHost dient. Der ScriptHost kümmert sich dabei um die geregelte Ausführung des Scripts. Ich habe eine Beispielmethode vorgestellt, auf die wir im nächsten Teil näher eingehen werden und diese dann auch in einem Script verwenden werden.

Ein weiterer, wichtiger Punkt wird in den nächsten Teilen dieser Serie die Content Pipeline, sowie das dynamische Kompilieren von Code sein. Ich werde euch erklären, wie ein Script möglichst bequem erstellt, kompiliert und ausgeführt werden kann und wie dies auf den Plattformen mit dem Compact-Framework (XBox, Zune und Windows Phone) möglich ist.

Fragen, Anregungen, Lob und Kritik nehme ich natürlich gerne in den Kommentaren entgegen und werde darauf natürlich näher eingehen.

Advertisements

Veröffentlicht am 11.03.2011 in Grundlagen, Mini-Tutorial, Windows Phone, XBox360, XNA, XNA 3.1, XNA 4.0 und mit , , , getaggt. Setze ein Lesezeichen auf den Permalink. 7 Kommentare.

  1. *erster*
    Was ist denn los? Hier war schon mal mehr los.

    Jedenfalls:
    Hast sehr interessante Themen angesprochen, die allerdings erst im dritten Teil folgen. Ich warte schon seit dem dieser Teil erschienen ist drauf 🙂
    Allerdings war ich enttäuscht, wie kurz das ganze doch war.. dachte ich werde mich mit etwas beschäftigen können. Aber dann wohl im nächsten Teil 😉

    • Mit interessante Themen meinst du sicherlich Script-Loader, FileSystem-Watcher und ContentPipeline-Extension, oder? 😉

      Dabei ist im Script-Loader eigentlich die gesamte Magie und die ContentPipeline-Extension nur ein wenig Reflection-Magic…

      Im dritten Teil werde ich wahrscheinlich die meisten dieser Konzepte besprechen. Der Code dafür steht bereits fast vollständig, aber ich muss den halt noch etwas tunen und dann den passenden Beitrag noch schreiben. Kann noch ein wenig dauern…

      • Ich kann den dritten Teil kaum noch erwarten.
        Denn sobald ich das ganze kann, kann ich mit meinem Projekt fortfahren 🙂

  2. Dein Tutorial hier ist wirklich verdammt interessant.
    Kann den dritten Teil kaum abwarten.

    Erklärst du später auch, wie man auf Objekte im Spiel vom Skript aus zugreifen kann? Das wäre nämlich eine Sache wo ich keine Herangehensweise für finde.
    Also wenn man zum Beispiel Objekt A in Richtung von Objekt B bewegen will, wie kann das Skript dann auf Objekt A und Objekt B zugreifen und woher weiß das Skript was Objekt A und Objekt B überhaupt tun können (also ob es bei diesen Objekten überhaupt vorgesehen ist, dass sie ihre Position ändern)?

    Mach weiter so, dein Blog ist echt klasse.

  3. könntest du ein Beispielprojekt hochladen?

    • Ich hab leider gerade kein Beispielprojekt zur Hand. Für konkrete Fragen würde ich dich gerne an indiedev verweisen. Wenn dort genügend Interesse bekundet wird, dann führe ich das Tutorial priorisiert weiter 😉

  1. Pingback: Tot? – Was macht der eigentlich den ganzen Tag? « "Mit ohne Haare"

Kommentar verfassen

Trage deine Daten unten ein oder klicke ein Icon um dich einzuloggen:

WordPress.com-Logo

Du kommentierst mit Deinem WordPress.com-Konto. Abmelden / Ändern )

Twitter-Bild

Du kommentierst mit Deinem Twitter-Konto. Abmelden / Ändern )

Facebook-Foto

Du kommentierst mit Deinem Facebook-Konto. Abmelden / Ändern )

Google+ Foto

Du kommentierst mit Deinem Google+-Konto. Abmelden / Ändern )

Verbinde mit %s

%d Bloggern gefällt das: