Lyphia
Landschaftstypen, Zoom und diagonale AutoTiles

Die Landschaftstypen-Interpretation der Farbwerte des Referenzbildes (wie von Goodjee vorgeschlagen) ist nun auch integriert:
Code: [AUSKLAPPEN]
Links der Rot-Wert, rechts der Bytecode vom Tile
OpenGL: 49.152 Tiles, 53 FPS (Zahl ohne AutoTiles, diese werden aber gezeichnet)
Hier sieht man auch schon die stufenlose Zoom-Funktion und insbesondere die diagonalen AutoTiles, die sogar mit runden Tiles kombiniert werden können. Ein simpler Algorithmus bestimmt anhand der Umgebungstiles, ob es rund oder diagonal gezeichnet wird.
Verbesserungsvorschläge? Fragen? Her damit
Nachtrag: Das verwendete Referenzbild:
Code: [AUSKLAPPEN]
[AutoTerrain]
R0 = 32
R178 = 10
R204 = 20
R255 = 74
R0 = 32
R178 = 10
R204 = 20
R255 = 74
Links der Rot-Wert, rechts der Bytecode vom Tile

OpenGL: 49.152 Tiles, 53 FPS (Zahl ohne AutoTiles, diese werden aber gezeichnet)
Hier sieht man auch schon die stufenlose Zoom-Funktion und insbesondere die diagonalen AutoTiles, die sogar mit runden Tiles kombiniert werden können. Ein simpler Algorithmus bestimmt anhand der Umgebungstiles, ob es rund oder diagonal gezeichnet wird.

Verbesserungsvorschläge? Fragen? Her damit

Nachtrag: Das verwendete Referenzbild:

Terraingenerierung + AutoTile Caching

Grundriss erstellen
Für den heutigen Tag habe ich mir ein besonderes Feature einfallen lassen, was eigentlich jeder 3D-Terrain-Editor kann: Die automatische Terraingenerierung durch ein Referenzbild. Es werden (noch) keine unterschiedlichen Landschaftstypen unterstützt, aber zumindest kann man dadurch den Grundriss erstellen.
http://www.youtube.com/watch?v=ptbo_s7u2QA
Das in dem Video verwendete Referenzbild:
AutoTile Caching
Die lange AutoTile-Zeichenfunktion aus meinem vorherigen Eintrag ist nun auf drei Zeilen zusammengeschrumpft:
Code: [AUSKLAPPEN]
Die AutoTile-Informationen werden nun beim Laden generiert und gespeichert:
Code: [AUSKLAPPEN]
Damit schlägt man im Prinzip zwei Fliegen mit einer Klappe: Die Caching-Variante ist nicht nur schneller, sondern arbeitet auch noch korrekt, da die AutoTiles nach der Höhe sortiert werden und die Zeichenreihenfolge folglich richtig ist. Leider werden immer noch zu viele AutoTiles gezeichnet, so dass das Alpha-Blending nicht 100%ig funktioniert, aber das wird demnächst behoben
Für den heutigen Tag habe ich mir ein besonderes Feature einfallen lassen, was eigentlich jeder 3D-Terrain-Editor kann: Die automatische Terraingenerierung durch ein Referenzbild. Es werden (noch) keine unterschiedlichen Landschaftstypen unterstützt, aber zumindest kann man dadurch den Grundriss erstellen.
http://www.youtube.com/watch?v=ptbo_s7u2QA
Das in dem Video verwendete Referenzbild:

