桥梁是一个木板链条,每个木板只知道其上方和下方的木板。每一步,木板查看其邻居的沉降和倾斜,混合其自身的值(站在木板上增加沉降,偏离中心增加倾斜),并平滑到结果。邻居复制彼此的倾斜,稍微放大(×1.4),所以按下一个木板会在整个桥梁上产生波纹,尽管没有木板知道桥梁的其余部分存在。

if (bridge_link_down != noone) {
    _sink_affected += bridge_link_down.y_sink;
    _side_sink_affected += bridge_link_down.side_sink * 1.4;
    _total_connections ++;
}
// (同样,对于bridge_link_up)

var _inst = instance_place(x,y,par_character);
_sink_affected += 2.5 * (_inst != noone);
_total_connections ++;

y_sink = math_lerp_smooth(y_sink, _sink_affected / _total_connections, 0.15);

side_sink_real = _inst != noone ? (x - _inst.x) / ((bbox_right - bbox_left) / 2) : 0;

提示:math_lerp_smoothmath_angle_lerp_smooth 不是 GameMaker 的内置函数,它们是我的项目中的帮助函数。math_lerp_smooth(current, target, speed)current 移动到 target 的剩余距离的 fraction,每帧移动一次,校正了 delta 时间,以便在任何帧率下表现一致:

function math_lerp_smooth(_current, _target, _speed) {
    return lerp(_current, _target, 1 - power(1 - _speed, DeltaFrame));
}

math_angle_lerp_smooth 与之相同,但用于角度,因此它会沿着短边移动(350° → 10°会通过 0°,而不是通过 180°)。您可以使用 angle_difference() 创建一个。 如果您锁定在 60fps 上,普通 lerp(current, target, speed) 就足够了,跳过校正。

有趣的部分:让玩家骑跨桥梁而不是漂浮在上面。木板下方写入两个目标:一个倾斜角度和一个沉降。沉降包括偏离中心的倾斜表面额外的深度,所以站在倾斜木板的低边缘会比站在中心更深。

if (_inst != noone && _inst == global.Player_Instance) {
    var _tilt = clamp(side_sink * 10, -25, 25);
    _inst.plank_sink_target = y_sink - ((_inst.x - x) * dsin(_tilt));
    _inst.plank_tilt_target = _tilt;
}

在玩家 End Step 中,我会缓慢地向这些目标靠近,并且这些目标本身会缓慢地向零靠近:

plank_tilt        = math_angle_lerp_smooth(plank_tilt, plank_tilt_target, 5, 0.1);
plank_sink        = math_lerp_smooth(plank_sink, plank_sink_target, 0.1);
plank_tilt_target = math_angle_lerp_smooth(plank_tilt_target, 0, 5, 0.1);
plank_sink_target = math_lerp_smooth(plank_sink_target, 0, 0.1);

为什么会出现双重平滑?我的第一版直接追随木板的值,跑过桥梁意味着每个木板都会重新目标玩家,导致非常糟糕的抖动。缓慢地向目标靠近,并且目标本身会缓慢地向零靠近解决了这个问题,掉落桥梁意味着不会再写入新目标,所以所有内容都会放松到零,没有清理代码。

渲染的诡计是,因为我的玩家不是一个sprite。它是预组合的调色板表面加上一堆覆盖(轮廓,边缘光,冻结/湿/火状态效果,防御闪烁)。而不是旋转每个层次并希望它们会对齐,每个层次都会运行其绘制位置通过一个帮助函数来旋转它周围一个在玩家脚下的.pivot,并且添加沉降:

body_blit_transform = function(_corner_x, _corner_y) {
    if (plank_tilt == 0 && plank_sink == 0) {
        return [_corner_x, _corner_y, 0];
    }

    var
    _offset_x = _corner_x - x,
    _offset_y = _corner_y - (y + y_height),
    _cos = dcos(plank_tilt),
    _sin = dsin(plank_tilt);

    return [
        x + (_offset_x * _cos) + (_offset_y * _sin),
        y + y_height + plank_sink - (_offset_x * _sin) + (_offset_y * _cos),
        plank_tilt
    ];
}

每个 draw_surface_ext 使用 _blit[0], _blit[1] 为位置和 _blit[2] 为角度,所以整个组合的玩家会倾斜和沉降。早期的输出意味着在不在桥梁的情况下没有成本。