完整代码见:zaizai77/OpenGLTo2DGame: 基于OpenGL制作2D游戏
物体本身的数据来检测碰撞会很复杂,一半使用重叠在物体上的更简单的外形来检测。
AABB - AABB 碰撞
AABB代表的是轴对齐碰撞箱(Axis-aligned Bounding Box),碰撞箱是指与场景基础坐标轴(2D中的是x和y轴)对齐的长方形的碰撞外形。
获取左上角和右下角点的位置。
检查两个物体的水平边界是否重合以及垂直边界是否重合。如果水平边界和垂直边界都有重叠那么我们就检测到一次碰撞。
将这一概念转化为代码也是很直白的。我们对两个轴都检测是否重叠,如果都重叠就返回碰撞:
GLboolean CheckCollision(GameObject &one, GameObject &two) // AABB - AABB collision
{// x轴方向碰撞?bool collisionX = one.Position.x + one.Size.x >= two.Position.x &&two.Position.x + two.Size.x >= one.Position.x;// y轴方向碰撞?bool collisionY = one.Position.y + one.Size.y >= two.Position.y &&two.Position.y + two.Size.y >= one.Position.y;// 只有两个轴向都有碰撞时才碰撞return collisionX && collisionY;
}
这样子检测确实有用,但是不是非常准确
AABB - 圆碰撞检测
使用圆形碰撞对于圆球来说更合理
定义球的矢量和半径
我们会找到AABB上距离圆最近的一个点,如果圆到这一点的距离小于它的半径,那么就产生了碰撞。
GLboolean CheckCollision(BallObject &one, GameObject &two) // AABB - Circle collision
{// 获取圆的中心 glm::vec2 center(one.Position + one.Radius);// 计算AABB的信息(中心、半边长)glm::vec2 aabb_half_extents(two.Size.x / 2, two.Size.y / 2);glm::vec2 aabb_center(two.Position.x + aabb_half_extents.x, two.Position.y + aabb_half_extents.y);// 获取两个中心的差矢量glm::vec2 difference = center - aabb_center;glm::vec2 clamped = glm::clamp(difference, -aabb_half_extents, aabb_half_extents);// AABB_center加上clamped这样就得到了碰撞箱上距离圆最近的点closestglm::vec2 closest = aabb_center + clamped;// 获得圆心center和最近点closest的矢量并判断是否 length <= radiusdifference = closest - center;return glm::length(difference) < one.Radius;
}
粒子
一个微粒,从OpenGL的角度看就是一个总是面向摄像机方向且(通常)包含一个大部分区域是透明的纹理的小四边形。一个微粒本身主要就是一个精灵(sprite)
一个粒子通常有下面的属性:
struct Particle {glm::vec2 Position, Velocity;glm::vec4 Color;GLfloat Life;Particle() : Position(0.0f), Velocity(0.0f), Color(1.0f), Life(0.0f) { }
};
渲染粒子的着色器:
#shader vertex#version 330 core
layout(location = 0) in vec4 vertex; // <vec2 position, vec2 texCoords>out vec2 TexCoords;
out vec4 ParticleColor;uniform mat4 projection;
uniform vec2 offset;
uniform vec4 color;void main()
{float scale = 10.0f;TexCoords = vertex.zw;ParticleColor = color;gl_Position = projection * vec4((vertex.xy * scale) + offset, 0.0, 1.0);
}#shader fragment#version 330 core
in vec2 TexCoords;
in vec4 ParticleColor;
out vec4 color;uniform sampler2D sprite;void main()
{color = (texture(sprite, TexCoords) * ParticleColor);
}
粒子循环函数:
GLuint nr_new_particles = 2;
// Add new particles
for (GLuint i = 0; i < nr_new_particles; ++i)
{int unusedParticle = FirstUnusedParticle();RespawnParticle(particles[unusedParticle], object, offset);
}
// Update all particles
for (GLuint i = 0; i < nr_particles; ++i)
{Particle &p = particles[i];p.Life -= dt; // reduce lifeif (p.Life > 0.0f){ // particle is alive, thus updatep.Position -= p.Velocity * dt;p.Color.a -= dt * 2.5;}
}
GLuint lastUsedParticle = 0;
GLuint FirstUnusedParticle()
{// Search from last used particle, this will usually return almost instantlyfor (GLuint i = lastUsedParticle; i < nr_particles; ++i){if (particles[i].Life <= 0.0f){lastUsedParticle = i;return i;}}// Otherwise, do a linear searchfor (GLuint i = 0; i < lastUsedParticle; ++i){if (particles[i].Life <= 0.0f){lastUsedParticle = i;return i;}}// Override first particle if all others are alivelastUsedParticle = 0;return 0;
}
如果是最后一种情况(返回0),就意味着你粒子的生命值太长了,在每一帧里面需要产生更少的粒子,或者你只是没有保留足够的粒子,
更新消亡的粒子:
void RespawnParticle(Particle &particle, GameObject &object, glm::vec2 offset)
{GLfloat random = ((rand() % 100) - 50) / 10.0f;GLfloat rColor = 0.5 + ((rand() % 100) / 100.0f);particle.Position = object.Position + random + offset;particle.Color = glm::vec4(rColor, rColor, rColor, 1.0f);particle.Life = 1.0f;particle.Velocity = object.Velocity * 0.1f;
}
glBlendFunc(GL_SRC_ALPHA, GL_ONE);
particleShader.Use();
for (Particle particle : particles)
{if (particle.Life > 0.0f){particleShader.SetVector2f("offset", particle.Position);particleShader.SetVector4f("color", particle.Color);particleTexture.Bind();glBindVertexArray(particleVAO);glDrawArrays(GL_TRIANGLES, 0, 6);glBindVertexArray(0);}
}
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
这里将glBlendFunc中的因子替换,将 GL_ONE_MINUS_SRC_ALPHA 替换为 GL_ONE
,产生粒子叠加在一起的平滑的发热效果(比如火焰粒子)。
参考:碰撞检测 - LearnOpenGL CN