AutoTile Caching
Die lange AutoTile-Zeichenfunktion aus meinem vorherigen Eintrag ist nun auf drei Zeilen zusammengeschrumpft:
Code: [AUSKLAPPEN]
For Local at:TAutoTile = EachIn Self.autoTiles[i, h]
DrawImage at.tt.autotile, i * Self.tileSizeX, h * Self.tileSizeY, at.direction
Next
DrawImage at.tt.autotile, i * Self.tileSizeX, h * Self.tileSizeY, at.direction
Next
Die AutoTile-Informationen werden nun beim Laden generiert und gespeichert:
Code: [AUSKLAPPEN]
' GenerateAutoTileInformation
Method GenerateAutoTileInformation(i:Int, h:Int)
' Create list
If Self.autoTiles[i, h] = Null
Self.autoTiles[i, h] = CreateList()
Else
Self.autoTiles[i, h].Clear()
EndIf
' Get surrounding tiles
Local tiles:TTileType[3, 3]
For Local a:Int = 0 To 2
For Local b:Int = 0 To 2
If i + a > 0 And h + b > 0 And i + a <= Self.width And h + b <= Self.height
tiles[a, b] = byteToTile[0, Self.tiles[0, i + a - 1, h + b - 1]]
Else
tiles[a, b] = TTileType.NullTile
EndIf
Next
Next
' Height information
Local ownHeight:Int = tiles[1, 1].height
' North
If tiles[1, 0].height > ownHeight
' Check clockwise
If tiles[1, 0] = tiles[2, 1]
Self.autoTiles[i, h].AddLast(TAutoTile.Create(tiles[1, 0], AT_NORTHEAST_INVERTED))
Else
Self.autoTiles[i, h].AddLast(TAutoTile.Create(tiles[1, 0], AT_NORTH))
EndIf
EndIf
' East
If tiles[2, 1].height > ownHeight
' Check clockwise
If tiles[2, 1] = tiles[1, 2]
Self.autoTiles[i, h].AddLast(TAutoTile.Create(tiles[2, 1], AT_SOUTHEAST_INVERTED))
Else
Self.autoTiles[i, h].AddLast(TAutoTile.Create(tiles[2, 1], AT_EAST))
EndIf
EndIf
' South
If tiles[1, 2].height > ownHeight
' Check clockwise
If tiles[1, 2] = tiles[0, 1]
Self.autoTiles[i, h].AddLast(TAutoTile.Create(tiles[1, 2], AT_SOUTHWEST_INVERTED))
Else
Self.autoTiles[i, h].AddLast(TAutoTile.Create(tiles[1, 2], AT_SOUTH))
EndIf
EndIf
' West
If tiles[0, 1].height > ownHeight
' Check clockwise
If tiles[0, 1] = tiles[1, 0]
Self.autoTiles[i, h].AddLast(TAutoTile.Create(tiles[0, 1], AT_NORTHWEST_INVERTED))
Else
Self.autoTiles[i, h].AddLast(TAutoTile.Create(tiles[0, 1], AT_WEST))
EndIf
EndIf
' South + East corner
If tiles[2, 2].height > ownHeight And tiles[2, 2] <> tiles[2, 1] And tiles[2, 2] <> tiles[1, 2] And tiles[2, 1].height <= tiles[2, 2].height And tiles[1, 2].height <= tiles[2, 2].height
Self.autoTiles[i, h].AddLast(TAutoTile.Create(tiles[2, 2], AT_SOUTHEAST))
EndIf
' South + West corner
If tiles[0, 2].height > ownHeight And tiles[0, 2] <> tiles[0, 1] And tiles[0, 2] <> tiles[1, 2] And tiles[0, 1].height <= tiles[0, 2].height And tiles[1, 2].height <= tiles[0, 2].height
Self.autoTiles[i, h].AddLast(TAutoTile.Create(tiles[0, 2], AT_SOUTHWEST))
EndIf
' North + West corner
If tiles[0, 0].height > ownHeight And tiles[0, 0] <> tiles[0, 1] And tiles[0, 0] <> tiles[1, 0] And tiles[0, 1].height <= tiles[0, 0].height And tiles[1, 0].height <= tiles[0, 0].height
Self.autoTiles[i, h].AddLast(TAutoTile.Create(tiles[0, 0], AT_NORTHWEST))
EndIf
' North + East corner
If tiles[2, 0].height > ownHeight And tiles[2, 0] <> tiles[1, 0] And tiles[2, 0] <> tiles[2, 1] And tiles[1, 0].height <= tiles[2, 0].height And tiles[2, 1].height <= tiles[1, 0].height
Self.autoTiles[i, h].AddLast(TAutoTile.Create(tiles[2, 0], AT_NORTHEAST))
EndIf
' Sort
Self.autoTiles[i, h].Sort(True, TAutoTile.CompareFunc)
End Method
Method GenerateAutoTileInformation(i:Int, h:Int)
' Create list
If Self.autoTiles[i, h] = Null
Self.autoTiles[i, h] = CreateList()
Else
Self.autoTiles[i, h].Clear()
EndIf
' Get surrounding tiles
Local tiles:TTileType[3, 3]
For Local a:Int = 0 To 2
For Local b:Int = 0 To 2
If i + a > 0 And h + b > 0 And i + a <= Self.width And h + b <= Self.height
tiles[a, b] = byteToTile[0, Self.tiles[0, i + a - 1, h + b - 1]]
Else
tiles[a, b] = TTileType.NullTile
EndIf
Next
Next
' Height information
Local ownHeight:Int = tiles[1, 1].height
' North
If tiles[1, 0].height > ownHeight
' Check clockwise
If tiles[1, 0] = tiles[2, 1]
Self.autoTiles[i, h].AddLast(TAutoTile.Create(tiles[1, 0], AT_NORTHEAST_INVERTED))
Else
Self.autoTiles[i, h].AddLast(TAutoTile.Create(tiles[1, 0], AT_NORTH))
EndIf
EndIf
' East
If tiles[2, 1].height > ownHeight
' Check clockwise
If tiles[2, 1] = tiles[1, 2]
Self.autoTiles[i, h].AddLast(TAutoTile.Create(tiles[2, 1], AT_SOUTHEAST_INVERTED))
Else
Self.autoTiles[i, h].AddLast(TAutoTile.Create(tiles[2, 1], AT_EAST))
EndIf
EndIf
' South
If tiles[1, 2].height > ownHeight
' Check clockwise
If tiles[1, 2] = tiles[0, 1]
Self.autoTiles[i, h].AddLast(TAutoTile.Create(tiles[1, 2], AT_SOUTHWEST_INVERTED))
Else
Self.autoTiles[i, h].AddLast(TAutoTile.Create(tiles[1, 2], AT_SOUTH))
EndIf
EndIf
' West
If tiles[0, 1].height > ownHeight
' Check clockwise
If tiles[0, 1] = tiles[1, 0]
Self.autoTiles[i, h].AddLast(TAutoTile.Create(tiles[0, 1], AT_NORTHWEST_INVERTED))
Else
Self.autoTiles[i, h].AddLast(TAutoTile.Create(tiles[0, 1], AT_WEST))
EndIf
EndIf
' South + East corner
If tiles[2, 2].height > ownHeight And tiles[2, 2] <> tiles[2, 1] And tiles[2, 2] <> tiles[1, 2] And tiles[2, 1].height <= tiles[2, 2].height And tiles[1, 2].height <= tiles[2, 2].height
Self.autoTiles[i, h].AddLast(TAutoTile.Create(tiles[2, 2], AT_SOUTHEAST))
EndIf
' South + West corner
If tiles[0, 2].height > ownHeight And tiles[0, 2] <> tiles[0, 1] And tiles[0, 2] <> tiles[1, 2] And tiles[0, 1].height <= tiles[0, 2].height And tiles[1, 2].height <= tiles[0, 2].height
Self.autoTiles[i, h].AddLast(TAutoTile.Create(tiles[0, 2], AT_SOUTHWEST))
EndIf
' North + West corner
If tiles[0, 0].height > ownHeight And tiles[0, 0] <> tiles[0, 1] And tiles[0, 0] <> tiles[1, 0] And tiles[0, 1].height <= tiles[0, 0].height And tiles[1, 0].height <= tiles[0, 0].height
Self.autoTiles[i, h].AddLast(TAutoTile.Create(tiles[0, 0], AT_NORTHWEST))
EndIf
' North + East corner
If tiles[2, 0].height > ownHeight And tiles[2, 0] <> tiles[1, 0] And tiles[2, 0] <> tiles[2, 1] And tiles[1, 0].height <= tiles[2, 0].height And tiles[2, 1].height <= tiles[1, 0].height
Self.autoTiles[i, h].AddLast(TAutoTile.Create(tiles[2, 0], AT_NORTHEAST))
EndIf
' Sort
Self.autoTiles[i, h].Sort(True, TAutoTile.CompareFunc)
End Method
Damit schlägt man im Prinzip zwei Fliegen mit einer Klappe: Die Caching-Variante ist nicht nur schneller, sondern arbeitet auch noch korrekt, da die AutoTiles nach der Höhe sortiert werden und die Zeichenreihenfolge folglich richtig ist. Leider werden immer noch zu viele AutoTiles gezeichnet, so dass das Alpha-Blending nicht 100%ig funktioniert, aber das wird demnächst behoben

