用 opengl 写一个小游戏 (2)

编程入门 行业动态 更新时间:2024-10-09 10:21:42

用 opengl 写一个<a href=https://www.elefans.com/category/jswz/34/1769974.html style=小游戏 (2)"/>

用 opengl 写一个小游戏 (2)

本节代码github
在这一节可以先写出这个游戏的雏形

如图,从启示旗子点到达出口点即可。而且当从底部掉落时玩家并不会死亡,而是在顶部相应的位置掉落。而如果顶部相应位置也有墙壁时玩家将会死亡。

    • 游戏场景
    • player
    • 文字渲染
    • 碰撞检测和处理
    • 渲染

游戏场景

构建游戏场景需要各种游戏对象,我们可以抽象出一个类
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;//记录启示点的位置和大小,方便后面渲染 playerglm::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;// 需要用到的 shaderShader 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)
{// 加载和设置 shaderthis->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;}// 生成 textureGLuint 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);} elsereturn 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 的源代码

更多推荐

用 opengl 写一个小游戏 (2)

本文发布于:2024-03-23 20:49:09,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/34/1742667.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:小游戏   opengl

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!