Entwicklung eines interaktiven 3D-Labyrinths. Studienarbeit



Ähnliche Dokumente
Lineargleichungssysteme: Additions-/ Subtraktionsverfahren

Erstellen einer Collage. Zuerst ein leeres Dokument erzeugen, auf dem alle anderen Bilder zusammengefügt werden sollen (über [Datei] > [Neu])

Erstellen von x-y-diagrammen in OpenOffice.calc

Erweiterung der Aufgabe. Die Notenberechnung soll nicht nur für einen Schüler, sondern für bis zu 35 Schüler gehen:

Binäre Bäume. 1. Allgemeines. 2. Funktionsweise. 2.1 Eintragen

Zahlen auf einen Blick

Zwischenablage (Bilder, Texte,...)

Domino Feldplaner 3.3

OECD Programme for International Student Assessment PISA Lösungen der Beispielaufgaben aus dem Mathematiktest. Deutschland

Mediator 9 - Lernprogramm

Grundlagen der Theoretischen Informatik, SoSe 2008

Primzahlen und RSA-Verschlüsselung

Wir arbeiten mit Zufallszahlen

Arbeiten mit UMLed und Delphi

1 topologisches Sortieren

1 Vom Problem zum Programm

Stammdatenanlage über den Einrichtungsassistenten

Produktschulung WinDachJournal

Sehr geehrter Herr Pichler, liebe Mitschüler, heute möchte ich euch einen kleinen Einblick in die Welt von gmax geben.

Professionelle Seminare im Bereich MS-Office

AGROPLUS Buchhaltung. Daten-Server und Sicherheitskopie. Version vom b

Gezielt über Folien hinweg springen

TESTEN SIE IHR KÖNNEN UND GEWINNEN SIE!

ways2gether ipad App Guide

Handbuch ECDL 2003 Basic Modul 5: Datenbank Access starten und neue Datenbank anlegen

Anleitung über den Umgang mit Schildern

Datensicherung. Beschreibung der Datensicherung

Buchhaltung mit WISO EÜR & Kasse 2011

PTV VISWALK TIPPS UND TRICKS PTV VISWALK TIPPS UND TRICKS: VERWENDUNG DICHTEBASIERTER TEILROUTEN

S/W mit PhotoLine. Inhaltsverzeichnis. PhotoLine

Einführung in. Logische Schaltungen

HANDBUCH PHOENIX II - DOKUMENTENVERWALTUNG

CMS.R. Bedienungsanleitung. Modul Cron. Copyright CMS.R Revision 1

Pädagogik. Melanie Schewtschenko. Eingewöhnung und Übergang in die Kinderkrippe. Warum ist die Beteiligung der Eltern so wichtig?

Inhalt. Allgemeine Einführung. Argumentationsvermögen. Räumliches Vorstellungsvermögen. Begabungen und Fähigkeiten messen

Anzeige von eingescannten Rechnungen

Outlook. sysplus.ch outlook - mail-grundlagen Seite 1/8. Mail-Grundlagen. Posteingang

Animationen erstellen

Erweitertes Kalkulationsfenster

1 Einleitung. Lernziele. automatische Antworten bei Abwesenheit senden. Einstellungen für automatische Antworten Lerndauer. 4 Minuten.

50. Mathematik-Olympiade 2. Stufe (Regionalrunde) Klasse Lösung 10 Punkte

FAQ Spielvorbereitung Startspieler: Wer ist Startspieler?

Objektorientierte Programmierung

Globale Tastenkombinationen für Windows

Lieferschein Dorfstrasse 143 CH Kilchberg Telefon 01 / Telefax 01 / info@hp-engineering.com

4. BEZIEHUNGEN ZWISCHEN TABELLEN

Konzepte der Informatik

PHP - Projekt Personalverwaltung. Erstellt von James Schüpbach

Dokumentation IBIS Monitor

Jederzeit Ordnung halten

M. Graefenhan Übungen zu C. Blatt 3. Musterlösung

Handbuch ECDL 2003 Modul 2: Computermanagement und Dateiverwaltung Der Task-Manager

Hinweise zur Datensicherung für die - Prüfmittelverwaltung - Inhalt

Anwendertreffen 20./21. Juni

Programme im Griff Was bringt Ihnen dieses Kapitel?

MORE Profile. Pass- und Lizenzverwaltungssystem. Stand: MORE Projects GmbH

Professionelle Seminare im Bereich MS-Office

L10N-Manager 3. Netzwerktreffen der Hochschulübersetzer/i nnen Mannheim 10. Mai 2016

Wie halte ich Ordnung auf meiner Festplatte?

Handbuch. NAFI Online-Spezial. Kunden- / Datenverwaltung. 1. Auflage. (Stand: )

Beispiel(unten ist der Spielfeldrand):

Softwareentwicklungspraktikum Sommersemester Grobentwurf

Simulation LIF5000. Abbildung 1

Hilfedatei der Oden$-Börse Stand Juni 2014

SRH - Kurzinformation

S7-Hantierungsbausteine für R355, R6000 und R2700

Satzhilfen Publisher Seite Einrichten

Projektmanagement in der Spieleentwicklung

1. Einführung Erstellung einer Teillieferung Erstellung einer Teilrechnung 6

Vektoren mit GeoGebra

Mehr Interaktion! Aber einfach und schnell!

Stellen Sie bitte den Cursor in die Spalte B2 und rufen die Funktion Sverweis auf. Es öffnet sich folgendes Dialogfenster

Informationen zum neuen Studmail häufige Fragen

Ist Excel das richtige Tool für FMEA? Steve Murphy, Marc Schaeffers

GEVITAS Farben-Reaktionstest

Informatik Kurs Simulation. Hilfe für den Consideo Modeler

EINFACHES HAUSHALT- KASSABUCH

Charakteristikum des Gutachtenstils: Es wird mit einer Frage begonnen, sodann werden die Voraussetzungen Schritt für Schritt aufgezeigt und erörtert.

Übungen zu Einführung in die Informatik: Programmierung und Software-Entwicklung: Lösungsvorschlag

Microsoft Access 2010 Navigationsformular (Musterlösung)

Der Frosch als Vektorgrafik

PowerPoint: Text. Text

Aufklappelemente anlegen

Gleichungen Lösen. Ein graphischer Blick auf Gleichungen

Installationsleitfaden kabelsafe backup professional unter MS Windows

Kreativ visualisieren

Kapitel 4 Die Datenbank Kuchenbestellung Seite 1

104 WebUntis -Dokumentation

Access [basics] Rechnen in Berichten. Beispieldatenbank. Datensatzweise berechnen. Berechnung im Textfeld. Reporting in Berichten Rechnen in Berichten