AutoTiles-Prinzip

Auf Wunsch von Skabus erkläre ich in diesem Posting die in Lyphia verwendete AutoTile-Technik.
Sie unterscheidet sich von den RPG Maker AutoTiles dadurch, dass die Tiles zu 100% generiert werden.
Es ist praktisch sogar möglich, dass die Tiles nach der Generierung nachbearbeitet werden, um so noch bessere Übergänge zu erzielen. Dafür benötigt man allerdings einen guten Pixler.
Der Ausgangspunkt ist, dass man ein Tile (hier: 32 x 32) hat:
Man benötigt 12 Masken: 4 für die Himmelsrichtungen, 4 für die Ecken und 4 für die Verbindung von 2 naheliegenden Tiles. Praktisch sind es nur 3 Einzelgrafiken, die jeweils 4 mal rotiert werden können.
N = Norden
E = Osten
S = Süden
W = Westen
NE = Nordost-Ecke
NW = Nordwest-Ecke
SE = Südost-Ecke
SW = Südwest-Ecke
NEI = Nordost
NWI = Nordwest
SEI = Südost
SWI = Südwest
Zusammengefasst:











Nun schreibt man sich ein externes Programm, welches das Ausgangstile nimmt und ein neues Bild erstellt, wobei die Pixel vom Ausgangstile nur dann in das neue Bild geschrieben werden, wenn der RGB-Wert größer als 0 ist (also kein schwarz). Anschließend wird der Rot-Wert (jede andere Farbe wäre möglich) der Maske als Alphawert für das neue Bild verwendet. Wenn das alles funktioniert hat, entsteht ein Bild wie dieses:
Dieses Bild wird letztendlich vom Spiel verwendet.
Im Quellcode muss man sich nun die Methode heraussuchen, die die Tiles zeichnet. "Meine" (ich habe keine Ahnung, wie andere das machen, aber diese Variante funktioniert) Idee war nun, dass man jedem Tile eine Höhe zuordnet und die Tiles im Uhrzeigersinn prüfen lässt, ob sie von einem höheren Tile umgeben werden, sprich: North prüft East, East prüft South, South prüft West, West prüft North. Die Umgebunstiles sollte man der Einfachheit halber in einem 3 x 3 Array zwischenspeichern. Dadurch kann man sehr einfach erkennen, welche der diagonalen Kombinationen benötigt wird. Schlägt eine Prüfung fehl - z.B. North prüft West und West ist nicht dasselbe Tile - dann wird nur das AutoTile zu North gezeichnet. Eine Ecke wird nur dann gezeichnet, wenn ausschließlich ein Eck-Tile des Umgebungsarrays höher gelegen ist.
3 x 3 Umgebungsarray, dargestellt ist der Fall, dass das zu prüfende Tile (in der Mitte) von keiner Seite beeinflusst wird, da es höher liegt
Ein Beispielcode, wie man es machen kann, ist im Folgenden zu sehen. Die Methode ist nicht optimiert, verwendet kein Caching und zeichnet einige "unnötige" AutoTiles, aber sie funktioniert und das reicht mir für den Anfang:
Code: [AUSKLAPPEN]
Wenn ihr diese - für Alpha-Blending nicht 100% korrekte - Methode verwenden wollt, müsst ihr den Code noch an euer Mapsystem anpassen.
Hoffe es hilft den zukünftigen RPG-Entwicklern
Sie unterscheidet sich von den RPG Maker AutoTiles dadurch, dass die Tiles zu 100% generiert werden.
Es ist praktisch sogar möglich, dass die Tiles nach der Generierung nachbearbeitet werden, um so noch bessere Übergänge zu erzielen. Dafür benötigt man allerdings einen guten Pixler.
Der Ausgangspunkt ist, dass man ein Tile (hier: 32 x 32) hat:

Man benötigt 12 Masken: 4 für die Himmelsrichtungen, 4 für die Ecken und 4 für die Verbindung von 2 naheliegenden Tiles. Praktisch sind es nur 3 Einzelgrafiken, die jeweils 4 mal rotiert werden können.
N = Norden

E = Osten

S = Süden

W = Westen

NE = Nordost-Ecke

NW = Nordwest-Ecke

SE = Südost-Ecke

SW = Südwest-Ecke

NEI = Nordost

NWI = Nordwest

SEI = Südost

SWI = Südwest

Zusammengefasst:












Nun schreibt man sich ein externes Programm, welches das Ausgangstile nimmt und ein neues Bild erstellt, wobei die Pixel vom Ausgangstile nur dann in das neue Bild geschrieben werden, wenn der RGB-Wert größer als 0 ist (also kein schwarz). Anschließend wird der Rot-Wert (jede andere Farbe wäre möglich) der Maske als Alphawert für das neue Bild verwendet. Wenn das alles funktioniert hat, entsteht ein Bild wie dieses:

