4.15 Synchronisation mit dem Drehbuch durch on stepframe und the actorlist

Ein großer Nachteil von Lingo ist, dass immer nur ein Skript gleichzeitig ausgeführt werden kann. Alle anderen Handler, als auch Eventhandler wie on mouseUp oder on keyDown müssen Warten, bis das aktuell ausgeführte Skript abgearbeitet ist, bevor sie ausgeführt werden können. Dies ist besonders hinderlich, wenn gleichzeitig mehrere Objekte unabhängig voneinander agieren sollen. In Java gibt es für diese Aufgabe Threads. Ein Thread läuft im Hintergrund, während das Hauptprogramm andere Aufgaben erledigt. Mehrere unabhängige Threads blockieren sich nicht. Zwar kann der Prozessor in der Regel auch nur einen Befehl auf einmal ausführen, doch die verschiedenen Threads können die verfügbare Rechenzeit teilen und den Prozessor abwechselnd nutzen.

Glücklicherweise schafft das Programmieren mit Objekten Abhilfe. Es kann zwar kein echtes Multithreading erreicht werden, aber dafür lassen sich einzelne Objekte über das Frametempo synchronisieren. Director setzt dazu ein Ereignis (Event) ein, dass nur Objekte erhalten können: stepframe. Dieses Ereignis tritt ein, wenn eine von zwei Bedingungen erfüllt ist:

1. im Drehbuch wird in einen neuen Frame gewechselt
oder
2. es wird mit updateStage die Bühne neu gezeichnet

Allerdings erhalten nicht alle Objekte automatisch dieses Event, sondern nur Objekte, die in der globalen Eigenschaft the actorlist stehen. Dadurch hat der Programmierer Kontrolle darüber, welche Objekte das stepframe-Event erhalten. Soll der stepframe-Handler eines Objektes ausgeführt werden, muss dieses Objekt der actorlist hinzugefügt werden. Wird das Objekt aus der actorlist herausgenommen, erhält es das Event nicht mehr. Director verhält sich gegenüber Einträgen in der actorlist fehlertolerant: Objekte, die keinen on stepframe-Handler enthalten, werden ignoriert, ebenso Einträge, die kein Objekt sind.

Diese Technik soll nun an einem Beispielprogramm demonstriert werden. Der Directorfilm enthält drei beliebige grafische Objekte in Spritekanal 1 bis 3:

Ein Frameskript sorgt dafür, dass der Film in einem Frame loopt.

on exitFrame
go to the frame
end exitFrame

Mit Hilfe des Parent-Skripts HuepfObjek" sollen diese Objekte wie ein Gummiball auf der Bühne springen und am unteren Bühnenrand abprallen.

-- dieses Objekt demonstriert ein hüpfendes Objekt

property mSprite, mBeschleunigung, mGeschwindigkeit, mY, mMaxY

on new me, spriteNummer
-- erzeugt ein springendes Objekt
-- Parameter spriteNummer: das Sprite, das das springende Objekt enthält
mSprite = spriteNummer
-- Beschleunigung des Objekts (wird zur Geschwindigkeit addiert)
mBeschleunigung = 1.0
-- Geschwindigkeit des Objekts (Pixel pro Frame)
mGeschwindigkeit = 0.0
-- vertikale Position des Objekts
my = sprite(mSprite).locV
-- maximale vertikale Position des Objekts
--(Objekt berührt den unteren Rand der Bühne)
mMaxY = (the stage).rect.height - sprite(mSprite).member.height
+ sprite(mSprite).member.regpoint.locV
return me
end new

on stepFrame me
-- hier wird die eigentliche Bewegung ausgeführt
mGeschwindigkeit = mGeschwindigkeit + mBeschleunigung
mY = mY + mGeschwindigkeit
-- prüfen, ob Objekt am unteren Bühnenrand angekommen ist
-- und ggf. Bewegung umkehren
if mY >= mMaxY then
mY = mMaxY + mMaxY - mY
mGeschwindigkeit = - mGeschwindigkeit
end if
-- Objekt an neue Position bewegen
sprite(mSprite).locV = mY
end stepFrame

Im new-Handler werden alle Eigenschaften gesetzt. Als Parameter wird die Nummer des Sprites angegeben, in dem das zu bewegende Objekt liegt. Daraus wird die anfängliche vertikale Position ermittelt. Außerdem wird errechnet, ab welcher Position das Objekt von der unteren Bühnenkante zurückgeworden wird.

Wenn wir den Film starten, ist noch keine Aktion auf dem Bildschirm zu sehen. Es müssen erst die Objekte instantiiert werden, die für die Bewegung der Grafiken verantwortlich sind:

kreisObj = script("HuepfObj").new(1)

Dieser Aufruf erzeugt ein Objekt, das als Argument Spritenummer 1 erhält, in unserem Fall das Sprite, in dem der Kreis liegt. Um den stepframe-Handler des Objektes in jedem Frame aufzurufen, fügen wir das Objekt der actorlist hinzu:

(the actorlist).add(kreisObj)

Der Kreis in Sprite 1 fängt an zu hüpfen. Ebenso können wir mit dem Quadrat und dem Buchstaben in Sprite 2 und 3 verfahren:

quadratObj = script("HuepfObj").new(2)
(the actorlist).add(quadratObj)
buchstabeObj = script("HuepfObj").new(3)
(the actorlist).add(buchstabeObj)