ZfP-Sonderpreis der DGZfP beim Regionalwettbewerb Jugend forscht BREMERHAVEN. Der Zauberwürfel-Roboter. Paul Giese. Schule: Wilhelm-Raabe-Schule

2. Im Admin Bereich drücken Sie bitte auf den Button Mediathek unter der Rubrik Erweiterungen.

UMSTELLUNG DER RÖNTGEN-SCHNITTSTELLE DÜRR-DBSWIN AUF DÜRR-VDDS

DNotI. Fax - Abfrage. GrEStG 1 Abs. 3 Anteilsvereinigung bei Treuhandverhältnissen. I. Sachverhalt:

Anton Ochsenkühn. amac BUCH VERLAG. Ecxel für Mac. amac-buch Verlag

Enigmail Konfiguration

BaseCamp OS X, Zümo 590 und die unterschiedliche Punkte in einer Route:

Stundenerfassung Version 1.8

Kreatives Gestalten mit Flash 5.0

ACHTUNG: Voraussetzungen für die Nutzung der Funktion s-exposé sind:

Berechnung der Erhöhung der Durchschnittsprämien

Transkript:

Entwicklung eines interaktiven 3D-Labyrinths Studienarbeit vorgelegt von Verena Kolba Institut Computervisualistik Arbeitsgruppe Computergraphik Betreuer und Prüfer: Dr.-Ing. Stefan Müller - November 2004 -

Inhaltsverzeichnis 1 Einleitung 3 2 Das Konzept 4 2.1 Der Spielablauf.............................. 4 2.2 Verwendete Software........................... 4 3 Die Umsetzung 7 3.1 Die Module................................ 7 3.2 Die zufällige Generierung des Labyrinths................ 8 3.3 Realisierung des Labyrinths mit Coin3d................ 12 3.3.1 Die Szene............................. 12 3.3.2 Die Kamerasteuerung...................... 17 3.3.3 Die Kollisionserkennung und das Picking............ 19 4 Ergebnisse 23 5 Fazit und Ausblick 28 2

1 Einleitung Motivation Seitdem der Personal Computer Einzug in das Privatleben der Menschen gefunden hat, erfreuen sich auch Computerspiele immer größerer Beliebtheit. Nachdem die grafische Umsetzung zu Anfang eher weniger ansprechend war, macht es der rasante Fortschritt im Bereich der 3D-Grafik Hardware mittlerweile möglich, zunehmend realistischere Szenen und Spielabläufe darzustellen. Im Laufe meines Studiums habe ich sehr viel über die Theorie der 3D-Grafik-Programmierung gelernt, hatte jedoch wenig Gelegenheit, diese auch in der Praxis zu erproben. Mit der Entscheidung, meine Studienarbeit im Bereich der Computergraphik zu schreiben, bot sich mir die Gelegenheit, dieses Defizit zu kompensieren. Der besondere Reiz an der Implemetierung eines Spiels liegt für mich in der Möglichkeit, interaktiv in das Geschehen auf dem Bildschirm einzugreifen. Außerdem wird eine abwechslungsreiche Aufgabenstellung geboten, wie zum Beispiel die Texturierung von Objekten oder Kollisionserkennung. Die Verwendung eines Szenegraphen stellt eine zusätzliche Herausforderung dar. Anspruch dieser Arbeit soll nicht sein, etwas noch nie da gewesenes zu erschaffen, sondern eher darin liegen anhand eines einfachen Spielekonzepts grundlegende Ansätze der 3D-Programmierung umzusetzen. Ziele Ziel dieser Arbeit ist die Konzeption und Entwicklung eines 3D- Labyrinths, das in verschiedenen Grössen zufällig generiert werden kann. Der Benutzer soll sich in dem Räumen interaktiv bewegen können, wobei Kollisionen mit Wänden automatisch vermieden werden. Ein Spielablauf soll zusätzlich berücksichtigt werden, wie zum Beispiel durch Einsammeln von Gegenständen. Diese Ausarbeitung beschäftigt sich im folgenden Kapitel (Kap.2) mit der Konzeption des Labyrinthspiels. Es wird erklärt, wie das fertige Spiel im Groben ablaufen soll. Außerdem stelle ich zum besseren Verständnis die bei der Umsetzung zum Einsatz gekommenen Komponenten des Szenegraphen vor. Das 3. Kapitel beschäftigt sich mit der Realisierung des Labyrinths. Die Definition der einzelnen Bausteine (Module), aus denen das Labyrinth aufgebaut ist, wird erläutert sowie mein Ansatz zur zufälligen Generierung. Ein weiteres Unterkapitel beschreibt den Aufbau der Szene in 3D. Es wird außerdem erklärt, wie der Benutzer sich durch die Szene bewegen kann und wie die Kamerasteuerung umgesetzt wird. Der letzte Teil dieses Kapitels befasst sich mit der Implementierung der Kollisionserkennung. Im Anschluß (Kap.4) wird das Resultat meiner Arbeit anhand von Screenshots präsentiert. Ein abschliessendes Fazit fasst die gewonnenen Erfahrungen zusammen und weist auf Ausbau- und Verbesserungsmöglichkeiten hin. 3

2 Das Konzept 2.1 Der Spielablauf Das Labyrinth soll in verschiedenen Grössen zufällig generiert werden können. Es besteht aus verschiedenen Bauelementen (Modulen), deren Datenstruktur das Grundgerüst für sowohl die zufällige Generierung als auch die Darstellung in 3D ist. Der Spieler sieht die Szene aus der Sicht der Kamera. Seine Startposition befindet sich vor dem Eingang des Labyrinths mit Blickrichtung ins Innere. Mit den Pfeil- Tasten kann sich der Benutzer durch das Labyrinth bewegen. Da ein Labyrinth mit durchlässigen Wänden keinen großen Sinn hat, muß eine Kollisionserkennung und -behandlung programmiert werden. An einigen Stellen im Labyrinth sind Münzen verteilt, von denen eine Mindestanzahl eingesammelt werden muss, bevor sich der Ausgang öffnet. Es können auch einige Karten gefunden werden, die die aktuelle Position des Spielers im Labyrinth anzeigen. Die Anzahl der vorkommenden Münzen und Karten ist abhängig von der Grösse des Labyrinths. Hat man genügend Münzen gesammelt und den Ausgang durchschritten, ist das Spiel erfolgreich beendet. 2.2 Verwendete Software Zur Realisierung in 3D wurde das szenegraphbasierte Open Inventor Toolkit (SGI) bzw. die Open-Source-Version Coin3D verwendet. Diese plattformunabhängige Software ist in C++ programmiert und benutzt die Graphikbibliothek OpenGL als Rendering-Maschine. Die einzelnen Objekte (Knoten) in einer Szene werden in einer Baumstruktur an den Wurzelknoten gehängt. Die Szene wird durch Traversierung dieses Baumes erstellt. An dieser Stelle werden nur die wichtigsten Knoten, die zur Realisierung des Spiels benötigt werden, kurz vorgestellt. Das Präfix So gibt an, dass es sich bei dem jeweiligen Knoten um ein Scene Object handelt. Das bedeutet, dass das Objekt die Darstellung der Szene direkt beeinflusst. Um einen detaillierteren Einblick in die Architektur und Syntax des Szenegraphen zu erhalten, verweise ich auf das Handbuch [1] The Inventor Mentor. SoSeparator Bei einem SoSeparator-Knoten handelt es sich um eine Unterklasse des SoGroup- Knotens. Er wird verwendet, um die Kind-Knoten in der jeweiligen Gruppe zu isolieren. Sie haben dann keine Auswirkungen auf den Rest des Szenegraphen. Nach der Traversierung seiner Kinder stellt ein SoSeparator-Knoten den vorherigen Traversierungsstand wieder her. Diese Art von Knoten wird zum Beispiel bei den Modulen verwendet, um die einzelnen Wandelemente zu positionieren. Das daraus 4