Dieses Bild wird letztendlich vom Spiel verwendet.
Im Quellcode muss man sich nun die Methode heraussuchen, die die Tiles zeichnet. "Meine" (ich habe keine Ahnung, wie andere das machen, aber diese Variante funktioniert) Idee war nun, dass man jedem Tile eine Höhe zuordnet und die Tiles im Uhrzeigersinn prüfen lässt, ob sie von einem höheren Tile umgeben werden, sprich: North prüft East, East prüft South, South prüft West, West prüft North. Die Umgebunstiles sollte man der Einfachheit halber in einem 3 x 3 Array zwischenspeichern. Dadurch kann man sehr einfach erkennen, welche der diagonalen Kombinationen benötigt wird. Schlägt eine Prüfung fehl - z.B. North prüft West und West ist nicht dasselbe Tile - dann wird nur das AutoTile zu North gezeichnet. Eine Ecke wird nur dann gezeichnet, wenn ausschließlich ein Eck-Tile des Umgebungsarrays höher gelegen ist.









3 x 3 Umgebungsarray, dargestellt ist der Fall, dass das zu prüfende Tile (in der Mitte) von keiner Seite beeinflusst wird, da es höher liegt
Ein Beispielcode, wie man es machen kann, ist im Folgenden zu sehen. Die Methode ist nicht optimiert, verwendet kein Caching und zeichnet einige "unnötige" AutoTiles, aber sie funktioniert und das reicht mir für den Anfang:
Code: [AUSKLAPPEN]
' DrawAutoTile
Method DrawAutoTile(i:Int, h:Int)
Const AT_NORTH:Int = 0
Const AT_EAST:Int = 1
Const AT_SOUTH:Int = 2
Const AT_WEST:Int = 3
Const AT_NORTHEAST:Int = 4
Const AT_NORTHWEST:Int = 5
Const AT_SOUTHEAST:Int = 6
Const AT_SOUTHWEST:Int = 7
Const AT_NORTHEAST_INVERTED:Int = 8
Const AT_NORTHWEST_INVERTED:Int = 9
Const AT_SOUTHEAST_INVERTED:Int = 10
Const AT_SOUTHWEST_INVERTED:Int = 11
' Get surrounding tiles
Local tiles:TTileType[3, 3]
For Local a:Int = 0 To 2
For Local b:Int = 0 To 2
If i + a > 0 And h + b > 0 And i + a <= Self.width And h + b <= Self.height
tiles[a, b] = byteToTile[0, Self.tiles[0, i + a - 1, h + b - 1]]
Else
tiles[a, b] = TTileType.NullTile
EndIf
Next
Next
' Height information
Local ownHeight:Int = tiles[1, 1].height
Local direction:Int
' North
If tiles[1, 0].height > ownHeight
' Check clockwise
If tiles[1, 0] = tiles[2, 1]
direction = AT_NORTHEAST_INVERTED
Else
direction = AT_NORTH
EndIf
DrawImage tiles[1, 0].autotile, i * Self.tileSizeX, h * Self.tileSizeY, direction
EndIf
' East
If tiles[2, 1].height > ownHeight
' Check clockwise
If tiles[2, 1] = tiles[1, 2]
direction = AT_SOUTHEAST_INVERTED
Else
direction = AT_EAST
EndIf
DrawImage tiles[2, 1].autotile, i * Self.tileSizeX, h * Self.tileSizeY, direction
EndIf
' South
If tiles[1, 2].height > ownHeight
' Check clockwise
If tiles[1, 2] = tiles[0, 1]
direction = AT_SOUTHWEST_INVERTED
Else
direction = AT_SOUTH
EndIf
DrawImage tiles[1, 2].autotile, i * Self.tileSizeX, h * Self.tileSizeY, direction
EndIf
' West
If tiles[0, 1].height > ownHeight
' Check clockwise
If tiles[0, 1] = tiles[1, 0]
direction = AT_NORTHWEST_INVERTED
Else
direction = AT_WEST
EndIf
DrawImage tiles[0, 1].autotile, i * Self.tileSizeX, h * Self.tileSizeY, direction
EndIf
' South + East corner
If tiles[2, 2].height > ownHeight And tiles[2, 2] <> tiles[2, 1] And tiles[2, 2] <> tiles[1, 2]
direction = AT_SOUTHEAST
DrawImage tiles[2, 2].autotile, i * Self.tileSizeX, h * Self.tileSizeY, direction
EndIf
' South + West corner
If tiles[0, 2].height > ownHeight And tiles[0, 2] <> tiles[0, 1] And tiles[0, 2] <> tiles[1, 2]
direction = AT_SOUTHWEST
DrawImage tiles[0, 2].autotile, i * Self.tileSizeX, h * Self.tileSizeY, direction
EndIf
' North + West corner
If tiles[0, 0].height > ownHeight And tiles[0, 0] <> tiles[0, 1] And tiles[0, 0] <> tiles[1, 0]
direction = AT_NORTHWEST
DrawImage tiles[0, 0].autotile, i * Self.tileSizeX, h * Self.tileSizeY, direction
EndIf
' North + East corner
If tiles[2, 0].height > ownHeight And tiles[2, 0] <> tiles[1, 0] And tiles[2, 0] <> tiles[2, 1]
direction = AT_NORTHEAST
DrawImage tiles[2, 0].autotile, i * Self.tileSizeX, h * Self.tileSizeY, direction
EndIf
End Method
Method DrawAutoTile(i:Int, h:Int)
Const AT_NORTH:Int = 0
Const AT_EAST:Int = 1
Const AT_SOUTH:Int = 2
Const AT_WEST:Int = 3
Const AT_NORTHEAST:Int = 4
Const AT_NORTHWEST:Int = 5
Const AT_SOUTHEAST:Int = 6
Const AT_SOUTHWEST:Int = 7
Const AT_NORTHEAST_INVERTED:Int = 8
Const AT_NORTHWEST_INVERTED:Int = 9
Const AT_SOUTHEAST_INVERTED:Int = 10
Const AT_SOUTHWEST_INVERTED:Int = 11
' Get surrounding tiles
Local tiles:TTileType[3, 3]
For Local a:Int = 0 To 2
For Local b:Int = 0 To 2
If i + a > 0 And h + b > 0 And i + a <= Self.width And h + b <= Self.height
tiles[a, b] = byteToTile[0, Self.tiles[0, i + a - 1, h + b - 1]]
Else
tiles[a, b] = TTileType.NullTile
EndIf
Next
Next
' Height information
Local ownHeight:Int = tiles[1, 1].height
Local direction:Int
' North
If tiles[1, 0].height > ownHeight
' Check clockwise
If tiles[1, 0] = tiles[2, 1]
direction = AT_NORTHEAST_INVERTED
Else
direction = AT_NORTH
EndIf
DrawImage tiles[1, 0].autotile, i * Self.tileSizeX, h * Self.tileSizeY, direction
EndIf
' East
If tiles[2, 1].height > ownHeight
' Check clockwise
If tiles[2, 1] = tiles[1, 2]
direction = AT_SOUTHEAST_INVERTED
Else
direction = AT_EAST
EndIf
DrawImage tiles[2, 1].autotile, i * Self.tileSizeX, h * Self.tileSizeY, direction
EndIf
' South
If tiles[1, 2].height > ownHeight
' Check clockwise
If tiles[1, 2] = tiles[0, 1]
direction = AT_SOUTHWEST_INVERTED
Else
direction = AT_SOUTH
EndIf
DrawImage tiles[1, 2].autotile, i * Self.tileSizeX, h * Self.tileSizeY, direction
EndIf
' West
If tiles[0, 1].height > ownHeight
' Check clockwise
If tiles[0, 1] = tiles[1, 0]
direction = AT_NORTHWEST_INVERTED
Else
direction = AT_WEST
EndIf
DrawImage tiles[0, 1].autotile, i * Self.tileSizeX, h * Self.tileSizeY, direction
EndIf
' South + East corner
If tiles[2, 2].height > ownHeight And tiles[2, 2] <> tiles[2, 1] And tiles[2, 2] <> tiles[1, 2]
direction = AT_SOUTHEAST
DrawImage tiles[2, 2].autotile, i * Self.tileSizeX, h * Self.tileSizeY, direction
EndIf
' South + West corner
If tiles[0, 2].height > ownHeight And tiles[0, 2] <> tiles[0, 1] And tiles[0, 2] <> tiles[1, 2]
direction = AT_SOUTHWEST
DrawImage tiles[0, 2].autotile, i * Self.tileSizeX, h * Self.tileSizeY, direction
EndIf
' North + West corner
If tiles[0, 0].height > ownHeight And tiles[0, 0] <> tiles[0, 1] And tiles[0, 0] <> tiles[1, 0]
direction = AT_NORTHWEST
DrawImage tiles[0, 0].autotile, i * Self.tileSizeX, h * Self.tileSizeY, direction
EndIf
' North + East corner
If tiles[2, 0].height > ownHeight And tiles[2, 0] <> tiles[1, 0] And tiles[2, 0] <> tiles[2, 1]
direction = AT_NORTHEAST
DrawImage tiles[2, 0].autotile, i * Self.tileSizeX, h * Self.tileSizeY, direction
EndIf
End Method
Wenn ihr diese - für Alpha-Blending nicht 100% korrekte - Methode verwenden wollt, müsst ihr den Code noch an euer Mapsystem anpassen.
Hoffe es hilft den zukünftigen RPG-Entwicklern

