Files |  Tutorials |  Articles |  Links |  Home |  Team |  Forum |  Wiki |  Impressum

Aktuelle Zeit: Di Mär 19, 2024 10:49

Foren-Übersicht » Sonstiges » Projekte
Unbeantwortete Themen | Aktive Themen



Ein neues Thema erstellen Auf das Thema antworten  [ 50 Beiträge ]  Gehe zu Seite Vorherige  1, 2, 3, 4
Autor Nachricht
 Betreff des Beitrags: Re: [Game] Gael
BeitragVerfasst: Mi Sep 26, 2012 22:09 
Offline
DGL Member
Benutzeravatar

Registriert: Mi Mär 09, 2005 15:54
Beiträge: 372
Wohnort: München
Programmiersprache: Delphi, C#, FPC
Wie im letzten Post angekündigt wollte ich noch ein paar Screenshots machen. Nun bin ich endlich dazu gekommen. Dies ist nur ein kurzer Update-Post und bezieht sich nur auf die Reflektionen.

Hier erst mal ein paar zum Anschauen (zum Vergrößern klicken)
Bild Bild Bild Bild Bild


Jetzt wollte ich noch kurz zeigen, welche Limitierungen diese Technik hat. Dazu zuerst zwei Bilder
Bild Bild
Wenn man sich das linke Bild anschaut, dann kann bei der Kugel schon ein paar Grafikfehler erkennen. Auf Grund der Distanz zur Kamera fallen diese aber nicht sofort ins Auge. Trotzdem eignet sich die Methode für solch starke Reflektionen eher bedingt. Ziemlich deutlich kann man die Limitierung am rechten Bild erkennen. Alles, was hinter der Kamera ist, ist schwarz. Das ist auch nicht änderbar, da die Technik im ScreenSpace läuft und Pixel hinter dem Betrachter dort nicht vorhanden sind. Zudem kann man deutlich erkennen, dass die Reflektion sehr "kantig" ist. Das lässt sich theoretisch beheben lassen, doch dann wäre es nicht mehr spielbar.

