ゲーム開発に数学がないと何も出来ない件 ノート
プログラムについて
これは、0からゲーム開発をしている時のノートです
0から
物理演算
描画システム
ワールド生成
を0から作る!というものです
これは、入門ではなくノートです
間違っている部分があったら教えてください
本編
コード
#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 <vector>
#include <cmath>
#include <fstream>
#include <sstream>
#include "imgui.h" // Dear ImGui本体
#include "backends/imgui_impl_glfw.h" // GLFWバックエンド
#include "backends/imgui_impl_opengl3.h" // OpenGLバックエンド
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 moveSpeed = 5.0f;
float sensitivity = 0.05f;
glm::vec3 playerPosition(0.0f, 1.0f, 0.0f); // 初期プレイヤー位置
glm::vec3 playerVelocity(0.0f, 0.0f, 0.0f); // 初期プレイヤー速度
const float gravity = -9.81f; // 重力加速度
const float jumpStrength = 5.0f;
const float playerHeight = 1.8f; // プレイヤーの高さ
const float playerWidth = 0.6f; // プレイヤーの幅
bool onGround = false;
struct CubeData {
glm::vec3 position;
glm::vec3 color;
bool drawWireframe;
bool isSolid;
};
class CubeWorld {
public:
CubeWorld(int type, const std::vector<CubeData>& 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);
glfwSetMouseButtonCallback(window, mouseButtonCallbackWrapper);
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);
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");
ImGui::CreateContext();
ImGuiIO& io = ImGui::GetIO();
io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard;
ImGui_ImplGlfw_InitForOpenGL(window, true);
ImGui_ImplOpenGL3_Init("#version 330");
if (type == 0) {
glClearColor(0.53f, 0.81f, 0.92f, 1.0f);
} else {
glClearColor(0.8f, 0.2f, 0.2f, 1.0f);
}
for (const CubeData& cube : initialCubes) {
//cubes.push_back({cube.position, glm::vec3(0.7f, 0.7f, 0.7f), false, true});
cubes.push_back({cube.position, glm::vec3(0.7f, 0.7f, 0.7f), false, true});
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();
ImGui_ImplOpenGL3_Shutdown();
ImGui_ImplGlfw_Shutdown();
ImGui::DestroyContext();
}
void AddCube(const glm::vec3& position, const glm::vec3& color, bool drawWireframe) {
cubes.push_back({position, color, drawWireframe, true});
std::cout << "Added cube at: (" << position.x << ", " << position.y << ", " << position.z << ")" << std::endl;
}
// 重要物品....
int jumpSpeed = 7;
void Update(float deltaTime) {
// カメラの前方向ベクトル(Y成分は無視して平面方向で計算)
glm::vec3 forwardDirection = glm::normalize(glm::vec3(cameraFront.x, 0.0f, cameraFront.z)); // Y成分を無視して平面での前進方向を計算
glm::vec3 rightDirection = glm::normalize(glm::cross(forwardDirection, cameraUp)); // 右方向ベクトルを計算
// プレイヤーの移動処理:WASDによる入力
glm::vec3 desiredVelocity = glm::vec3(0.0f);
// WASDによる移動入力処理
if (glfwGetKey(window, GLFW_KEY_W) == GLFW_PRESS) desiredVelocity += forwardDirection * moveSpeed;
if (glfwGetKey(window, GLFW_KEY_S) == GLFW_PRESS) desiredVelocity -= forwardDirection * moveSpeed;
if (glfwGetKey(window, GLFW_KEY_A) == GLFW_PRESS) desiredVelocity -= rightDirection * moveSpeed;
if (glfwGetKey(window, GLFW_KEY_D) == GLFW_PRESS) desiredVelocity += rightDirection * moveSpeed;
if (playerPosition.y < -20){
std::cout << playerPosition.y << std::endl;
glm::vec3 placePosition = playerPosition;
placePosition.x = round(placePosition.x);
placePosition.y = -22;
placePosition.z = round(placePosition.z);
std::cout << "DEBUG: Placing block at: (" << placePosition.x << ", " << placePosition.y << ", " << placePosition.z << ")" << std::endl;
AddCube(placePosition, glm::vec3(0.7f, 0.7f, 0.7f), true);
}
// スペースキーでジャンプ
if (glfwGetKey(window, GLFW_KEY_SPACE) == GLFW_PRESS && onGround) {
playerVelocity.y = jumpSpeed; // ジャンプ速度設定
onGround = false; // 地面から離れる
}
// 水平移動処理:XとZ軸方向の移動
glm::vec3 movement = desiredVelocity * deltaTime;
// X軸方向の移動(衝突チェック)
playerPosition.x += movement.x;
if (checkHorizontalCollision(playerPosition)) {
playerPosition.x -= movement.x; // 衝突があった場合、移動をキャンセル
}
// Z軸方向の移動(衝突チェック)
playerPosition.z += movement.z;
if (checkHorizontalCollision(playerPosition)) {
playerPosition.z -= movement.z; // 衝突があった場合、移動をキャンセル
}
// 垂直方向の移動(重力適用とジャンプ)
if (!onGround) {
playerVelocity.y += gravity * deltaTime; // 重力を加算
playerPosition.y += playerVelocity.y * deltaTime; // Y軸方向の移動
if (checkVerticalCollision(playerPosition)) {
playerVelocity.y = 0; // 衝突した場合は速度をゼロに
onGround = true; // 地面に着地した
}
} else {
playerVelocity.y = 0; // 地面にいる場合はY軸の速度をゼロに
playerPosition.y = floor(playerPosition.y); // 地面に合わせて位置を調整
}
// 最終的なカメラ位置更新:プレイヤー位置にカメラのオフセットを追加
cameraPosition = playerPosition + glm::vec3(0.0f, 1.5f, 0.0f); // プレイヤーの上にカメラ
}
// レイとキューブの交差をデバッグ表示する関数
void debugRayIntersection(const glm::vec3& rayOrigin, const glm::vec3& rayDir, const CubeData& cube) {
float tMin = 0.0f;
if (rayIntersectsCube(rayOrigin, rayDir, cube, tMin)) {
glm::vec3 hitPosition = rayOrigin + rayDir * tMin;
//std::cout << "Ray hit cube at: (" << hitPosition.x << ", " << hitPosition.y << ", " << hitPosition.z << ")" << std::endl;
}
}
// 変更されたImGuiのデバッグ情報表示部分
void Render() {
// ImGuiの描画開始
ImGui_ImplOpenGL3_NewFrame();
ImGui_ImplGlfw_NewFrame();
ImGui::NewFrame();
// レイの交差をデバッグ表示
glm::vec3 rayOrigin = cameraPosition;
glm::vec3 rayDir = glm::normalize(cameraFront);
for (const CubeData& cube : cubes) {
debugRayIntersection(rayOrigin, rayDir, cube);
}
// ImGuiのUI描画
ImGui::Render();
ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
// 描画ループ内
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);
}
}
bool checkVerticalCollision(const glm::vec3& position) {
for (const CubeData& cube : cubes) {
if (!cube.isSolid) continue;
// 各ブロックの最小・最大座標
glm::vec3 cubeMin = cube.position - glm::vec3(0.5f);
glm::vec3 cubeMax = cube.position + glm::vec3(0.5f);
// プレイヤーとブロックの位置が重なっているかを判定
if (position.x >= cubeMin.x && position.x <= cubeMax.x &&
position.z >= cubeMin.z && position.z <= cubeMax.z) {
// プレイヤーの上下位置とブロックの上下位置が重なっている場合
if (position.y + playerHeight / 2.0f > cubeMin.y && position.y - playerHeight / 2.0f < cubeMax.y) {
return true; // 衝突発生
}
}
}
return false; // 衝突なし
}
bool checkHorizontalCollision(const glm::vec3& position) {
for (const CubeData& cube : cubes) {
if (!cube.isSolid) continue;
glm::vec3 cubeMin = cube.position - glm::vec3(0.5f);
glm::vec3 cubeMax = cube.position + glm::vec3(0.5f);
// Y方向はそのまま、X/Z方向の衝突を検出
if (position.y >= cubeMin.y && position.y - playerHeight / 2.0f <= cubeMax.y &&
position.x >= cubeMin.x && position.x <= cubeMax.x &&
position.z >= cubeMin.z && position.z <= cubeMax.z) {
return true;
}
}
return false;
}
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));
if (world) {
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) {
glm::vec3 oldPosition = playerPosition;
if (key == GLFW_KEY_W)
playerPosition += cameraSpeed * glm::normalize(glm::vec3(cameraFront.x, 0.0f, cameraFront.z));
if (key == GLFW_KEY_S)
playerPosition -= cameraSpeed * glm::normalize(glm::vec3(cameraFront.x, 0.0f, cameraFront.z));
if (key == GLFW_KEY_A)
playerPosition -= cameraSpeed * glm::normalize(glm::cross(cameraFront, cameraUp)) * glm::vec3(1.0f, 0.0f, 1.0f);
if (key == GLFW_KEY_D)
playerPosition += cameraSpeed * glm::normalize(glm::cross(cameraFront, cameraUp)) * glm::vec3(1.0f, 0.0f, 1.0f);
if (key == GLFW_KEY_E)
exit(0);
if (key == GLFW_KEY_P){
std::cout << playerPosition.y << std::endl;
glm::vec3 placePosition = playerPosition;
placePosition.x = round(placePosition.x);
placePosition.y = round(placePosition.y);
placePosition.z = round(placePosition.z);
std::cout << "DEBUG: Placing block at: (" << placePosition.x << ", " << placePosition.y << ", " << placePosition.z << ")" << std::endl;
AddCube(placePosition, glm::vec3(0.7f, 0.7f, 0.7f), true);
}
if (key == GLFW_KEY_SPACE && onGround) {
playerVelocity.y = jumpStrength;
onGround = false;
}
if (checkHorizontalCollision(playerPosition)) {
playerPosition = oldPosition; // 衝突が発生した場合は元の位置に戻す
}
}
}
static void mouseButtonCallbackWrapper(GLFWwindow* window, int button, int action, int mods) {
CubeWorld* world = reinterpret_cast<CubeWorld*>(glfwGetWindowUserPointer(window));
if (world) {
world->mouseButtonCallback(window, button, action, mods);
}
}
void mouseButtonCallback(GLFWwindow* window, int button, int action, int mods) {
if (button == GLFW_MOUSE_BUTTON_LEFT && action == GLFW_PRESS) {
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 : 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;
AddCube(placePosition, glm::vec3(0.7f, 0.7f, 0.7f), false);
} else {
std::cout << "No block detected within max distance." << 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
};
std::string loadShaderSource(const char* filepath) {
std::ifstream file(filepath);
if (!file.is_open()) {
std::cerr << "ERROR::SHADER::FILE_NOT_READABLE: " << filepath << std::endl;
return "";
}
std::stringstream buffer;
buffer << file.rdbuf();
return buffer.str();
}
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() {
// シェーダーソースをファイルから読み込む
std::string vertexShaderSource = loadShaderSource("../Shader/vertex.glsl");
std::string fragmentShaderSource = loadShaderSource("../Shader/fragment.glsl");
unsigned int vertexShader = compileShader(GL_VERTEX_SHADER, vertexShaderSource.c_str());
unsigned int fragmentShader = compileShader(GL_FRAGMENT_SHADER, fragmentShaderSource.c_str());
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);
}
//static bool rayIntersectsCube(const glm::vec3& rayOrigin, const glm::vec3& rayDir, const CubeData& cube, float& tMin) {
bool rayIntersectsCube(const glm::vec3& rayOrigin, const glm::vec3& rayDir, const CubeData& cube, float& tMin) {
glm::vec3 cubeMin = cube.position - glm::vec3(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 tmin = std::fmax(std::fmax(std::fmin(t1, t2), std::fmin(t3, t4)), std::fmin(t5, t6));
float tmax = std::fmin(std::fmin(std::fmax(t1, t2), std::fmax(t3, t4)), std::fmax(t5, t6));
if (tmax < 0 || tmin > tmax) {
//std::cout << "tmax < 0 || tmin > tmax - true" << std::endl;
return false; // No intersection
}
tMin = tmin;
//std::cout << "tmax < 0 || tmin > tmax - else" << std::endl;
return true; // Intersection detected
}
bool isPointIntersectingWithScene(const glm::vec3& point) {
float tMin;
for (const CubeData& cube : cubes) {
if (rayIntersectsCube(point, glm::vec3(0.0f, -1.0f, 0.0f), cube, tMin)) {
if (tMin <= playerHeight / 2.0f) {
return true;
}
}
}
return false;
}
};
#endif
/*
//main.cpp
#define IMGUI_HAS_DOCK
#define IMGUI_ENABLE_DOCKING
#include "imgui.h"
#include "../Core/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, false, isSolid });
}
return cubes;
}
static bool imguiInitialized = false;
int main() {
// CSVからCubeDataを読み込む
std::string filePath = "../Data/CubeData.csv";
std::vector<CubeData> initialCubes = loadCubesFromCSV(filePath);
// CubeWorldを初期化
CubeWorld world(0, initialCubes); // 0 は背景色などの初期化パラメータ
// GLFWウィンドウを登録
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;
}
*/
ゲーム開発に数学がないと何もできない件
ゲーム開発を始めると、必ずと言っていいほど直面する壁があります。それは「数学」です。グラフィックス、物理演算、AI、カメラ制御、さらにはレベル生成に至るまで、数学がゲーム開発のあらゆる側面に深く関わっています。この記事では、数学がどのようにゲーム開発を支えているのか、いくつかの具体例を挙げながら解説します。
1. 3Dグラフィックスの基盤:線形代数
ゲームの3Dグラフィックスは、線形代数がなければ存在しません。例えば、以下のような処理に線形代数が用いられています。
- 座標変換:ゲーム内のオブジェクトを移動・回転・拡大縮小するための変換行列。
- カメラ操作:視点の設定や、ワールド座標からカメラ座標への変換。
- 照明計算:法線ベクトルを使った光と影のリアルなシミュレーション。
例えば、カメラがプレイヤーを追従する場合、glm::lookAt
関数を使ってカメラの視点を設定しますが、その内部では行列計算が行われています。
glm::mat4 view = glm::lookAt(cameraPosition, cameraTarget, cameraUp);
これがなければ、プレイヤーがどこを向いているのかすら表現できません。
2. 物理演算:微積分と力学
ブロックの上にのぼるプレイヤー
ゲーム内でキャラクターがジャンプしたり、物体が重力で落下したりする動きは、すべて物理演算によって制御されています。この物理演算は、ニュートンの運動方程式を基にしており、微分や積分が関与します。
例:キャラクターのジャンプ
const float gravity = -9.81f; // 重力加速度
playerVelocity.y += gravity * deltaTime; // 重力による速度変化
playerPosition.y += playerVelocity.y * deltaTime; // Y座標の更新
重力の影響で徐々に減速し、頂点に達した後、加速しながら落下する動きは、数学的には「放物線」の形で表されます。
3. AIとパスファインディング:グラフ理論
敵キャラクターがプレイヤーを追いかけたり、最短ルートで目的地に向かう動きは、グラフ理論を利用しています。例えば、A*アルゴリズムでは次のような数学的概念が使われます。
- ヒューリスティック関数:ゴールまでの推定距離を計算。
- ノードとエッジ:ゲーム内の地形や障害物を抽象化して表現。
これにより、敵キャラクターが賢く動くように見せられるのです。
4. レベル生成とノイズ関数
ノイズ関数によるワールド生成
ランダムに生成される地形やダンジョンは、ノイズ関数を用いています。有名な例はPerlinノイズやSimplexノイズで、これらを使うとリアルで自然な形状を作り出すことができます。
float height = perlinNoise(position.x, position.z);
terrain.setHeight(position, height);
これにより、Minecraftのようなゲームで無限に広がる地形が実現可能になります。
5. ユーザーインターフェースとベジェ曲線
ボタンやスライダー、アニメーションなど、UIにも数学は欠かせません。特にベジェ曲線は、スムーズな動きや曲線を描く際に使われます。
例:二次ベジェ曲線
glm::vec2 bezierPoint(float t, glm::vec2 p0, glm::vec2 p1, glm::vec2 p2) {
return (1 - t) * (1 - t) * p0 + 2 * (1 - t) * t * p1 + t * t * p2;
}
滑らかなアニメーションを作ることで、ゲームの見た目が一気に洗練されます。
6. 衝突判定とベクトル計算
レイによるブロックの追加
ゲームでは、キャラクターや弾丸、障害物の衝突を判定する必要があります。この処理は、AABB(軸平行境界ボックス)やレイキャストといった数学的アルゴリズムを使って実現されています。
bool checkCollision(const AABB& a, const AABB& b) {
return (a.min.x <= b.max.x && a.max.x >= b.min.x) &&
(a.min.y <= b.max.y && a.max.y >= b.min.y) &&
(a.min.z <= b.max.z && a.max.z >= b.min.z);
}
プレイヤーが壁をすり抜けないためには、これらの計算が必須です。
数学がゲームに命を吹き込む
これらの例からわかるように、数学がなければゲームはただの動かないオブジェクトの集まりになってしまいます。数学の力を借りてこそ、キャラクターが動き、物語が展開し、プレイヤーが没入できる世界が形作られるのです。
もしあなたが「ゲーム開発をしたいけど数学が苦手」と思っているなら、恐れずに取り組んでみてください。数学の知識を活かして、思い描いたゲーム世界を実現しましょう!
Discussion