본문 바로가기
-----ETC-----/C++ 게임 개발 시리즈

[C++ 게임 개발 시리즈] Day 26: 3D 게임 프로젝트 시작 (3)

by cogito21_cpp 2024. 8. 1.
반응형

3D 게임 프로젝트 시작 (3)

어제는 3D 게임 프로젝트에 카메라 컨트롤과 조명을 추가하여 장면을 더욱 현실감 있게 만드는 방법을 학습했습니다. 오늘은 추가 기능을 구현하여 게임을 더욱 발전시키겠습니다. 적 캐릭터의 AI와 간단한 충돌 처리를 추가하겠습니다.

적 캐릭터 AI 구현

적 캐릭터는 플레이어를 추적하고 공격하는 간단한 AI를 가집니다. 이를 위해 적 캐릭터가 플레이어의 위치를 추적하고 일정 거리 안으로 들어오면 공격하는 동작을 구현합니다.

 

Enemy 클래스 작성

Enemy 클래스를 작성하여 적 캐릭터의 위치와 AI 동작을 관리합니다.

#ifndef ENEMY_H
#define ENEMY_H

#include <glm/glm.hpp>

class Enemy {
public:
    glm::vec3 Position;
    float Speed;

    Enemy(glm::vec3 position, float speed) : Position(position), Speed(speed) {}

    void moveTowards(glm::vec3 targetPosition, float deltaTime) {
        glm::vec3 direction = glm::normalize(targetPosition - Position);
        Position += direction * Speed * deltaTime;
    }

    bool isWithinAttackRange(glm::vec3 targetPosition, float range) {
        return glm::distance(Position, targetPosition) <= range;
    }
};

#endif

 

충돌 처리

플레이어와 적 캐릭터 간의 충돌 처리를 위해 간단한 충돌 감지 기능을 구현합니다.

 

충돌 감지 함수

bool checkCollision(glm::vec3 pos1, glm::vec3 pos2, float radius) {
    return glm::distance(pos1, pos2) < radius;
}

 

main.cpp 수정

main.cpp 파일을 수정하여 적 캐릭터 AI와 충돌 처리를 추가합니다.

#include <GL/glew.h>
#include <GLFW/glfw3.h>
#include <iostream>
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>
#include "Shader.h"
#include "Camera.h"
#include "Enemy.h"

// 정점 셰이더 소스 코드
const char* vertexShaderSource = "path/to/vertex_shader.glsl";
const char* fragmentShaderSource = "path/to/fragment_shader.glsl";

// 카메라 설정
Camera camera(glm::vec3(0.0f, 0.0f, 3.0f));
float lastX = 400, lastY = 300;
bool firstMouse = true;

// 시간 설정
float deltaTime = 0.0f;
float lastFrame = 0.0f;

// 적 캐릭터 생성
Enemy enemy(glm::vec3(0.0f, 0.0f, -5.0f), 1.0f);

// 콜백 함수
void framebuffer_size_callback(GLFWwindow* window, int width, int height);
void mouse_callback(GLFWwindow* window, double xpos, double ypos);
void scroll_callback(GLFWwindow* window, double xoffset, double yoffset);
void processInput(GLFWwindow *window);
bool checkCollision(glm::vec3 pos1, glm::vec3 pos2, float radius);

