5.11 Kommunikation zwischen Verhalten und Objekten

Die Möglichkeit, in Lingo objektorientiert zu programmieren gibt es in der jetzigen Form seit Director 5. Allerdings kommen Objektskripte in der Praxis nicht oft zum Einsatz: Director wird zum Großteil von Multimedia-Autoren, nicht von gelernten Informatikern programmiert, und die schätzen an Director vor allem die visuelle Arbeitsweise. In Programmiersprachen ohne ein visuelles Interface, z.B. Java oder C++, wird dort, wo in Lingo das Drehbuch das Hauptwerkzeug ist, auf Skripting gesetzt, d.h. Animationen werden in einer externen Datei oder internen Ressource definiert, häufig in von Menschen lesbarem Textformat. Diese Möglichkeit bietet Director natürlich auch; aus einer Text-Datei bzw. einem Textdarsteller können z.B. die Eigenschaften bestimmter Variablen oder Objekte generiert werden. Das Einlesen dieser Dateien wird auch als Parsing bezeichnet. Diesen Parser, also der Programmteil, der das Auslesen übernimmt, kann man entweder selbst schreiben oder den seit Director 7 mitgelieferten XML-Parser benutzen. XML ist ein flexible Datenbeschreibungsformat, das HTML, also dem Datenbeschreibungsformat des World Wide Web ähnelt. Auf XML näher einzugehen, würde den Rahmen dieser Arbeit sprengen. Wenn man jedoch auf das schnelle Auslesen größerer, mehr oder weniger abstrakter Datenmengen angewiesen ist, ist XML für Director die richtige Wahl.

XML kann so aussehen, wie dieser Ausschnitt einer XML Datei aus einem Phantombildgenerator-Programm. Es definiert die verfügbaren Einzelteile, ihren Zusammenhang, ihre Tiefeninformation etc.

<?xml version="1.0"?>
<PHANTOMBILDER>

<SET NAME="Mensch">

<!-- Augen -->

<ELEMENT>
<MEMBER>A-augen1</MEMBER>
<Z-INDEX>80</Z-INDEX>
<GRUPPE>Augen</GRUPPE>
<DEFAULTLOC X="-50" Y="27"/>
<BRUDER>
<MEMBER>B-augen1</MEMBER>
<Z-INDEX>80</Z-INDEX>
<DEFAULTLOC X="51" Y="26"/>
</BRUDER>
<EXKLUSIV/>
<AUTOSCALE/>
<AUTOROTATE/>
</ELEMENT>

<ELEMENT>
<MEMBER>A-augen2</MEMBER>
<Z-INDEX>80</Z-INDEX>
<GRUPPE>Augen</GRUPPE>
<DEFAULTLOC X="-45" Y="19"/>
<BRUDER>
<MEMBER>B-augen2</MEMBER>
<Z-INDEX>80</Z-INDEX>
<DEFAULTLOC X="46" Y="18"/>
</BRUDER>
<EXKLUSIV/>
<AUTOSCALE/>
<AUTOROTATE/>
</ELEMENT>
...

Wenn immer möglich ist in Director die Montage von Layouts und Animationen im Drehbuch dem Skripting vorzuziehen. Die Schnittstelle zwischen Drehbuch und Skript stellen die Verhalten dar: sie kommunizieren Interaktion an das Programm weiter. Verhalten sind Objekte, die genau wie Parent-Skripte eingesetzt werden können. Ihr entscheidender Nachteil ist jedoch ihre Anonymität: Sie werden vom Drehbuch generiert und lassen sich nur umständlich direkt ansprechen. Child-Objekte sind dagegen direkt über eine Referenzvariable mit aussagekräftigem Namen zugänglich. Sie eignen sich generell besser für die Sammlung von Daten und Funktionen. Verhalten dagegen sind Spezialisten in der Verarbeitung von Ereignissen. Es liegt daher nahe, beide zu koppeln: die Ereignisse werden von den Verhalten verarbeitet und diese wiederum erzeugen Objekte bzw. geben Werte an Objekte weiter. Zum Beispiel könnte ein Verhalten auf einer Spielplangrafik am Programmanfang die Spritenummer und die Dimensionen des Spielplans an ein Spielplanobjekt kommunizieren; diese Werte müssten nicht hart kodiert werden und bei einem Wechsel der Größe des Spielfelds oder der Verschiebung um einige Spritekanäle müsste keine Zeile Programmcode geändert werden.
Das folgende Beispiel soll die Vorteile dieser Vorgehensweise verdeutlichen.