Nun noch ein Bild, mit dem man die Flexibilität des Algorithmus sehen kann.
Bild
Fast alle sichtbaren Flächen reflektieren und es spielt dabei keine Rolle, ob die Fläche eben ist oder abgerundet (wie bei den Kugeln). Das ganze ist auch noch schnell genug, dass es spielbar bleibt (bei mir am Laptop in (fast) FullHD (der Rahmen vom Editor nimmt ein paar Pixel weg) mit 30 FPS.

Damit ihr den Unterschied besser sehen könnt, habe ich ein paar Vergleichsbilder gemacht. Damit ihr das gut erkennen könnt, empfehle ich die Bilder wie eine Art Daumenkino anzuschauen (also gleiches Fenster, gleiche Größe, schnell hin- und her wechseln).
Bild Bild Bild Bild
Von Links nach Rechts:
  • Nur direktes Licht (indirektes Licht aus, Reflektionen aus)
  • Direktes Licht mit Reflektionen (indirektes Licht aus, Reflektionen an)
  • Direktes+Indirektes Licht (indirektes Licht an, Reflektionen aus)
  • Direktes+Indirektes Licht + Reflektionen

So, ich hoffe es gefällt.

Gruß
David

_________________
Aktuelles Projekt: Gael - Development Blog
Website: LightBlackSoft.com


Nach oben
 Profil  
Mit Zitat antworten  
 Betreff des Beitrags: Re: [Game] Gael
BeitragVerfasst: Sa Jan 05, 2013 20:47 
Offline
DGL Member
Benutzeravatar

Registriert: Mi Mär 09, 2005 15:54
Beiträge: 372
Wohnort: München
Programmiersprache: Delphi, C#, FPC
So, die Feiertage (und leider auch mein Urlaub) neigen sich nun dem Ende. Ich habe in den letzten Wochen wieder viel an Gael weitergearbeitet - aus zeitlichen Gründen schaffe ich es sonst kaum noch, an Gael weiter zu arbeiten.

Allgemeine Planungen
Vielleicht erinnert sich noch jemand an die letzte Demo, bei der bereits viel Spiellogik drinnen war. Ich glaube, dass ich da wieder einiges löschen und neu machen muss. Das liegt daran, dass ich damals einige Sachen falsch oder nur unzureichend geplant und umgesetzt hatte. Das ganze ist für mich natürlich kein wirklicher Motivationbringer, aber da muss ich wahrscheinlich einfach durch. Angefangen von der weiter unten angesprochenen Änderungen bei der Resourcenverwaltungen hinüber zu einem überarbeiteten API für die ScriptEngine.

Das gleiche gilt für das Rendering-System von Models. Da gibt es noch eine Einschränkung, die mir nicht wirklich passt: im Moment kann für ein Model nur ein Material definiert werden. Sobald ein Model aber unterschiedliche Materials hat (z.B. Kopf und Beine haben eine andere Textur), funktioniert das ganze nicht mehr. Insgesamt ist das Modelsystem doch sehr mit der heißen Nadel gestrickt, daher kommt da auch wieder viel Arbeit auf mich zu.

Ein weiterer Punkt ist das GUI-System. Es ist zwar relativ flexibel, muss aber nochmal komplett durchgeschaut und teilweise neu implementiert werden. Da ich beruflich letzten Herbst viel mit WPF aus dem .NET-Framework gearbeitet habe, habe ich auch einige gute Ansätze im Kopf. Diese will ich in das GUI-System einbauen. Hier kommen dann auch wieder Änderungen für die Script-API, die dann auch wieder ein weiterer Grund für den oben angesprochenen Rewrite der Spiellogik sind.

Der Editor
Ein weiterer riesiger Brocken ist der Editor. Der hat zwar so leidlich funktioniert, aber das reicht nicht. Am Editor habe ich bereits viel Überarbeitet und langsam komme ich an einen Punkt wo ich sagen würde: es geht voran. Ein riesiger Brocken war eine "Anpassung" an eine grundlegende Umstellung bei der Resourcenverwaltung der Engine. Das alte Resourcensystem hatte vier bis fünf xml-Dateien, in denen die einzelnen Resourcen für eine Map definiert wurden. Ein riesiges xml-File, in der alle Texturen, Materials, usw. definiert waren, ein großes xml-File, in der alle Shader eingebunden waren, usw. Das ganze System war überhaupt nicht wartbar. Wenn ich z.B. ein Material ändern wollte oder nur eine Textur für ein Material austauschen wollte, gab es ein riesen Problem: ich musste die Änderung manuell in jede Map neu einpflegen. Das konnte jedoch zu weiteren Problemen führen - z.B. wenn unterschiedliche Versionen des Materials im Umlauf waren. Dieses komplette Konstrukt habe ich auseinander genommen und neu aufgebaut: jedes Material, jede Shaderdefinition, alles hat nun eine eigene Datei. In den Maps wird dann nur noch auf die jeweilige Daten referenziert und fertig. Das ganze brachte aber weitere Arbeit: ich brauchte nun eine Möglichkeit, diese ganzen Dateien sinnvoll zu erweitern. Da habe ich mir etwas Inspiration vom Content Editor aus dem UnrealED zufliegen lassen und auch nun auch einen solchen Content-Editor. Dieser ist noch nicht ganz fertig, aber das dauert nicht mehr lange. Mit diesem Editor kann ich schnell neue Materials erstellen, bearbeiten und einzelnen Objekten in der aktuell im Editor gelandenen Map zuweisen, Texturen importierten, usw. Was noch fehlt sind ein paar Komfortfunktionen wie "Duplikat erstellen", "Umbenennen", etc.

Es gibt noch viele weitere Punkte, dich ich aber erst dann beschreibe, wenn ich sie zu Ende gedacht habe und sie auch zum Teil auch schon umgesetzt habe.

Was fürs Auge: Vertex Painting
Um die Motivation nicht komplett in den Keller sinken zu lassen, versuche ich zwischendurch immer mal wieder neue Sachen in die Engine einzubauen. Ab und zu komme ich selbst auf neue Ideen, wann anders finde ich Implementierungen die ich per YouTube finde, sehr interessant. So ist es auch diesmal: Per Zufall bin ich auf Vertex Painting Shader aufmerksam geworden und fand das extrem super.

Das Grundprinzip ist relativ einfach: bei jedem Vertex wird zusätzlich noch eine Farbe gespeichert. Ein Shader überblendet dann ein oder mehrere Texturen - und für die Überblendung wird der Farbwert der Vertice hergenommen. Einfaches Beispiel: ich habe zwei Texturen, eine Steintextur und eine Grastextur. Über eine einfache lineare Interpolation wird zwischen den beiden Texturen hin- und hergeblendet. Als dritten Parameter für die Interpolation (GLSL-Funktion: mix) wird der rote Farbwert der Vertex-Farbe hergenommen. Da die Farben zwischen den einzelnen Vertice auch interpoliert werden, ist der Farbverlauf sehr weich.

Das besondere ist nun, dass man im Zusammenspiel mit speziellen Shadern direkt auf einem Mesh malen kann - einfach indem man die Farbwerte der einzelnen Vertice anpasst. Je mehr Vertice ein Mesh dabei hat, desto genauer kann man zeichnen. Da man mit aktuellen Grafikkarten nicht mehr so stark Dreiecks-beschränkt ist, funktioniert das auch ohne Probleme. Wobei man dazu sagen muss, dass man nicht all zu viele Dreiecke für gute Ergebnisse braucht. Der Vorteil dieser Technik ist, dass man extrem viele Variationen mit nur einem einzelnen Material erreichen kann. Trotz Texture-Tiling kann man mit dieser Technik einer Wand ohne große Probleme sehr gut individualisieren.

Da die Überblendung für fast jedes Material einen eigenen, speziell angepassten Shader braucht, habe ich das Materialsystem nun etwas flexibler gestaltet. Grundlegend kann man jedem Material einen Materialshader zuweisen. Dieser Materialshader ist eine Zusammenfassung von mehreren GLSL-Shadern - jeweils für die einzelnen Render-Schritte. Nun kann ein Material zusätzlich noch einzelne GLSL-Shader aus diesen Materialshader-Sets überschreiben bzw. diese überschreiben. Dadurch, dass die GLSL-Klasse "#include" - Anweisungen auflösen kann, muss man nicht immer den kompletten Shader-Code schreiben - man kann statt dessen einzelne Schritte per #define überscheiben.

Damit ihr euch das ganze auch mal anschauen könnt, habe ich auch mal wieder ein Video erstellt. Das ganze könnt ihr euch wie immer hier auf YouTube anschauen.

Was kommt als nächstes?
Zunächst werde ich noch länger am Editor schrauben. Es fehlen noch weitere Tools, weitere Verbesserungen, etc. Danach werde ich unter anderem die oben genannten Probleme lösen. Sobald das ganze so grob funktioniert, werde ich die Script-API neu aufbauen (GUI noch nicht drinnen). Ich plane die Möglichkeit, direkt im Editor das Spiel zu starten und so auch das Debuggen leichter zu machen. Das wird sehr komplex, wird aber das Entwickeln und das Testen der Spiellogik viel schneller und leichter machen. Wann ich damit wirklich anfangen kann, weiß ich aber leider noch nicht.

Grüße
David

_________________
Aktuelles Projekt: Gael - Development Blog
Website: LightBlackSoft.com


Nach oben
 Profil  
Mit Zitat antworten  
 Betreff des Beitrags: Re: [Game] Gael
BeitragVerfasst: Di Mär 12, 2013 23:28 
Offline
DGL Member
Benutzeravatar

Registriert: Mi Mär 09, 2005 15:54
Beiträge: 372
Wohnort: München
Programmiersprache: Delphi, C#, FPC
Hallo allerseits,

Script-API und Script-Engine
im letzten Post habe ich ja bereits geschrieben, dass ich einiges zu überarbeiten habe. Das geht auch schon ganz gut voran. Die Erstellung einer neuen Script-API für die Engine hat schon gewisse Konturen angenommen. Jedoch musste ich dafür auch mal wieder meine Script-Engine erweitern. Der Compiler hat nun ein paar nette Features mehr, ein paar grobe Bugs weniger und - am wichtigsten - die Kompilierreihenfolge der einzelnen Scripts wird vom Compiler nun besser aufgelöst. Das liegt daran, dass in meiner Script-Engine - die ja fast identisch zu Delphi ist - eine andere Definition einer Unit hat. Eine Unit ist nicht eine einfache Datei sondern eher ein Package oder ein Namespace. Innerhalb eines solchen Namespaces können somit auch mehrere Dateien vorhanden sein. Der Compiler ist nun in der Lage, die einzelnen Dateien in eine korrekte Reihenfolge zu bringen, so dass dieser den Namespace kompilieren kann. Wenn der Compiler es nicht schafft, eine mögliche Reihenfolge zu ermitteln, dann liegt es am Script-Code. Cross-Referenzen kann er noch nicht auflösen. Dieses automatische Auflösen der Reihenfolge nimmt mir sehr viel Arbeit ab und war auch ein Grund, warum die bisherige API nicht ganz so gut war. Denn vorher habe ich die Reihenfolge manuell einstellen müssen, was späteres Hinzufügen von neuen Dateien zum Teil zur Qual gemacht hat.

Optimierungen
Des weiteren habe ich einige Optimierungen und Verbesserungen eingebaut. Vor allem die Shader sind zum Teil deutlich schneller geworden, indem ich alte Code-Fetzen rausgehauen habe oder einfachere Algorithmen für bestimmte Probleme benutzt habe. Jedoch liegt noch weitere Arbeit vor mir, denn die Shader sind zum Teil noch sehr aufwendig und somit resourcenhungrig.

Eine weitere Optimierung, die ich nun fertig gestellt habe, ist eine Abstraktionsschicht zu OpenGL. Diese hat die Aufgabe, redundante State-Changes so gut wie möglich zu verhindern. Eine solche Schicht ist wirklich schnell geschrieben und bringt bei einer Projektgröße, wie Gael es mittlerweile erreicht hat, einiges an Performance-Schub. Bisher habe ich auf solche Sachen weniger geachtet, doch nun bin ich da wirklich schlauer. Ich habe die Anzahl der OGL-Calls pro Frame auf ca. die 1/3 reduziert (bei manchen Situationen sogar bis zu 1/5). Ein paar Punkte muss ich noch umsetzen um weitere OGL-Calls zu sparen, doch da hilft mir die Abstraktionsschicht nicht viel, da ich da konzeptionell ein paar Sachen ändern muss. Doch das sind nur ein paar kleine Sachen, die aber eine große Wirkung haben werden.

Was ich sonst noch mache
Aktuell arbeite ich an einer neuen Map, die schon sehr vielversprechend aussieht. Das mache ich immer mal wieder nebenher, wenn ich mal wieder eine Idee habe oder einfach Lust habe, ein wenig weiter zu machen. Nun hatte ich wieder eine Idee, die ich jedoch so erst mal nicht umsetzen konnte. Also Ärmel hochgekrempelt und den Engine-Code mal wieder erweitert.

Sellmann hat im Kommentar-Thread vor etwas mehr als 3 Jahren gefragt, ob ich wieder Wasser wie bei meiner alten Engine einbauen werde. Damals habe ich noch nein gesagt. Zum einen dachte ich, dass ich das nicht brauche, zum anderen wäre es zu aufwändig gewesen, eine Reflektionstextur zu erstellen. Denn dafür muss ich einen kompletten Deferred-Pass sowie einen Lighting-Pass durchführen. Das ist nicht ohne. Doch für meine Idee brauchte ich Wasser. Also musste ich das Problem irgendwie lösen. Dann kam mir die Idee, dass ich statt einer Reflektionstextur eigentlich auch meine Realtime Local Reflections benutzen könnte. Also habe ich es mal ausprobiert und es schaut meiner Meinung nach wirklich gut aus. Natürlich haben sich die Einschränkungen bei RLR nicht verändert, doch man muss das Wasser ja nur leicht spiegeln lassen ;-).

