本节代码github
在这一节可以先写出这个游戏的雏形
如图,从启示旗子点到达出口点即可。而且当从底部掉落时玩家并不会死亡,而是在顶部相应的位置掉落。而如果顶部相应位置也有墙壁时玩家将会死亡。
游戏场景
构建游戏场景需要各种游戏对象,我们可以抽象出一个类
GameObject.h
#ifndef GAME_GAMEOBJECT_H
#define GAME_GAMEOBJECT_H
#define GLEW_STATIC
#include <GL/glew.h>
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include "../utility/texture.h"
#include "spriteRenderer.h"
enum Type {
NOTHING,
BRICK,
END,
BEGIN
};
class GameObject {
public:
//颜色
glm::vec3 color;
//位置,大小,速度
glm::vec2 position, size, velocity;
//纹理
Texture2D sprite;
//旋转
GLfloat rotation;
//类型
Type type;
GameObject();
GameObject(Texture2D sprite, glm::vec2 position, glm::vec2 size, glm::vec3 color = glm::vec3(1.0f));
//渲染
virtual void draw(SpriteRenderer &renderer);
};
#endif //GAME_GAMEOBJECT_H
GameObject.cpp
#include "gameObject.h"
GameObject::GameObject()
: color(1.0f), position(0, 0), size(1, 1), sprite(),rotation(0.0f) {}
GameObject::GameObject(Texture2D sprite, glm::vec2 position, glm::vec2 size, glm::vec3 color)
: color(color), position(position), size(size), sprite(sprite),rotation(0.0f),velocity(0.0f,0.0f),type(NOTHING) {}
void GameObject::draw(SpriteRenderer &renderer) {
renderer.drawSprite(this->sprite, this->position, this->size, this->rotation, this->color);
}
一个场景中有许多的游戏对象,每个对象都渲染一遍是十分麻烦的。我们可以把场景抽象成一个文本。比如本节开头图片里的场景可以抽象成
1 1 1 1 1 1 1 0 0 1 0 0 1 1 1 1
0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 1 0 0 0 1 0 0
2 0 0 0 0 0 0 0 0 1 0 0 0 0 0 3
1 1 1 1 1 1 1 0 0 1 0 0 1 1 1 1
0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
其中 1 代表砖块, 3 代表起点, 2 代表终点, 而 0 代表什么都没有。现在我们需要一个类来读取这个文件来渲染场景。
GameLevel.h
#ifndef GAME_GAMELEVEL_H
#define GAME_GAMELEVEL_H
#include <vector>
#include <iostream>
#define GLEW_STATIC
#include <GL/glew.h>
#include <glm/glm.hpp>
#include "gameObject.h"
#include "../utility/texture.h"
#include "../resourceManager.h"
#include "spriteRenderer.h"
class GameLevel {
public:
//一个游戏物体对象的向量
std::vector<GameObject> bricks;
//记录启示点的位置和大小,方便后面渲染 player
glm::vec2 pos, size;
GameLevel() {}
//加载游戏场景文件
void load(const GLchar *file, GLuint LevelWidth, GLuint levelHeight);
//渲染场景
void draw(SpriteRenderer &renderer);
private:
void init(std::vector<std::vector<GLuint>> titleData, GLuint levelWidth, GLuint levelHeight);
};
#endif //GAME_GAMELEVEL_H
GameLevel.cpp
#include "gameLevel.h"
#include <fstream>
#include <sstream>
//加载文件并存入数组中
void GameLevel::load(const GLchar *file, GLuint levelWidth, GLuint levelHeight) {
std::string line;
std::ifstream fstream(file);
GLuint tileCode;
std::vector<std::vector<GLuint>> tileData;
if (fstream) {
while (std::getline(fstream, line)) {
std::istringstream sstream(line);
std::vector<GLuint> row;
while (sstream >> tileCode)
row.push_back(tileCode);
tileData.push_back(row);
};
if (tileData.size() > 0)
init(tileData, levelWidth, levelHeight);
}
}
//渲染场景
void GameLevel::draw(SpriteRenderer &renderer) {
for (GameObject &tile : this->bricks)
tile.draw(renderer);
}
//初始化
void GameLevel::init(std::vector<std::vector<GLuint>> titleData, GLuint levelWidth, GLuint levelHeight) {
//根据文件计算出每个物体的长和宽
GLuint hight = titleData.size();
GLuint width = titleData[0].size();
GLfloat unitHeight = levelHeight / hight;
GLfloat unitWidth = levelWidth / static_cast<GLfloat>(width);
//根据类型的不同来构造不同的对象
for (int i = 0; i < hight; i++) {
for (int j = 0; j < width; j++) {
if (titleData[i][j] == 1) {
glm::vec2 pos(unitWidth * j, unitHeight * i);
glm::vec2 size(unitWidth, unitHeight);
GameObject obj(ResourceManager::getTexture("brick"), pos, size);
obj.type = BRICK;
this->bricks.push_back(obj);
}
if (titleData[i][j] == BEGIN || titleData[i][j] == END) {
glm::vec2 pos(unitWidth * j, unitHeight * i);
glm::vec2 size(unitWidth, unitHeight);
GameObject obj(ResourceManager::getTexture("flag"), pos, size);
obj.type = (Type) titleData[i][j];
this->bricks.push_back(obj);
if (titleData[i][j] == BEGIN) {
this->pos = glm::vec2(pos.x, pos.y);
this->size = glm::vec2(size.x / 5 * 4, size.y / 3);
}
}
}
}
}
player
我们还需要一个 player 对象来让玩家来操纵。
player.h
#ifndef GAME_PLAYER_H
#define GAME_PLAYER_H
#define GLEW_STATIC
#include <GL/glew.h>
#include <GLFW/glfw3.h>
#include "gameObject.h"
class Player : public GameObject {
public:
//左右移动时的加速度
GLfloat acceleration=200.0f;
//是否处于跳跃状态中
GLboolean isJumped = GL_FALSE;
//是否左移中
GLboolean isAed = GL_TRUE;
//是否右移中
GLboolean isDed = GL_TRUE;
//左移速度
GLfloat velocitya = 0;
//右移速度
GLfloat velocityd = 0;
//重力加速度
GLfloat gravity;
//跳跃给予物体向上的速度
GLfloat jumpVelocity;
Player():GameObject(),gravity(1.0f){}
Player(Texture2D sprite, glm::vec2 position, glm::vec2 size, glm::vec3 color = glm::vec3(1.0f),GLfloat gravity=200.0f)
:GameObject(sprite, position, size, color),gravity(gravity){}
//对象位置的更新
void move(GLfloat dt);
//按键反馈
void processInput(GLfloat dt,GLboolean* keys);
};
#endif //GAME_PLAYER_H
player.cpp
#include "player.h"
void Player::move(GLfloat dt) {
//重力加速度
if (this->velocity.y < 200.f) {
this->velocity.y += gravity * dt;
} else {
this->velocity.y = 200;
}
this->position += this->velocity * dt;
//左移和右移的界限
if (this->position.x <= 0.0f) {
this->velocity.x = -this->velocity.x;
this->position.x = 0.0f;
} else if (this->position.x + this->size.x >= 800) {
this->velocity.x = -this->velocity.x;
this->position.x = 800 - this->size.x;
}
if (this->position.y >= 600) {
this->position.y = 0.0f;
}
}
void Player::processInput(GLfloat dt, GLboolean *keys) {
//跳跃
if (keys[GLFW_KEY_SPACE]) {
if (isJumped == GL_TRUE) {
this->velocity.y = -jumpVelocity;
isJumped = GL_FALSE;
}
}
//左移和右移,但在左移或右移只能执行其一。
if (isAed == true) {
if (keys[GLFW_KEY_A]) {
velocitya -= acceleration * dt;
if (velocitya < -100.0f)
velocitya = -100.0f;
if (this->position.x >= 0) {
this->position.x += velocitya * dt;
}
isDed = GL_FALSE;
} else {
isDed = GL_FALSE;
velocitya += acceleration * dt;
if (velocitya > 0.0f) {
velocitya = 0.0f;
isDed = GL_TRUE;
}
if (this->position.x >= 0) {
this->position.x += velocitya * dt;
}
}
}
if (isDed == GL_TRUE) {
if (keys[GLFW_KEY_D]) {
velocityd += acceleration * dt;
if (velocityd > 100.0f)
velocityd = 100.0f;
if (this->position.x >= 0) {
this->position.x += velocityd * dt;
}
isAed = GL_FALSE;
} else {
isAed = GL_FALSE;
velocityd -= acceleration* dt;
if (velocityd < 0.0f) {
velocityd = 0.0f;
isAed = GL_TRUE;
}
if (this->position.x >= 0) {
this->position.x += velocityd * dt;
}
}
}
}
文字渲染
spriteRenderer.h
#ifndef GAME_TEXTRENDER_H
#define GAME_TEXTRENDER_H
#include <map>
#define GLEW_STATIC
#include <GL/glew.h>
#include <glm/glm.hpp>
#include "../utility/texture.h"
#include "../utility/shader.h"
struct Character {
GLuint TextureID; // ID 字形纹理
glm::ivec2 Size; // 字形尺寸
glm::ivec2 Bearing; // 向上的偏移量
GLuint Advance; // 水平偏移量
};
class TextRenderer
{
public:
std::map<GLchar, Character> Characters;
// 需要用到的 shader
Shader TextShader;
TextRenderer(GLuint width, GLuint height);
// 加载字形文件
void load(std::string font, GLuint fontSize);
// 渲染指定的文本
void renderText(std::string text, GLfloat x, GLfloat y, GLfloat scale, glm::vec3 color = glm::vec3(1.0f));
private:
GLuint VAO, VBO;
};
#endif //GAME_TEXTRENDER_H
spriteRenderer.cpp
#include <iostream>
#include <glm/gtc/matrix_transform.hpp>
#include <ft2build.h>
#include FT_FREETYPE_H
#include "textRenderer.h"
#include "../resourceManager.h"
TextRenderer::TextRenderer(GLuint width, GLuint height)
{
// 加载和设置 shader
this->TextShader = ResourceManager::loadShader("../shaders/text.vs.glsl", "../shaders/text.frag.glsl", nullptr, "text");
this->TextShader.use().SetMatrix4("projection", glm::ortho(0.0f, static_cast<GLfloat>(width), static_cast<GLfloat>(height), 0.0f), GL_TRUE);
this->TextShader.SetInteger("text", 0);
glGenVertexArrays(1, &this->VAO);
glGenBuffers(1, &this->VBO);
glBindVertexArray(this->VAO);
glBindBuffer(GL_ARRAY_BUFFER, this->VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(GLfloat) * 6 * 4, NULL, GL_DYNAMIC_DRAW);
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, 4 * sizeof(GLfloat), 0);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindVertexArray(0);
}
void TextRenderer::load(std::string font, GLuint fontSize)
{
// 清除已加载的字形
this->Characters.clear();
// FreeType 库的初始化
FT_Library ft;
if (FT_Init_FreeType(&ft))
std::cout << "ERROR::FREETYPE: Could not init FreeType Library" << std::endl;
FT_Face face;
if (FT_New_Face(ft, font.c_str(), 0, &face))
std::cout << "ERROR::FREETYPE: Failed to load font" << std::endl;
// 设置字体大小
FT_Set_Pixel_Sizes(face, 0, fontSize);
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
for (GLubyte c = 0; c < 128; c++) // lol see what I did there
{
if (FT_Load_Char(face, c, FT_LOAD_RENDER))
{
std::cout << "ERROR::FREETYTPE: Failed to load Glyph" << std::endl;
continue;
}
// 生成 texture
GLuint texture;
glGenTextures(1, &texture);
glBindTexture(GL_TEXTURE_2D, texture);
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
);
// 设置 texture 选项
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 = {
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<GLchar, Character>(c, character));
}
glBindTexture(GL_TEXTURE_2D, 0);
FT_Done_Face(face);
FT_Done_FreeType(ft);
}
void TextRenderer::renderText(std::string text, GLfloat x, GLfloat y, GLfloat scale, glm::vec3 color)
{
this->TextShader.use();
this->TextShader.SetVector3f("textColor", color);
glActiveTexture(GL_TEXTURE0);
glBindVertexArray(this->VAO);
std::string::const_iterator c;
for (c = text.begin(); c != text.end(); c++)
{
Character ch = Characters[*c];
GLfloat xpos = x + ch.Bearing.x * scale;
GLfloat ypos = y + (this->Characters['H'].Bearing.y - ch.Bearing.y) * scale;
GLfloat w = ch.Size.x * scale;
GLfloat h = ch.Size.y * scale;
GLfloat vertices[6][4] = {
{ xpos, ypos + h, 0.0, 1.0 },
{ xpos + w, ypos, 1.0, 0.0 },
{ xpos, ypos, 0.0, 0.0 },
{ xpos, ypos + h, 0.0, 1.0 },
{ xpos + w, ypos + h, 1.0, 1.0 },
{ xpos + w, ypos, 1.0, 0.0 }
};
glBindTexture(GL_TEXTURE_2D, ch.TextureID);
glBindBuffer(GL_ARRAY_BUFFER, this->VBO);
glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(vertices), vertices);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glDrawArrays(GL_TRIANGLES, 0, 6);
x += (ch.Advance >> 6) * scale;
}
glBindVertexArray(0);
glBindTexture(GL_TEXTURE_2D, 0);
}
碰撞检测和处理
这里将物体都抽象成长方形来处理,进行碰撞检测
//碰撞方向
enum Direction {
UP,
RIGHT,
DOWN,
LEFT
};
//碰撞元组
typedef std::tuple<GLboolean, Direction, glm::vec2> Collision;
//判断碰撞方向
Direction VectorDirection(GameObject &one, GameObject &two) {
GLuint best_match = -1;
if ((one.position.y + one.size.y - 5) < two.position.y)
best_match = UP;
else if (one.position.y > (two.position.y + two.size.y - 5))
best_match = DOWN;
else if (one.position.x > two.position.x)
best_match = RIGHT;
else if (one.position.x < two.position.x)
best_match = LEFT;
return (Direction) best_match;
}
Collision checkCollision(GameObject &one, GameObject &two) {
// 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;
if (collisionX && collisionY) {
glm::vec2 difference = two.position - one.position;
return std::make_tuple(GL_TRUE, VectorDirection(one, two), difference);
} else
return std::make_tuple(GL_FALSE, UP, glm::vec2(0, 0));
}
碰撞处理
void Game::doCollisions() {
player->isJumped = GL_FALSE;
//使 player 与每个对象进行碰撞检测
for (GameObject &box : this->levels[this->level].bricks) {
Collision collision = checkCollision(*player, box);
//有碰撞时,并根据不同情况进行处理
if (std::get<0>(collision)) {
if (box.type == BRICK) {
if (player->position.y == 0) {
this->deathNumber++;
reset();
player->position = this->levels[level].pos;
return;
}
Direction dir = std::get<1>(collision);
glm::vec2 diff_vector = std::get<2>(collision);
if (dir == UP) {
player->velocity.y = 0;
player->position.y = box.position.y - player->size.y - 0.1f;
player->isJumped = GL_TRUE;
}
if (dir == DOWN) {
if (player->velocity.y < 0)
player->velocity.y = 0;
}
if (dir == RIGHT) {
if (player->velocity.x < 0)
player->velocity.x = 0;
player->position.x = box.position.x + box.size.x + 0.1f;
}
if (dir == LEFT) {
if (player->velocity.x > 0)
player->velocity.x = 0;
player->position.x = box.position.x - player->size.x - 0.1f;
}
}
//到达终点时
if (box.type == END) {
this->level++;
if (this->level >= this->levels.size()) {
state = GAME_WIN;
this->level = 0;
}
reset();
player->position = this->levels[level].pos;
}
}
}
}
渲染
void Game::render() {
std::stringstream sDeathNumber;
sDeathNumber << this->deathNumber;
if (this->state == GAME_ACTIVE) {
ResourceManager::getShader("sprite").use();
renderer->drawSprite(ResourceManager::getTexture("background"), glm::vec2(0, 0),
glm::vec2(this->width, this->height));
this->levels[this->level].draw(*renderer);
player->draw(*renderer);
std::stringstream sLevel;
sLevel << this->level;
Text->renderText("level:" + sLevel.str(), 5.0f, 5.0f, 1.0f, glm::vec3(0.0f, 0.0f, 1.0f));
std::stringstream sFps;
sFps << this->fps;
Text->renderText("fps:" + sFps.str(), 5.0f, 30.0f, 1.0f, glm::vec3(0.0f, 0.0f, 1.0f));
Text->renderText("The number of death: " + sDeathNumber.str(), 500.0f, 5.0f, 1.0f, glm::vec3(0.0f, 0.0f, 1.0f));
}
if (this->state == GAME_MENU) {
ResourceManager::getShader("sprite").use();
renderer->drawSprite(ResourceManager::getTexture("background"), glm::vec2(0, 0),
glm::vec2(this->width, this->height));
Text->renderText("Press ENTER to start", 250.0f, height / 2, 1.0f);
}
if (this->state == GAME_WIN) {
ResourceManager::getShader("sprite").use();
renderer->drawSprite(ResourceManager::getTexture("background"), glm::vec2(0, 0),
glm::vec2(this->width, this->height));
Text->renderText("You win!", 250.0f, height / 2, 1.0f);
Text->renderText("The number of death: " + sDeathNumber.str(), 250.0f, height / 2 - 30.0f, 1.0f,
glm::vec3(0.0f, 0.0f, 1.0f));
Text->renderText("Press R to restart ", 250.0f, height / 2 - 60.0f, 1.0f, glm::vec3(0.0f, 0.0f, 1.0f));
}
}
需要的纹理 shader 和一些细节可参照 github 的源代码