Statusanzeige

Soeben noch die Statusanzeige fertiggestellt, plattform- und schnittstellenübergreifend:
Außerdem ein großes Danke an mDave für die neue Skillgrafik

Außerdem ein großes Danke an mDave für die neue Skillgrafik

AutoTiles 99% complete

AutoTiles sind nun so gut wie fertig, wer möchte kann sich das Video zum Editor anschauen:
http://www.youtube.com/watch?v=0YBjHCyryDM
Als nächstes müssen die Höhenunterschiede festgelegt werden. Wasser hat z.B. eine sehr niedrige Höhe und wird damit so gut wie immer von anderen Tiles überlagert. Erde ist etwas höher, Gras noch höher. All diese Eigenschaften sind in den INI-Dateien für jedes Tile konfigurierbar. Außerdem ist eine Umsetzung von Alphamaps geplant. Das Blending funktioniert momentan per monochromem Masking. Die nächste Version wird Alphainformationen für noch schönere Übergänge beinhalten
Edit: Hier noch ein Bild:
http://www.youtube.com/watch?v=0YBjHCyryDM
Als nächstes müssen die Höhenunterschiede festgelegt werden. Wasser hat z.B. eine sehr niedrige Höhe und wird damit so gut wie immer von anderen Tiles überlagert. Erde ist etwas höher, Gras noch höher. All diese Eigenschaften sind in den INI-Dateien für jedes Tile konfigurierbar. Außerdem ist eine Umsetzung von Alphamaps geplant. Das Blending funktioniert momentan per monochromem Masking. Die nächste Version wird Alphainformationen für noch schönere Übergänge beinhalten

Edit: Hier noch ein Bild:

AutoTiles auf dem Weg

Der Editor beherrscht nun einen Abrundungsmechanismus, der anhand von Maskdateien die Übergänge berechnet. Das tolle daran ist, dass das System vollkommen dynamisch ist und alles selbst berechnet, ich musste keinen einzigen Pixel für zusätzliche Grafiken zeichnen.
Der einzige Nachteil ist, dass Übergänge auf Bodentexturen beschränkt sind. Aber dort werden sie schließlich auch am häufigsten gebraucht
Das System ist noch nicht vollständig ausgebaut, weil 4 Richtungen fehlen, aber ich wollte wegen der vielen Nachfragen schonmal eine Vorschau in den Worklog einbinden.
Das Skillsystem benutzt nun die GUI zum Darstellen von (momentan 8) Skillgrafiken.
Danke an mDave für die gelungene Feuerballgrafik
Neuigkeiten im Überblick:
Fast fertiges AutoTile-System
Sound Manager implementiert
Bugs in der GUI behoben
Außerdem möchte ich noch 3 neue Teammitglieder vorstellen, die verschiedene Dinge zum Projekt beisteuern:
mDave, Skilldesigner (überlegt sich zusätzliche Skills und -effekte)
M0rgenstern, Pixler für diverse Sachen (z.B. Monster)
Kaddy, Mapdesigner (bedient den Editor im späteren Verlauf der Entwicklung)

