🐙

MineCraftもどきを作ってみた! GLFW GLAD GLMで一から作るゲーム開発その1

2024/11/16に公開

環境

mac

macだとOpenGLの制限やGLMがうまく見つかんなかったりするから手動でやっていきます

GLADここからGIPをダウンロードして
作業デレクトリで解凍

目次

MakeFileの作成
..
├── Class
│   └── Link.hpp <=4
├── CmakeLists.txt <= 1
├── Data
│   └── CubeData.csv <=5
├── build
│   └── CMakeCache.txt
├── library
│   └── glad
│       ├── CmakeLists.txt <= 2
│       ├── include
│       │   ├── KHR
│       │   │   └── khrplatform.h
│       │   └── glad
│       │       └── glad.h
│       └── src
│           └── glad.c
└── src
    └── main.cpp <= 3

1

目次の1の部分です

cmake
CmakeList
cmake_minimum_required(VERSION 3.12)

# プロジェクト名の設定
project(advent_gl)

# C++11の設定
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

# 必須ライブラリの存在チェック
find_package(glfw3 REQUIRED)

# GLMのパスを手動で指定する
find_path(GLM_INCLUDE_DIR glm/glm.hpp PATHS /opt/homebrew/include /opt/homebrew/Cellar/glm/1.0.1/include)

# GLMが見つかった場合、インクルードディレクトリを追加
if (GLM_INCLUDE_DIR)
    include_directories(${GLM_INCLUDE_DIR})
else()
    message(FATAL_ERROR "GLM not found")
endif()

# glad関係
include_directories(library/glad/include)
add_subdirectory(library/glad)

# コンパイルオプション
add_compile_options(-O2 -Wall)

# 実行ファイルの指定
add_executable(advent_gl src/main.cpp)

# 実行ファイルにリンクするライブラリの指定
target_link_libraries(advent_gl glad glfw ${CMAKE_DL_LIBS})


cmake

目次の2の部分です

CmakeList
cmake_minimum_required(VERSION 3.12)

# gladという名前でCMakeに認識させるライブラリを作成
# 静的ライブラリとしてコンパイル。含まれるソースを指定。
add_library(glad STATIC
    src/glad.c
)
main.cpp

目次の3の部分です

main.cpp
#include "../Class/Link.hpp"
#include <glm/glm.hpp>
#include <fstream>
#include <sstream>
#include <string>
#include <vector>
#include <iostream>

// CSVからCubeDataを読み込む関数
std::vector<CubeData> loadCubesFromCSV(const std::string& filePath) {
    std::ifstream ifs(filePath);
    if (!ifs.is_open()) {
        std::cerr << "Error: Unable to open file for reading." << std::endl;
        return {};
    }

    std::vector<CubeData> cubes;
    std::string line;
    while (std::getline(ifs, line)) {
        std::istringstream ss(line);
        std::string value;

        glm::vec3 position;
        glm::vec3 color;
        bool isSolid;

        // position.x
        std::getline(ss, value, ',');
        position.x = std::stof(value);

        // position.y
        std::getline(ss, value, ',');
        position.y = std::stof(value);

        // position.z
        std::getline(ss, value, ',');
        position.z = std::stof(value);

        // color.x
        std::getline(ss, value, ',');
        color.x = std::stof(value);

        // color.y
        std::getline(ss, value, ',');
        color.y = std::stof(value);

        // color.z
        std::getline(ss, value, ',');
        color.z = std::stof(value);

        // isSolid
        std::getline(ss, value, ',');
        isSolid = (std::stoi(value) == 1);

        cubes.push_back({ position, color, isSolid });
    }
    return cubes;
}

