在2D游戏中使用简单的碰撞函数实现角色移动

在这学期我们寝室共同制作的2D游戏项目中,我仔细研究了2D游戏角色的操作逻辑。

在我们的项目中,Player 类是玩家类。这个类中的 move( ) 函数负责玩家移动。

玩家移动需要考虑四个方向:跳跃。向右移动、向左移动、重力。
另外,我们还要考虑碰撞。

1、move( ) 函数部分

我将 move( ) 函数分为三个部分:

  • 左右移动
  • 跳跃
  • 重力作用下的向下移动

分别在move函数中调用这三个移动函数,角色就可以实现移动功能。

1.1、左右移动函数

左右移动是最简单的部分。我是这样实现的:

  • 创建两个临时的变量 x‘、y’ ;将玩家坐标 x、y 保存。
  • 进行判断
    • 如果同时按下 A、D 按键,不进行任何操作。
    • 只按 A 不按 D ,如果玩家x坐标在窗口范围内;更改玩家x坐标使其向左移动。
    • 只按 D 不按 A ,如果玩家x坐标在窗口范围内;更改玩家x坐标使其向右移动。
  • 调用碰撞函数(后面会详细说)检测碰撞,如果碰撞就将玩家位置改为 x'、y‘。

1.2、跳跃函数

跳跃比较复杂,我经过思考后,决定使用状态机的思想实现跳跃。

为Player类创建一个 int 变量表示跳跃状态,Player的状态有下面三种:

  • 状态0:处于实体上
    如果玩家按下空格,游戏就要作出反应,将状态切换为1
  • 状态1:跳跃上升中
    此状态说明玩家正在按下空格。
    处于此状态下时,如果检测到玩家没有按下空格,则立即将状态切换成状态2
  • 状态2:正在下落
    此时重力部分正在起作用。
    处于此状态时,如果调用表面检测函数检测到Player正在处于实体上,就将状态改为1。
    下落时玩家按下空格不会有反应,不会跳跃。

跳跃函数需要计算按下空格时,每帧将Player向上移动多远的距离。
但是跳跃一般是非线性的。
这意味着我们不能只知道两帧之间的时间差 Δt,我们还需要知道玩家按下空格的时间 st。

st 只在状态1中有用,且每次跳跃刚开始的时候都是0,因此我们需要在状态切换的时候清空 st。

st 还能用来判断按下空格时间是否达到最大跳跃时间。
这样能够避免一直按下空格一直向上运动。

在跳跃函数的开头,我们要检测并切换当前Player状态。

  • 当前状态为0且按下空格:状态变为1,清空 st。
  • 当前状态为1且没有按下空格:状态变为2,清空 st。
  • 当前状态为1且 st 已经达到了最大跳跃时间:状态变为2,清空 st。
  • 当前状态为2且在实体上方表面:状态变为0,清空 st。

随后进行操作:

  • 状态0:处于实体上
    • 无操作
  • 状态1:跳跃上升中
    • 更新空格按下时长 st += Δt
    • 创建两个临时的变量 x‘、y’ ;将玩家坐标 x、y 保存。
    • 计算应当上升多少像素。
    • 更改Player的 y 值。
    • 调用碰撞函数检测碰撞,如果碰撞就返回x'、y'的位置。
  • 状态2:正在下落
    • 无操作

1.3、重力函数

重力部分比较简单。

  • 创建两个临时的变量 x‘、y’ ;将玩家坐标 x、y 保存。
  • 检查跳跃状态:
    • 如果处于上升状态就直接return
    • 如果不处于上升状态,更改 Player 的 y 坐标使其向下移动。
  • 调用碰撞函数(后面会详细说)检测碰撞,如果碰撞就将玩家位置改为 x'、y‘。

考虑到一个问题,如果有这样的情况:
如果每帧重力令 Player 下降2个像素,而 Player 现在正处于实体的一个像素上方;
这样每帧调用碰撞函数都会检测到碰撞,那么 Player 就会永远悬空实体一个像素。

解决方法我想到两个:

  • 更改逻辑,使得碰撞函数能够检测重叠距离,如果发生重叠可以只移动重叠距离即可,不需要回到原来位置。
  • (代码量少但是效率低)当重力函数检测到碰撞的时候,尝试将物体向下移动一个像素,再检测碰撞。

2、碰撞

一个函数负责检测碰撞(即 Player 与其他实体是否有重叠)。
另外还有一个函数负责检测 Player 是否在实体表面。

下面的函数负责检测两个 GameObject 之间是否有碰撞:

bool CheckCollision(GameObject& one, GameObject& two) 
    // 碰撞检测
{
    // collision x-axis?
    bool collisionX = one.Position.x + one.Size.x >= two.Position.x &&
        two.Position.x + two.Size.x >= one.Position.x;
    // collision y-axis?
    bool collisionY = one.Position.y + one.Size.y >= two.Position.y &&
        two.Position.y + two.Size.y >= one.Position.y;
    // collision only if on both axes
    return collisionX && collisionY;
}

由于本项目中,Player类与其他实体都是GameObject类的子类。
因此写一个函数,令Player与其他所有的实体(例如砖块等)检测一遍碰撞即可。

3、总结

最终我们决定角色可以无限上升(体现幽灵飘的特性),而不是开始的时候设想的类似马里奥的跳跃感觉。

所以我最初写的跳跃逻辑没有了用武之地,不过好在最后游戏的呈现效果不错。

posted @ 2022-11-13 21:52  IDEA_W  阅读(438)  评论(0编辑  收藏  举报