跳转至

10-单向碰撞检测和重力模拟

1 玩家的水平移动

首先在Player中定义速度跑动速度常量

protected:
    const float run_velocity = 0.55f;       //跑动速度

之后在on_update方法中在direction!=0时,计算水平移动的位置
float distance = direction * run_velocity * delta;
on_run(distance);

我们希望把玩家跑动的具体逻辑抽象为函数,方便在后续过程中进行统一管理
所以在Player中定义on_run方法来实现移动

virtual void on_run(float distance)
{
    position.x += distance;
}

2 重力模拟

在整个游戏场景中的物体始终都在收到重力加速度的牵引,并且有着竖直方向上加速的重力。

我们在Player类中定义重力常量,以及速度矢量。

protected:
    const float gravity = 1.6e-3f;
    Vector2 velocity;

void move_and_collide(int delta)
{
    velocity.y += gravity * delta;
    position += velocity * (float)delta;
}

在on_update中调用这个函数

3 单向平台碰撞

由于玩家可以从平台下访跳跃到平台上,所以和平台的碰撞我们只需要在玩家下坠时,也就是玩家速度向下时进行检测就可以了。

由于需要获取场景中所有的平台对象,所以先引入头文件和外部变量

#include "platform.h"

extern std::vector<Platform> platform_list;

if( velocity.y > 0)
{
    for(const Platform& platform : platform_list) {
        const Platform::CollisionShape &shape = platform.shape;
    }
}

这时我们遇到了一个问题,即玩家的碰撞外形还没有被定义。

我们已经定义了玩家位置的二维向量,那么只需要再定义一个size的二维向量,就能实现玩家的矩形碰撞箱了。

先检测水平方向上是否发生碰撞,如果

\[ x_{max}-x_{min} \le 矩形宽度 +线段长度 \]

则说明,两个形状在水平方向上发生了重合(碰撞)


Pasted image 20260303201558.png

而当碰撞线的y值坐标位于矩形碰撞体的y值范围内,则说明在竖直方向上发生了碰撞


Pasted image 20260303201811.png

bool is_collide_x = (std::max(position.x + size.x, shape.right) - std::min(position.x, shape.left)) 
        <= (size.x + shape.right - shape.left);
bool is_collide_y = shape.y >= position.y && shape.y <= position.y + size.y;

检测到发生碰撞,我们就要进行后续操作了。即对玩家的坐标进行修正。
我们可以直接把玩家坐标修正到平台上方吗?不可以。让我们考虑下面这种情况:
当玩家向上跳起,但是只有身体的一部分穿过平台,然后开始下落,此时我们检测到它与平台发生碰撞。如果我们把它设置到平台上方,就会看到:刚刚下落的玩家被瞬移到了平台上方,这不是我们想看到的。

为了应对这种情况。我们需要确保只有玩家整个身体都穿过了平台,随后开始下落时,才能判断玩家和平台发生碰撞。所以说我们应该判断上一帧玩家位于平台上方,并且这一帧满足发生碰撞的条件,才会执行坐标修正的逻辑。

if (is_collide_x && is_collide_y) {
    float delta_pos_y = velocity.y * delta;
    float last_tick_foot_pos_y = position.y - delta_pos_y + size.y;
    if(last_tick_foot_pos_y <= shape.y) {
        position.y = shape.y - size.y;
        velocity.y = 0;

        break;
    }
}

在完成这部分内容后,我们不要忘记在角色子类的构造函数中,对玩家尺寸size变量的赋值.两种角色的尺寸都如下所示:

size.x = 96, size.y = 96;

为了看到单向碰撞检测的完整效果,我们还需要实现玩家跳跃的逻辑。
首先定义跳跃速度常量

const float jump_velocity = -0.85f;     // 跳跃速度

我们类比on_run方法,定义on_jump方法。
virtual void on_jump()
{
    velocity.y += jump_velocity;    
}

在on_input方法中,读取并处理跳跃按键。在 P1 的 W 按下,以及 P2 的上方向键按下时,调用on_jump()方法

case 'W':
    on_jump();
    break;
...
case VK_UP:
    on_jump();
    break;

但是现在,玩家可以“左脚踩右脚”一直跳跃飞上天。所以我们需要给跳跃次数添加限制。我们想让玩家角色重新回到地面上时,才可以在此执行跳跃的逻辑。
为了简单起见,我们可以当竖直方向速度为0时才允许跳跃。

virtual void on_jump()
{
    if (velocity.y != 0) {
        return;
    }
    velocity.y += jump_velocity;
}

这只是一个偷懒的做法,并不完美。因为理论上在最顶端时,玩家的竖直速度可能也为0。不过玩家竖直方向的速度与重力有关,有一定跨度,很可能始终无法让竖直速度为0;

评论

如果你已登录 GitHub,就可以直接在这里评论。