Wie der Titel schon sagt, stelle ich hier den Raycaster vor, der innerhalb der letzten Wochen entstanden ist.
Wer nicht weiss, was Raycasting ist, der kann sich hier darüber informieren.
Kurz gefasst ist es eine alternative 3D - Renderart, die in den ersten 3D - Spielen eingesetzt wurde (Wolfenstein 3D und eine fortgeschrittene Version in Doom).
Meine Version ist nicht besonders komplex, beherrscht jedoch das Rendern von Boden, Decke, texturierten Wänden und Sprites, was eigentlich schon genug ist für eine sehr simple 3D - Simulation (beispielsweise der Labyrinth - Bildschirmschoner früherer Windowseditionen).
Ich habe versucht, den Algorithmus so weit wie möglich zu optimieren, jedoch frisst das Berechnen der Sprites leider (zu) viel Rechenleistung, da mir unter anderem kein schneller Sichtbarkeitscheck engefallen ist.
Man kann auch das WaitTimer rausnehmen und schauen, wie schnell es bei ihm läuft - auf meinem Intel Celeron läuft es (mit ausgeschaltetem Debugger!) zwischen 100 - 200 FPS, was noch genug Freiraum für anderweitige Berechnungen zulässt..
Achtung: Das ist kein Aufruf, seine FPS hier zu posten!
Da sich das Programm auf Metadaten verlässt, habe ich das ganze gezippt hier hochgeladen.
Die Texturen und das Sprite sind auch bei den B3D - Samples beigelegt und sind schnell gefunden, sollte der Link tot sein.
EDIT: Fehler wegen dem WritePixelFast fixed!
BlitzBasic: [AUSKLAPPEN] [EINKLAPPEN] Const MAPSIZE = 16 Const GRIDSIZE = 64 Const GRIDSHIFTS = 6 Const GWIDTH = 320 Const GHEIGHT = 200 Const ANGLEOFVIEW = 60 Const CASTINGSTEP# = ANGLEOFVIEW/Float( GWIDTH ) Const TILES = 2 Const TEXTURESIZE = 64 Const SCALEFACTOR# = TEXTURESIZE/GRIDSIZE Const FLOORTILE = 2 Const CEILINGTILE = 2 Const HD = ( SCALEFACTOR# <> 1 ) Const CEILING = True
Graphics GWIDTH, GHEIGHT, 32, 2
Global CamX# = 5*GRIDSIZE + GRIDSIZE/2, CamY# = 5*GRIDSIZE + GRIDSIZE/2, ProjectionDistance# = ( GWIDTH/2 )/Tan( ANGLEOFVIEW/2 ) Global CameraAngle = 270, PlayerHeight = 32, PlaneDisplacement
Dim MapData( MAPSIZE - 1, MAPSIZE - 1 ) Dim TextureData( TILES*( TEXTURESIZE)*( TEXTURESIZE ) ) Dim Distances#( GWIDTH - 1 )
For i = 0 To Tiles - 1 Texture = LoadImage( "Tile" + ( i + 1 ) + ".png" ) SetBuffer ImageBuffer( Texture ) LockBuffer ImageBuffer( Texture ) For x = 0 To TEXTURESIZE - 1 For y = 0 To TEXTURESIZE - 1 TextureData( i*TEXTURESIZE*TEXTURESIZE + ( x*TEXTURESIZE + y ) ) = ReadPixelFast( x, y ) Next Next FreeImage Texture Next
Type Sprite Field X# Field Y# Field Height Field Width Field ImageData[ 256*256 ] End Type
Dummy = LoadImage( "Alien.png" ) LockBuffer ImageBuffer( Dummy ) For i = 1 To 10 Drink.Sprite = New Sprite Drink\X# = Rnd( 0, MAPSIZE*GRIDSIZE ) Drink\Y# = Rnd( 0, MAPSIZE*GRIDSIZE ) Drink\Height = ImageHeight( Dummy ) Drink\Width = ImageWidth( Dummy ) For x = 0 To Drink\Width - 1 For y = 0 To Drink\Height - 1 Drink\ImageData[ x*Drink\Width + y ] = ReadPixelFast( x, y, ImageBuffer( Dummy ) ) Next Next If MapData( Drink\X# Shr GRIDSHIFTS, Drink\Y# Shr GRIDSHIFTS ) Then i = i - 1 Next FreeImage( Dummy )
SetBuffer BackBuffer()
Restore MapData For i = 0 To MAPSIZE - 1 For t = 0 To MAPSIZE - 1 Read MapData( t, i ) Next Next
Timer = CreateTimer( 60 )
DisplayFPS = 0 FPS = 0 Counter = MilliSecs()
While Not KeyHit( 1 ) Cls LockBuffer BackBuffer() RenderScreen() RenderSprites() UnlockBuffer BackBuffer() MoveCamera() If MilliSecs() - Counter > 1000 Then DisplayFPS = FPS Counter = MilliSecs() FPS = 0 EndIf Text 0, 0, DisplayFPS Flip 0 WaitTimer Timer FPS = FPS + 1 Wend
End
Function MoveCamera() XSpeed# = -Sgn( KeyDown( 208 ) - KeyDown( 200 ) )*Cos( CameraAngle ) YSpeed# = Sgn( KeyDown( 208 ) - KeyDown( 200 ) )*Sin( CameraAngle ) TileX = ( CamX# + XSpeed# + Sgn( XSpeed# ) ) Shr GRIDSHIFTS TileY = ( CamY# + YSpeed# + Sgn( YSpeed# ) ) Shr GRIDSHIFTS OldTileX = CamX# Shr GRIDSHIFTS OldTileY = CamY# Shr GRIDSHIFTS If Not MapData( OldTileX, TileY ) Then CamY# = CamY# + YSpeed# If Not MapData( TileX, OldTileY ) Then CamX# = CamX# + XSpeed# CameraAngle = CameraAngle - KeyDown( 203 ) + KeyDown( 205 ) PlaneDisplacement = PlaneDisplacement - KeyDown( 74 ) + KeyDown( 78 ) End Function
Function RenderScreen() For i = 0 To GWIDTH - 2 Angle# = ( CameraAngle - ANGLEOFVIEW/2 + CASTINGSTEP#*i ) Mod 360 Angle# = ( 360 + Angle# ) Mod 360 YDirection = 1 - ( Angle# < 180 )*2 XDirection = -1 + ( Angle# > 270 Or Angle# < 90 )*2 Ya# = CamY# Mod GRIDSIZE If YDirection = 1 Then Ya# = GRIDSIZE - Ya# Xa# = Ya#/Tan( Angle# ) IntersectionY# = CamY# + YDirection*Ya# IntersectionX# = CamX# - YDirection*Xa# TileY = ( IntersectionY# + YDirection ) Shr GRIDSHIFTS TileX = IntersectionX# Shr GRIDSHIFTS If TileX >= 0 And TileX < MAPSIZE And TileY >= 0 And TileY < MAPSIZE Then If MapData( TileX, TileY ) Then WallTile = MapData( TileX, TileY ) Else Ya# = YDirection Shl GRIDSHIFTS Xa# = Ya#/Tan( Angle# ) Repeat IntersectionX# = IntersectionX# - Xa# IntersectionY# = IntersectionY# + Ya# TileY = ( IntersectionY# + YDirection ) Shr GRIDSHIFTS TileX = IntersectionX# Shr GRIDSHIFTS If TileX >= 0 And TileX < MAPSIZE And TileY >= 0 And TileY < MAPSIZE Then If MapData( TileX, TileY ) Then WallTile = MapData( TileX, TileY ) Exit EndIf Else Exit EndIf Forever EndIf EndIf WallX# = IntersectionX# WallY# = IntersectionY# Xa# = CamX# Mod GRIDSIZE If XDirection = 1 Then Xa# = GRIDSIZE - Xa# Ya# = Xa#*Tan( Angle# ) IntersectionX# = CamX# + XDirection*Xa# IntersectionY# = CamY# - XDirection*Ya# TileY = IntersectionY# Shr GRIDSHIFTS TileX = ( IntersectionX# + XDirection ) Shr GRIDSHIFTS If TileX >= 0 And TileX < MAPSIZE And TileY >= 0 And TileY < MAPSIZE Then If MapData( TileX, TileY ) Then IntersectionTile = MapData( TileX, TileY ) Else Xa# = XDirection Shl GRIDSHIFTS Ya# = Xa#*Tan( Angle# ) Repeat IntersectionX# = IntersectionX# + Xa# IntersectionY# = IntersectionY# - Ya# TileY = IntersectionY# Shr GRIDSHIFTS TileX = ( IntersectionX# + XDirection ) Shr GRIDSHIFTS If TileX >= 0 And TileX < MAPSIZE And TileY >= 0 And TileY < MAPSIZE Then If MapData( TileX, TileY ) Then IntersectionTile = MapData( TileX, TileY ) Exit EndIf Else Exit EndIf Forever EndIf EndIf DistanceVertically# = Abs( ( WallY# - CamY# )/Sin( Angle# ) ) DistanceHorizontally# = Abs( ( IntersectionX# - CamX# )/Cos( Angle# ) ) If DistanceHorizontally# < DistanceVertically# Then SmallerDistance# = DistanceHorizontally# If WallTile > 0 Then If HD Then TextureRow = Floor( ( WallX# Mod GRIDSIZE )*SCALEFACTOR# ) Else TextureRow = Floor( WallX# ) Mod GRIDSIZE EndIf EndIf If HD Then TextureRow = Floor( ( IntersectionY# Mod GRIDSIZE )*SCALEFACTOR# ) Else TextureRow = Floor( IntersectionY# ) Mod GRIDSIZE EndIf Else SmallerDistance# = DistanceVertically# If IntersectionTile > 0 Then If HD Then TextureRow = Floor( ( IntersectionY# Mod GRIDSIZE )*SCALEFACTOR# ) Else TextureRow = Floor( IntersectionY# ) Mod GRIDSIZE EndIf EndIf If HD Then TextureRow = Floor( ( WallX# Mod GRIDSIZE )*SCALEFACTOR# ) Else TextureRow = Floor( WallX# ) Mod GRIDSIZE EndIf EndIf WallHeight = ( GRIDSIZE/( SmallerDistance#*Cos( CASTINGSTEP#*i - ANGLEOFVIEW/2 ) ) )*ProjectionDistance# If WallTile = 0 And IntersectionTile > 0 Then WallTile = IntersectionTile TStart = ( GHEIGHT/2 + PlaneDisplacement ) - WallHeight/2 TEnd = ( GHEIGHT/2 + PlaneDisplacement ) + WallHeight/2 If TStart < 0 Then YCorrection = Abs( TStart ) If TEnd >= GHEIGHT Then YCorrection2 = TEnd - GHEIGHT + 1 For t = TStart + YCorrection To TEnd - YCorrection2 If WallTile = 1 Then WritePixelFast i, t, $FFFFFFFF Else ARGB = TextureData( ( WallTile - 2 )*TEXTURESIZE*TEXTURESIZE + ( TextureRow*TEXTURESIZE + ( t - GHEIGHT/2 + WallHeight/2 - PlaneDisplacement )*( TEXTURESIZE/Float( WallHeight ) ) ) ) WritePixelFast i, t, ARGB EndIf Next TStart = ( GHEIGHT/2 + PlaneDisplacement ) + WallHeight/2 TStart = TStart*( TStart >= 0 ) TEnd = GHEIGHT - 1 For t = TStart To TEnd Length# = ( ( GRIDSIZE/2 )/( ( t - GHEIGHT/2 - PlaneDisplacement )/ProjectionDistance# ) )/Cos( CASTINGSTEP#*i - ANGLEOFVIEW/2 ) FloorX = CamX# + Cos( Angle# )*Length# FloorY = CamY# - Sin( Angle# )*Length# TextureColumn = FloorY Mod TEXTURESIZE TextureRow = FloorX Mod TEXTURESIZE ARGB = TextureData( ( FLOORTILE - 1 )*TEXTURESIZE*TEXTURESIZE + ( TextureRow*TEXTURESIZE + TextureColumn ) ) WritePixelFast i, t, ARGB Next If CEILING Then TStart = 0 TEnd = ( GHEIGHT/2 + PlaneDisplacement ) - WallHeight/2 TEnd = TEnd*( TEnd < GHEIGHT ) + ( GHEIGHT - 1 )*( TEnd >= GHEIGHT ) For t = TStart To TEnd Length# = ( ( GRIDSIZE/2 )/( ( GHEIGHT/2 - t + PlaneDisplacement )/ProjectionDistance# ) )/Cos( CASTINGSTEP#*i - ANGLEOFVIEW/2 ) CeilingX = CamX# + Cos( Angle# )*Length# CeilingY = CamY# - Sin( Angle# )*Length# TextureRow = CeilingX Mod TEXTURESIZE TextureColumn = CeilingY Mod TEXTURESIZE ARGB = TextureData( ( CEILINGTILE - 1 )*TEXTURESIZE*TEXTURESIZE + ( TextureRow*TEXTURESIZE + TextureColumn ) ) WritePixelFast i, t, ARGB Next EndIf Distances#( i ) = SmallerDistance# TextureRow = 0 IntersectionTile = 0 WallTile = 0 Saviour = 0 YCorrection = 0 YCorrection2 = 0 Next End Function
Function RenderSprites() For Drink.Sprite = Each Sprite Distance# = Sqr( ( CamX# - Drink\X# )*( CamX# - Drink\X# ) + ( CamY# - Drink\Y# )*( CamY# - Drink\Y# ) ) Height = Drink\Height - 1 Width = Drink\Width SpriteHeight = ( Height/Distance# )*ProjectionDistance# SpriteWidth = Width*( SpriteHeight/Float( Height ) ) Difference# = ATan( ( Drink\Y# - CamY# )/( Drink\X# - CamX# ) ) If Drink\X# - CamX# < 0 Then Difference# = 180 + Difference# Angle# = ( -Difference# - CameraAngle + 360 ) Mod 360 StartSlice = ( Angle# + ANGLEOFVIEW/2 )/CASTINGSTEP# - SpriteWidth/2 If ( StartSlice < 0 Or StartSlice > GWIDTH ) And Abs( Angle# ) > 360 - ANGLEOFVIEW/2 - SpriteWidth/2 Then Angle# = Angle# - Sgn( Angle# )*360 StartSlice = ( Angle# + ANGLEOFVIEW/2 )/CASTINGSTEP# - SpriteWidth/2 EndIf XStart = StartSlice*( StartSlice >= 0 ) XEnd = StartSlice + SpriteWidth - 1 XEnd = XEnd*( XEnd < GWIDTH ) + ( GWIDTH - 1 )*( XEnd > GWIDTH ) YStart = ( GHEIGHT/2 - Spriteheight/2 + PlaneDisplacement )*( GHEIGHT/2 - SpriteHeight/2 + PlaneDisplacement >= 0 ) YEnd = GHEIGHT/2 + SpriteHeight/2 - 1 + PlaneDisplacement YEnd = YEnd*( YEnd < GHEIGHT ) + ( GHEIGHT - 1)*( YEnd >= GHEIGHT ) For x = XStart To XEnd For y = YStart To YEnd If Distances#( x ) > Distance# Then ARGB = Drink\ImageData[ Floor( ( x - StartSlice )*( Width/Float( SpriteWidth ) ) )*Drink\Width + ( y - GHEIGHT/2 + SpriteHeight/2 - PlaneDisplacement )*( Height/Float( SpriteHeight ) ) ] If ARGB - $FF000000 <> 0 Then WritePixelFast x, y, ARGB EndIf EndIf Next Next Next End Function
.MapData Data 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2 Data 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 2 Data 1, 0, 2, 0, 2, 0, 2, 0, 2, 2, 2, 2, 2, 2, 0, 2 Data 2, 2, 2, 0, 0, 0, 2, 0, 0, 2, 0, 0, 0, 0, 0, 2 Data 2, 2, 2, 2, 2, 2, 2, 2, 0, 2, 2, 2, 0, 2, 2, 2 Data 2, 2, 2, 0, 0, 0, 0, 2, 0, 0, 0, 2, 0, 2, 0, 2 Data 2, 2, 2, 0, 0, 0, 0, 2, 2, 2, 0, 0, 0, 2, 0, 2 Data 2, 2, 2, 0, 2, 2, 0, 2, 2, 2, 2, 2, 0, 2, 0, 2 Data 2, 2, 2, 0, 2, 2, 0, 2, 0, 0, 0, 2, 0, 2, 0, 2 Data 2, 2, 2, 0, 0, 0, 0, 2, 0, 2, 0, 2, 0, 0, 0, 2 Data 2, 0, 2, 0, 0, 0, 0, 2, 0, 2, 0, 2, 2, 2, 0, 2 Data 2, 0, 2, 0, 0, 0, 0, 2, 0, 2, 0, 2, 0, 0, 0, 2 Data 2, 0, 2, 0, 0, 0, 0, 2, 0, 2, 0, 0, 0, 2, 0, 2 Data 2, 0, 2, 2, 2, 2, 0, 2, 2, 2, 2, 2, 2, 2, 0, 2 Data 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2 Data 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2
Edit:
Jetzt habe ich doch tatsächlich vergessen, die bekannten Bugs aufzuzählen
Es kann manchmal vorkommen, dass Strahlen durch die Wände 'durchsausen'.
Das macht sich dadurch bemerkbar, dass Streifen der Textur einer dahinterliegenden Wand auf der vorderen sichtbar werden, oder dass an einer Ecke plötzlich der Eckpunkt fehlt und man das dahinter sieht.
Sollte ich den Fehler finden, werde ich ihn nachbessern - euch steht es selbstverständlich frei, den Code selbst auszubessern.
|