基于Pygame的Python光线投射器:水平轴墙体交点识别故障排查
I'm building a ray caster in Python with Pygame, adapting a C implementation from a tutorial. I replaced my original pixel-by-pixel ray casting method with the grid-algorithm approach from the tutorial, but my horizontal wall intersection detection isn't working at all. Here's my full code, with the broken cast_rays function focused on horizontal rays:
import pygame import sys import math pygame.init() screen_height = 480 screen_width = screen_height * 2 map_size = 8 tile_size = screen_height / 8 player_x = screen_width / 4 player_y = screen_width / 4 FOV = math.pi / 3 HALF_FOV = FOV / 2 player_angle = math.pi + math.pi / 2 casted_rays = 120 step_angle = FOV / casted_rays scale = screen_height / casted_rays MAP = ( '########', '# #', '# ## #', '# ## #', '# #', '### #', '### #', '########' ) def draw_map(): for row in range(8): for col in range(8): # square index square = row * map_size + col pygame.draw.rect(win, (200,200,200) if MAP[square] == '#' else (100,100,100),(row * tile_size, col * tile_size, tile_size - 2, tile_size - 2)) pygame.draw.circle(win, (255,0,0), (player_x, player_y), 8) def cast_rays(): ''' start_angle = player_angle - HALF_FOV for ray in range(casted_rays): for depth in range(screen_height): target_x = player_x + math.cos(start_angle) * depth target_y = player_y + math.sin(start_angle) * depth pygame.draw.line(win, (255,255,0), (player_x, player_y), (target_x, target_y) ,3) row = int(target_x / tile_size) col = int(target_y / tile_size) square = int(row * map_size + col) if MAP[square] == "#": pygame.draw.rect(win, (0,255, 0),(row * tile_size, col * tile_size, tile_size - 2, tile_size - 2)) wall_height = 21000 / (depth + 0.00001) pygame.draw.rect(win, (100,100,100), (screen_height + ray * scale, (screen_height - wall_height) / 2 ,scale,wall_height)) break start_angle += step_angle ''' #dof = 0 r = 0 ra = player_angle ry = 0 rx = 0 while r < 1: dof = 0 aTan = -1/math.tan(ra); if ra > math.pi: ry = ((ry * tile_size) / tile_size) - 0.0001 rx = (player_y - ry) * aTan + player_x yo = -64 xo = -yo * aTan if ra < math.pi: ry = ((ry * tile_size) / tile_size) + 64 rx = (player_y - ry) * aTan + player_x yo = 64 xo = -yo * aTan if ra == 0 or ra == math.pi: dof = 8 ra = 0 rx = player_x ry = player_y while dof < 8: mx = rx * tile_size my = ry * tile_size mp = my * tile_size if mp < tile_size * 8 * tile_size * 8 and MAP[int(mp)] == '#': dof = 8 else: rx += xo ry += yo pygame.draw.line(win, (255,255,0), (player_x, player_y), (rx, ry) ,3) r += 1 win = pygame.display.set_mode((screen_width, screen_height)) clock = pygame.time.Clock() while True: for event in pygame.event.get(): if event.type == pygame.QUIT: pygame.quit() sys.exit() pygame.draw.rect(win, (0,0,0), (0, 0, screen_width, screen_height)) draw_map() cast_rays() keys = pygame.key.get_pressed() if keys[pygame.K_LEFT]: player_angle -= 0.1 if keys[pygame.K_RIGHT]: player_angle += 0.1 if keys[pygame.K_UP]: player_x, player_y = player_x + math.cos(player_angle) * 3, player_y + math.sin(player_angle) * 3 if keys[pygame.K_DOWN]: player_x, player_y = player_x - math.cos(player_angle) * 3, player_y - math.sin(player_angle) * 3 pygame.display.flip() clock.tick(30)
Let's break down the issues in your cast_rays function step by step—there are a few critical bugs messing up the horizontal ray detection:
1. Uninitialized & Misaligned Starting Coordinates
Your ry variable starts at 0, but you need to calculate the initial horizontal grid intersection based on the player's actual y-position. The original C code aligns ry to the nearest tile boundary relative to the player, but your current code uses ry * tile_size (which is 0 at startup) leading to invalid starting points.
2. Broken Map Index Calculation
The way you convert ray coordinates to map grid indices is completely incorrect:
mx = rx * tile_sizeandmy = ry * tile_sizeare backwards—you need to divide by tile size to get grid columns/rows, not multiply.mp = my * tile_sizemakes no sense for a 1D map index. The correct formula isrow * map_size + col, whererowis the y-grid position andcolis the x-grid position.
3. Angle Edge Case Handling
When ra is 0 or π (straight left/right), setting ra = 0 breaks the loop logic. You should just mark dof = 8 to exit the inner loop without modifying the angle.
4. Ray Loop Only Runs Once
Your while r < 1 loop only casts a single ray, not the full casted_rays count needed to cover your FOV. You need to iterate over all rays, adjusting the angle for each one.
Fixed cast_rays Function
Here's the corrected version with all these issues addressed:
def cast_rays(): start_angle = player_angle - HALF_FOV for r in range(casted_rays): ra = start_angle + r * step_angle # Normalize angle to 0-2π to avoid edge case bugs ra = ra % (2 * math.pi) dof = 0 aTan = -1 / math.tan(ra) rx, ry = player_x, player_y xo, yo = 0, 0 # Horizontal ray calculation (account for Pygame's downward-increasing y) if ra > math.pi: # Looking "up" (toward lower y values) ry = (int(player_y / tile_size) * tile_size) - 0.0001 rx = (player_y - ry) * aTan + player_x yo = -tile_size xo = -yo * aTan if ra < math.pi: # Looking "down" (toward higher y values) ry = (int(player_y / tile_size) * tile_size) + tile_size rx = (player_y - ry) * aTan + player_x yo = tile_size xo = -yo * aTan if ra == 0 or ra == math.pi: # Straight left/right, no horizontal hit possible dof = 8 rx = player_x ry = player_y # Check horizontal grid intersections while dof < 8: # Convert ray position to map grid indices mx = int(rx / tile_size) my = int(ry / tile_size) # Ensure we're within map bounds to avoid index errors if 0 <= mx < map_size and 0 <= my < map_size: mp = my * map_size + mx if MAP[mp] == '#': dof = 8 # Draw debug ray to hit point pygame.draw.line(win, (255,255,0), (player_x, player_y), (rx, ry), 1) # Highlight hit wall tile for debugging pygame.draw.rect(win, (0,255,0), (mx * tile_size, my * tile_size, tile_size - 2, tile_size - 2)) else: rx += xo ry += yo dof += 1 else: # Ray went outside map bounds dof = 8
Additional Notes
- Pygame's coordinate system has y increasing downward, so the "up" and "down" ray checks are reversed compared to standard mathematical coordinates—this is handled in the angle logic above.
- Added bounds checking for
mxandmyto avoid index errors when rays go outside the map. - The function now casts all
casted_raysto cover your full FOV. - Debug drawing is included to visualize ray hits (you can remove these once everything works).
Once horizontal rays are working, you'll need to add vertical ray detection, then compare the distances of horizontal/vertical hits to draw the correct wall height (just like the original tutorial does).
内容的提问来源于stack exchange,提问作者dis_quake3_1