int main() {
    // CSVからCubeDataを読み込む
    std::string filePath = "/Users/riookabe/Desktop/main_project/OpenGL/Game-dev/GameDevNo.1/Data/CubeData.csv";
    std::vector<CubeData> initialCubes = loadCubesFromCSV(filePath);

    // 読み込んだデータでCubeWorldを初期化
    CubeWorld world(0, initialCubes); // `0` は他の初期化パラメータを想定

    glfwSetWindowUserPointer(world.getWindow(), &world);

    while (!glfwWindowShouldClose(world.getWindow())) {
        float currentFrame = glfwGetTime();
        deltaTime = currentFrame - lastFrame;
        lastFrame = currentFrame;

        world.Update(deltaTime);
        world.Render();
        glfwSwapBuffers(world.getWindow());
        glfwPollEvents();
    }

    return 0;
}
Link.hpp

目次の4の部分です

Link.hpp
#ifndef LINK_HPP
#define LINK_HPP

#include <cstdlib>
#include <glad/glad.h>
#include <GLFW/glfw3.h>
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>
#include <iostream>
#include <iterator>
#include <vector>
#include <math.h>

const unsigned int WINDOW_WIDTH = 1440;
const unsigned int WINDOW_HEIGHT = 810;

// カメラの位置と向きを管理する変数
glm::vec3 cameraPosition(0.0f, 0.0f, 3.0f);
glm::vec3 cameraFront(0.0f, 0.0f, -1.0f);
glm::vec3 cameraUp(0.0f, 1.0f, 0.0f);
float lastX = WINDOW_WIDTH / 2.0f;
float lastY = WINDOW_HEIGHT / 2.0f;
float yaw = -90.0f, pitch = 0.0f;
bool firstMouse = true;

float deltaTime = 0.0f, lastFrame = 0.0f;
float moveSpeed = 5.0f;
float sensitivity = 0.05f;

struct CubeData {
    glm::vec3 position;
    glm::vec3 color;
    bool drawWireframe;
    bool isSolid; // 新しいメンバーを追加
};

class CubeWorld {
public:
    //std::vector<CubeData> debug;
    CubeWorld(int type, const std::vector<CubeData>& initialCubes = {}) {
        //debug = initialCubes;
        if (!glfwInit()) {
            std::cerr << "Failed to initialize GLFW" << std::endl;
            return;
        }
        glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
        glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
        glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);

        window = glfwCreateWindow(WINDOW_WIDTH, WINDOW_HEIGHT, "Cube World", nullptr, nullptr);
        if (window == nullptr) {
            std::cerr << "Failed to create GLFW window" << std::endl;
            glfwTerminate();
            return;
        }
        glfwMakeContextCurrent(window);
        glfwSetWindowUserPointer(window, this);
        glfwSetKeyCallback(window, keyHandlerWrapper);