int main() {
    // GLFW 초기화
    if (!glfwInit()) {
        std::cerr << "Failed to initialize GLFW" << std::endl;
        return -1;
    }

    // OpenGL 버전 설정 (3.3)
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);

    // 창 생성
    GLFWwindow* window = glfwCreateWindow(800, 600, "3D Game Project", NULL, NULL);
    if (!window) {
        std::cerr << "Failed to create GLFW window" << std::endl;
        glfwTerminate();
        return -1;
    }

    glfwMakeContextCurrent(window);
    glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);
    glfwSetCursorPosCallback(window, mouse_callback);
    glfwSetScrollCallback(window, scroll_callback);

    // 마우스 캡처
    glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED);

    // GLEW 초기화
    glewExperimental = GL_TRUE;
    if (glewInit() != GLEW_OK) {
        std::cerr << "Failed to initialize GLEW" << std::endl;
        return -1;
    }

    // 셰이더 프로그램 생성
    Shader ourShader(vertexShaderSource, fragmentShaderSource);

    // 정점 데이터
    float vertices[] = {
        // 위치           // 법선           // 텍스처 좌표
         0.5f,  0.5f, -0.5f,  0.0f,  0.0f, -1.0f,  1.0f, 1.0f,
        -0.5f,  0.5f, -0.5f,  0.0f,  0.0f, -1.0f,  0.0f, 1.0f,
        -0.5f, -0.5f, -0.5f,  0.0f,  0.0f, -1.0f,  0.0f, 0.0f,
         0.5f, -0.5f, -0.5f,  0.0f,  0.0f, -1.0f,  1.0f, 0.0f,

         0.5f,  0.5f,  0.5f,  0.0f,  0.0f,  1.0f,  1.0f, 1.0f,
        -0.5f,  0.5f,  0.5f,  0.0f,  0.0f,  1.0f,  0.0f, 1.0f,
        -0.5f, -0.5f,  0.5f,  0.0f,  0.0f,  1.0f,  0.0f, 0.0f,
         0.5f, -0.5f,  0.5f,  0.0f,  0.0f,  1.0f,  1.0f, 0.0f,
    };

    unsigned int indices[] = {
        0, 1, 2, 2, 3, 0,  // 앞
        4, 5, 6, 6, 7, 4,  // 뒤
        0, 1, 5, 5, 4, 0,  // 위
        2, 3, 7, 7, 6, 2,  // 아래
        1, 2, 6, 6, 5, 1,  // 왼쪽
        0, 3, 7, 7, 4, 0   // 오른쪽
    };

    GLuint VBO, VAO, EBO;
    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, 8 * sizeof(float), (void*)0);
    glEnableVertexAttribArray(0);
    glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(3 * sizeof(float)));
    glEnableVertexAttribArray(1);
    glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(6 * sizeof(float)));
    glEnableVertexAttribArray(2);

    glBindBuffer(GL_ARRAY_BUFFER, 0);
    glBindVertexArray(0);

    // 텍스처 로딩
    GLuint texture;
    glGenTextures(1, &texture);
    glBindTexture(GL_TEXTURE_2D, texture);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    int width, height;
    unsigned char* image = SOIL_load_image("path/to/your/texture.png", &width, &height, 0, SOIL_LOAD_RGB);
    if (image) {
        glTex

Image2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, image);
        glGenerateMipmap(GL_TEXTURE_2D);
    } else {
        std::cerr << "Failed to load texture" << std::endl;
    }
    SOIL_free_image_data(image);
    glBindTexture(GL_TEXTURE_2D, 0);

    // OpenGL 상태 설정
    glEnable(GL_DEPTH_TEST);

    // 조명 설정
    glm::vec3 lightDir(0.2f, 1.0f, 0.3f);
    glm::vec3 lightColor(1.0f, 1.0f, 1.0f);

    // 메인 루프
    while (!glfwWindowShouldClose(window)) {
        // 시간 계산
        float currentFrame = glfwGetTime();
        deltaTime = currentFrame - lastFrame;
        lastFrame = currentFrame;

        // 입력 처리
        processInput(window);

        // 적 캐릭터 이동
        enemy.moveTowards(camera.Position, deltaTime);

        // 적 캐릭터 공격 범위 확인
        if (enemy.isWithinAttackRange(camera.Position, 1.0f)) {
            std::cout << "Enemy is attacking!" << std::endl;
        }

        // 충돌 감지
        if (checkCollision(camera.Position, enemy.Position, 0.5f)) {
            std::cout << "Collision detected!" << std::endl;
        }

        // 화면 지우기
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

        // 셰이더 프로그램 사용
        ourShader.use();

        // 카메라 변환 행렬 설정
        glm::mat4 projection = glm::perspective(glm::radians(camera.Zoom), 800.0f / 600.0f, 0.1f, 100.0f);
        glm::mat4 view = camera.GetViewMatrix();

        GLint projectionLoc = glGetUniformLocation(ourShader.Program, "projection");
        GLint viewLoc = glGetUniformLocation(ourShader.Program, "view");

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

        // 모델 변환 행렬 설정
        glm::mat4 model = glm::mat4(1.0f);
        GLint modelLoc = glGetUniformLocation(ourShader.Program, "model");
        glUniformMatrix4fv(modelLoc, 1, GL_FALSE, glm::value_ptr(model));

        // 조명 설정
        glUniform3f(glGetUniformLocation(ourShader.Program, "lightDir"), lightDir.x, lightDir.y, lightDir.z);
        glUniform3f(glGetUniformLocation(ourShader.Program, "lightColor"), lightColor.r, lightColor.g, lightColor.b);
        glUniform3f(glGetUniformLocation(ourShader.Program, "objectColor"), 1.0f, 0.5f, 0.31f);

        // 텍스처 바인딩
        glBindTexture(GL_TEXTURE_2D, texture);

        // 객체 그리기
        glBindVertexArray(VAO);
        glDrawElements(GL_TRIANGLES, 36, GL_UNSIGNED_INT, 0);
        glBindVertexArray(0);

        // 적 캐릭터 그리기
        model = glm::translate(glm::mat4(1.0f), enemy.Position);
        glUniformMatrix4fv(modelLoc, 1, GL_FALSE, glm::value_ptr(model));
        glDrawElements(GL_TRIANGLES, 36, GL_UNSIGNED_INT, 0);

        // 버퍼 교환
        glfwSwapBuffers(window);
        glfwPollEvents();
    }

    // 자원 해제
    glDeleteVertexArrays(1, &VAO);
    glDeleteBuffers(1, &VBO);
    glDeleteBuffers(1, &EBO);

    glfwTerminate();
    return 0;
}

