1、目录简介:
Util类:一些运动常量和工具方法的定义
GameResourceManager:缓存加载过的shader和texture,并提供通过key获取缓存的shader和texture
Shader:着色器编译
Texture:纹理加载
Sprite:渲染的基本单位,负责创建VAO、VBO,更新着色器中的变量值
GameObj:游戏中可渲染的基本单元,持有一些位置、速度、大小、纹理等信息
Level:游戏中的关卡类,包含关卡所需的所有类型的砖块数据
Game:核心类,继承自BaseGLView,负责管理关卡,初始化数据,更新纹理、检测碰撞以及处理碰撞。
GameBall:乒乓球类,包含了一些球的属性和一个move的方法。
2、游戏主要包含物体的渲染,更新、小球的移动、关卡的加载、碰撞检测、碰撞处理,我们其他的直接可以看源码很容易理解,这里主要分析一下碰撞检测和处理。
碰撞检测:
小球和屏幕边缘的碰撞比较简单:
水平方向上,当position.x小于0或者大于viewport的宽度时,反转一下水平方向上的速度即可,顶部当position.y小于0时,反转一下竖直方向上的速度,底部则不处理
小球和砖块的碰撞:
● AABB-AABB碰撞
我们脑子里首先浮现的方案是检测球和砖块的x坐标和y坐标,如果他们都有相交,则判断发生了碰撞,代码也比较简单实现,但这种方案有一个问题,就是我们的球是圆形的,这样导致常常会出现球碰撞了砖块但此时球精灵还没有接触到砖块:
所以我们采用AABB-圆碰撞更加合理。
● AABB-圆碰撞
如图,P是该球距离模型最近的点,我们只需要求的P点的坐标,然后减去球心C的坐标,得到它们的矢量差,然后拿他跟圆的半径比较,这样就可以判断是否发生了碰撞。
首先我们要获取C和B的矢量差D,即C点的坐标减去B点的坐标
接下来用AABB的半边长w、h来限制矢量D得到P点
什么是限制运算?:
限制运算把一个值限定在给定范围内并返回限制后的值
let clamped = GLKVector2Make(clamp(value: difference.x, maxValue: -aabb_half_extents.x, minValue: aabb_half_extents.x), clamp(value: difference.y, maxValue: -aabb_half_extents.y, minValue: aabb_half_extents.y))
可以看出,D=C-B,则以B为原点,限制在AABB内,则x超出被限制为-w大小,y没有超限为原值。得到P点坐标
C-B即最近距离, 将它与小球半径比对,得出是否碰撞的结果。
这样就完成了碰撞检测。
碰撞处理:
发生了碰撞之后,我们要做2件事
● 将球复位到刚与AABB接触的位置,即P点
● 使球以相反的方向弹回
复位: 为什么需要复位?如果不复位,小球会一直检测到碰撞而不停地反转速度,这会导致异常的场景。
我们只需要将球往反方向位移侵入量R,则可以复位到刚接触的位置。
侵入量R刚好等于我们上面检测函数中的difference与球半径的差,但是我怎么知道是复位x坐标还是y坐标呢,我怎么知道球是碰撞了AABB的哪一个边?我怎么知道反转水平还是竖直方向的速度?所以我们还需要知道球与AABB的碰撞方向。
碰撞方向: 还记得我们之前介绍向量的乘法时提到的点乘吗,单位向量的点乘积等于他们的夹角的余弦值,如果我们定义上下左右4个方向的单位向量,并将球的速度归一化,这样分别与4个方向相乘,取最大值,是不是就可以知道是从哪边发生的碰撞了。
得到碰撞方向后,我们可以复位球,也知道如何反转小球的速度,这样我们就能完全处理碰撞了。
挡板与球的碰撞则是简单采用了AABB-AABB碰撞。
总结:
碰撞算法是一个比较复杂的部分,一般游戏里的碰撞检测都是由引擎提供,而不是我们这样简单的算法,上面的算法存在缺陷,当球在一帧内穿过砖块时,就好像从未发生过碰撞,但在这里我们的主要目的还是熟悉opengl es 和一些简单的渲染知识,上面的算法已经足够了。游戏里很多地方还可以优化,比如添加计分板、粒子效果、添加道具等。