Der einzige Nachteil ist, dass Übergänge auf Bodentexturen beschränkt sind. Aber dort werden sie schließlich auch am häufigsten gebraucht

Das System ist noch nicht vollständig ausgebaut, weil 4 Richtungen fehlen, aber ich wollte wegen der vielen Nachfragen schonmal eine Vorschau in den Worklog einbinden.
Das Skillsystem benutzt nun die GUI zum Darstellen von (momentan 8) Skillgrafiken.
Danke an mDave für die gelungene Feuerballgrafik

Neuigkeiten im Überblick:



Außerdem möchte ich noch 3 neue Teammitglieder vorstellen, die verschiedene Dinge zum Projekt beisteuern:



Aufgegriffen

Nach etwa einem Jahr Pause versuche ich nun, das Open Source Projekt fortzuführen.
Eines der größten Teil-Module ist immer noch die GUI, bei der ich einen Bug in Zusammenhang mit relativen Koordinaten beseitigt habe. Auf der ToDo-Liste für die GUI ist das Textfeld mit hoher Priorität angesetzt, denn die (einzilige) Texteingabe wird besonders für den Editor wichtig sein.
Neu hinzugekommen ist das Fertigkeiten-System. Es erlaubt, eine Fertigkeit TSkill, die von TAction abgeleitet ist und somit in Slots gepackt werden kann, mit Attributen wie Cast- und Cooldown-Zeit zu versehen. Die Skill-Animation wird in den abstrakten Methoden Cast() und Use() programmiert. Momentan sind die Fertigkeiten "hardgecodet". Ob die Skill-Animationen und -Daten später in Skriptdateien stehen werden, steht zum jetzigen Zeitpunkt noch nicht fest.
Das Partikelsystem wird in Zukunft in Gruppen unterteilt. Man kann bestimmte Partikelgruppen anlegen, die verschiedene Partikeltypen beinhalten. Man könnte z.B. die Gruppen "Fertigkeiten", "Effekte" und "GUI-Effekte" erstellen, die alle zu einem verschiedenen Zeitpunkt gerendert werden können. Ebenso wird es eine Z-Gruppierung geben, damit die Partikel auch in der richtigen Reihenfolge in der Nähe vom Spieler gezeichnet werden.
Wer ein wenig Klickibunti sehen will, kann sich hier die kompilierte Pre-Alpha 7 herunterladen und die Skills auf D und F benutzen:
Download Pre-Alpha 7 (835 KB, Debug + Profiler aktiviert)
Die kompilierte EXE liegt im src-Ordner.
Steuerung:
D = Ice Wave (Skill, Cooldown 1.0 Sek, Castzeit 0.5 Sek)
F = Fire Ball (Skill, Cooldown 0.1 Sek, Castzeit 0.25 Sek)
Linke Maustaste = Ice Wave (ohne Cooldown/Cast)
Strg + E = Editor öffnen
Pfeiltasten zur Bewegung
Im Editor:
Strg + L = Map laden
Strg + S = Map speichern
Wichtigste Änderungen im Überblick:
Scrolling
Grundlegendes Fertigkeitensystem
Bugfix: Relative Koordinaten bei der GUI werden nun korrekt interpretiert
Wer nichts zu tun hat, kann mir ja die "Lyphia.profiler.html" mit den Profilerdaten im Ordner der Anwendung schicken.
Eines der größten Teil-Module ist immer noch die GUI, bei der ich einen Bug in Zusammenhang mit relativen Koordinaten beseitigt habe. Auf der ToDo-Liste für die GUI ist das Textfeld mit hoher Priorität angesetzt, denn die (einzilige) Texteingabe wird besonders für den Editor wichtig sein.
Neu hinzugekommen ist das Fertigkeiten-System. Es erlaubt, eine Fertigkeit TSkill, die von TAction abgeleitet ist und somit in Slots gepackt werden kann, mit Attributen wie Cast- und Cooldown-Zeit zu versehen. Die Skill-Animation wird in den abstrakten Methoden Cast() und Use() programmiert. Momentan sind die Fertigkeiten "hardgecodet". Ob die Skill-Animationen und -Daten später in Skriptdateien stehen werden, steht zum jetzigen Zeitpunkt noch nicht fest.
Das Partikelsystem wird in Zukunft in Gruppen unterteilt. Man kann bestimmte Partikelgruppen anlegen, die verschiedene Partikeltypen beinhalten. Man könnte z.B. die Gruppen "Fertigkeiten", "Effekte" und "GUI-Effekte" erstellen, die alle zu einem verschiedenen Zeitpunkt gerendert werden können. Ebenso wird es eine Z-Gruppierung geben, damit die Partikel auch in der richtigen Reihenfolge in der Nähe vom Spieler gezeichnet werden.
Wer ein wenig Klickibunti sehen will, kann sich hier die kompilierte Pre-Alpha 7 herunterladen und die Skills auf D und F benutzen:

Download Pre-Alpha 7 (835 KB, Debug + Profiler aktiviert)
Die kompilierte EXE liegt im src-Ordner.
Steuerung:
D = Ice Wave (Skill, Cooldown 1.0 Sek, Castzeit 0.5 Sek)
F = Fire Ball (Skill, Cooldown 0.1 Sek, Castzeit 0.25 Sek)
Linke Maustaste = Ice Wave (ohne Cooldown/Cast)
Strg + E = Editor öffnen
Pfeiltasten zur Bewegung
Im Editor:
Strg + L = Map laden
Strg + S = Map speichern
Wichtigste Änderungen im Überblick:



Wer nichts zu tun hat, kann mir ja die "Lyphia.profiler.html" mit den Profilerdaten im Ordner der Anwendung schicken.
Trigger, Action und Slot