resultierende Modul wird ebenfalls unter einen Separator gehängt und kann dann im Weltkoordinaten-System an die richtige Stelle gesetzt werden. Abbildung 1: Symbole für verwendete Knoten SoSwitch/SoBlinker Der SoSwitch-Knoten ist ebenfalls eine Unterklasse von SoGroup. Er wählt jedoch nur einen seiner Kind-Knoten aus, um ihn zu traversieren. Die Kind-Knoten können alle über ihren Index angesprochen werden. Er gibt an, an welcher Position (0...n) im SoSwitch-Knoten sich das jeweilige Kind befindet. Über die Angabe des entsprechenden Index wird festgelegt, welcher Kinds-Knoten traversiert werden soll.. Ein SoSwitch-Knoten wird im Spiel verwendet, um zwischen zwei Kamerasichten zu wechseln. Ein SoBlinker-Knoten besucht seine Kinds-Knoten zyklisch. Mit Hilfe dieses Knotens wird zum Beispiel ein rot-blinkendes Warnschild dargestellt. SoEventCallback Ein EventCallback-Knoten wird verwendet, um das Verhalten des Programms zu definieren, wenn ein bestimmtes Ereignis auftaucht. Der Callback besteht aus einer selbtgeschriebenen Funktion die aufgerufen wird,wenn die HandleEventActionÊden Knoten traversiert. Um zu bestimmen, welche Art von Ereignis von Interesse ist, wird die addeventcallback()-methode verwendet. Hat die Callback-Funktion ein Ereignis abgehandelt, muss explizit sethandled() aufgerufen werden. Im Labyrinthspiel wird dieser Knoten benutzt, um die Kamera-Steuerung durch Tastatur- Eingabe zu realisieren. Der Ereignistyp ist ein KeyPressEvent. 5

SoPerspectiveCamera Der SoPerspectiveCamera-Knoten definiert eine perspektivische Kamera. Sie soll das menschliche Auge emulieren. Die Position der Kamera stimmt mit dem Augpunkt überein. Durch den Kamera-Knoten kann außerdem die Near- und Far Clipping-Plane angegeben werden. Im Programm wird z.b. bei jeder Bewegung die Position oder die Orientierung der Kamera in diesem Knoten neu gesetzt. SoFaceSet Ein SoFaceSet-Knoten ist ein Shape-Knoten, der Flächen aus vorgegebenen Koordinaten, Normalen und Texturen erstellt und damit ein polygonales Objekt bildet. Er wird verwendet, wenn komplexere Formen als zum Beispiel ein Quader oder eine Kugel benötigt werden. Die Karten, die im Labyrinth gefunden werden können, sind mit einem Faceset realisiert, da sie aus mehreren abgeknickten Flächen bestehen. SoElapsedTime Bei diesem Knoten handelt es sich um eine Engine. Engines werden in Coin3d hauptsächlich dazu verwendet, Teile der Szene zu animieren. Sie verfügen über eingebaute Funktionen. Die SoElapsedTime Engine entspricht einer Stoppuhr, die als Ausgabe die verstrichene Zeit seit ihrem Start hat. Verknüpft man die SoElapsedTime Engine mit einem Transform-Knoten, kann somit eine kontinuierliche Bewegung des zugehörigen Objekts erreicht werden. Auf diese Weise wird im Spiel realisiert, dass sich die aufzusammelnden Gegenstände um sich selbst drehen. SoAlarmSensor Ein Sensor-Knoten reagiert auf Änderungen der Szenegraph-Datenbank oder auf spezifische Ereignisse in der Zeit. Tritt ein bestimmtes Ereignis auf, wird eine vom Benutzer definierte Callback-Funktion aktiv. Ein SoAlarmSensor kann mit einem Wecker verglichen werden, der erst nach einer bestimmten Zeit startet. Mit der settimefromnow(time)-methode kann bestimmt werden, welche Zeitspanne das sein soll. Der Sensor ruft dann seine Callback-Funktion auf. Ein SoAlarmSensor wird im Spiel dazu benutzt, den Switch zur mapcam auf 15 Sekunden zu beschränken. Bei Aufruf der Callback-Funktion wird wieder zur Default- Kamera zurückgewechselt. SoRayPickAction Diese Aktion findet Objekte entlang eines Strahls von der Kamera aus durch einen Punkt auf der Nearclippingplane. Durch die Methode setray() kann ein Strahl in Weltkoordinaten definiert werden. Dazu wird ein Startpunkt und eine Richtung angegeben. Außerdem ist es möglich den Raum, in dem das Picking erfolgen soll 6

durch die Angabe einer Near- bzw. Far Distance zu beschränken. Die RayPickAction kann an einen Graphen oder Subgraphen angehängt werden, der dann traversiert wird und den Pfad zu den gefundenen Objekten zurückgibt. Dieser Knoten wird im Labyrinth dazu verwendet, Kollisionen mit den Wänden oder den zu findenden Gegenständen zu erkennen. 3 Die Umsetzung 3.1 Die Module Abbildung 2: Definition der Module Um ein Labyrinth zu erstellen, muß zuerst festgelegt werden, wie das Grundgerüst aussehen soll. Statt einzelne Wände zu setzen, habe ich mich für die Verwendung von vordefinierten Modulen entschieden. Unter einem Modul versteht man ein Gangelement, das durch die Positionierung der Wände ein Wegstück definiert. Die einzelnen Module müssen über mindestens einen Eingang und einen Ausgang verfügen, können aber auch mehr haben. Sackgassen entstehen also erst bei der Generierung des Labyrinths durch die Kombination verschiedener Module. Die Kennziffer eines Moduls setzt sich aus der binären Addition seiner Öffnungen zusammen. Jedes Modul kann über die Angabe einer 4-Bit-Sequenz definiert werden. Wenn eine Öffnung existiert, ist in der Binärdarstellung das entsprechende Bit auf 1 gesetzt. Geht man von einer Draufsicht von oben auf die einzelnen Module aus, wird: oben offen binär durch die 1(0001), 7