Jetzt gab es aber noch ein Problem: die Refraktion. Dafür wollte ich natürlich auch keinen extra Render-Pass verschenken. Aber da ich ja alle notwendigen Informationen für die Refraktion bereits in den Texturen habe, kann ich die ja einfach verwenden und die Refraktion als Post-Scene-Effekt einbauen. Also habe ich den Engine-Code erstmal so erweitert, dass ich als Renderfläche für Post-Effekts nicht nur ein 2D-Fullscreen-Quad benutzen kann, sondern auch 3D-Geometrie. Einen kleinen Schönheitsfehler hatte das ganze aber noch. Ich habe keinen Depth-Buffer, mit dem ich einen Depth-Test machen lassen konnte. Somit musste ich den Depth-Test deaktivieren und einen eigenen im Fragment-Shader für die Wasseroberfläche implementieren. Da ich dort Zugriff auf den Depth-Buffer aus dem Deferred-Render-Pass habe, war das eigentlich kein Problem. Ich habe nur mehrere Stunden gebraucht um die richtigen Werte miteinander zu vergleichen - manchmal sieht man den Wald vor lauter Bäumen nicht.

Da ich dann eh mit dem Depth-Buffer herumgespielt habe, habe ich das gleich genutzt um die Refraktion abhängig von der "Wassertiefe" zu machen. Somit vermeide ich komische Artefakte, die bei Objekten entstehen, die aus dem Wasser ragen. Im unteren Video kann man das gut sehen. Dort verschiebe ich die Wasseroberfläche mal sehr nah an den Boden und die Verwurschtelung nimmt sichtbar ab.

Aber erst mal will ich euch das Ergebnis das Ergebnis mit 4 Screenshots zeigen. Bei den Screenshots habe ich von links nach rechts einzelne Features hinzugefügt. Ich habe für die Screens nur eine einfache Ebene genommen - ich könnte aber auch beliebige 3D-Objekte verwenden, da ich keine Ebene brauche, für die ich die Texturen erzeugen müsste.
  • 1. Einfache Ebene, die durch ihre Farbe und ihren Transparenzwert den Farbpuffer verändert
  • 2. Nebeleffekt, der undurchsichtigeres Wasser simulieren soll. Er ist abhängig von der Distanz zwischen dem ursprünglichen Tiefenwert und den Tiefenwert des Pixels der Wasseroberfläche. Da ich noch Licht und Schatten beachten muss, ist dieser noch lange nicht perfekt - aber er reicht aus.
  • 3. Refraktion des ursprünglich gerenderten Bildes mit Hilfe der Normalenvektoren der Wasserebene. Hier ist die Reflektion ausgeschaltet.
  • 4. Alle Effekte aktiv.

Bild Bild Bild Bild

Für alle, die das noch in Bewegung sehen wollen - natürlich gibt es davon wieder mal ein Video auf YouTube

Gruß
David

_________________
Aktuelles Projekt: Gael - Development Blog
Website: LightBlackSoft.com


Nach oben
 Profil  
Mit Zitat antworten  
 Betreff des Beitrags: Re: [Game] Gael
BeitragVerfasst: Mo Jun 17, 2013 00:08 
Offline
DGL Member
Benutzeravatar

Registriert: Mi Mär 09, 2005 15:54
Beiträge: 372
Wohnort: München
Programmiersprache: Delphi, C#, FPC
Hi,

