跳到正文

重力与平台碰撞 ​

平台游戏的手感来自两个数:每帧累加的重力,和只在落地时允许触发的跳跃。

速度积分 ​

每帧先给 dy 加重力,再用 dy 改位置。顺序反了,起跳第一帧就没有速度;一帧只积分一次。

lua
player.dy = player.dy + GRAVITY * dt
player.y = player.y + player.dy * dt

Y 轴向下为正,所以起跳速度是负数。-300 除以 900 得到上升时间约 0.33 秒,来回滞空约 0.67 秒。先加再乘与先乘再加,结果差一帧的重力。

常量值作用
TILE_SIZE16格子边长
GRAVITY900每秒增加的下落速度
PLAYER_JUMP_VELOCITY-300起跳初速度
PLAYER_WALK_SPEED60水平移动速度

跳跃的触发条件 ​

跳跃只在 idle 或 walking 状态响应空格,wasPressed 保证按住不会连跳。

lua
if love.keyboard.wasPressed('space') then
    self.player:changeState('jump')
end

function PlayerJumpState:enter(params)
    gSounds['jump']:play()
    self.player.dy = PLAYER_JUMP_VELOCITY
end

enter 里赋速度,update 里只做积分。dy >= 0 时上升结束,切到 falling,两个状态共用同一帧动画。enter 只执行一次,按住不会重复触发。

用 dy == 0 判断落地并不可靠:跳到最高点时 dy 也接近 0。改成状态判断后,只有真正站在地面上的状态才接受起跳,状态判断比数值判断稳。

点采样代替 AABB ​

地图是静态网格,不必对每个 tile 做 AABB。把角色边缘的像素坐标除以 TILE_SIZE,直接取出那一个格子。只查角色周围,开销固定。

lua
local tileBottomLeft = map:pointToTile(x + 1, y + height)
local tileBottomRight = map:pointToTile(x + width - 1, y + height)
方向采样位置用途
头顶左右上角各一撞块、进洞
脚下左右下角各一落地、走出边缘
左侧左上与左下向左卡墙
右侧右上与右下向右卡墙
读输入施加重力按速度移动采样边缘格子修正位置切换状态

左右各取一点,因为角色可能横跨两个格子:只查左边,右半边会陷进墙里。每个方向两个点,一帧最多四次查询。采样点都在精灵内部。

两个点都不可站立才算悬空,所以脚下判断要同时看左右。两个点正好覆盖角色宽度。

位置修正 ​

碰撞后不能只把速度清零,还要把角色推回格子外,否则下一帧仍然重叠。

lua
if tileLeft:collidable() or tileRight:collidable() then
    player.dy = 0
    player.y = (tileLeft.y - 1) * TILE_SIZE - player.height
end

tile.y 是行号,从 1 开始;乘 TILE_SIZE 前减一,才是格子上边缘的像素坐标。再减角色高度,让脚底正好贴上边缘。

侧向同理,用 tile.x 反推:向左撞墙时 x = (tile.x - 1) * TILE_SIZE + tile.width。撞地面改 y 和 dy,撞墙只改 x。修正用格子坐标,不用速度反推,两轴一起改会把角色弹飞。

手感调整 ​

初速度和重力决定最大跳高:h = v² / (2g),代入 300² / (2 × 900) 得到 50 像素,约 3 格。这决定了砖块能放在哪:离地超过三格就顶不到。公式假设初速与重力恒定。

参数调大调小
重力下落更快更飘
跳跃初速跳更高更矮
水平速度跑更快更稳

按住空格跳得高、轻点跳得矮,是常见的手感补丁。在上升阶段松开按键时截断速度即可。

lua
function love.keyreleased(key)
    if key == 'space' and player.dy < 0 then
        player.dy = player.dy * 0.5
    end
end

只在 keyreleased 里截断一次,不能写在 update 里,否则按住不放时每帧都在减速。截断比例越小,轻点跳得越矮,截断只发生一次。这段逻辑只处理上升期。

边界情况 ​

课件没有给下落速度设上限,dy 会一直增大。单帧位移超过 16 像素后,点采样可能从墙的一侧跳到另一侧,直接穿过薄地板。终端速度能防穿透。

补法是限制最大 dy,或把一帧拆成多个子步,每步位移不超过 TILE_SIZE / 2。单向平台还要在 dy > 0 时判定,并要求移动前的脚底在平台顶面之上。累加器要限制补帧次数。

dt 随帧率波动,同一段跳跃在不同机器上高度会略有差异。固定成 1/60 秒的物理步长,结果就稳定了,也顺带解决高速穿透;渲染仍按真实时间。

  • 采样点落在精灵内
  • 格子里有 tile
  • collidable 返回真
  • 只改一个轴
  • 清零对应速度
  • 最后切状态