rechts offen durch die 2 (0010), unten offen durch die 4 (0100) und links offen durch die 8 (1000) dargestellt (siehe Abb.2). Insgesamt ergeben sich 11 verschiedene Module, die durch Mehrfachverwendung und unterschiedliche Kombinationen das Labyrinth ergeben. Ein zusätzlicher Modulblock hat keinerlei Ein- oder Ausgänge und wird als Füllelement verwendet. Er wird binär durch die 0 (0000) dargestellt. Eine erste Idee, wie die Module in 3D aussehen könnten, soll folgende Abbildung veranschaulichen. Die 3D-Graphiken wurden entsprechend der gegebenen Moduldefinitionen erstellt und beispielhaft mit Texturen versehen. Die endgültige Größe und Texturierung wird später festgelegt. Abbildung 3: Beispiel-Module in 3D 3.2 Die zufällige Generierung des Labyrinths Bevor das Spiel beginnen kann, muss über die Konsole zuerst eine Zahl zwischen 10 und 30 eingegeben werden, die die Grösse des Labyrinths angibt. In der 3D- Szene soll das Labyrinth im Ursprung beginnen und sich entlang der x- und z- Achsen ausbreiten. Die Grösse bestimmt die Anzahl von Modulen entlang dieser Achsen. Der Einfachheit halber gehe ich davon aus, dass in jeder Richtung gleich viele Module gesetzt werden. Es können also nur quadratische Labyrinthe erstellt werden. Im Voraus werden verschiedene Untermengen von Modulen gebildet, die bestimmte Kriterien erfüllen. Die Menge static int nachuntenweiter[7] = {3,5,7,9,11,13,15}; besteht zum Beispiel aus allen Modulen, die oben eine Öffnung haben, mit denen man also ein nach unten geöffnetes Modul fortsetzen kann. Jede Untermenge wird in Form eines n- dimensionalen Arrays abgelegt, wobei n die Anzahl der verschiedenen Module mit den entsprechenden Kriterien angibt. Die Module werden über ihre Kennziffern angegeben. Zur tatsächlichen Generierung des Labyrinths dient die Klasse Lab (siehe Abb.4). 8

Die Methode Lab(int *size) erzeugt ein Objekt vom Typ Lab in der übergebenen Grösse (2-dimensionales Array). Die Methode void genlab(void) füllt dieses derart mit Modulen, dass ein sinnvolles Labyrinth entsteht. Sinnvoll bedeutet in diesem Zusammenhang, dass das Labyrinth nur über einen Eingang und einen Ausgang verfügt und ansonsten nur aus erreichbaren Sackgassen besteht. Die Generierung eines derartigen Labyrinths erwies sich als schwieriger, als gedacht. Anfangs wurden die Felder ohne eine nähere Überprüfung ihrer Umgebung gefüllt, was zur Folge hatte, dass der Ausgang oft nicht zu erreichen war und es viele Wege gab, die nicht betretbar waren. Also implementierte ich einen Umgebungs-Test, der die Nachbarn des zu füllenden Feldes auf ihre Belegung überprüft. Auf dieses Umfeld-Problem wird im Folgenden noch näher eingegangen. Abbildung 4: Die Klasse Lab Die Klasse enthält die wichtigsten Methoden, die zur Generierung und Realisierung des Labyrinths benötigt werden. Die SoSeparator-Definitionen werden zur Erstellung der Szene benutzt und im nächsten Kapitel näher erklärt. An dieser Stelle werden nur die zur Generierung benötigten Methoden vorgestellt und ihre Funktion erklärt. void genlab(void) Nachdem das Labyrinth-Array in der übergebenen Grösse initialisiert wurde, wird es zuerst mit Nullen gefüllt. Das erste Feld im Array an der Position lab[0][0] entspricht dem Eingang des Labyrinths, das letzte Feld an der Position lab[size-1][size-1] dem Ausgang. An diesen beiden Positionen soll immer ein 9