nachdem ich in letzter Zeit einige Punkte abarbeiten konnte, wollte ich mal wieder ein kleines Statusupdate machen.

Script-API und Script-Engine
Im letzten Post habe ich ja bereits geschrieben, dass ich mit der Umstellung der Script-API ganz gut voran komme. Nun habe ich endlich einen wichtigen Meilenstein für mich erreicht: ich kann direkt im Editor das Spiel starten :-). Somit ist nun Entwickeln der eigentlichen Spiellogik sehr viel schneller, da man nicht immer erst die Game-Exe neu starten muss und das Level neu laden muss. Ich starte einfach die Logik und lasse sie auf die aktuell geladene Map los. Das klappt wirklich wunderbar und macht sehr viel Laune :-).

Das ganze ist relativ einfach: der Editor schaut in einem bestimmten Verzeichnis nach kompilierten Script-Dateien. Hat er eine gefunden, läd er die Datei und schaut nach, ob es eine Klasse gibt, die von einer vorgegebenen Script-Klasse abgeleitet ist. Falls ja, dann wird der Dateiname sowie der Klassenname in einer Liste gespeichert. Ich kann im Editor in einem Reiter nun einfach meine gewünschte Logikklasse auswählen, F9 drücken und schon gehts los. Der Editor wechselt dabei in einen speziellen Modus, in einige Editor-spezifischen Sachen deaktiviert werden. Danach übergibt er das Handling fast komplett an die Script-Engine. Diese kümmert sich nun um alles: also das nachladen von dynamischen Objekten (z.B. der Taschenlampe, von Models, etc.), das Abhandeln der Spiellogik sowie das starten des Rendervorgangs für jeden Frame. Sobald ich ESC drücke, unterbricht der Editor sofort das Script, räumt alles nachgeladene wieder auf und wechselt wieder zurück in die Editoransicht. Im ausgelagerten Script-Editor kann ich die Scripte schnell ändern und neu kompilieren. Bei jedem Start im Editor wird die Datei neu geladen, wodurch immer der aktuellste Stand benutzt wird. Das ganze System funktioniert bisher sehr gut und ich bin damit sehr zufrieden.

Gamma-Correction
Bisher habe ich das Thema Gamma-Correction komplett ignoriert - weil ich die Notwendigkeit nicht so wirklich verstanden hatte. Durch Zufall bin ich vor nicht all zu langer Zeit jedoch auf diesen Artikel in GPU Gems 3 gestoßen. Dort wird - wie ich finde - die Notwendigkeit von Gamma-Correction beim Imput und beim Output ziemlich gut erklärt. Gerade die Sache mit dem Input war mir wirklich relativ neu. Ich wusste zwar, dass es z.B. das sRGB-Format auf der Grafikkarte gibt - die Verwendung war mir aber noch nicht so ganz klar. Jedoch habe ich das nun eingebaut und bin über die Ergebnisse sehr überrascht - im positiven Sinne. Kein extremes Überblenden mehr, wenn mehrere Lichtquellen auf eine Stelle leuchten, keine Farbänderungen bei den Specular-Werten, etc. pp. Der Aufwand (auch wenn er relativ klein war, ca. 5 Stunden) hat sich wirklich gelohnt.

Ich würde jedem, der schon etwas weiter mit OpenGL ist, diesen Artikel wirklich ans Herz legen. Gamma-Correction ist ein nicht zu vernachlässigendes Thema. Der Aufwand ist wirklich nicht so riesig. Man ist zwar ganz am Anfang erst mal enttäuscht, denn die Grafikausgabe schaut danach doch einiges anders aus. Doch mit ein paar Anpassungen bei euren Lichtern und beim Output-Gamma lässt sich das ganz gut wieder hinbiegen.

Stereo-3D-Rendering
Aktuell erweitere ich die Engine so, dass eine Side-By-Side Ausgabe möglich ist. Ich bin noch nicht ganz fertig, denn einiges funktioniert noch nicht. Aber ich brauche das unbedingt. Jetzt wird sich der ein oder andere sicher Fragen: warum auch noch diesen Aufwand? Die Antwort besteht aus zwei Wörtern: Oculus Rift. Manchen von euch ist das wahrscheinlich bereits ein Begriff, anderen noch nicht. Um es kurz zu fassen: Oculus Rift ist ein Virtual-Reality-Headset, welches eigentlich relativ schnell erklärt ist: Es ist eine Art Skimaske, in der zum einen ein Monitor und zum anderen ein Orientation-Sensor eingebaut ist. Mit dem Sensor wird die Lage eures Kopfes gemessen und das HMD (Head Mounted Display) dient als Ausgabemonitor. Durch die extrem kurze Distanz zwischen Monitor und euren Augen sowie ein paar Linsen entsteht der Eindruck, dass der Bildschirm euer komplettes Sichtfeld umfasst. Durch den extrem schnellen Sensor wird die Kopfrotation sehr gut erkannt und die Ausgabe am Monitor ist somit fast ohne Lag. Jedes Auge bekommt ein eigens Render-Bild, wodurch ein sehr realer 3D-Effekt erzeugt wird (daher auch die Integration in meine Engine). Insgesamt entsteht so ein komplettes Mittendrin-Gefühl, welches - laut den bisherigen Berichten - wirklich extrem stark und unbeschreiblich ist. Es gibt auf YouTube auch schon viele Reaktionsvideos von Leuten, die das Oculus Rift tragen.

Falls euch das genauer interessiert, könnt ihr einfach mal bei http://www.oculusvr.com vorbeischauen und euch das ganze mal genauer anschauen. Es gibt mittlerweile auch viele Artikel, die das ganze ganz gut beschreiben.

Als ich davon Wind bekommen habe, war ich sofort begeistert. Ich habe direkt ein Developer-Kit vorbestellt (die Consumer-Version wird wahrscheinlich frühestens Ende 2014 da sein). Leider ist die Wartezeit relativ lang, aber ich hoffe, dass ich mein Dev-Kit bis spätestens Ende Juli haben werde. Bis dahin versuche ich dann alles notwendige fertig zu haben, so dass ich die Engine möglichst bald nach dem Erhalt damit wirklich mal damit ausprobieren kann.

Sobald es in dieser Richtung was neues gibt, werde ich wieder schreiben.

Bis dahin,
Gruß
David

_________________
Aktuelles Projekt: Gael - Development Blog
Website: LightBlackSoft.com


Nach oben
 Profil  
