在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、总结
最终我们决定角色可以无限上升(体现幽灵飘的特性),而不是开始的时候设想的类似马里奥的跳跃感觉。
所以我最初写的跳跃逻辑没有了用武之地,不过好在最后游戏的呈现效果不错。