Dieser Worklogeintrag ist eher theoretischer Natur, es geht um das Prinzip von Verknüpfungen.
Ja, was verknüpfen? Einen Auslöser mit einer Aktion, und zwar wie folgt:
Man erstellt einen Trigger ("Auslöser"):
Code: [AUSKLAPPEN]
Der Auslöser ist das Drücken einer Taste (TKeyTrigger). Im Quellcode wird New eigentlich nicht direkt verwendet, daher müsste es eigentlich so geschrieben werden:
Code: [AUSKLAPPEN]
Der Konstruktor "Create" kann nun dem KeyTrigger übermitteln, welche Taste die KeyTrigger-Instanz prüfen soll:
Code: [AUSKLAPPEN]
Nun speichert die TKeyTrigger-Instanz, dass sie prüfen soll, ob KEY_SPACE gedrückt wurde.
Wenn nun die Leertaste gedrückt wird, liefert
Code: [AUSKLAPPEN]
True zurück.
Nun kann man diesen Auslöser mit einer Aktion verbinden, z.B. einer Magieattacke. Dazu erstellt man eine Aktionsklasse, die von dem Interface TAction abgeleitet ist. Dieses erfordert die Methode Exec(trigger:TTrigger). Ein Beispiel:
Code: [AUSKLAPPEN]
Wieso ich leere Init-Funktionen benutze? Weil es einheitlich wirkt und ich beim Profiler die Calls zählen kann
Nun muss nur noch die Instanz erstellt erwerden:
Code: [AUSKLAPPEN]
Jetzt können der Auslöser und die Aktion in einem Slot untergebracht werden. Slots speichern mehrere Trigger für eine Aktion. Wer MMORPGs kennt, der hat sicherlich schonmal die Technikleisten (meist am Bildschirmrand) gesehen. Die Slots in Lyphia funktionieren ähnlich, allerdings verwende ich sogar für die Bewegung Slots. Die Taste "Pfeil Oben" wird mit der Aktion "TActionMove" und dem Parameter DIRECTION_UP verbunden. Bei diesem Beispiel braucht es nur zwei Zeilen, um Trigger und Action zu verbinden:
Code: [AUSKLAPPEN]
Im Code kann man nun, nachdem man "TInputSystem.Update()" aufgerufen hat, den Slot prüfen:
Code: [AUSKLAPPEN]
Wenn einer der Trigger True zurückgibt, wird dieser Trigger der Aktion übergeben und diese wird aktiviert. Es sind mehrere Trigger möglich, weil die Steuerung eventuell über mehrere Geräte erfolgen kann. Man könnte einen zweiten JoyStickTrigger hinzufügen, der die Joystickachsen abfragt. Liefert nun Tastatur oder Joystick True zurück, wird die entsprechende Aktion aktiviert. Der Trigger wird der Aktion übergeben, damit die Aktion dem Trigger evtl. zusätzliche Informationen entnehmen kann, wie zum Beispiel den genauen JoyX()-Wert, um eine rein "boolsche Bewegung" zu vermeiden.
Die wichtigsten Änderungen im Überblick:
Trigger/Action/Slot-System
Bewegungsanimationen funktionieren nun
Partikelsystem ist frameunabhängig
GUI hat nun leicht verbessertes Rendering für transparente Fenster
Fokus für Fenster (aktive und inaktive Fenster, kann z.B. durch Alpha visualisiert werden)
Diesmal gibt es keine Demo, weil sich äußerlich nicht viel verändert hat, aber der Kern ist sehr viel flexibler geworden.
Ja, was verknüpfen? Einen Auslöser mit einer Aktion, und zwar wie folgt:
Man erstellt einen Trigger ("Auslöser"):
Code: [AUSKLAPPEN]
Local keyTrigger:TTrigger = New TKeyTrigger
Der Auslöser ist das Drücken einer Taste (TKeyTrigger). Im Quellcode wird New eigentlich nicht direkt verwendet, daher müsste es eigentlich so geschrieben werden:
Code: [AUSKLAPPEN]
Local keyTrigger:TTrigger = TKeyTrigger.Create()
Der Konstruktor "Create" kann nun dem KeyTrigger übermitteln, welche Taste die KeyTrigger-Instanz prüfen soll:
Code: [AUSKLAPPEN]
Local keyTrigger:TTrigger = TKeyTrigger.Create(KEY_SPACE)
Nun speichert die TKeyTrigger-Instanz, dass sie prüfen soll, ob KEY_SPACE gedrückt wurde.
Wenn nun die Leertaste gedrückt wird, liefert
Code: [AUSKLAPPEN]
keyTrigger.Triggered()
True zurück.
Nun kann man diesen Auslöser mit einer Aktion verbinden, z.B. einer Magieattacke. Dazu erstellt man eine Aktionsklasse, die von dem Interface TAction abgeleitet ist. Dieses erfordert die Methode Exec(trigger:TTrigger). Ein Beispiel:
Code: [AUSKLAPPEN]
' TActionMagic
Type TActionMagic Extends TAction
' Init
Method Init()
End Method
' Exec
Method Exec(trigger:TTrigger)
' Technik ausführen, in diesem Beispiel aber einfach nur:
Print "42"
End Method
' Create
Function Create:TActionMagic()
Local action:TActionMagic = New TActionMagic
action.Init()
Return action
End Function
End Type
Type TActionMagic Extends TAction
' Init
Method Init()
End Method
' Exec
Method Exec(trigger:TTrigger)
' Technik ausführen, in diesem Beispiel aber einfach nur:
Print "42"
End Method
' Create
Function Create:TActionMagic()
Local action:TActionMagic = New TActionMagic
action.Init()
Return action
End Function
End Type
Wieso ich leere Init-Funktionen benutze? Weil es einheitlich wirkt und ich beim Profiler die Calls zählen kann