Mit Zitat antworten  
 Betreff des Beitrags: Re: [Game] Gael
BeitragVerfasst: Di Sep 17, 2013 22:20 
Offline
DGL Member
Benutzeravatar

Registriert: Mi Mär 09, 2005 15:54
Beiträge: 372
Wohnort: München
Programmiersprache: Delphi, C#, FPC
Ich meld mich auch mal wieder.

In der Zeit seit dem letzten Post habe ich wieder viel an Gael weiterarbeiten können und habe nun wieder einen Stand, über den ich mal wieder berichten kann. Der Post ist diesmal etwas lang geworden ;-), aber vielleicht dürfte es den ein oder anderen interessieren. Es geht diesmal hauptsächlich darum, wie ich die Interaktion zwischen Map und Spieler ermögliche - und zwar möglichst einfach und flexibel.

Oculus Rift
Nachdem ich mein Developer Kit bekommen habe, habe ich erstmal mehrere Tage damit herumgespielt und war total gefesselt. Man schaut mit dem Ding auf dem Kopf wirklich etwas bescheuert aus, aber einem selber interessiert das überhaupt nicht. Es ist Wahnsinn, denn der Eindruck, dass man sich IN etwas bewegt ist so überwältigend. Natürlich habe ich meine Engine so schnell wie möglich Rift-Ready gemacht und es ging wirklich erstaunlich schnell. Der schwierigste Part war das Stereo-Rendering, was ich vorher ja bereits gemacht hatte. Die eigentliche Integration war dank des guten SDKs wirklich kein Problem und war innerhalb von wenigen Stunden erledigt. Hinzu kam noch das Schreiben des Post-Scene-Effects für das Barrel-Distortion um die Linsen des HMD auszugleichen und ein paar kleine Bugfixes. Ohne den Stereo-Part hatte ich nach drei Tagen einen funktionieren Stand.

Falls die Spiellogik ein Oculus Rift findet, aktiviert es automatisch den entsprechenden Post-Scene-Effect und schaltet auf ein anderes Control-Scheme um. Dies ist notwendig, da die Maus mit dem HMD nicht mehr die einzige Möglichkeit ist, sich umzusehen - denn man kann ja auch einfach seinen Kopf drehen oder mit dem Kopf nach oben/unten schauen.

Script-API
Das Scripting-Interface ist nun sehr sauber abgekapselt und gut strukturiert. Es sind zwar noch nicht alle Klassen der alten API vorhanden (z.B. GUI), aber das liegt daran, dass diese komplett neu schreiben will. Das bisherige System war zwar ganz nett, ist aber nicht sauber definiert gewesen und somit nicht sehr flexibel.

Das Scripting ist nun in mehrere Module unterteilt, die jeweils aufeinander aufbauen.
  • Das Basis-Modul besteht nur aus dem Import der nativen Klassen der Engine, auf die man über Scripts zugreifen kann. Hier kann man z.B. auf die VBO-Klasse, Texturen-Klasse, Material-Klasse, eigentlich alle Klassen, zugreifen. Dieses Modul ist in zwei Teile unterteilt. Zum einen die Methoden und Klassen der Engine (also der native Part) sowie "generierte" Script-Dateien, die den Zugriff auf den nativen Teil ermöglichen. Generiert heißt hier, dass diese Scripts automatisch generiert werden - sie müssen manuell gepflegt werden. Jedoch fasse ich diese Klassen nur an, wenn ich die nativen Klassen erweitere/ändere. Sie werden also von mir "generiert" :-).
  • Das zweite Modul ist eine Art Utility-, Actor- und Attribute-Modul, welches noch keine spielspezifischen Klassen beinhaltet, jedoch trotzdem wichtige Logik für das eigentliche Spiel bereitstellt. Das ist neu und werde ich weiter unten genauer beschreiben.
  • Das dritte Modul ist das eigentliche Spiel, welches die anderen beiden Module benutzt, um mit der Engine zu kommunizieren.

ScriptEngine
Während der Entwicklung sind mir immer wieder Features aufgefallen, die ich innerhalb der ScriptEngine bräuchte, um mir das Leben einfacher zu machen. Dadurch habe ich nun einige Erweiterungen in die ScriptEngine eingebaut und auch ein paar lästige Bugs beseitigt. Zum einen kann die ScriptEngine jetzt auf Meta-Informationen aller Klassen zugreifen. Dabei benutzt es eine Klasse ähnlich zu TClass in Delphi. Zwar unterstütze ich keine virtuellen statischen Methoden, was TClass von Delphi so mächtig macht, aber für ein paar andere Sachen habe ich jetzt schon öfter mal den Script-Klassennamen aus einem Script heraus an die Engine übergeben müssen. Ein string ist hier aber sehr fehleranfällig, ein TObject.FullName ist da besser, da es ja schon zur Kompilierzeit überprüft wird. Es sind noch ein paar weitere Features aktuell in der Pipeline, wie z.B. Fibers. Diese brauche ich, um später sehr einfach Coroutinen erstellen zu können. Mit Hilfe dieser Coroutinen kann z.B. ein Script-Methode über mehrere Frames hinweg ausgeführt werden, was Animationen sehr viel einfacher machen kann.

Was als nächstes auf meiner ToDo-Liste steht
Über die Dauer des Projektes habe ich bereits mehrere ToDo-Listen geführt - jedoch haben diese mir nicht wirklich gut geholfen. Ich habe z.T. die Listen vergessen upzudaten oder Punkte in die Liste mit aufzunehmen. Doch nun bin ich bei einer relativ einfachen Methode hängen geblieben, die für mich wirklich gut funktioniert. Auf meinem Tablet habe ich eine MindMap, die als ToDo-Liste fungiert. Da ich das Tablet während der Implementierung immer vor mir habe, aktualisiere ich die Mindmap ständig. Sobald ich eine Idee habe, trage ich es in die Mindmap ein. Das hat alles sofort funktioniert, ohne mich groß dazu zwingen zu müssen. Hätte ich das doch nur früher gewusst ;-) ...

Naja, wie dem auch sei, folgende große Punkte habe ich aktuell auf meiner Liste
  • Basisunterstützung für "Modular Level Design". Ich habe bereits sehr viel darüber gelesen will das auch für mich benutzen.
  • Mesh-System mit Instancing (brauche ich z.B. auch für den genannten Punkt)
  • GUI und HUD-Renderer mit Stereo-Rendering (3D-Menüs, etc.)
  • Besserer Sky-Renderer
  • Debug-Shape-Rendering für einfacheres InGame-Debugging
  • (wenn ich Lust habe) Vegitation mit GPU-Based Animation
  • 3DS-Mesh-Import

