文章目录
- 特殊效果
- 数据结构
- 生成逻辑
- 更新逻辑
- 文本渲染
- 类结构
- 构造函数
- 加载函数
- 渲染函数
特殊效果
为提高游戏的趣味性,在游戏中提供了六种特殊效果。
数据结构
PowerUp
类只存储存活数据,实际逻辑在游戏代码中通过Type字段来区分执行
class PowerUp : public GameObject
{
public:
// powerup state
std::string Type;
float Duration;
bool Activated;
// constructor
PowerUp(std::string type, glm::vec3 color, float duration, glm::vec2 position, Texture2D texture)
: GameObject(position, POWERUP_SIZE, texture, color, VELOCITY), Type(type), Duration(duration), Activated() { }
};
Game中存在容器存储目前存活的PowerUp对象
std::vector<PowerUp> PowerUps;
生成逻辑
当玩家消灭一个方块后,按固定概率随机生成或不生成一个随机能力的砖块。
//Game::DoCollision()
// destroy block if not solid
if (!box.IsSolid)
{
box.Destroyed = true;
this->SpawnPowerUps(box);
}
生成策略
bool ShouldSpawn(unsigned int chance)
{
unsigned int random = rand() % chance;
return random == 0;
}
void Game::SpawnPowerUps(GameObject& block)
{
if (ShouldSpawn(75)) // 1 in 75 chance
this->PowerUps.push_back(PowerUp("speed", glm::vec3(0.5f, 0.5f, 1.0f), 0.0f, block.Position, ResourceManager::GetTexture("powerup_speed")));
if (ShouldSpawn(75))
this->PowerUps.push_back(PowerUp("sticky", glm::vec3(1.0f, 0.5f, 1.0f), 20.0f, block.Position, ResourceManager::GetTexture("powerup_sticky")));
if (ShouldSpawn(75))
this->PowerUps.push_back(PowerUp("pass-through", glm::vec3(0.5f, 1.0f, 0.5f), 10.0f, block.Position, ResourceManager::GetTexture("powerup_passthrough")));
if (ShouldSpawn(75))
this->PowerUps.push_back(PowerUp("pad-size-increase", glm::vec3(1.0f, 0.6f, 0.4), 0.0f, block.Position, ResourceManager::GetTexture("powerup_increase")));
if (ShouldSpawn(15)) // Negative powerups should spawn more often
this->PowerUps.push_back(PowerUp("confuse", glm::vec3(1.0f, 0.3f, 0.3f), 15.0f, block.Position, ResourceManager::GetTexture("powerup_confuse")));
if (ShouldSpawn(15))
this->PowerUps.push_back(PowerUp("chaos", glm::vec3(0.9f, 0.25f, 0.25f), 15.0f, block.Position, ResourceManager::GetTexture("powerup_chaos")));
}
更新逻辑
生成完后会在Update中调用以下函数下落更新,并每帧检查是否与玩家发生碰撞
不同状态下能力内部标志位变化:
Destoryed | Activated | |
---|---|---|
下落状态 | false | false |
与玩家碰撞后 | true | true |
倒计时结束后 | true | false |
Destroyed决定是否要渲染其图像,Activated决定是否要启用其计时器,最后的状态则决定了是否要将其移除出列表
刚碰撞时根据Type字段执行附加能力的逻辑,倒计时结束后执行取消附加能力的逻辑。流程完毕后移除出数组
//绘制
for (PowerUp& powerUp : this->PowerUps)
if (!powerUp.Destroyed)
powerUp.Draw(*Renderer);
//检查碰撞
for (PowerUp& powerUp : this->PowerUps)
{
if (!powerUp.Destroyed)
{
if (powerUp.Position.y >= this->Height)
powerUp.Destroyed = true;
if (CheckCollision(*Player, powerUp))
{ // collided with player, now activate powerup
ActivatePowerUp(powerUp);
powerUp.Destroyed = true;
powerUp.Activated = true;
}
}
}
//启用能力
void ActivatePowerUp(PowerUp& powerUp)
{
if (powerUp.Type == "speed")
{
Ball->Velocity *= 1.2;
}
else if (powerUp.Type == "sticky")
{
Ball->Sticky = true;
Player->Color = glm::vec3(1.0f, 0.5f, 1.0f);
}
else if (powerUp.Type == "pass-through")
{
Ball->PassThrough = true;
Ball->Color = glm::vec3(1.0f, 0.5f, 0.5f);
}
else if (powerUp.Type == "pad-size-increase")
{
Player->Size.x += 50;
}
else if (powerUp.Type == "confuse")
{
if (!Effects->Chaos)
Effects->Confuse = true; // only activate if chaos wasn't already active
}
else if (powerUp.Type == "chaos")
{
if (!Effects->Confuse)
Effects->Chaos = true;
}
}
//每帧更新,轮询何时撤销能力
void Game::UpdatePowerUps(float dt)
{
for (PowerUp& powerUp : this->PowerUps)
{
powerUp.Position += powerUp.Velocity * dt;
if (powerUp.Activated)
{
powerUp.Duration -= dt;
if (powerUp.Duration <= 0.0f)
{
// remove powerup from list (will later be removed)
powerUp.Activated = false;
// deactivate effects
if (powerUp.Type == "sticky")
{
if (!IsOtherPowerUpActive(this->PowerUps, "sticky"))
{ // only reset if no other PowerUp of type sticky is active
Ball->Sticky = false;
Player->Color = glm::vec3(1.0f);
}
}
else if (powerUp.Type == "pass-through")
{
if (!IsOtherPowerUpActive(this->PowerUps, "pass-through"))
{ // only reset if no other PowerUp of type pass-through is active
Ball->PassThrough = false;
Ball->Color = glm::vec3(1.0f);
}
}
else if (powerUp.Type == "confuse")
{
if (!IsOtherPowerUpActive(this->PowerUps, "confuse"))
{ // only reset if no other PowerUp of type confuse is active
Effects->Confuse = false;
}
}
else if (powerUp.Type == "chaos")
{
if (!IsOtherPowerUpActive(this->PowerUps, "chaos"))
{ // only reset if no other PowerUp of type chaos is active
Effects->Chaos = false;
}
}
}
}
}
// Remove all PowerUps from vector that are destroyed AND !activated (thus either off the map or finished)
// Note we use a lambda expression to remove each PowerUp which is destroyed and not activated
this->PowerUps.erase(std::remove_if(this->PowerUps.begin(), this->PowerUps.end(),
[](const PowerUp& powerUp) { return powerUp.Destroyed && !powerUp.Activated; }
), this->PowerUps.end());
}
文本渲染
文本渲染部分依赖于一个库FreeType,在项目中只拿来读取ttf生成像素数据,然后记录在自定义的Character结构中。
// 宽度、高度、左上角偏移、水平距离
struct Character
{
unsigned int TextureID;
glm::ivec2 Size;
glm::ivec2 Bearing;
unsigned int Advance;
};
创建文字渲染器类TextRenderer来负责文字的渲染
类结构
class TextRenderer
{
public:
// 预处理记录的需要的字符内容(借助FreeType)
std::map<char, Character> Characters;
// 文字渲染Shader,就是渲染一个四边形,然后采样纹理并渲染上去
Shader TextShader;
TextRenderer(unsigned int width, unsigned int height);
void Load(std::string font, unsigned int fontSize);
void RenderText(std::string text, float x, float y, float scale, glm::vec3 color = glm::vec3(1.0f));
private:
unsigned int VAO, VBO;
};
构造函数
构造函数中负责初始化shader参数,初始化VAO和VBO,但VBO的具体顶点数据会在绘制时动态计算出来
TextRenderer::TextRenderer(unsigned int width, unsigned int height)
{
// load and configure shader
this->TextShader = ResourceManager::LoadShader("res/shaders/text.vs", "res/shaders/text.frag", nullptr, "text");
this->TextShader.SetMatrix4("projection", glm::ortho(0.0f, static_cast<float>(width), static_cast<float>(height), 0.0f), true);
this->TextShader.SetInteger("text", 0);
// configure VAO/VBO for texture quads
glGenVertexArrays(1, &this->VAO);
glGenBuffers(1, &this->VBO);
//绑定VAO,VBO
glBindVertexArray(this->VAO);
glBindBuffer(GL_ARRAY_BUFFER, this->VBO);
//填充VBO数据
glBufferData(GL_ARRAY_BUFFER, sizeof(float) * 6 * 4, NULL, GL_DYNAMIC_DRAW);
//规划VBO数据布局
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, 4 * sizeof(float), 0);
//解绑
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindVertexArray(0);
}
加载函数
外部提供字体文件(.ttf)路径和字体大小,内部利用FreeType将前128字符转换为使用Characters存储
void TextRenderer::Load(std::string font, unsigned int fontSize)
{
// 如果之前已经Load过,清除
this->Characters.clear();
// 初始化并加载 FreeType library
FT_Library ft;
if (FT_Init_FreeType(&ft))
std::cout << "ERROR::FREETYPE: Could not init FreeType Library" << std::endl;
// 将字符加载为face
FT_Face face;
if (FT_New_Face(ft, font.c_str(), 0, &face))
std::cout << "ERROR::FREETYPE: Failed to load font" << std::endl;
// 设置加载字体的大小,这里是设置为 fontSize 参数指定的大小。第一个参数为 0,表示不限制宽度,第二个参数是目标字体大小
FT_Set_Pixel_Sizes(face, 0, fontSize);
// 关闭了 OpenGL 中的字节对齐限制,使得字节数据可以按 1 字节对齐,这样可以避免在纹理生成时出现内存填充问题
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
// 通过循环加载前 128 个 ASCII 字符。
for (GLubyte c = 0; c < 128; c++)
{
// FT_Load_Char 用来加载字符的字形信息,如果加载失败,则输出错误信息并跳过该字符
if (FT_Load_Char(face, c, FT_LOAD_RENDER))
{
std::cout << "ERROR::FREETYTPE: Failed to load Glyph" << std::endl;
continue;
}
// 为每个字符生成一个纹理
unsigned int texture;
glGenTextures(1, &texture);
glBindTexture(GL_TEXTURE_2D, texture);
//使用 GL_RED 格式来存储单通道的灰度数据
glTexImage2D(
GL_TEXTURE_2D,
0,
GL_RED,
face->glyph->bitmap.width,
face->glyph->bitmap.rows,
0,
GL_RED,
GL_UNSIGNED_BYTE,
face->glyph->bitmap.buffer
);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
//将原始数据转换为Character数组
Character character = {
texture,
glm::ivec2(face->glyph->bitmap.width, face->glyph->bitmap.rows),
glm::ivec2(face->glyph->bitmap_left, face->glyph->bitmap_top),
face->glyph->advance.x
};
Characters.insert(std::pair<char, Character>(c, character));
}
// 清理资源
glBindTexture(GL_TEXTURE_2D, 0);
FT_Done_Face(face);
FT_Done_FreeType(ft);
}
渲染函数
- 使用并设置文字shader,使用当前的VAO
- 遍历所有提供的字符,在Characters中取出对应的数据
- 计算位置长宽,更新顶点缓存,绑定记录的纹理
- 绘制完后偏移光标位置
void TextRenderer::RenderText(std::string text, float x, float y, float scale, glm::vec3 color)
{
// activate corresponding render state
this->TextShader.Use();
this->TextShader.SetVector3f("textColor", color);
glActiveTexture(GL_TEXTURE0);
glBindVertexArray(this->VAO);
// iterate through all characters
std::string::const_iterator c;
for (c = text.begin(); c != text.end(); c++)
{
Character ch = Characters[*c];
// 计算位置和长宽
float xpos = x + ch.Bearing.x * scale;
float ypos = y + (this->Characters['H'].Bearing.y - ch.Bearing.y) * scale;
float w = ch.Size.x * scale;
float h = ch.Size.y * scale;
// 为每个字符更新对应的VBO缓存
float vertices[6][4] = {
{ xpos, ypos + h, 0.0f, 1.0f },
{ xpos + w, ypos, 1.0f, 0.0f },
{ xpos, ypos, 0.0f, 0.0f },
{ xpos, ypos + h, 0.0f, 1.0f },
{ xpos + w, ypos + h, 1.0f, 1.0f },
{ xpos + w, ypos, 1.0f, 0.0f }
};
// 绑定指定的纹理
glBindTexture(GL_TEXTURE_2D, ch.TextureID);
// update content of VBO memory
glBindBuffer(GL_ARRAY_BUFFER, this->VBO);
glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(vertices), vertices);
glBindBuffer(GL_ARRAY_BUFFER, 0);
// render quad
glDrawArrays(GL_TRIANGLES, 0, 6);
// 更新光标位置
x += (ch.Advance >> 6) * scale; // bitshift by 6 to get value in pixels (1/64th times 2^6 = 64)
}
glBindVertexArray(0);
glBindTexture(GL_TEXTURE_2D, 0);
}