        if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) {
            std::cerr << "Failed to initialize GLAD" << std::endl;
            return;
        }

        glEnable(GL_DEPTH_TEST);
        glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED);
        glfwSetCursorPosCallback(window, mouseCallback);
        glfwSetMouseButtonCallback(window, mouseButtonCallback);

        shaderProgram = createShaderProgram();

        glGenVertexArrays(1, &VAO);
        glGenBuffers(1, &VBO);
        glGenBuffers(1, &EBO);

        glBindVertexArray(VAO);
        glBindBuffer(GL_ARRAY_BUFFER, VBO);
        glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
        glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);

        glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)0);
        glEnableVertexAttribArray(0);
        glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)(3 * sizeof(float)));
        glEnableVertexAttribArray(1);

        viewLoc = glGetUniformLocation(shaderProgram, "view");
        projectionLoc = glGetUniformLocation(shaderProgram, "projection");
        modelLoc = glGetUniformLocation(shaderProgram, "model");

        if (type == 0) {
            glClearColor(0.53f, 0.81f, 0.92f, 1.0f);
        } else {
            glClearColor(0.8f, 0.2f, 0.2f, 1.0f);
        }

        // Add the initial cubes to the world
        for (const CubeData& cube : initialCubes) {
            cubes.push_back(cube);
            std::cout << "Position: ("
                      << cube.position.x << ", "
                      << cube.position.y << ", "
                      << cube.position.z << ")" << std::endl;
        }
    }

    ~CubeWorld() {
        glDeleteVertexArrays(1, &VAO);
        glDeleteBuffers(1, &VBO);
        glDeleteBuffers(1, &EBO);
        glDeleteProgram(shaderProgram);
        glfwDestroyWindow(window);
        glfwTerminate();
    }

    void AddCube(const glm::vec3& position, const glm::vec3& color, bool drawWireframe) {
        cubes.push_back({position, color, drawWireframe});
        std::cout << "add" << std::endl;
    }

    void Update(float deltaTime) {
        // カメラの更新処理
    }

    void Render() {
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

        glm::mat4 view = glm::lookAt(cameraPosition, cameraPosition + cameraFront, cameraUp);
        glm::mat4 projection = glm::perspective(glm::radians(45.0f), (float)WINDOW_WIDTH / (float)WINDOW_HEIGHT, 0.1f, 100.0f);

        glUseProgram(shaderProgram);
        glUniformMatrix4fv(viewLoc, 1, GL_FALSE, glm::value_ptr(view));
        glUniformMatrix4fv(projectionLoc, 1, GL_FALSE, glm::value_ptr(projection));

        for (const CubeData& cube : cubes) {
            glm::mat4 model = glm::mat4(1.0f);
            model = glm::translate(model, cube.position);
            glUniformMatrix4fv(modelLoc, 1, GL_FALSE, glm::value_ptr(model));
            glBindVertexArray(VAO);
            glPolygonMode(GL_FRONT_AND_BACK, cube.drawWireframe ? GL_LINE : GL_FILL);
            glDrawElements(GL_TRIANGLES, 36, GL_UNSIGNED_INT, 0);
        }
    }

    GLFWwindow* getWindow() const { return window; }

    static void keyHandlerWrapper(GLFWwindow* window, int key, int scancode, int action, int mods) {
        CubeWorld* world = reinterpret_cast<CubeWorld*>(glfwGetWindowUserPointer(window));
        world->keyHandler(window, key, scancode, action, mods);
    }

    void keyHandler(GLFWwindow* window, int key, int scancode, int action, int mods) {
        float cameraSpeed = moveSpeed * deltaTime * 2;

        if (action == GLFW_PRESS || action == GLFW_REPEAT) {
            if (key == GLFW_KEY_W)
                cameraPosition += cameraSpeed * glm::normalize(glm::vec3(cameraFront.x, 0.0f, cameraFront.z));
            if (key == GLFW_KEY_S)
                cameraPosition -= cameraSpeed * glm::normalize(glm::vec3(cameraFront.x, 0.0f, cameraFront.z));
            if (key == GLFW_KEY_A)
                cameraPosition -= cameraSpeed * glm::normalize(glm::cross(cameraFront, cameraUp)) * glm::vec3(1.0f, 0.0f, 1.0f);
            if (key == GLFW_KEY_D)
                cameraPosition += cameraSpeed * glm::normalize(glm::cross(cameraFront, cameraUp)) * glm::vec3(1.0f, 0.0f, 1.0f);
            if (key == GLFW_KEY_SPACE)
                cameraPosition.y += cameraSpeed;
            if (key == GLFW_KEY_LEFT_SHIFT)
                cameraPosition.y -= cameraSpeed;
            if (key == GLFW_KEY_E)
                glfwSetWindowShouldClose(window, true);
            if (key == GLFW_KEY_Z) {
                std::system("clear");//画面クリア
                std::cout << "print data" << std::endl;
                for (const CubeData& cube : cubes) {
                    std::cout << "Position: ("
                                << cube.position.x << ", "
                                << cube.position.y << ", "
                                << cube.position.z << ")" << std::endl;
                }
            }
        }
    }