Actors und ein Event-System für die Spiellogik
Das Thema "Interaktion Spieler <-> Umwelt" habe ich in der ganzen Zeit total vernachlässigt. Ich hatte zwar früh meine Script-Engine integriert, aber wirklich was damit machen konnte ich nicht. Für ein paar rotierende Ventilatoren hat es gereicht, mehr wurde aber schwerer bis unmöglich. Zudem war es unflexibel und fehleranfällig. Nun habe ich mich endlich an das Thema ran gesetzt, aber nicht wieder irgendwie nach ein paar Minuten/Stunden überlegen. Stattdessen habe ich mich in das Thema richtig eingelesen: Vor zwei Jahren habe ich mir das Buch "Game Engine Architecture" gekauft und habe dafür mal wieder drin geblättert. Darin wurden für das Problem mehrere Möglichkeiten präsentiert, eine hat mir jedoch besonders gefallen. Nachdem ich mehrere Tage lang überlegt habe, was ich genau brauche und will, habe ich mich entschieden und sofort losgelegt.

Was für mich wichtig war: es sollte flexibel, schnell, einfach zu benutzen und erweiterbar sein. Nehmen wir folgendes Beispiel: der Spieler schießt mit einer Waffe auf einen Gegenstand. Nun gibt es viele Möglichkeiten, die passieren können. Zum einen soll ein Partikel-Effekt am Einschusspunkt angezeigt werden. Der ist natürlich abhängig vom Material des Objektes. Des weiteren sollte ein Impact-Sound abgespielt werden - wieder abhängig vom Material. Das ließe sich ja alles noch über einfache Attribute steuern, da diese sich mit der Zeit nicht ändern (normalerweise). Schwieriger wird der Fall, dass die Physik-Engine einen Impuls für das Objekt bekommt - falls es sich den um ein dynamisches Objekt handelt natürlich. Ein Attribute könnte hierbei beim ersten Nachdenken auch noch ausreichen. Doch wenn z.B. eine Runde auf der Karte beendet ist, soll das Objekt wieder zurückgesetzt werden. Oder noch komplizierter: das Objekt hat ein Health-Attribute und kann zerstört werden. Eine Tür könnte aus den Angeln gerissen werden oder ein Benzinfass explodieren. Die Anzahl der Attribute und Kombinationsmöglichkeiten wird dann sehr schnell unübersichtlich. Auch zu beachten wäre hierbei die Größe und Komplexität der Routine, die mit all den Kombinationsmöglichkeiten umgehen können müsste.

Also einfache Attribute reichen nicht. Die nächste Idee: Script-Klassen. Durch Vererbung könnte man ein relativ schnell Klassen erstellen bzw. erweitern (eine einfache virtuelle Methode "ShootedAt()" und die Vererbung alles machen lassen) - NEIN - halt: Vererbung geht hier nicht, jedenfalls nicht wenn man keine Mehrfachvererbung hat (die jedoch auch nicht gut ist; Stichwort Diamond of Death). Nehmen wir an, ich habe eine Klasse die dafür verantwortlich ist, dass ein Objekt bei einem Einschuss einen physikalischen Impuls bekommt und somit von der Physik-Engine verarbeitet wird. Nun will ich noch ein explodierendes Fass, dass sich natürlich auch physikalisch verhalten soll -> kein Problem, denn über Inheritance ist das ohne Probleme möglich. Doch was ist nun, wenn ich ein Objekt habe, dass komplett statisch ist, aber explodieren kann? Die Vererbung umdrehen geht nicht, sonst wäre jedes physikalische Objekt automatisch explosiv. Ich könnte die Physik jedoch auch über ein Boolean-Flag de-aktivierbar machen - auch nicht wirklich schön, denn irgendwann habe ich dann eine Basisklasse mit 100 Config-Flags. Oder ich kopiere den "Explosion"-Part in eine neue Klasse - oh, doppelten Code.

Vererbung geht nicht. Also was nun? Wie wäre es mit Komponenten! JA. Es gibt eine einfache Klasse "TExplosible" die ein Objekt explodieren lässt. Und es gibt eine Klasse "TBulletPhysicReaction". Nun hat ein Objekt nicht eine Script-Klasse die alles beinhalten muss, sondern mehrere "Komponenten"-Klassen, die sich komplett abgeschlossen um einzelne Teile kümmern. Jedem Objekt, welches ich "herumschießen" kann, füge ich eine Instanz der Klasse "TBulletPhysicReaction" hinzu. Und jedem Objekt, welches explodieren kann, eine Instanz der Klasse "TExplosible". Natürlich können die einzelnen Klassen von anderen Klassen abgeleitet sein, aber der Vorteil ist: ich vererbe nicht die Objekte, sondern nur die Eigenschaften. Dadurch habe ich eine flachere, einfachere, wartungs- und erweiterungsfreundliche Hierarchie. Das ist wirklich extrem flexibel und einfach. Es besitzt die Einfachheit und Flexibilität von Attributen, jedoch gleichzeitig die Power von Klassen. Nun gibt es nur noch ein Problem, welches sich aber auch ohne große Probleme lösen lässt.

Wenn ich nun auf ein Objekt schieße, wie sage ich dem getroffenen Objekt, dass ich es getroffen habe? Hole ich mir alle Klassen des Objektes und schaue, ob sie von einer bestimmten Basis-Klasse "TShootable" abgeleitet sind? Nein, dass ist mir wieder zu unflexibel. Denn es gibt vielleicht Klassen, die wissen wollen, wie oft der Spieler geschossen hat und somit nicht direkt mit einem sichtbaren Objekt im Level verbunden sind. Solche eine Klasse ist aber nicht "TShootable", da sie nicht mit einem Objekt verbunden sind. Oder eine Objekt, welches wissen will, ob auf ein anderes Objekt geschossen wurde. Die Lösung für das Problem ist aber relativ einfach: Events. Wenn ich auf einen Gegenstand schieße, werfe ich ein Event mit dem Namen "WeaponShootHit" zusammen mit den entsprechenden Parametern (Objekt, Material, Position, etc.). Jede Attribute-Klasse kann einen eigenen EventHandler für jedes beliebige Event bei einem globalen EventServer registrieren. Über Conditions kann dann zusätzlich noch eingestellt werden, ob die Parameter passen (z.B. ist die Stärke der Waffe größer als 42?). Wenn diese Conditions passen, wird der EventHandler aufgerufen. Fast geschenkt bekommt man mit diesem EventServer sogar das auslösen von zeitlich versetzten Aktionen. Ich kann einfach sagen: feuere das Event erst in 2 Sekunden. Dafür füge ich das Event zu einer Queue hinzu, bei der ich einmal pro Frame schaue, ob ich ein ein darin befindliches Event aufrufen muss. Somit habe ich sogar eine State-Machine, wobei ein Event den Übergang von einem Status zum nächsten Status definiert. Ich kann also ein Ölfass erstellen, dass zuerst das brennen anfängt und erst nach 2 Sekunden explodiert. Falls ich aber mit einer starken Waffe darauf schieße, kann ich den Brennen-Zustand auch überspringen und es direkt explodieren lassen.