// 창 크기 변경 시 호출되는 콜백 함수
void framebuffer_size_callback(GLFWwindow* window, int width, int height) {
    glViewport(0, 0, width, height);
}

// 마우스 이동 시 호출되는 콜백 함수
void mouse_callback(GLFWwindow* window, double xpos, double ypos) {
    if (firstMouse) {
        lastX = xpos;
        lastY = ypos;
        firstMouse = false;
    }

    float xoffset = xpos - lastX;
    float yoffset = lastY - ypos;

    lastX = xpos;
    lastY = ypos;

    camera.ProcessMouseMovement(xoffset, yoffset);
}

// 마우스 휠 사용 시 호출되는 콜백 함수
void scroll_callback(GLFWwindow* window, double xoffset, double yoffset) {
    camera.ProcessMouseScroll(yoffset);
}

// 키보드 입력 처리
void processInput(GLFWwindow *window) {
    if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)
        glfwSetWindowShouldClose(window, true);

    if (glfwGetKey(window, GLFW_KEY_W) == GLFW_PRESS)
        camera.ProcessKeyboard(FORWARD, deltaTime);
    if (glfwGetKey(window, GLFW_KEY_S) == GLFW_PRESS)
        camera.ProcessKeyboard(BACKWARD, deltaTime);
    if (glfwGetKey(window, GLFW_KEY_A) == GLFW_PRESS)
        camera.ProcessKeyboard(LEFT, deltaTime);
    if (glfwGetKey(window, GLFW_KEY_D) == GLFW_PRESS)
        camera.ProcessKeyboard(RIGHT, deltaTime);
}

// 충돌 감지 함수
bool checkCollision(glm::vec3 pos1, glm::vec3 pos2, float radius) {
    return glm::distance(pos1, pos2) < radius;
}

 

결론

오늘은 3D 게임 프로젝트에 적 캐릭터 AI와 충돌 처리를 추가하는 방법을 학습했습니다. 이를 통해 게임 플레이가 더욱 현실감 있고 동적인 경험이 되었습니다. 질문이나 추가적인 피드백이 있으면 언제든지 댓글로 남겨 주세요. 내일은 "Day 27: 멀티플레이어 게임 개발 기초"에 대해 학습하겠습니다.

반응형