diff --git a/v2/raycaster.py b/v2/raycaster.py index a456809..ca0c046 100755 --- a/v2/raycaster.py +++ b/v2/raycaster.py @@ -10,62 +10,81 @@ import sdl2.ext import math import time -MAP_WIN_WIDTH = 640 -MAP_WIN_HEIGHT = 640 -RAYCAST_WIN_WIDTH = 400 -RAYCAST_WIN_HEIGHT = 255 -DUNGEON_WIDTH = MAP_WIN_WIDTH -DUNGEON_HEIGHT = MAP_WIN_HEIGHT -PLAYER_SPEED = 10 -PLAYER_ROTATION_SPEED = 0.17 -RAY_LENGTH = 100 -MAP_SCALE = 40 -COLLISION = True +# Map cfg +MAP_HIDDEN = True +MAP_SCALE = 24 +MAP_SIZE = 17 +MAP_WIN_WIDTH = MAP_SIZE * MAP_SCALE +MAP_WIN_HEIGHT = MAP_SIZE * MAP_SCALE -MAP = [ - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, - 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, - 1, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 0, 0, 1, - 1, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 1, - 1, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 1, - 1, 0, 0, 0, 0, 0, 2, 2, 2, 0, 0, 0, 2, 0, 0, 1, - 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 1, - 1, 0, 0, 0, 0, 0, 2, 2, 0, 0, 0, 0, 2, 0, 0, 1, - 1, 0, 0, 0, 0, 0, 0, 2, 2, 0, 0, 0, 2, 0, 0, 1, - 1, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 0, 0, 1, - 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, - 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, - 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, - 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, -] -MAP_SIZE = 16 +# Textures cfg +TEXTURE_SIZE = 8 + +# Raycast cfg +RAYCAST_WIN_WIDTH = 1000 +RAYCAST_WIN_HEIGHT = 600 +RAYCAST_RESOLUTION_SCALING = 4 +RAYCAST_RENDER_WIDTH = int(RAYCAST_WIN_WIDTH / RAYCAST_RESOLUTION_SCALING) +RAYCAST_RENDER_HEIGHT = int(RAYCAST_WIN_HEIGHT / RAYCAST_RESOLUTION_SCALING) DOF = 2*MAP_SIZE # Depth Of Field +# Player cfg +PLAYER_SPEED = 8 +PLAYER_ROTATION_SPEED = 0.1 +PLAYER_SPAWN_POSITION = {"x": int(MAP_SCALE * 2), "y": int(MAP_SCALE * 5), "r": 0} # r is rotation in radiants + +# Dungeon data +MAP = [ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 2, 2, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, + 3, 3, 3, 3, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, + 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, + 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, + 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, + 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 0, 3, 3, 3, 3, 3, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, + 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, + 3, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, + 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, + 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, + 3, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, + 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, +] TEXTURES = [ [ - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 1, 1, 1, 1, 1, 0, - 0, 1, 0, 1, 1, 1, 1, 0, - 0, 1, 1, 0, 1, 1, 1, 0, - 0, 1, 1, 1, 0, 1, 1, 0, - 0, 1, 1, 1, 1, 0, 1, 0, - 0, 1, 1, 1, 1, 1, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, + 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 0, 1, 1, 1, 1, + 1, 1, 0, 0, 0, 1, 1, 1, + 1, 1, 0, 0, 0, 1, 1, 1, + 1, 1, 1, 0, 1, 1, 1, 1, + 1, 1, 0, 0, 0, 1, 1, 1, + 1, 1, 1, 0, 1, 1, 1, 1, ], [ - 1, 1, 1, 0, 0, 1, 1, 1, - 1, 1, 0, 1, 1, 0, 1, 1, - 1, 0, 1, 1, 1, 1, 0, 1, - 0, 1, 1, 1, 1, 1, 1, 0, - 0, 1, 1, 1, 1, 1, 1, 0, - 1, 0, 1, 1, 1, 1, 0, 1, - 1, 1, 0, 1, 1, 0, 1, 1, - 1, 1, 1, 0, 0, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 0, 1, 0, 1, 1, 1, + 1, 1, 1, 0, 0, 0, 1, 1, + 1, 1, 0, 0, 0, 1, 1, 1, + 1, 1, 1, 0, 1, 0, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, ], + [ + 1, 1, 1, 1, 1, 1, 1, 1, + 1, 0, 1, 0, 1, 1, 1, 1, + 1, 0, 0, 0, 1, 1, 1, 1, + 1, 0, 0, 0, 1, 1, 1, 1, + 1, 1, 0, 1, 1, 1, 1, 1, + 1, 1, 0, 1, 1, 1, 1, 1, + 1, 1, 0, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, + ] ] -TEXTURE_SIZE = 8 class Main: @@ -76,16 +95,17 @@ class Main: # Graphics sdl2.ext.init() - self.mapWindow = sdl2.ext.Window("2D Map", size=(MAP_WIN_WIDTH, MAP_WIN_HEIGHT)) - self.mapWindow.show() - self.mapSurface = self.mapWindow.get_surface() + if not MAP_HIDDEN: + self.mapWindow = sdl2.ext.Window("2D Map", size=(MAP_WIN_WIDTH, MAP_WIN_HEIGHT)) + self.mapWindow.show() + self.mapSurface = self.mapWindow.get_surface() self.raycastWindow = sdl2.ext.Window("3D View", size=(RAYCAST_WIN_WIDTH, RAYCAST_WIN_HEIGHT)) self.raycastWindow.show() self.raycastSurface = self.raycastWindow.get_surface() # Player - self.player_position = {"x": int(DUNGEON_WIDTH/2), "y": int(DUNGEON_HEIGHT/2), "r": 0} # r is rotation in radiants + self.player_position = PLAYER_SPAWN_POSITION return @@ -97,42 +117,44 @@ class Main: while running: events = sdl2.ext.get_events() for event in events: - if event.type == sdl2.SDL_QUIT: + if event.type == sdl2.SDL_QUIT or (event.type == sdl2.SDL_KEYDOWN and event.key.keysym.sym == sdl2.SDLK_ESCAPE): running = False break - if event.type == sdl2.SDL_KEYDOWN: - # Rotate player - if event.key.keysym.sym == sdl2.SDLK_LEFT: - self.player_position["r"] = self.player_position["r"] - PLAYER_ROTATION_SPEED - elif event.key.keysym.sym == sdl2.SDLK_RIGHT: - self.player_position["r"] = self.player_position["r"] + PLAYER_ROTATION_SPEED - # Compute deltax and deltay based on player direction - player_delta_x = math.cos(self.player_position["r"]) * PLAYER_SPEED - player_delta_y = math.sin(self.player_position["r"]) * PLAYER_SPEED + keystate = sdl2.SDL_GetKeyboardState(None) + # Rotate player + if keystate[sdl2.SDL_SCANCODE_LEFT]: + self.player_position["r"] = self.player_position["r"] - PLAYER_ROTATION_SPEED + elif keystate[sdl2.SDL_SCANCODE_RIGHT]: + self.player_position["r"] = self.player_position["r"] + PLAYER_ROTATION_SPEED - # Move player based on its direction - if event.key.keysym.sym == sdl2.SDLK_UP: - self.movePlayerRelative(player_delta_x, player_delta_y) - elif event.key.keysym.sym == sdl2.SDLK_DOWN: - self.movePlayerRelative(-player_delta_x, -player_delta_y) + # Compute deltax and deltay based on player direction + player_delta_x = math.cos(self.player_position["r"]) * PLAYER_SPEED + player_delta_y = math.sin(self.player_position["r"]) * PLAYER_SPEED - # Limit position into dungeon bounds - if self.player_position["x"] < 0: - self.player_position["x"] = 0 - if self.player_position["x"] > DUNGEON_WIDTH: - self.player_position["x"] = DUNGEON_WIDTH - if self.player_position["y"] < 0: - self.player_position["y"] = 0 - if self.player_position["y"] > DUNGEON_HEIGHT: - self.player_position["y"] = DUNGEON_HEIGHT - if self.player_position["r"] > 2*math.pi: - self.player_position["r"] = 0 - if self.player_position["r"] < 0: - self.player_position["r"] = 2*math.pi + # Move player based on its direction + if keystate[sdl2.SDL_SCANCODE_UP]: + self.movePlayerRelative(player_delta_x, player_delta_y) + elif keystate[sdl2.SDL_SCANCODE_DOWN]: + self.movePlayerRelative(-player_delta_x, -player_delta_y) + + # Limit position into dungeon bounds + if self.player_position["x"] < 0: + self.player_position["x"] = 0 + if self.player_position["x"] > MAP_WIN_WIDTH: + self.player_position["x"] = MAP_WIN_WIDTH + if self.player_position["y"] < 0: + self.player_position["y"] = 0 + if self.player_position["y"] > MAP_WIN_HEIGHT: + self.player_position["y"] = MAP_WIN_HEIGHT + if self.player_position["r"] > 2*math.pi: + self.player_position["r"] = 0 + if self.player_position["r"] < 0: + self.player_position["r"] = 2*math.pi self.draw() - self.mapWindow.refresh() + if not MAP_HIDDEN: + self.mapWindow.refresh() self.raycastWindow.refresh() # Calculate FPS @@ -165,12 +187,9 @@ class Main: self.player_position["y"] = newPlayerY def draw(self): - sdl2.ext.draw.fill(self.mapSurface, sdl2.ext.Color(0,0,0,255)) # Clears map screen - sdl2.ext.draw.fill(self.raycastSurface, sdl2.ext.Color(0,0,128,255), (0, 0, RAYCAST_WIN_WIDTH, RAYCAST_WIN_HEIGHT/2)) # Clears upper raycast screen (draws ceiling) - sdl2.ext.draw.fill(self.raycastSurface, sdl2.ext.Color(0,128,0,255), (0, RAYCAST_WIN_HEIGHT/2, RAYCAST_WIN_WIDTH, RAYCAST_WIN_HEIGHT/2)) # Clears upper raycast screen (draws floor) - - self.draw2Dmap() - self.drawPlayer() + if not MAP_HIDDEN: + self.draw2Dmap() + self.drawPlayer() self.drawRays() def drawPlayer(self): @@ -186,6 +205,7 @@ class Main: def draw2Dmap(self): # 2D map + sdl2.ext.draw.fill(self.mapSurface, sdl2.ext.Color(0,0,0,255)) # Clears map screen for i in range(len(MAP)): posX = i % MAP_SIZE * MAP_SCALE posY = math.floor(i / MAP_SIZE) * MAP_SCALE @@ -195,12 +215,15 @@ class Main: sdl2.ext.draw.fill(self.mapSurface, sdl2.ext.Color(color,color,color,255), (posX, posY, MAP_SCALE - 1, MAP_SCALE - 1)) def drawRays(self): + sdl2.ext.draw.fill(self.raycastSurface, sdl2.ext.Color(0,0,128,255), (0, 0, RAYCAST_WIN_WIDTH, RAYCAST_WIN_HEIGHT/2)) # Clears upper raycast screen (draws ceiling) + sdl2.ext.draw.fill(self.raycastSurface, sdl2.ext.Color(0,128,0,255), (0, RAYCAST_WIN_HEIGHT/2, RAYCAST_WIN_WIDTH, RAYCAST_WIN_HEIGHT/2)) # Clears upper raycast screen (draws floor) + # Casts rays for raycasting playerAngle = self.player_position["r"] # Cast one ray for every window pixel, from -0,5 rads to +0,5 rads (about 60° viewing angle) - for i in range(RAYCAST_WIN_WIDTH): - rayAngle = playerAngle + (i/RAYCAST_WIN_WIDTH) - 0.5 + for i in range(RAYCAST_RENDER_WIDTH): + rayAngle = playerAngle + (i/RAYCAST_RENDER_WIDTH) - 0.5 if rayAngle < 0: rayAngle = math.pi * 2 + rayAngle if rayAngle > math.pi * 2: @@ -296,16 +319,17 @@ class Main: rayY = horizRayY shortestDist = horizDist - # Draw rays in 2D view - sdl2.ext.draw.line(self.mapSurface, sdl2.ext.Color(0,0,255,255), (self.player_position["x"], self.player_position["y"], rayX, rayY)) + if not MAP_HIDDEN: + # Draw rays in 2D view + sdl2.ext.draw.line(self.mapSurface, sdl2.ext.Color(0,0,255,255), (self.player_position["x"], self.player_position["y"], rayX, rayY)) # ------ Draw 3D view ------ # Calculate line height based on distance - lineHeight = MAP_SCALE * RAYCAST_WIN_HEIGHT / shortestDist + lineHeight = MAP_SCALE * RAYCAST_RENDER_HEIGHT / shortestDist # Center line vertically in window - lineOffset = RAYCAST_WIN_HEIGHT / 2 - lineHeight / 2 + lineOffset = RAYCAST_RENDER_HEIGHT / 2 - lineHeight / 2 # Draw pixels vertically from top to bottom to obtain a line textureSegmentEnd = 0 @@ -331,13 +355,21 @@ class Main: texel = TEXTURES[texIndex][texColumn + textureColumnPixel * TEXTURE_SIZE] # Calculate color resulting from texture pixel value + shading - color = sdl2.ext.Color(texel*shading,texel*shading,texel*shading,255) + color = sdl2.ext.Color(texel*shading,texel*(shading/5),texel*(shading/5),255) # Clipping lineEnd = textureSegmentEnd - if textureSegmentEnd > lineOffset + lineHeight: - textureSegmentEnd = lineOffset + lineHeight + if lineEnd > lineOffset + lineHeight: + lineEnd = lineOffset + lineHeight + lineStart = textureSegmentStart + if lineStart < 0: + lineStart = 0 + # Upscaling + lineStart = lineStart * RAYCAST_RESOLUTION_SCALING + lineEnd = lineEnd * RAYCAST_RESOLUTION_SCALING # Draw segment - sdl2.ext.draw.line(self.raycastSurface, color, (i, int(textureSegmentStart), i, int(textureSegmentEnd))) + for repeat in range(1, RAYCAST_RESOLUTION_SCALING + 1): + x = i * RAYCAST_RESOLUTION_SCALING + repeat + sdl2.ext.draw.line(self.raycastSurface, color, (x, int(lineStart), x, int(lineEnd))) def dist(self, ax, ay, bx, by):