Puh, das war jetzt sehr viel und relativ theoretisch, aber vielleicht hat der ein oder andere trotzdem verstanden, was ich meine. Damit ihr euch eine solche "Attribut-Klasse" besser vorstellen könnt, ist hier mal ein Beispiel aus dem Script-Code:
Code:
  1. type
  2.   // TGEObjectActor -> der Actor ist mit einem konkreten (nativen) EngineObjekt verknüpft. Dies
  3.   // kann ein Material, ein Mesh, eine Textur, ein Shader, ein Licht, ... sein.
  4.   TExplosible = class(TGEObjectActor)
  5.   private
  6.     FHealth : single;
  7.   protected  
  8.     procedure OnMapReset(Sender: TObject; EventArgs: TGEEventArgs);
  9.     procedure OnWeaponHit(Sender: TObject; EventArgs: TGEEventArgs);
  10.   public
  11.     constructor Create;
  12.   end;
  13.  
  14. constructor TExplosible.Create;
  15. begin
  16.   inherited;
  17.  
  18.   // Events Registrieren
  19.  
  20.   // WeaponShootHit -> ein Waffenschuss hat irgendwas getroffen
  21.   //                   mit der Condition sage ich, dass der EventHandler nur gefeuert werden soll
  22.   //                   wenn mein assoziiertes Objekt (=EngineObject) getroffen wurde
  23.   var p := AddEvent('WeaponShootHit', @OnWeaponHit);
  24.   AddCondition(p, 'ImpactObject', EngineObject, cpEqual);
  25.  
  26.   // Die Map soll zurückgesetzt werden
  27.   AddEvent('MapReset', @OnMapReset);
  28. end;
  29.  
  30. procedure TExplosible.OnMapReset(Sender: TObject; EventArgs: TGEEventArgs);
  31. begin
  32.   // Map wird Zurückgesetzt, also wieder alles auf Anfang
  33.   FHealth := 100;
  34.   EngineObject.Visible := True;
  35.   EngineObject.Enabled := True;
  36. end;
  37.  
  38. procedure TExplosible.OnWeaponHit(Sender: TObject; EventArgs: TGEEventArgs);
  39. begin
  40.   // Objekt ist bereits "Tot", abbrechen
  41.   if FHealth <= 0 then
  42.      exit;  
  43.  
  44.   // Health reduzieren
  45.   FHealth := FHealth - EventArgs.Args.Values['WeaponPower'].AsFloat;
  46.  
  47.   // Objekt muss zerstört werden?
  48.   if FHealth <= 0 then
  49.   begin
  50.     // sich selbst unsichtbar machen (ist ja explodiert)
  51.     EngineObject.Visible := False;
  52.     EngineObject.Enabled := False;
  53.  
  54.     // Explosions-Event werfen
  55.     // Eine andere Klasse könnte auf das Event hören und ein Explosions-Partikel-Effekt
  56.     //      starten.
  57.     // Eine zweite Klasse könnte ein Explosions-Sound abspielen
  58.     // Eine dritte Klasse könnte die Kamera etwas wackeln lassen, wenn sich der Spieler
  59.     //      nicht zu weit entfernt aufhält.
  60.     // usw.
  61.     using (var Event := TGEEvent.Create())
  62.     begin
  63.       Event.Name := 'Explosion';
  64.       Event.Args.Values['ExplosionPosition'].AsVec3 := EngineObject.CurrentPosition;
  65.       Event.Args.Values['ExplosionPower'].AsFloat := 40;
  66.       Event.Args.Values['ExplosionRadius'].AsFloat := 10;
  67.       Event.Args.Values['ExplosionObject'].AsObject := EngineObject;
  68.  
  69.       // Event an den globalen Event-Server übergeben
  70.       RaiseEvent(Event, Self);
  71.     end;
  72.   end;
  73. end;
  74.  

Die Klasse ist wirklich sehr einfach. Ich kann sie aber an jedes beliebige Mesh hängen. Dabei muss man natürlich aufpassen, dass man die Klasse nicht an Materials oder ähnliches hängt - denn ein Material kann nicht "visible := False" ausführen.

In der Theorie sollten alle Komponenten komplett unabhängig voneinander sein, doch in der Praxis schaut das ganze schon etwas anders aus. Sagen wir mal, es gibt zwei Komponenten, die eine Health-Property benötigen. Wenn ich nun beide Komponenten an eine Klasse binden würde, würde jeder Actor eine eigene Health-Variable haben. Das wäre nicht so gut. Der erste Schritt, das Problem zu lösen geht noch relativ schnell: Ich baue eine Komponente, die einfach nur eine Health-Variable hat. Nun kommt der zweite Schritt: wie bekommt der TExplosible-Actor aus dem Beispiel nun an diese Variable? Er könnte zum einen eine Message losschicken, in die die Health-Property den aktuellen Wert reinschreibt. Doch performance-technisch gesehen wäre das nun nicht so gut. Also bin ich einen anderen, einfacheren Weg gegangen. Er ist ein Kompromiss für mich, reicht aber aus: die TExplosible-Klasse kann sagen, welche anderen Klassen ebenfalls an einen Actor gehängt werden müssen. Bei der Zuweisung werden dann diese Komponenten automatisch auch an das Objekt gehängt. Die TExplosible-Klasse kann direkt über eine einfache Funktion die Instanz der THealth-Klasse aus seinem eigenen Objekt holen und die benötigten Variablen auslesen. Das geht schnell und funktioniert immer, da der Editor überprüft, ob alle benötigten Klassen zu einem Objekt zugewiesen wurden. Die TExplosible-Klasse schaut dann ungefähr so aus:
Code:
  1. type
  2.   TExplosible = class
  3.   private
  4.     // wird vom Editor aufgerufen um die abhängigen Klassen auszulesen
  5.     class function Dependencies: TClassArray; export;
  6.   protected
  7.     procedure OnWeaponHit(Sender: TObject; EventArgs: TGEEventArgs);
  8.     /// ...
  9.   end;
  10.  
  11. implementation
  12.  
  13. class function TExplosible.Dependencies: TClassArray;
  14. begin
  15.   result := TClassArray.Create(THealth);
  16. end;
  17.  
  18. procedure TExplosible.OnWeaponHit(Sender: TObject; EventArgs: TGEEventArgs);
  19. begin
  20.   // Health Komponente holen
  21.   var Health := Actors[THealth] as THealth;
  22.   // Wert manipulieren
  23.       Health.CurrentValue := Health.CurrentValue - ...;
  24.  
  25.   /// ...
  26. end;