Der Vorteil dieser Vorgehensweise wird nun offensichtlich: die drei Objekte bewegen sich völlig unabhängig voneinander, ohne dass wir uns darum kümmern müssen. Soll sich ein Objekt nicht mehr bewegen, löschen wir es einfach aus der actorlist:

(the actorlist).deleteOne(kreisObj)

Der Kreis bleibt nun stehen, ohne dass wir das Objekt selbst gelöscht haben. Um die Bewegung fortzuführen, genügt es, kreisObj der actorlist wieder hinzuzufügen.

Als drittes Skript enthält das Beispielprogramm einen startMovie-Handler, der die Aufgabe hat, the actorlist zu löschen. Dadurch wird verhindert, dass Objekte, die nicht aus der Liste gelöscht wurden, bei einem erneutem Programmstart zu unvorhersehbaren Ergebnissen führen.

on startmovie
-- Globale löschen, insbesondere the actorlist
clearGlobals
end startmovie

Quellcode des Beispiel

Das komplette Löschen der actorlist beim Starten des Programms empfiehlt sich immer, wenn mit der actorlist gearbeitet wird. Dafür gibt es drei Möglichkeiten:

  1. das Löschen aller Globalen einschließlich the actorlist mit clearGlobals
  2. das Löschen der actorlist mit (the actorlist).deleteAll()
  3. das Löschen der actorlist durch das Zuweisen einer leeren Liste
    the actorlist = []

Ein Objekt kann ohne weiteres sich selbst der actorlist hinzufügen, z.B. im new-Handler. Es kann sich auch in der eigenen stepframe-Methode aus der actorlist löschen, auch wenn Macromedia dies nicht empfiehlt12. Dies liegt am Umstand, dass Director beim Abarbeiten der actorlist nicht mit einer Kopie arbeitet und beim Löschen eines Objektes im stepframe-Handler das darauffolgende Objekt überspringt.

Im Programmablauf, der in der Grafik schematisch dargestellt ist, wird in diesem Stepframe-Zyklus der stepframe-Handler von Objekt 3 nicht aufgerufen, da sich das Objekt 2 unmittelbar vorher durch einen Aufruf in seinem stepframe-Handler gelöscht hat. Es kommt jedoch zu keinem Fehler. Das Übergehen des Objekts kommt beim Programmablauf nur zum Tragen, wenn das Objekt bei jedem Neuzeichnen der Bühne tatsächlich auf die Ausführung des stepframe-Handlers angewiesen ist. Ist dies der Fall, kann man sich mit folgendem Trick behelfen:

Das statische Objekt ObjektLoeschenObjekt wird mit dem Aufruf

script("ObjektLoeschenObjekt").loeschen(objektReferenz)

aufgerufen. Es prüft daraufhin, ob seine Eigenschaft mLoeschenListe schon initialisiert wurde und veranlasst dies gegebenenfalls. Dann fügt es dieser Liste das übergebene Objekt hinzu. Daraufhin prüft es, ob es schon am Ende der actorlist steht. Wenn nicht, wird es ans Listenende hinzugefügt und ebenfalls der Liste mLoeschenListe hinzugefügt.

Im stepframe-Handler des Objekts löscht es eben diese Einträge aus der Liste. Da dieses Objekt das letzte der actorlist ist, kann es sich daraufhin am Ende selbst löschen. Zuvor prüft es jedoch, ob es wirklich an letzter Stelle steht, es ist ja möglich, dass in diesem Stepframe-Zyklus noch Objekte angefügt worden sind. Steht es nicht am Ende, fügt es sich selbst seiner eigenen Löschen-Liste und der actorlist hinzu und stellt damit sicher, dass alle zu löschenden Objekte immer vom letzten Objekt der Liste gelöscht werden und kein Objekt von Director übergangen wird.

Wichtig: Dieses Skript wird nur gebraucht, wenn ein Objekt im stepframe-Handler sich selbst oder ein anderes Objekt aus der actorlist löschen will! In allen anderen Fällen ist das Löschen aus der actorlist vollkommen unkritisch.

-- statisches Objekt, dass das Löschen von Objekten in "the actorlist"
-- aus einem stepframe-Handler ermöglicht

property mLoeschenListe

on loeschen me, objekt
-- wenn mLoeschenListe noch nicht initialisiert ist, dies jetzt nachholen
if not listP(mLoeschenListe) then
mLoeschenListe = []
end if
-- Objekt auf die Abschussliste setzen
mLoeschenListe.add(objekt)
-- wenn diese Skript noch nicht am Ende der Actorlist steht,
-- es hinzufügen, ebenfalls mLoeschenListe hinzufügen
if (the actorlist).getLast() <> me then
(the actorlist).add(me)
mLoeschenListe.add(me)
end if
end loeschen

on stepframe me
-- prüfen, ob dieses Skript am Ende der actorlist steht
if (the actorlist).getLast() <> me then
-- Skript an das Ende der actorlist und mLoeschenListe anhängen
(the actorlist).add(me)
mLoeschenListe.add(me)
else
-- Skript steht am Ende: jetzt alle Einträge in mLoeschenListe löschen
-- in der Liste steht auch dieses Skript und wird ebenfalls gelöscht
repeat with objekt in mLoeschenListe
(the actorlist).deleteOne(objekt)
end repeat
end if
end stepframe

Quellcode des Beispiels

4.14 Beispiel 6: Dynamische Vererbung 4.16 Beispiel 7: Warhol's Flowers welken