Das Parent-Skript BallObjekt bewegt ein Sprite auf der Bühne auf einer geraden Bahn; wenn es am Rand eines Rechtecks angekommen ist, prallt es an der jeweiligen Kante ab. Dieses Rechteck ist mit festen Werten programmiert; der Abprall-Algorithmus ist nicht sonderlich ausgefeilt, es wird lediglich die Bewegungsrichtung umgekehrt, doch für dieses Beispiel ist dies ausreichend. Als Parameter sind das Sprite zu übergeben und die anfängliche Richtung als Bewegungsvektor in Punktform (point(dx, dy)). Da die Bewegung in einem stepframe-Handler stattfindet, ist das Objekt der actorlist hinzuzufügen.

property mSprite, mBewegungsvektor

on new me, spriteNummer, anfangsVektor
-- Sprite-Kanal
mSprite = spriteNummer
-- anfänglicher Bewegungsvektor: Werte aus Behavior einlesen
mBewegungsvektor = anfangsVektor
return me
end new

on stepframe me
newLoc = me.calculateNewLoc()
if (newLoc.locH < 75) or (newLoc.locH > 515) then
-- Ball prallt von senkrechter Linie ab
mBewegungsvektor = mBewegungsvektor * point(-1, 1)
newLoc = me.calculateNewLoc()
end if
if (newLoc.locV < 75) or (newLoc.locV > 355) then
-- Ball prallt von waagerechter Linie ab
mBewegungsvektor = mBewegungsvektor * point(1, -1)
newLoc = me.calculateNewLoc()
end if
sprite(mSprite).loc = newLoc
end stepframe

on calculateNewLoc me
return (sprite(mSprite).loc + mBewegungsvektor)
end calculateNewLoc


Das Erzeugen der Objekte kann in einem startMovie-Handler stattfinden:

on startMovie
clearGlobals
(the actorList).add(script("BallObjekt", 1, 30, 40)
(the actorList).add(script("BallObjekt", 2, 10, -10)
(the actorList).add(script("BallObjekt", 3, -1, -4)
end startMovie

Hier werden 3 Objekte erzeugt, die Sprite 1 bis 3 bewegen. Der clearGlobals-Befehl hat die Funktion, die actorList von eventuell aus einem vorherigen Programmablauf erzeugten Objekten zu befreien.

Mit dieser Vorgehensweise haben wird jedoch das Problem, dass bei jeder Verschiebung eines Sprites das Skript aktualisiert werden muss und ebenso, wenn ein neues Sprite hinzugefügt wird.

Das BallVerhalten schafft hier Abhilfe: es erzeugt selbständig ein neues Objekt mit dem richtigen Sprite und entfernt es, sobald das Sprite endet (das schließt das Beenden des Films mit ein). Das Hinzufügen eines neuen Balles ist denkbar einfach: das Verhalten wird einfach auf den Darsteller auf der Bühne gezogen. Als Dreingabe kann der Anfangsbewegungsvektor komfortabel in einer Dialogbox mithilfe von Schiebereglern eingestellt werden.

property spriteNum, mObjekt, mSpeedX, mSpeedY

on beginSprite me
mObjekt = script("BallObjekt").new (spriteNum, point(mSpeedX, mSpeedY))
(the actorlist).add(mObjekt)
end beginSprite
on endSprite me
(the actorlist).deleteOne(mObjekt)
end endSprite
on getPropertyDescriptionList me
description = [:]
addProp description,#mSpeedX,[#default:0,
#format:#integer,#comment:"horizontale Geschwindigkeit:",
#range:[#min:-20, #max: 20]]
addProp description,#mSpeedY,[#default:0,
#format:#integer,#comment:"vertikale Geschwindigkeit:",
#range:[#min:-20, #max: 20]]
return description
end getPropertyDescriptionList
on getBehaviorDescription me
return "Behaviour für den springenden Ball."
end getBehaviorDescription

Natürlich wäre es auch möglich, die gesamte Funktion in einem einzigen Verhalten zu vereinen. Das würde das Programm jedoch unnötig verkomplizieren. Objektorientierte Programmierung bietet die Möglichkeit der Abstraktion, und das Aufteilen in eine Ereignisebene und eine Funktionsebene stellt ebene eine solche dar.

Quellcode des Beispiels

5.10 Ermitteln der Verhalten zu einem Sprite mit the scriptInstanceList und the scriptList 5.12 Beispiel 10: Erweiterung des Ball-Beispiels