Nun muss nur noch die Instanz erstellt erwerden:
Code: [AUSKLAPPEN]
Local fireball:TActionMagic = TActionMagic.Create()
Jetzt können der Auslöser und die Aktion in einem Slot untergebracht werden. Slots speichern mehrere Trigger für eine Aktion. Wer MMORPGs kennt, der hat sicherlich schonmal die Technikleisten (meist am Bildschirmrand) gesehen. Die Slots in Lyphia funktionieren ähnlich, allerdings verwende ich sogar für die Bewegung Slots. Die Taste "Pfeil Oben" wird mit der Aktion "TActionMove" und dem Parameter DIRECTION_UP verbunden. Bei diesem Beispiel braucht es nur zwei Zeilen, um Trigger und Action zu verbinden:
Code: [AUSKLAPPEN]
Local slot:TSlot = TSlot.Create(fireball)
slot.AddTrigger(keyTrigger)
slot.AddTrigger(keyTrigger)
Im Code kann man nun, nachdem man "TInputSystem.Update()" aufgerufen hat, den Slot prüfen:
Code: [AUSKLAPPEN]
slot.Update()
Wenn einer der Trigger True zurückgibt, wird dieser Trigger der Aktion übergeben und diese wird aktiviert. Es sind mehrere Trigger möglich, weil die Steuerung eventuell über mehrere Geräte erfolgen kann. Man könnte einen zweiten JoyStickTrigger hinzufügen, der die Joystickachsen abfragt. Liefert nun Tastatur oder Joystick True zurück, wird die entsprechende Aktion aktiviert. Der Trigger wird der Aktion übergeben, damit die Aktion dem Trigger evtl. zusätzliche Informationen entnehmen kann, wie zum Beispiel den genauen JoyX()-Wert, um eine rein "boolsche Bewegung" zu vermeiden.
Die wichtigsten Änderungen im Überblick:





Diesmal gibt es keine Demo, weil sich äußerlich nicht viel verändert hat, aber der Kern ist sehr viel flexibler geworden.
InGame-Experimente

Um mal ein wenig Fortschritt zu zeigen, die wenig spektakuläre "Schnee-Attacke" (nicht wirklich):
Das ist bislang nur ein Partikeltest, der nicht einmal frameunabhängig ist. Das wird sich natürlich ändern und es wird ein OOP-lastiges Techniksystem geben. Aber dazu schreibe ich erst, wenn ich mehr Zeit habe. Durch die vielen Klausuren und Referate bin ich mitten im Prüfungsstress, aber dieses Wochenende werde ich wahrscheinlich wieder mehr Zeit investieren können.
Wer selber mal Schneepartikel fallen lassen will:
Download Pre-Alpha 6 (478 KB)
Kompilierte EXE liegt im src-Ordner.
Linke Maustaste = Schnee fallen lassen bei der Mausposition
Pfeiltasten = Nicht animierte Bewegung des Charakters
Leertaste = Partikeltest beim Charakter
Strg + E = Editor aktivieren
Im Editor:
Strg + L = Laden
Strg + S = Speichern
Linke Maustaste = Tile setzen
Mittlere Maustaste = Bereich füllen
Rechte Maustaste = Tile kopieren
Ist alles noch hochexperimentell und wird hoffentlich dieses Wochenende "richtig" gemacht.

Das ist bislang nur ein Partikeltest, der nicht einmal frameunabhängig ist. Das wird sich natürlich ändern und es wird ein OOP-lastiges Techniksystem geben. Aber dazu schreibe ich erst, wenn ich mehr Zeit habe. Durch die vielen Klausuren und Referate bin ich mitten im Prüfungsstress, aber dieses Wochenende werde ich wahrscheinlich wieder mehr Zeit investieren können.
Wer selber mal Schneepartikel fallen lassen will:
Download Pre-Alpha 6 (478 KB)
Kompilierte EXE liegt im src-Ordner.
Linke Maustaste = Schnee fallen lassen bei der Mausposition
Pfeiltasten = Nicht animierte Bewegung des Charakters
Leertaste = Partikeltest beim Charakter
Strg + E = Editor aktivieren
Im Editor:
Strg + L = Laden
Strg + S = Speichern
Linke Maustaste = Tile setzen
Mittlere Maustaste = Bereich füllen
Rechte Maustaste = Tile kopieren
Ist alles noch hochexperimentell und wird hoffentlich dieses Wochenende "richtig" gemacht.
Skins

In der neuen Version habe ich mich auf das GUI-System konzentriert.
Selbst wenn Lyphia nicht fertig werden sollte (was hoffentlich nicht passiert), so entsteht im Rahmen dieses Projekts zumindest eine für andere Spiele nutzbare GUI-Engine.
TGUI sowie die Widgets wurden verbessert und TWindow kann nun über Skins und INI-Dateien modifiziert werden.
Hier sieht man den "Simple"-Skin, wo ich natürlich die Optik außen vor gelassen habe:
Meine nächsten Ziele sind die Klasse TLayout und der Einsatz von Viewports beim Zeichnen.
Außerdem wird es in der nächsten Version wahrscheinlich mehr Tiles geben, um TLayout zu testen, wobei diese allerdings weiterhin als Platzhalter fungieren.
Download Pre-Alpha 5 (430 KB)
Selbst wenn Lyphia nicht fertig werden sollte (was hoffentlich nicht passiert), so entsteht im Rahmen dieses Projekts zumindest eine für andere Spiele nutzbare GUI-Engine.
TGUI sowie die Widgets wurden verbessert und TWindow kann nun über Skins und INI-Dateien modifiziert werden.
Hier sieht man den "Simple"-Skin, wo ich natürlich die Optik außen vor gelassen habe:

Meine nächsten Ziele sind die Klasse TLayout und der Einsatz von Viewports beim Zeichnen.
Außerdem wird es in der nächsten Version wahrscheinlich mehr Tiles geben, um TLayout zu testen, wobei diese allerdings weiterhin als Platzhalter fungieren.
Download Pre-Alpha 5 (430 KB)
Gehe zu Seite Zurück 1, 2, 3 Weiter