You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于Pygame的Python光线投射器:水平轴墙体交点识别故障排查

Pygame Ray Caster: Horizontal Wall Intersection Detection Failing

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_size and my = ry * tile_size are backwards—you need to divide by tile size to get grid columns/rows, not multiply.
  • mp = my * tile_size makes no sense for a 1D map index. The correct formula is row * map_size + col, where row is the y-grid position and col is 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 mx and my to avoid index errors when rays go outside the map.
  • The function now casts all casted_rays to 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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.27 14:43:13