private:
    GLFWwindow* window;
    unsigned int shaderProgram;
    unsigned int VAO, VBO, EBO;
    int viewLoc, projectionLoc, modelLoc;

    std::vector<CubeData> cubes;

    float vertices[216] = {
        // 頂点データ: 頂点座標と色
        -0.5f, -0.5f, -0.5f, 1.0f, 0.0f, 0.0f,
         0.5f, -0.5f, -0.5f, 0.0f, 1.0f, 0.0f,
         0.5f,  0.5f, -0.5f, 0.0f, 0.0f, 1.0f,
        -0.5f,  0.5f, -0.5f, 1.0f, 1.0f, 0.0f,
        -0.5f, -0.5f,  0.5f, 1.0f, 0.0f, 1.0f,
         0.5f, -0.5f,  0.5f, 0.0f, 1.0f, 1.0f,
         0.5f,  0.5f,  0.5f, 1.0f, 1.0f, 1.0f,
        -0.5f,  0.5f,  0.5f, 0.5f, 0.5f, 0.5f
    };

    unsigned int indices[36] = {
        0, 1, 2, 2, 3, 0, 4, 5, 6, 6, 7, 4,
        0, 4, 7, 7, 3, 0, 1, 5, 6, 6, 2, 1,
        3, 2, 6, 6, 7, 3, 0, 1, 5, 5, 4, 0
    };

    const char* vertexShaderSource = R"(
    #version 330 core
    layout(location = 0) in vec3 aPos;
    layout(location = 1) in vec3 aColor;
    out vec3 vertexColor;
    uniform mat4 model;
    uniform mat4 view;
    uniform mat4 projection;
    void main() {
        vertexColor = aColor;
        gl_Position = projection * view * model * vec4(aPos, 1.0);
    }
    )";

    const char* fragmentShaderSource = R"(
    #version 330 core
    in vec3 vertexColor;
    out vec4 FragColor;
    void main() {
        FragColor = vec4(vertexColor, 1.0);
    }
    )";

    unsigned int compileShader(unsigned int type, const char* source) {
        unsigned int shader = glCreateShader(type);
        glShaderSource(shader, 1, &source, nullptr);
        glCompileShader(shader);
        int success;
        char infoLog[512];
        glGetShaderiv(shader, GL_COMPILE_STATUS, &success);
        if (!success) {
            glGetShaderInfoLog(shader, 512, nullptr, infoLog);
            std::cerr << "ERROR::SHADER::COMPILATION_FAILED\n" << infoLog << std::endl;
        }
        return shader;
    }

    unsigned int createShaderProgram() {
        unsigned int vertexShader = compileShader(GL_VERTEX_SHADER, vertexShaderSource);
        unsigned int fragmentShader = compileShader(GL_FRAGMENT_SHADER, fragmentShaderSource);

        unsigned int shaderProgram = glCreateProgram();
        glAttachShader(shaderProgram, vertexShader);
        glAttachShader(shaderProgram, fragmentShader);
        glLinkProgram(shaderProgram);

        int success;
        char infoLog[512];
        glGetProgramiv(shaderProgram, GL_LINK_STATUS, &success);
        if (!success) {
            glGetProgramInfoLog(shaderProgram, 512, nullptr, infoLog);
            std::cerr << "ERROR::PROGRAM::LINKING_FAILED\n" << infoLog << std::endl;
        }

        glDeleteShader(vertexShader);
        glDeleteShader(fragmentShader);

        return shaderProgram;
    }

    static void mouseCallback(GLFWwindow* window, double xpos, double ypos) {
        static float lastX = WINDOW_WIDTH / 2.0f;
        static float lastY = WINDOW_HEIGHT / 2.0f;
        static bool firstMouse = true;

        if (firstMouse) {
            lastX = xpos;
            lastY = ypos;
            firstMouse = false;
        }

        float xOffset = xpos - lastX;
        float yOffset = lastY - ypos;  // Y座標は上下逆
        lastX = xpos;
        lastY = ypos;

        xOffset *= sensitivity;
        yOffset *= sensitivity;

        yaw += xOffset;
        pitch += yOffset;

        if (pitch > 89.0f) pitch = 89.0f;
        if (pitch < -89.0f) pitch = -89.0f;

        glm::vec3 front;
        front.x = cos(glm::radians(yaw)) * cos(glm::radians(pitch));
        front.y = sin(glm::radians(pitch));
        front.z = sin(glm::radians(yaw)) * cos(glm::radians(pitch));
        cameraFront = glm::normalize(front);
        //show cube data
        //std::cout << "add cube" << (string)cubes << std::endl;
        //--

    }

    static bool rayIntersectsCube(const glm::vec3& rayOrigin, const glm::vec3& rayDir, const CubeData& cube, float& tMin) {
        // キューブの境界を定義する
        glm::vec3 cubeMin = cube.position - glm::vec3(0.5f); // 中心から半径0.5fの立方体
        glm::vec3 cubeMax = cube.position + glm::vec3(0.5f);

        float t1 = (cubeMin.x - rayOrigin.x) / rayDir.x;
        float t2 = (cubeMax.x - rayOrigin.x) / rayDir.x;
        float t3 = (cubeMin.y - rayOrigin.y) / rayDir.y;
        float t4 = (cubeMax.y - rayOrigin.y) / rayDir.y;
        float t5 = (cubeMin.z - rayOrigin.z) / rayDir.z;
        float t6 = (cubeMax.z - rayOrigin.z) / rayDir.z;

        float tMinLocal = std::max({std::min(t1, t2), std::min(t3, t4), std::min(t5, t6)});
        float tMaxLocal = std::min({std::max(t1, t2), std::max(t3, t4), std::max(t5, t6)});

        if (tMaxLocal < 0 || tMinLocal > tMaxLocal) return false;

        tMin = tMinLocal;
        return tMin >= 0;
    }
    // シーン内の当たり判定の例 (仮実装)
    // 必要に応じて、適切な判定方法に置き換えてください。
    bool isPointIntersectingWithScene(const glm::vec3& point) {
        float tMin;
        for (const CubeData& cube : cubes) {
            if (rayIntersectsCube(cameraPosition, glm::normalize(cameraFront), cube, tMin)) {
                // レイの長さが point までの距離より短ければヒット
                if (tMin <= glm::length(point - cameraPosition)) {
                    return true;
                }
            }
        }
        return false;
    }


    static void mouseButtonCallback(GLFWwindow* window, int button, int action, int mods) {
        if (button == GLFW_MOUSE_BUTTON_LEFT && action == GLFW_PRESS) {
            CubeWorld* world = reinterpret_cast<CubeWorld*>(glfwGetWindowUserPointer(window));

            glm::vec3 rayOrigin = cameraPosition;
            glm::vec3 rayDirection = glm::normalize(cameraFront);

            float maxDistance = 10.0f;
            glm::vec3 hitPosition;
            bool hit = false;
            float tMinClosest = maxDistance;

            // ブロックにヒットするかどうかを確認
            for (const CubeData& cube : world->cubes) {
                float tMin;
                if (rayIntersectsCube(rayOrigin, rayDirection, cube, tMin)) {
                    if (tMin < tMinClosest && tMin >= 0) {
                        tMinClosest = tMin;
                        hitPosition = rayOrigin + rayDirection * tMin;
                        hit = true;
                    }
                }
            }

            if (hit) {
                // レイがヒットした位置からレイの方向ベクトルを用いて一つ手前に戻る
                glm::vec3 placePosition = hitPosition - rayDirection * 0.5f;  // 少し戻すことで手前に配置

                // 座標を四捨五入して配置位置を整数にする
                placePosition.x = round(placePosition.x);
                placePosition.y = round(placePosition.y);
                placePosition.z = round(placePosition.z);

                std::cout << "Placing block at: (" << placePosition.x << ", " << placePosition.y << ", " << placePosition.z << ")" << std::endl;
                world->AddCube(placePosition, glm::vec3(0.7f, 0.7f, 0.7f), false);
            } else {
                std::cout << "No block detected within max distance." << std::endl;
            }
        }
    }



};
#endif

CubeData.csv

目次の5の部分です

CubeData.csv
0,0,0,0,0,0,0,0
1,0,0,0,0,0,0,0
0,1,0,0,0,0,0,0
0,0,1,0,0,0,0,0

最後に


macの場合

ライブラリをダウンロード
brew install glfw
brew install glm
brew install cmake
作成してビルド
mkdir build
cd build
cmake .
make
実行
 ./advent_gl



Discussion