Python 现代开发学习笔记

(十)从轴向重叠区间推导接触时间

1. 先只看一个坐标轴

一个轴上,玩家区间从 a0a_0 到 a1a_1,障碍从 b0b_0 到 b1b_1。玩家相对位移为 d,在比例 t 时,两端变成 a0+d⋅ta_0+d\cdot t、a1+d⋅ta_1+d\cdot t。重叠需要同时满足:

a1+d⋅t>b0,a0+d⋅t<b1.a_1+d\cdot t>b_0,\qquad a_0+d\cdot t<b_1.

两侧恰好接触的位置分别给出 (b0-a1)/d 与 (b1-a0)/d。d 的正负会改变哪一个先到,排序后就得到进入与离开的比例。d 为 0 时,两段一直重叠就覆盖整步,一直分离就没有重叠时间。

# 项目文件:dodge_project/src/dodgegame/geometry.py
# 用浮点矩形保存逻辑位置;仅共边时没有正面积重叠。
from math import inf


def overlaps(a, b):
    ax, ay, aw, ah = a
    bx, by, bw, bh = b
    return ax < bx + bw and ax + aw > bx and ay < by + bh and ay + ah > by


def axis_interval(a_min, a_max, b_min, b_max, delta):
    if delta == 0:
        if a_max <= b_min or a_min >= b_max:
            return None
        return -inf, inf
    first = (b_min - a_max) / delta
    second = (b_max - a_min) / delta
    return min(first, second), max(first, second)

用刚才的 y 轴数据代入:玩家为 [110, 134],旧障碍为 [60, 72],相对位移 d 为 −100。

计算 值 对应位置
(60−134)/(−100) 0.74 玩家另一端离开障碍区间
(72−110)/(−100) 0.38 玩家这一端进入障碍区间
排序后的时间段 (0.38, 0.74) 中间有正长度的重叠时间
把相对运动代入两次接触的位置,排序得到y轴重叠时间0.38至0.74

2. 两个轴在同一时间重叠

矩形碰撞要求 x、y 同时重叠。分别取得两个轴的时间段,再与本步 [0, 1] 取交集:

# 续写同一文件:dodge_project/src/dodgegame/geometry.py
def collision_time(a, b, dx, dy):
    # 把 b 固定,将两者相对位移 dx、dy 加在 a 上,寻找本步的重叠时间。
    if overlaps(a, b):
        return 0.0
    ax, ay, aw, ah = a
    bx, by, bw, bh = b
    horizontal = axis_interval(ax, ax + aw, bx, bx + bw, dx)
    vertical = axis_interval(ay, ay + ah, by, by + bh, dy)
    if horizontal is None or vertical is None:
        return None
    enter = max(horizontal[0], vertical[0], 0.0)
    leave = min(horizontal[1], vertical[1], 1.0)
    # 只有一个接触时刻而没有重叠区间,例如擦过角点,不算撞上。
    return enter if enter < leave else None

进入时刻取两个轴中较晚的进入位置,离开时刻取较早的离开位置。enter < leave 表示中间确实有一段重叠;只在一个角点碰到、随后立刻分开时,两者相等,不当作撞上。

在竖直穿透例子中,x 轴一直重叠,y 轴从 0.38 到 0.74 重叠,因此第一次接触在 0.38。若同时向右移动 200,x 轴在 0.2 已经分离,而 y 轴到 0.38 才进入,两个时间段没有交集,就不碰撞。

现在把这个结果交回同一份输入核对:

# 项目文件:demos/tunneling.py
# 障碍从上方跨到下方,两个端点都未重叠;相对位移计算找回途中接触。
import pygame

from dodgegame.geometry import collision_time

player = pygame.Rect(100, 110, 48, 24)
old = pygame.Rect(100, 60, 40, 12)
new = old.move(0, 100)
print("前后两次", player.colliderect(old), player.colliderect(new))
when = collision_time(tuple(player), tuple(old), 0, -100)
print("首次接触比例", f"{when:.2f}")
print("接触时障碍y", f"{old.y + 100 * when:.1f}")

输出首次接触比例 0.38,接触时障碍 y 为 98.0。此时障碍底部是 98 + 12 = 110,恰好到达玩家上边,与手算一致。

对照两个轴重叠时间有交集和没有交集的两条轨迹,接触比例由相同时段推导

游戏在每个逻辑步把坐标从旧位置沿直线推进到新位置。算到本步会撞上时,统一推进到第一次接触的位置,再进入结束状态,所以画面会停在碰到障碍的地方。

笔记正文与全部题解 · Ctrl / ⌘ K