gerades Wegmodul in x-richtung gesetzt werden. Es wird also jeweils eine 10 eingetragen. ( vgl. Abb.2 straightpathx = 10 = 1010 = 1000 + 0010 = links offen + rechts offen). private int Lab::possible(int act, int x, int y,int *weg)) Von der aktuellen Position aus wird geprüft, wohin der Gang der Modulbelegung nach weitergehen kann. Dazu wird die Kennziffer des aktuellen Moduls act dieser Methode übergeben. Die x-y-ebene im 2-dimensionalen Array entspricht der x-z- Ebene im 3D-Raum. Das Modul wird nun darauf untersucht, in welche Richtungen es Öffnungen besitzt und ob das nächste Feld in diese Richtung überhaupt frei ist oder es sich schon um den Rand handelt. Der Test wird mit Hilfe des logischen Bit-Operators & ausgeführt, der die Kennziffer des aktuellen Moduls mit den 4 möglichen Öffnungsrichtungen bitweise verundet und somit prüft, ob das entsprechende Öffnungs-Bit gesetzt ist (Beispiel: if (act&oben && (y!= 0)). Wenn beide Fälle nicht zutreffen, wird auf die Lauf- Variable possiblestep das Bit der möglichen Richtung aufaddiert. Die Funktion liefert eine Zahl, die in der Binärrepräsentation an den Positionen eine 1 hat, an denen der Gang fortgesetzt werden kann (11 = 1011 = oben, rechts und links offen). private void Lab::take(int possiblestep,int *x, int *y, int *weg) Die Position für das nächste Modul wird zufällig gewählt oder bei nur einer Möglichkeit übernommen. Ist die Richtung gewählt, muß die Entscheidung getroffen werden, welches Modul bzw. welche Kennziffer an diese Position gesetzt wird. Dies ist abhängig von der Belegung der umliegenden Felder (siehe Abb. 5). Bei den 3 Feldern, die untersucht werden müssen (in der Abbildung mit x gekennzeichnet), wird überprüft ob sie noch auf Null initialisiert sind, also leer, oder schon eine Modulkennziffer gesetzt ist und das Feld somit schon Teil eines anderen Ganges ist. Aus den zuvor definierten Untermengen von Modulen wird dann diejenige gewählt, die die für die Umgebung entsprechenden Kriterien erfüllt. Per Zufall wird ein Modul daraus ermittelt, dessen Kennziffer in das aktuelle Feld eingetragen wird. Im Beispiel wird von der aktuellen Position lab[2][0] die einzig mögliche richtung 4 (0100) genommen. Da ein Feld (rechts) bereits belegt ist, die anderen beiden jedoch frei, wird aus der Untermenge static int nachuntennorechts[2]={13,15} das Modul 13 gesetzt. 10

Abbildung 5: Das Umfeld-Problem Dieser Ablauf wird iterativ für jede neue Position durchgeführt. Die Methode void genlab(void) steuert über eine switch-anweisung das Befüllen des Arrays. Wird ein Weg gebaut, ist für dieses Vorgehen die Variable weg auf 1 gesetzt. Sollten bei dem Umgebungstest alle drei Felder belegt sein, ist der aktuelle Weg zu Ende. Dann wird weg auf 2 gesetzt und mit der switch-anweisung ((siehe Abb.6 Die Switch- Anweisung) in die entsprechende Schleife gewechselt. Das Array wird von links nach rechts durchlaufen und alle Module werden daraufhin überprüft, ob sie noch eine frei Öffnung haben. Dieser Test wird wieder durch die Methode private int Lab::possible() durchgeführt. Wenn ein solches Modul gefunden wird, springt das Programm wieder in die Methode private void Lab::take() und baut von der übergebenen Position des Moduls einen neuen Weg (weg=1) bis der Algorithmus in die nächste Sackgasse gerät. Am Ende eines Durchlaufs ist das Ergebnis ein gefülltes 2-dimensionales Array, welches das Labyrinth in 2D präsentiert. Die Module sind durch ihre Kennziffern eindeutig bestimmt. Nach einigen Testdurchläufen, musste ich leider feststellen, dass immer noch Labyrinthe generiert wurden, deren Ausgang nicht erreicht werden konnte. Um sicher zu gehen, dass ein erreichbarer Weg vom Eingang zum Ausgang erstellt wird, müssen die Felder um das Ausgansmodul erweitert untersucht werden. Nach der Fertigstellung des Labyrinths wird vom Ausgang aus rückwärts getestet, ob die kritschen Felder (siehe Abb.5 grau unterlegt) eine Verbindung zwischen dem Ausgang und dem restlichen Labyrinth bilden. Dazu werden die Modulkennziffern der Felder auf Öffnungen zum übrigen Labyrinth hin untersucht. Wird keine Öffnung gefunden, wird ein neues Labyrinth erstellt. 11

Abbildung 6: Die Switch-Anweisung 3.3 Realisierung des Labyrinths mit Coin3d 3.3.1 Die Szene Es bietet sich an, Diagramme des verwendeten Szenegraphen zu verwenden, um den Aufbau der Szene zu erläutern. Die Hierarchie-Anordnung und die verschiedenen Zugehörigkeiten können so besser nachvollzogen werden. Der ganzen Szene ist ein Root-Knoten vom Typ SoSeparator vorangestellt (siehe Abb.7). Zuerst wird die Beleuchtung und ein SoSwitch-Knoten mit zwei Kameras angehängt. Der Switch-Knoten ist per Default auf camera gesetzt. Zur mapcam wird nur gewechselt, wenn der Spieler eine Karte findet. Die dritte Kamera, die angehängt wird, kommt erst bei der Kollisionserkennung zum Einsatz. Damit das Labyrinth nicht im freien Raum schwebt, ist es von einer Skybox umgeben, die der Szene einen realistischen Charakter geben soll. Sie besteht aus mehreren SoFaceset-Knoten, die jeweils eine Seite der Skybox definieren. Die Umsetzung in Form einzelner Faceset-Knoten ist umständlicher als die Verwendung eines SoCube- Knotens. Da es mir jedoch nicht gelungen ist, die Seiten eines Cubes einzeln zu texturieren, habe ich einzelne Facesets verwendet. 12

Abbildung 7: Der Szenegraph vom Root-Knoten aus Die Module reihen sich vom Ursprung aus entlang der positiven x- und z- Achsen auf. Sie sind von einer zusätzlich definierten Wand umgeben, da bei der Generierung des Labyrinths die Aussenwände nicht berücksichtigt werden. Um einen ausreichenden Bewegungs-Spielraum innerhalb des Labyrinths zu garantieren, haben die Module jeweils eine Größe von 2*2*2 cm. Die Größe des Labyrinths in 3D beläuft sich auf size*2 cm. Die Außenwände bestehen aus je einem texturierten Quader, der dementsprechend die Breite size*2 hat. Abbildung 8: Der Subgraph Laby 13

Die Module (siehe Abb.8) werden aus mehreren texturierten Quadern aufgebaut. Da die Verwendung einzelner Facesets in diesem Fall zu aufwändig ist, werden kleine Fehler in der Textur auf den Schmalseiten der Wände akzeptiert. In einer Schleife, die über das gesamte Lab-Array läuft, werden die Kennziffern der einzelnen Module ausgelesen. Mit jedem Aufruf der Member-Funktion newmodul = lab->getmod(xcounter, ycounter) wird ein neues Modul in den Szenegraphen gehängt. Die richtige Position in der Szene wird berechnet, indem für jedes Feld des Arrays in x-richtung die x-koordinate des Moduls in Weltkoordinaten um 2. hochgezählt wird. Dementsprechend erhöht sich die z-koordinate eines Moduls, wenn im Array in y- Richtung weitergegangen wird (siehe Abb. 9 Schleife, um Module zu setzen). Die Funktion SoSeparator *Lab::getMod(int x, int y) sucht an der übergebenen Stelle im Array nach der Kennziffer des Moduls, generiert dieses und gibt es als SoSeparator-Knoten zurück. Der Subgraph modules fasst alle Module zusammen. Jedes Modul verfügt über einen Transform-Knoten, der die Position in der Szene enthält und einem weiteren Separator-Knoten, der die Geometrie des jeweiligen Modul beschreibt. Abbildung 9: Schleife, um Module zu setzen Wenn das Labyrinth erstellt ist, werden im nächsten Schritt die Münzen, die es einzusammeln gilt, und einige Karten, die als Orientierungshilfe dienen sollen, platziert. Dazu wird zuerst berechnet, wie viele Münzen bzw. Karten im Labyrinth verteilt werden sollen. Es wurde vorab festgelegt, dass ein Labyrinth jeweils (size/2)+1 Münzen bzw. (size/2)-1 Karten enthält. Die Member-Funktionen SoSeparator *Lab::getCoin(void) und SoSeparator *Lab::makeMap(void) liefern jeweils ein Coin- bzw. ein Karten- Objekt, das in den entsprechenden Subgraphen gehängt wird (siehe Abb.11a). Der Aufbau der Subgraphen entspricht im Prinzip dem der der Module. Im Fall der Münze handelt es sich bei dem Shape-Objekt um einen flachen, aufrecht stehenden Zylinder, der sich kontinuierlich um sich selbst dreht. Diese Animation wird erreicht, indem eine SoElapsedTime Engine mit dem Winkel der Rotation verknüpft wird. Durch die Verwendung der Methode connectfrom() wird die Aus- 14

Abbildung 11: Die Subgraphen Coins und Maps gabe der SoElapsedTime Engine in ein lesbares Winkelformat konvertiert und dem SoRotationXYZ-Knoten übergeben. Abbildung 10: zeitgesteuerte Rotation Die Karte (siehe Abb.11 b) benutzt einen SoFaceset-Knoten um die Form darzustellen, wird aber auf die gleiche Weise animiert wie die Münzen. Zu Anfang wurde die Karte mit einem Nurbs-Surface realisiert, was aber bei der späteren Implementierung des Pickings zu Problemen führte. Deshalb mußte auf diese realistischere Umsetzung zu Gunsten der Funktionalität verzichtet werden. Wo die Münzen und Karten im Labyrinth platziert werden, wird zufällig festgelegt ( rand()). Um die Sache etwas zu erschweren, soll in den ersten 3*3 Modulen kein Gegenstand auftauchen. Dies wird in der Implementierung so umgesetzt, dass der Zufallszahlen-Generator nur aus einer Menge von size+2-4 Modulpositionen wählen kann (siehe Abb. 12 Position von Müenzen zufällig bestimmen). Da die Module eine Seitenlänge von 2 cm haben, werden 4 cm, also zwei Module, abgezogen. Bei der tatsächlichen Angabe der Translation des Gegenstandes in Weltkoordina- 15

ten, werden die 4 cm wieder aufaddiert. Somit ist garantiert, dass die Felder direkt um den Eingang nicht besetzt werden. Allerdings kann es vorkommen, dass sich auf einer Position zwei Gegenstände befinden. Da dieser Umstand das Spiel allenfalls vereinfacht, wurde auf eine zusätzliche Abfrage verzichtet. Der Zufallszahlen-Generator muß nur für die x- und z-koordinaten benutzt werden. Die y-koordinate bleibt fest, damit sichergestellt werden kann, dass die Gegenstände in der Szene auf dergleichen Höhe wie die Kamera sind. Abbildung 12: Position von Münzen zufällig bestimmen Für den Fall, dass der Spieler beim Herumirren im Labyrinth versehentlich wieder zum Eingang gelangt, und diesen fälschlicherweise für den Ausgang hält, ist dort ein Schild(in) platziert, auf dem ein rotes Kreuz blinkt. Dies verdeutlicht, dass das der falsche Weg ist. Zur Realisierung des blinkenden Kreuzes wird ein SoBlinker-Knoten verwendet (siehe Abb.14 Der Subgraph In). Es werden zwei verschiedene Texturen angehängt, wobei es sich bei der ersten um ein schwarzes Kreuz auf Holz handelt (wood), bei der zweiten um ein Rotes (woodred). Der Blinker-Knoten wechselt in der angegebenen Geschwindigkeit zwischen den beiden Kind-Knoten hin und her. Abbildung 13: Der SoBlinker-Knoten 16

Der Ausgang des Labyrinths ist zu Beginn mit einer Tür verschlossen, die als SoSeparator-Knoten out an den Root-Knoten gehängt wird. Diese Tür verschwindet im Laufe des Spiels, wenn maxanzahlcoins = (size/2)+1 eingesammelt wurden. Das heißt also, dass ein Benutzer das Spiel nicht unbedingt erfolgreich beenden kann, indem er auf dem schnellsten Weg zum Ausgang findet. Es kann mitunter nötig sein, Umwege zu gehen, um die Mindestanzahl an Münzen zu erreichen. Abbildung 14: Der Subgraph In Die Geometrie der Szene ist somit vollständig aufgebaut. Im nächsten Schritt geht es darum, die User-Interaktion zu implementieren und einen Spieleablauf einzubinden. Dem Spieler soll sich, bzw. die Kamera, durch die Szene bewegen können. Es muß also entscheiden werden, wie die Steuerung realisiert wird. 3.3.2 Die Kamerasteuerung Die Kamera wird, wie schon erwähnt, vor den Eingang mit Blickrichtung ins Innere des Labyrinths positioniert. Da der Labyrinth-Eingang sich an der Position (0., 1.5, 1. ) im Weltkoordinatensystem befindet, wird die Kamera ein Stück zurückgesetzt an die Position camera->position.setvalue(-1.,1.5,1.), die Blickrichtung ist entlang der positiven x-achse. Für die Kamera-Steuerung werden die Pfeiltasten verwendet. Wird der Up-Arrow gedrückt, bewegt sich die Kamera nach vorn, beim Down-Arrow zurück. Dabei bleibt die Blickrichtung nach vorn gerichtet. Mit den Right- und Left-Arrows rotiert die Kamera in die entsprechende Richtung. Um das Verhalten des Programms für eine solche Tastatur-Eingabe zu definieren, wird ein SoEventCallback-Knoten benutzt. 17

Abbildung 15: Der Callback-Knoten Die Parameter der Funktion definieren, auf welche Art von Ereignis der Callback reagiert, die Callback-Funktion KeyPressCB selbst und den Knoten der dem Callback übergeben wird. In diesem Fall ist das die Kamera. Tritt eine Tastatureingabe (KeyPressEvent) auf, wird die Callback-Funktion aufgerufen, die überprüft, ob es sich bei der Eingabe um eine der Pfeil-Tasten handelt. Ist dies der Fall, wird die neue Position der Kamera berechnet. Für andere Tasten ist kein Verhalten definiert und es geschieht garnichts. Soll die Kamera vor oder zurück bewegt werden, wird der Translations-Vektor transl berechnet, der sich aus der Multiplikation der Richtung mit der als SPEED definierten Geschwindigkeit 0.2f ergibt. Die Kamera wird auf die neue Position cam->position.setvalue(cam->position.getvalue() + transl) gesetzt und der Eventcallback als sethandled() definiert. Handelt es sich bei der Tastatur-Eingabe um die linke oder rechte Pfeiltaste, wird die rotierte Blickrichtung bzw. Orintierung der Kamera ermittelt. Dem Rotations- Vektor rot wird die Rotationsachse und der Winkel übergeben. Die aktualisierte Orientierung der Kamera ergibt sich aus der Multiplikation der als Radians-Winkel gegebenen Orientierung der Kamera und dem als RotAngle definierten Winkel von 4 Grad ( π 45 in Radians). Auch in diesen beiden Fällen wird der Event-Callback am Ende auf sethandled() gesetzt. 18

Abbildung 16: Die Callback-Funktion 3.3.3 Die Kollisionserkennung und das Picking Um zu verhindern, dass man in dem Labyrinth durch die Wände gehen kann, muss eine Kollisionserkennung zwischen der Kamera und der Geometrie der Szene implementiert werden. Auch das Einsammeln der Münzen und Karten basiert auf einer vorangegangenen Kollisionserkennung. Die Implementierung dieses Verfahrens hat mir am meisten Schwierigkeiten bereitet. Obwohl meine Probleme dabei schlussendlich nur auf kleineren Mißverständnissen und Definitionen an der falschen Stelle beruhten, hat die Kollisionserkennung mehr Zeit, als geplant in Anspruch genommen. Am Ende habe ich dadurch jedoch ein tieferes Verständnis für den Aufbau eines Szenegraphen und seine Verwendung erlangt. Da die Möglichkeit einer Kollision direkt vom Verhalten bzw. der Steuerung der Kamera abhängig ist, muß die Kollisionserkennung und die nötige -behandlung im KeyPressCallback ausgeführt werden. 19

In der Callback-Funktion wird die testcam mit den übergebenen Daten der Kamera initialisiert. Für jede Steuerungs-Eingabe wird die nächste Position oder Orientierung zuerst daraufhin überprüft, ob sie noch kollisionsfrei erreicht werden kann. Abbildung 17: Die Test-Kamera Die Kollionserkennung wird mit Hilfe der SoRayPickAction *intersectioncheck ausgeführt (siehe Abb.23). Die RayPickAction wird mit den Positionskoordinaten und der Blickrichtung der Test-Kamera initialisiert und auf dem Root-Knoten ausgeführt. Das heißt, dass ein Strahl von der aktuellen Position aus in Blickrichtung geschickt wird, der das erste Objekt entlang dieses Strahls findet. Gibt es ein solches Objekt, liefert die RayPickAction mit dem Aufruf intersectioncheck->getpickedpoint() den Schnittpunkt zwischen Strahl und Objekt. Ansonsten gibt die Funktion NULL zurück. Mit der Überprüfung dieses Wertes wird bestimmt, wie sich das Programm weiter verhalten soll. Da die Picking-Aktion nur an Objekten, die sich in unmittelbarer Nähe befinden interessiert ist, wird sie auf den Bereich 0.1 cm bis 0.4 cm vor der Test-Kamera beschränkt. Objekte, die sich weiter weg befinden, werden nicht untersucht. Mit dieser Beschränkung wird zugleich ein Mindestabstand von 0.4 cm definiert, bis auf den sich die Kamera den Wänden nähern kann, bevor eine Kollisionserkennung nötig ist. Befindet sich die Test-Kamera mehr als 0.4 cm von den umliegenden Objekten entfernt, liefert die Abfrage intersectioncheck->getpickedpoint() den Wert 0. In diesem Fall wird die Kameraposition oder- orientierung auf die Position oder Orientierung der Test-Kamera gesetzt und der Callback wird verlassen. Wird für den Bereich < 0.4 vor der Kamera ein kollidierendes Objekt gefunden, muss ermittelt werden, ob es sich um ein Wandelement, eine Münze oder eine Karte handelt. Es wird ein Schnittpunkt ungleich 0 vom Typ SoPickedPoint zurückgegeben. Da es sich bei dem Schnittpunkt um einen const-wert handelt, muss für jede Bewegung eine neue RayPickAction aufgerufen werden. Andernfalls kann die Kamera nach der ersten Kollision nicht mehr bewegt werden, da schon ein Schnittpunkt!= 0 gefunden wurde. Den Aufruf interp->getdetail() liefert zusätzliche Information über das Objekt, mit dem ein Schnittpunkt existiert. Die Information, die für die Objekterkennung von Interesse ist, bezieht sich auf den Shape-Typ des Objekts. Da nur bestimmte Objekte über eine SoDetail-Subklasse verfügen, muß darauf geachtet werden, dass alle in der Szene verwendeten Objekte dieses Kriterium erfüllen. Aus diesem Grund habe ich das zu Anfang benutzte Nurbs-Surface, mit dem die Karte realisiert wurde, verworfen und statt dessen einen Faceset-Knoten verwendet. Nurbs-Surfaces 20

verfügen über keine SoDetail-Information. Es ist natürlich ebenfalls zu bedenken, dass Objekte, die ein unterschiedliches Verhalten des Programms erfordern, auch verschiedene Erscheinungsformen haben sollten. Bei den drei in der Szene verwendeten Shape-Objekten handelt es sich um einen Cube (Wände), der über die Subklasse SoCubeDetail verfügt, einen Zylinder (Münzen) mit der SoCylinderDetail-Klasse und ein Faceset (Karten) mit dem entsprechenden SoFaceDetail-Knoten. Wird eine Kollision entdeckt, gibt es also drei verschiedene Möglichkeiten, wie das Programm darauf reagieren soll. Abbildung 18: Die Kollisionerkennung und -behandlung 21

1. Handelt es sich bei dem geschnittenen Objekt um einen SoCube, also um ein Wandelement, behält die Kamera ihre alte Position bzw. Orientierung einfach bei. Der Callback wird verlassen, ohne dass irgendwelche Änderungen vorgenommen werden. Es wird somit verhindert, dass die Kamera sich durch eine Wand hindurch bewegen kann und die Wände erwecken so den Eindruck, aus massivem Stein zu bestehen. 2. Liefert der Aufruf interp->getdetail() ein SoCylinderDetail, handelt es sich um eine Münze. Diese wird dann aus dem Szenegraphen entfernt und ein Zähler, der die Anzahl der bereits gefundenen Müzen angibt, wird um 1 hochgezählt (ccounter++). Ein SoPickedPoint-Knoten verfügt über die Methode getpath(), die den Pfad zu dem geschnittenen Objekt zurück gibt. Die Knoten in diesem Pfad können über ihren Index angesprochen werden. Diese Funktion habe ich mir zu Nutze gemacht, um die entsprechende Münze aus dem Szenegraphen zu löschen. Da der Szenegraph während der Traversierung nicht verändert werden kann, wird das Abhängen innerhalb des Pfades ausgeführt. Der letzte Knoten im Pfad ist der SoCylinder-Knoten der entsprechenden Münze. Da hier aber der komplette darüber liegende SoSeparator-Knoten setcoin gelöscht werden soll, wird das Element mit dem Index 2 (vom Ende des Pfades aus gezählt), welches dem SoSeparator-Knoten coins entspricht (vgl. Abb. 11a Die Subgraphen Coins und Maps) instanziiert und der entsprechende setcoin-knoten (Index 1) abgehängt. Es muß an dieser Stelle noch überprüft werden, ob die Mindestanzahl an Münzen schon gefunden wurde. Ist dies der Fall, wird die Tür, die den Ausgang verschließt ebenfalls aus dem Szenegraphen gelöscht. Wie viele Münzen noch benötigt werden, wird über die Konsole ausgegeben. 3. Als dritte Möglichkeit kann ein SoFaceDetail zurück gegeben werden, was bedeutet, dass der Spieler eine Karte gefunden hat. Auch hier wird über den Pfad auf den entsprechenden setmap-knoten zugegriffen und er wird aus dem Szenegraphen gelöscht. Der angehängte SoAlarmSensor wird dann eingeplant und das Labyrinth wird füer 15 Sekunden aus Sicht der mapcam, also von oben, gezeigt. Um die Position des Spielers in der Szene zu markieren, wird für die Dauer dieser Kamera- Einstellung eine rote Kugel auf die aktuelle Position der Kamera gesetzt. Sind die 15Ssekunden abgelaufen, ruft der AlarmSensor seine Callback-Funktion auf, die den CameraSwitch wieder auf camera stellt. Die rote Kugel wird wieder aus dem Graphen entfernt und das Spiel kann fortgesetzt werden. Wenn der Spieler genügend Münzen gesammelt und den Ausgang durchschritten hat, ist das Spiel beendet. Der camswitch wechselt zu der Ansicht der mapcam und der 2-dimensionaler Text You Won (SoText2-Knoten) wird angezeigt. 22

4 Ergebnisse Abbildung 19: Die Anfangsposition der Kamera mit Blick ins Innere des Labyrinths Abbildung 20: Die Tür vor dem Ausgang 23

Abbildung 21: Das Warnschild vor dem Eingang Abbildung 22: Eine Münze 24

Abbildung 23: Eine Karte Abbildung 24: Sicht von oben auf ein 20*20-Labyrinth 25

Abbildung 25: Sicht von oben auf ein 11*11-Labyrinth Abbildung 26: Gewonnen 26

Abbildung 27: Panorama in +x/+z-richtung Abbildung 28: Panorama in -x/-z-richtung 27

5 Fazit und Ausblick Die Entwicklung dieses interaktiven 3D-Spiels war für mich eine gute Möglichkeit, ein Gefühl dafür zu entwickeln, wie die Planung und Umsetzung eines derartigen Projekt ablaufen sollte. Neben der Vertiefung meiner Kenntnisse der Programmiersprache C++ war für mich die Verwendung eines Szenegraphen eine interessante Herausforderung. Bei der Programmierung waren einige Bereiche einfacher als gedacht umzusetzen, wogegen ich an anderen Stellen auf Probleme stieß, mit denen ich nicht gerechnet hatte. Die Erstellung der Module in 3D benötigte überraschend wenig Zeit und vermittelte mir einen guten Einstieg in die Verwendung eines Szenegraphen. Die zufällige Generierung des Labyrinths hingegen erwies sich als schwieriger, als erwartet, da mehr Ausnahemefälle zu behandeln waren, als vorab angenommen. Die größten Schwierigkeiten hatte ich jedoch, wie erwartet, mit der Implementierung der Kollisionserkennung und -behandlung. Die richtige Anwendung der RayPickAction und die Programmierung der Interaktion mit den zu sammelnden Gegenständen erforderte viel Arbeit und Geduld. Aus der Vielzahl von Funktion in Coin3D diejenige zu finden, die das aktuelle Problem beseitigt, war ebenfalls sehr zeitaufwändig. Das Handbuch The Open Inventor Mentor war mir dabei zwar eine große Hilfe, im Nachhinein betrachet wäre es mir jedoch lieber gewesen, mich in die verwendete Software vor dem tatsächlichen Beginn der Studienarbeit einzuarbeiten und somit mehr Zeit für die Implementierung zu haben. Mit zunehmender Komplexität des Programms fielen mir immer mehr Features ein, mit denen ich das Spiel erweitern könnte. Auch wenn ich es nicht mehr geschafft habe, alle zu implementieren, möchte ich an dieser Stelle noch einige erwähnen. In der jetzigen Version befindet sich der Ein- und Ausgang immer an der gleiche Stelle. Das heißt, man weiß immer in ungefähr in welche Richtung man sich bewegen muß. Um das Spiel zu erschweren, wäre es deshalb schön, wenn sowohl der Eingang, als auch der Ausgang variabel gesetzt werden könnten. Da ein Spiel ohne Ton immer etwas eintönig erscheint, wäre die Einbindung von Sound eine interessante Erweiterung. Auch die Implementierung einer Zeitbegrenzung wäre denkbar. Die abgelaufene Zeit sollte dann stets im Blickfeld des Spielers sein, was eventuell durch die Projektion von Text auf die Nearplane realisiert werden könnte. Mein Hauptanliegen wäre es jedoch, aus dem Spiel eine freistehende Applikation zu machen, bei der die Ein- und Ausgabe nicht mehr über die Konsole erfolgt. Abschließend kann ich nur sagen, dass ich wirklich viel gelernt habe und mit dem Ergebnis zufrieden bin. 28

Abbildungsverzeichnis 1 Symbole für verwendete Knoten..................... 5 2 Definition der Module.......................... 7 3 Beispiel-Module in 3D.......................... 8 4 Die Klasse Lab.............................. 9 5 Das Umfeld-Problem........................... 11 6 Die Switch-Anweisung.......................... 12 7 Der Szenegraph vom Root-Knoten aus................. 13 8 Der Subgraph Laby............................ 13 9 Schleife, um Module zu setzen...................... 14 11 Die Subgraphen Coins und Maps.................... 15 10 zeitgesteuerte Rotation.......................... 15 12 Position von Münzen zufällig bestimmen................ 16 13 Der SoBlinker-Knoten.......................... 16 14 Der Subgraph In............................. 17 15 Der Callback-Knoten........................... 18 16 Die Callback-Funktion.......................... 19 17 Die Test-Kamera............................. 20 18 Die Kollisionerkennung und -behandlung................ 21 19 Die Anfangsposition der Kamera mit Blick ins Innere des Labyrinths 23 20 Die Tür vor dem Ausgang........................ 23 21 Das Warnschild vor dem Eingang.................... 24 22 Eine Münze................................ 24 23 Eine Karte................................ 25 24 Sicht von oben auf ein 20*20-Labyrinth................ 25 25 Sicht von oben auf ein 11*11-Labyrinth................ 26 26 Gewonnen................................. 26 27 Panorama in +x/+z-richtung...................... 27 28 Panorama in -x/-z-richtung....................... 27 29

Literatur [1] The Inventor Mentor: Programming Object-Oriented 3D Graphics with Open Inventor, Release 2, [Wer94] Wernecke, J., Addision-Wesley, 1994 [2] Open Inventor T M C++ Reference Manual: The Official Reference Document for Open Inventor, Release 2. Open Inventor Architecture Group, Addison- Wesley Publishing Company, July 1994 [3] www.coin3d.org [4] www-eleves-isia.cma.fr/documentation/openinventordoc/coin/ 30