【问题标题】:Problem with recognising where a ray in raycaster intersects a wall along the horizontal axis识别光线投射器中的光线沿水平轴与墙壁相交的位置的问题
【发布时间】:2022-09-22 23:22:03
【问题描述】:

我正在用 pygame 在 python 中制作射线投射器。 https://youtu.be/gYRrGTC7GtA?t=407 问题是 cast_rays 函数。我已经注释掉了我之前使用的方法。我使用了上面用 C 编写的视频并将其改编为 Python。 我想在上面的视频中使用光线投射算法,因为它会被投射然后逐像素检查。我尝试只检查水平线并检查那里是否有墙。但是,它不起作用。

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)
    #pygame.draw.line(win, (0,255,0), (player_x, player_y), (player_x + math.cos(player_angle) * 50,  player_y + math.sin(player_angle) * 50) ,3)
    #pygame.draw.line(win, (0,255,0), (player_x, player_y), (player_x + math.cos(player_angle - HALF_FOV) * 50,  player_y + math.sin(player_angle - HALF_FOV) * 50) ,3)
    #pygame.draw.line(win, (0,255,0), (player_x, player_y), (player_x + math.cos(player_angle + HALF_FOV) * 50,  player_y + math.sin(player_angle + HALF_FOV) * 50) ,3)
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)
  • 在第一个中,我们逐像素检查,而在第二个中,我们检查每条水平线,这使其更快。

标签: python pygame raycasting


【解决方案1】:

计算射线向量:

rx = math.cos(player_angle)
ry = math.sin(player_angle)

计算地图中玩家位置的行列:

map_x = player_x // tile_size
map_y = player_y // tile_size

计算相对于瓦片和方向的初始位置:

t_max_x = player_x/tile_size - map_x
if rx > 0:
    t_max_x = 1 - t_max_x
t_max_y = player_y/tile_size - map_y
if ry > 0:
    t_max_y = 1 - t_max_y

向前走,直到您离开地图或在循环中撞到一个块。这是性能关键部分,即实际的光线投射循环:

while True:
    if ry == 0 or t_max_x < t_max_y * abs(rx / ry):
        side = 'x'
        map_x += 1 if rx > 0 else -1
        t_max_x += 1
        if map_x < 0 or map_x >= map_size:
            break
    else:
        side = 'y'
        map_y += 1 if ry > 0 else -1
        t_max_y += 1
        if map_x < 0 or map_y >= map_size:
            break
    if MAP[int(map_x * map_size + map_y)] == "#":
        break

计算命中块边缘的位置:

if side == 'x':
    x = (map_x + (1 if rx < 0 else 0)) * tile_size
    y = player_y + (x - player_x) * ry / rx
else:
    y = (map_y + (1 if ry < 0 else 0)) * tile_size
    x = player_x + (y - player_y) * rx / ry

绘制射线:

pygame.draw.line(win, (0,255, 0), (player_x, player_y), (x, y))

完整示例:

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)
    #pygame.draw.line(win, (0,255,0), (player_x, player_y), (player_x + math.cos(player_angle) * 50,  player_y + math.sin(player_angle) * 50) ,3)
    #pygame.draw.line(win, (0,255,0), (player_x, player_y), (player_x + math.cos(player_angle - HALF_FOV) * 50,  player_y + math.sin(player_angle - HALF_FOV) * 50) ,3)
    #pygame.draw.line(win, (0,255,0), (player_x, player_y), (player_x + math.cos(player_angle + HALF_FOV) * 50,  player_y + math.sin(player_angle + HALF_FOV) * 50) ,3)

def cast_rays():
    rx = math.cos(player_angle)
    ry = math.sin(player_angle)
    map_x = player_x // tile_size
    map_y = player_y // tile_size

    t_max_x = player_x/tile_size - map_x
    if rx > 0:
        t_max_x = 1 - t_max_x
    t_max_y = player_y/tile_size - map_y
    if ry > 0:
        t_max_y = 1 - t_max_y

    while True:
        if ry == 0 or t_max_x < t_max_y * abs(rx / ry):
            side = 'x'
            map_x += 1 if rx > 0 else -1
            t_max_x += 1
            if map_x < 0 or map_x >= map_size:
                break
        else:
            side = 'y'
            map_y += 1 if ry > 0 else -1
            t_max_y += 1
            if map_x < 0 or map_y >= map_size:
                break
        if MAP[int(map_x * map_size + map_y)] == "#":
            break

    if side == 'x':
        x = (map_x + (1 if rx < 0 else 0)) * tile_size
        y = player_y + (x - player_x) * ry / rx
    else:
        y = (map_y + (1 if ry < 0 else 0)) * tile_size
        x = player_x + (y - player_y) * rx / ry

    pygame.draw.line(win, (0,255, 0), (player_x, player_y), (x, y))
        

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)

我实际上在一个简单的体素光线追踪器中使用了相同的算法(只是 3D 版本):
Voxel Ray Tracing

【讨论】:

  • @dis_quake3_1 接受 -> 不接受 -> 接受 -> 不接受 ...投票和接受完全是自愿的。我不在乎你是否接受或投票。你试图引起我的注意或勒索我是完全没有用的。我不会回应它。
  • 对不起,我想因为我的问题没有完全纠正。我以为我明白了,但中途我有一个疑问,因此,我认为我不应该接受它。
  • 我现在应该接受吗?
猜你喜欢
  • 2022-01-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-09-16
  • 2023-03-10
相关资源
最近更新 更多