用 opengl 写一个小游戏 (2)

本节代码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 的源代码

  • 5
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值