学习链接:中文---------英语原文
这一节主要讲粒子,还是比较好理解的,类似Unity的粒子系统,但这里是比较简单的。
ParticleGenerator.h:
#pragma once
//注意把Shader.h放在前面
#include "Shader.h"
#include
#include
#include "Texture2D.h"
#include "GameObject.h"
#include
struct Particle
{
glm::vec2 position, velocity;
glm::vec4 color;
GLfloat life;
Particle() :position(0.0f), velocity(0.0f), color(1.0f), life(0.0f) {}
};
class ParticleGenerator
{
public:
ParticleGenerator(Shader shader, Texture2D texture, GLuint amount);
void Update(GLfloat dt, GameObject& object, GLuint newParticles, glm::vec2 offset = glm::vec2(0.0f, 0.0f));
void Draw();
private:
std::vector<Particle> particles;
GLuint amount;
Shader shader;
Texture2D texture;
GLuint VAO;
void Init();
GLuint FirstUnusedParticle();
void RespawnParticle(Particle& particle, GameObject& object, glm::vec2 offset = glm::vec2(0.0f, 0.0f));
};
ParticleGenerator.cpp:
#include "ParticleGenerator.h"
ParticleGenerator::ParticleGenerator(Shader shader, Texture2D texture, GLuint amount) : shader(shader), texture(texture), amount(amount)
{
this->Init();
}
void ParticleGenerator::Update(GLfloat dt, GameObject& object, GLuint newParticles, glm::vec2 offset)
{
// Add new particles
for (GLuint i = 0; i < newParticles; ++i)
{
int unusedParticle = this->FirstUnusedParticle();
this->RespawnParticle(this->particles[unusedParticle], object, offset);
}
// Update all particles
for (GLuint i = 0; i < this->amount; ++i)
{
Particle& p = this->particles[i];
p.life -= dt; // reduce life
if (p.life > 0.0f)
{ // particle is alive, thus update
p.position -= p.velocity * dt;
p.color.a -= dt * 2.5;
}
}
}
//render all particles
void ParticleGenerator::Draw()
{
// Use additive blending to give it a 'glow' effect
glBlendFunc(GL_SRC_ALPHA, GL_ONE);
this->shader.Use();
for (Particle particle : this->particles)
{
if (particle.life > 0.0f)
{
this->shader.SetVector2f("offset", particle.position);
this->shader.SetVector4f("color", particle.color);
this->texture.Bind();
glBindVertexArray(this->VAO);
glDrawArrays(GL_TRIANGLES, 0, 6);
glBindVertexArray(0);
}
}
// Don't forget to reset to default blending mode
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
}
void ParticleGenerator::Init()
{
//set up mesh and attribute properties
GLuint VBO;
GLfloat particleQuad[] =
{
0.0f, 1.0f, 0.0f, 1.0f,
1.0f, 0.0f, 1.0f, 0.0f,
0.0f, 0.0f, 0.0f, 0.0f,
0.0f, 1.0f, 0.0f, 1.0f,
1.0f, 1.0f, 1.0f, 1.0f,
1.0f, 0.0f, 1.0f, 0.0f
};
glGenVertexArrays(1, &this->VAO);
glGenBuffers(1, &VBO);
glBindVertexArray(this->VAO);
//fill mesh buffer
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(particleQuad), particleQuad, GL_STATIC_DRAW);
//set mesh attributes
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, 4 * sizeof(GLfloat), (GLvoid*)0);
glBindVertexArray(0);
//create this->amount default particle instances
for (GLuint i = 0; i < this->amount; i++)
{
this->particles.push_back(Particle());
}
}
//stores the index of the last particle used
GLuint lastUsedParticle = 0;
GLuint ParticleGenerator::FirstUnusedParticle()
{
//First search from last used particle,this will usually return almost instantly
for (GLuint i = lastUsedParticle; i < this->amount; i++)
{
if (this->particles[i].life <= 0.0f)
{
lastUsedParticle = i;
return i;
}
}
//otherwise,do a linear search
for (GLuint i = 0; i < lastUsedParticle; i++)
{
if (this->particles[i].life <= 0.0f)
{
lastUsedParticle = i;
return i;
}
}
//all particles are taken,override the first one
lastUsedParticle = 0;
return 0;
}
void ParticleGenerator::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;
}
Game.cpp:
#include "ResourceManager.h"
#include "Game.h"
#include "SpriteRenderer.h"
#include "BallObject.h"
#include "ParticleGenerator.h"
SpriteRenderer* renderer;
GameObject* player;
BallObject* ball;
ParticleGenerator* particles;
Game::Game(GLuint w, GLuint h) :state(GAME_ACTIVE), keys(), width(w), height(h) {}
Game::~Game()
{
delete renderer;
delete player;
delete ball;
delete particles;
}
void Game::Init()
{
//load shader
ResourceManager::LoadShader("Breakout/Shaders/Sprite.vs", "Breakout/Shaders/Sprite.fs", nullptr, "sprite");
ResourceManager::LoadShader("Breakout/Shaders/Particle.vs", "Breakout/Shaders/Particle.fs", nullptr, "particle");
//config shader
glm::mat4 projection = glm::ortho(0.0f, static_cast<GLfloat>(this->width), static_cast<GLfloat>(this->height), 0.0f, -1.0f, 1.0f);
ResourceManager::GetShader("sprite").Use().SetInteger("image", 0);
ResourceManager::GetShader("sprite").SetMatrix4("projection", projection);
ResourceManager::GetShader("particle").Use().SetInteger("sprite", 0);
ResourceManager::GetShader("particle").SetMatrix4("projection", projection);
//load texture
ResourceManager::LoadTexture("Breakout/Images/background.jpg", GL_TRUE, "background");
ResourceManager::LoadTexture("Breakout/Images/block.png", GL_FALSE, "block");
ResourceManager::LoadTexture("Breakout/Images/block_solid.png", GL_FALSE, "block_solid");
ResourceManager::LoadTexture("Breakout/paddle.png", true, "paddle");//player
ResourceManager::LoadTexture("Breakout/face.png", true, "ball");//ball
ResourceManager::LoadTexture("Breakout/particle.png", true, "particle");//particle
//setup render control
renderer = new SpriteRenderer(ResourceManager::GetShader("sprite"));
particles = new ParticleGenerator(ResourceManager::GetShader("particle"), ResourceManager::GetTexture("particle"), 500);
//load levels
GameLevel one; one.Load("Breakout/Levels/1.lvl", this->width, this->height * 0.5);
GameLevel two; two.Load("Breakout/Levels/2.lvl", this->width, this->height * 0.5);
GameLevel three; three.Load("Breakout/Levels/3.lvl", this->width, this->height * 0.5);
GameLevel four; four.Load("Breakout/Levels/4.lvl", this->width, this->height * 0.5);
this->levels.push_back(one);
this->levels.push_back(two);
this->levels.push_back(three);
this->levels.push_back(four);
this->currentLevel = 0;
//player init
glm::vec2 playerPos = glm::vec2(this->width / 2 - PLAYER_SIZE.x / 2, this->height - PLAYER_SIZE.y);
player = new GameObject(playerPos, PLAYER_SIZE, ResourceManager::GetTexture("paddle"));
//ball init 原点位置是左上角
glm::vec2 ballPos = playerPos + glm::vec2(PLAYER_SIZE.x / 2 - BALL_RADIUS, -BALL_RADIUS * 2);
ball = new BallObject(ballPos, BALL_RADIUS, INITIAL_BALL_VELOCITY, ResourceManager::GetTexture("ball"));
particle
//particles = new ParticleGenerator(ResourceManager::GetShader("particle"), ResourceManager::GetTexture("particle"), 500);
//glGetError();
}
void Game::ProcessInput(GLfloat dt)
{
if (this->state == GAME_ACTIVE)
{
GLfloat velocity = PLAYER_VELOCITY * dt;
//移动挡板
if (this->keys[GLFW_KEY_A])
{
if (player->position.x >= 0)
{
player->position.x -= velocity;
if (ball->stuck)
ball->position.x -= velocity;
}
}
if (this->keys[GLFW_KEY_D])
{
if (player->position.x <= this->width - player->size.x)
{
player->position.x += velocity;
if (ball->stuck)
ball->position.x += velocity;
}
}
if (this->keys[GLFW_KEY_SPACE])
ball->stuck = false;
}
}
void Game::Update(GLfloat dt)
{
//update ball
ball->Move(dt, this->width);
//collision detection
this->DoCollisions();
//particle
particles->Update(dt, *ball, 2, glm::vec2(ball->radius / 2));
//球是否接触底部边界?
if (ball->position.y >= this->height)
{
this->ResetLevel();
this->ResetPlayer();
}
}
void Game::Render()
{
if (this->state == GAME_ACTIVE)
{
//绘制背景
renderer->DrawSprite(ResourceManager::GetTexture("background"), glm::vec2(0, 0), glm::vec2(this->width, this->height), 0.0f);
//绘制关卡
this->levels[this->currentLevel].Draw(*renderer);
//绘制玩家挡板
player->Draw(*renderer);
//绘制粒子
particles->Draw();
//绘制球
ball->Draw(*renderer);
}
}
Direction VectorDirection(glm::vec2 target)
{
glm::vec2 compass[] =
{
glm::vec2(0.0f,1.0f),
glm::vec2(1.0f,0.0f),
glm::vec2(0.0f,-1.0f),
glm::vec2(-1.0f,0.0f),
};
GLfloat max = 0.0f;
GLuint bestMatch = -1;
for (GLuint i = 0; i < 4; i++)
{
GLfloat dot = glm::dot(glm::normalize(target), compass[i]);
if (dot > max)
{
max = dot;
bestMatch = i;
}
}
return (Direction)bestMatch;
}
//碰撞检测::AABB
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;
}
//碰撞检测:方形和圆形
Collision 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 加上clampled这样就得到了碰撞箱上距离最近点closest
glm::vec2 closest = aabb_center + clamped;
//获得圆心center和最近点closest的矢量,并判断是否length<=radius
difference = closest - center;
if (glm::length(difference) < one.radius)
return std::make_tuple(GL_TRUE, VectorDirection(difference), difference);
else
return std::make_tuple(GL_FALSE, UP, glm::vec2(0, 0));
}
void Game::DoCollisions()
{
for (GameObject& box : this->levels[this->currentLevel].bricks)
{
if (!box.destroyed)
{
Collision collision = CheckCollision(*ball, box);
if (std::get<0>(collision))
{
if (!box.isSoild)
box.destroyed = GL_TRUE;
//碰撞处理
Direction dir = std::get<1>(collision);
glm::vec2 diff_vector = std::get<2>(collision);
if (dir == LEFT || dir == RIGHT)
{
ball->velocity.x = -ball->velocity.x;//反转水平速度
//重定位
GLfloat penetration = ball->radius - std::abs(diff_vector.x);
if (dir == LEFT)
ball->position.x += penetration;//球右移
else
ball->position.x -= penetration;//球左移
}
else//垂直方形碰撞
{
ball->velocity.y = -ball->velocity.y;//反转垂直速度
//重定位
GLfloat penetration = ball->radius - std::abs(diff_vector.y);
if (dir == UP)
ball->position.y -= penetration;//球上移
else
ball->position.y += penetration;//球下移
}
}
//玩家--球碰撞
Collision result = CheckCollision(*ball, *player);
if (!ball->stuck && std::get<0>(result))
{
//检查碰到了挡板的哪个位置,并根据碰撞到哪个位置改变速度
GLfloat centerBoard = player->position.x + player->size.x / 2;
GLfloat distance = (ball->position.x + ball->radius) - centerBoard;
GLfloat percentage = distance / (player->size.x / 2);
//依据结果移动
GLfloat strength = 2.0f;
glm::vec2 oldVelocity = ball->velocity;
ball->velocity.x = INITIAL_BALL_VELOCITY.x * percentage * strength;
//ball->velocity.y = -ball->velocity.y;
//解决粘板问题:保证最后的Y方向始终向上
ball->velocity.y = -1 * abs(ball->velocity.y);
//新的速度:新的归一化方向*原来速度的大小
ball->velocity = glm::normalize(ball->velocity) * glm::length(oldVelocity);
}
}
}
}
void Game::ResetLevel()
{
if (this->currentLevel == 0)
this->levels[0].Load("Breakout/Levels/1.lvl", this->width, this->height * 0.5f);
else if (this->currentLevel == 1)
this->levels[1].Load("Breakout/Levels/2.lvl", this->width, this->height * 0.5f);
else if (this->currentLevel == 2)
this->levels[1].Load("Breakout/Levels/3.lvl", this->width, this->height * 0.5f);
else if (this->currentLevel == 3)
this->levels[1].Load("Breakout/Levels/4.lvl", this->width, this->height * 0.5f);
}
void Game::ResetPlayer()
{
player->size = PLAYER_SIZE;
player->position = glm::vec2(this->width / 2 - PLAYER_SIZE.x / 2, this->height - PLAYER_SIZE.y);
ball->Reset(player->position + glm::vec2(PLAYER_SIZE.x / 2 - ball->radius, -(BALL_RADIUS * 2)), INITIAL_BALL_VELOCITY);
}
粒子顶点着色器:
#version 330 core
layout (location = 0) in vec4 vertex; //
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);
}
粒子片元着色器:
#version 330 core
in vec2 TexCoords;
in vec4 ParticleColor;
out vec4 color;
uniform sampler2D sprite;
void main()
{
color = (texture(sprite, TexCoords) * ParticleColor);
}```