Actors und Prefabs
Nein, ich bin noch nicht fertig ;-). Jetzt kommt aber nicht mehr ganz so viel wie gerade. Wie eben beschrieben, kann ich mit Hilfe dieses Actor-Systems sehr flexibel alles mögliche erstellen und einstellen. Doch nun gibt es noch ein kleines Problem. Für jedes Objekt muss ich die Liste der zuständigen Actors manuell einstellen. Das kann bei mehreren Objekten sehr lange dauern und ist fehler- und wartungsanfällig (ein falscher oder fehlender Actor ist da schon ein Problem; oder ein neuen Actor zu vielen Objekten hinzufügen). Daher habe ich die flache Actor-Liste pro Objekt noch um einen weiteren Punkt erweitert: Prefabs. Ein Prefab ist einfach eine Art Konfiguration, die die richtigen Actors mit den richtigen Eigenschaften definiert. Jedem Objekt kann ich mehrere Prefabs zuweisen. Ändere ich ein Prefab, so ändern sich auch alle damit verbundenen Objekte. Somit habe ich eine zentrale Stelle, um verschiedene Eigenschaften zu definieren. Natürlich kann ich weiteren noch weitere Actors für jedes Objekt einzeln hinzufügen, aber der Wartungsaufwand ist sehr viel kleiner und neue Objekte bekommen viel schneller die benötigten Eigenschaften.

Newton Game Dynamics
Jetzt habe ich die ganze Zeit öfter mal von Physik-Engine, dynamischen Objekten die auf Schüsse reagieren, etc. geredet. Doch die Physik-Engine selber habe ich noch nicht erwähnt - die ist ja auch neu hinzugekommen ;-). Ich habe nun die Newton Game Dynamics Engine in meine Engine integriert. Hiermit noch ein Dank an Sascha und alle sonstigen beteiligten für den Header. Zwar bin ich noch lange nicht fertig, aber es gibt schon ein paar funktionierende Sachen wie Boxes, Spheres, Zylinders, Static Geometry und ein paar Joints. Natürlich war das ganze auch gleich ein richtig guter Test für das EventSystem.

Test des Actor-Event-Systems
Im Editor habe ich mir schnell eine einfache Welt zusammengebastelt mit mehreren Kisten, Kugeln und Fässer, wobei ein paar davon "explosiv" sind. Zudem habe ich eine paar Klassen erstellt, die unterschiedliche Partikel-Effekte für unterschiedliche Materialen bereitstellt (Funken bei Metal, Holzsplitter bei Holz, etc.) sowie ein paar Prefabs.

Wenn ich keinen Actor zuweise und das Spiel im Editor einfach starte, kann ich zwar schießen, habe aber keine Impact-Sounds, keine Partikel und die Objekte lassen sich nicht herumschießen. Wenn ich jedem Objekt nun das Prefab "WeaponImpactPhysic" zuweise, kann ich diese mit Hilfe der Waffe durch die Gegend schießen - und sollte irgendwas explodieren, würde alles durch die Gegend fliegen. "Würde" ist hier der kurze Stichpunkt: ich füge einfach noch die Explosible-Klasse den Benzinfässern hinzu und schon geht es rund - aber leider noch ohne Partikel oder Impact-Sounds. Das ist aber nicht so schwer das hinzubekommen: einfach den Material-Editor öffnen, die jeweiligen Materials auswählen und die entsprechenden, gerade erstellten Prefabs setzen. Nun kann ich rumballern, es fliegen Funken und Fetzen egal wohin ich schieße und die Ölfässer explodieren - ohne Ton und ohne Partikel-Effekt für die Explosion. Ok, auch schon schnell zwei Actors erstellt, beide global an das Level gebunden (die beiden Actors sind unabhängig von einem Objekt oder einem Material) und schon habe ich alles was ich brauche - und zwar im Prinzip einfach per Drag&Drop. Meine ursprünglichen Ziele sind erreicht.

Jetzt fehlt noch eine Sache, die jedoch nicht so kompliziert sein sollte. Und zwar das Synchronisieren von Objekt-Zuständen über das Netzwerk. Aber das gehe ich erst an, wenn ich mir noch genauer Gedanken gemacht habe.

Video
Nach dem langen Text gibts auch mal wieder ein was zu sehen. In einem Video habe ich den Physiktest und den Actor-Test festgehalten.

So, das war jetzt auch eine ganze Menge, doch ich finde die Thematik der Spiellogik-Implementierung auch sehr wichtig. Daher wollte ich etwas mehr schreiben mit der Hoffnung, dass es vielleicht dem ein oder anderen hilft oder zumindest einen Startpunkt gibt.

Gruß
David

_________________
Aktuelles Projekt: Gael - Development Blog
Website: LightBlackSoft.com


Nach oben
 Profil  
Mit Zitat antworten  
Beiträge der letzten Zeit anzeigen:  Sortiere nach  
Ein neues Thema erstellen Auf das Thema antworten  [ 50 Beiträge ]  Gehe zu Seite Vorherige  1, 2, 3, 4
Foren-Übersicht » Sonstiges » Projekte


Wer ist online?

Mitglieder in diesem Forum: 0 Mitglieder und 3 Gäste


Du darfst keine neuen Themen in diesem Forum erstellen.
Du darfst keine Antworten zu Themen in diesem Forum erstellen.
Du darfst deine Beiträge in diesem Forum nicht ändern.
Du darfst deine Beiträge in diesem Forum nicht löschen.
Du darfst keine Dateianhänge in diesem Forum erstellen.

Suche nach:
Gehe zu:  
cron
  Powered by phpBB® Forum Software © phpBB Group
Deutsche Übersetzung durch phpBB.de
[ Time : 0.028s | 17 Queries | GZIP : On ]