반응형
멀티플레이어 게임 개발 기초
멀티플레이어 게임은 네트워크를 통해 여러 플레이어가 동시에 게임을 즐길 수 있게 합니다. 오늘은 멀티플레이어 게임의 기초를 학습하고, 간단한 네트워크 동기화를 구현해 보겠습니다.
네트워크 동기화 기초
멀티플레이어 게임에서는 네트워크를 통해 플레이어 간의 상태를 동기화해야 합니다. 이를 위해 클라이언트-서버 모델을 사용합니다. 서버는 게임 상태를 관리하고, 클라이언트는 플레이어의 입력을 서버로 전송하여 게임 상태를 업데이트합니다.
네트워크 라이브러리 설정
네트워크 프로그래밍을 위해 Boost.Asio
라이브러리를 사용하겠습니다. Boost.Asio
는 비동기 입출력 기능을 제공하여 네트워크 프로그래밍을 쉽게 할 수 있게 해줍니다.
Boost.Asio 설치 및 설정
- Boost 설치:
- Boost 공식 사이트에서 Boost 라이브러리를 다운로드하거나 패키지 관리자를 통해 설치합니다.
- CMake 설정:
CMakeLists.txt
파일을 수정하여 Boost.Asio를 포함합니다.
cmake_minimum_required(VERSION 3.10)
project(3DGameProject)
set(CMAKE_CXX_STANDARD 11)
find_package(OpenGL REQUIRED)
find_package(GLEW REQUIRED)
find_package(glfw3 REQUIRED)
find_package(Boost REQUIRED COMPONENTS system)
include_directories(${OPENGL_INCLUDE_DIRS} ${GLEW_INCLUDE_DIRS} ${GLFW_INCLUDE_DIRS} ${Boost_INCLUDE_DIRS} include)
add_executable(3DGameProject src/main.cpp src/Shader.cpp src/Server.cpp src/Client.cpp)
target_link_libraries(3DGameProject ${OPENGL_LIBRARIES} ${GLEW_LIBRARIES} glfw ${Boost_LIBRARIES})
서버 구현
서버는 클라이언트의 연결을 받아들이고, 클라이언트 간의 상태를 동기화합니다.
Server.h
#ifndef SERVER_H
#define SERVER_H
#include <boost/asio.hpp>
#include <iostream>
#include <thread>
#include <vector>
using boost::asio::ip::tcp;
class Server {
public:
Server(boost::asio::io_service& io_service, short port)
: acceptor_(io_service, tcp::endpoint(tcp::v4(), port)), socket_(io_service) {
do_accept();
}
private:
void do_accept() {
acceptor_.async_accept(socket_, [this](boost::system::error_code ec) {
if (!ec) {
std::make_shared<Session>(std::move(socket_))->start();
}
do_accept();
});
}
tcp::acceptor acceptor_;
tcp::socket socket_;
};
class Session : public std::enable_shared_from_this<Session> {
public:
Session(tcp::socket socket) : socket_(std::move(socket)) {}
void start() {
do_read();
}
private:
void do_read() {
auto self(shared_from_this());
boost::asio::async_read(socket_, boost::asio::buffer(data_, max_length), [this, self](boost::system::error_code ec, std::size_t length) {
if (!ec) {
do_write(length);
}
});
}
void do_write(std::size_t length) {
auto self(shared_from_this());
boost::asio::async_write(socket_, boost::asio::buffer(data_, length), [this, self](boost::system::error_code ec, std::size_t /*length*/) {
if (!ec) {
do_read();
}
});
}
tcp::socket socket_;
enum { max_length = 1024 };
char data_[max_length];
};
#endif
클라이언트 구현
클라이언트는 서버에 연결하여 플레이어의 상태를 전송하고, 서버로부터 다른 플레이어의 상태를 수신합니다.
Client.h
#ifndef CLIENT_H
#define CLIENT_H
#include <boost/asio.hpp>
#include <iostream>
#include <thread>
using boost::asio::ip::tcp;
class Client {
public:
Client(boost::asio::io_service& io_service, const std::string& host, const std::string& port)
: socket_(io_service) {
tcp::resolver resolver(io_service);
auto endpoints = resolver.resolve(host, port);
do_connect(endpoints);
}
void write(const std::string& msg) {
boost::asio::post(socket_.get_io_service(), [this, msg]() {
auto self(shared_from_this());
boost::asio::async_write(socket_, boost::asio::buffer(msg.data(), msg.size()), [this, self](boost::system::error_code ec, std::size_t /*length*/) {
if (!ec) {
do_read();
}
});
});
}
private:
void do_connect(const tcp::resolver::results_type& endpoints) {
boost::asio::async_connect(socket_, endpoints, [this](boost::system::error_code ec, tcp::endpoint) {
if (!ec) {
do_read();
}
});
}
void do_read() {
auto self(shared_from_this());
boost::asio::async_read(socket_, boost::asio::buffer(data_, max_length), [this, self](boost::system::error_code ec, std::size_t length) {
if (!ec) {
std::cout << "Server: " << std::string(data_, length) << std::endl;
do_read();
}
});
}
tcp::socket socket_;
enum { max_length = 1024 };
char data_[max_length];
};
#endif
main.cpp 수정
main.cpp
파일을 수정하여 서버와 클라이언트를 초기화하고 네트워크 동기화를 처리합니다.
#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 <boost/asio.hpp>
#include "Shader.h"
#include "Camera.h"
#include "Enemy.h"
#include "Server.h"
#include "Client.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);
// 네트워크 설정
boost::asio::io_service io_service;
std::shared_ptr<Server> server;
std::shared_ptr<Client> client;
// 콜백 함수
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(int argc, char* argv[]) {
// 서버 또는 클라이언트 모드 설정
bool isServer = false;
if (argc > 1) {
isServer = std::string(argv[1]) == "server";
}
if (isServer) {
server = std::make_shared<Server>(io_service, 12345);
std::thread serverThread([]() { io_service.run(); });
serverThread.detach();
} else {
client = std::make_shared<Client>(io_service, "localhost", "12345");
std::thread clientThread([]() { io_service.run(); });
clientThread.detach();
}
// 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) {
glTexImage2D(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;
}
// 클라이언트 상태 전송
if (client) {
std::string msg = "Player position: " + std::to_string(camera.Position.x) + " " + std::to_string(camera.Position.y) + " " + std::to_string(camera.Position.z);
client->write(msg);
}
// 화면 지우기
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;
}
결론
오늘은 멀티플레이어 게임의 기초를 학습하고, 간단한 네트워크 동기화를 구현했습니다. 이를 통해 클라이언트-서버 모델을 사용하여 플레이어의 상태를 동기화하는 방법을 배웠습니다. 질문이나 추가적인 피드백이 있으면 언제든지 댓글로 남겨 주세요. 내일은 "Day 28: 네트워크 동기화와 지연 처리"에 대해 학습하겠습니다.
반응형
'-----ETC----- > C++ 게임 개발 시리즈' 카테고리의 다른 글
[C++ 게임 개발 시리즈] Day 29: 게임 최적화 기법 (0) | 2024.08.01 |
---|---|
[C++ 게임 개발 시리즈] Day 30: 게임 배포와 커뮤니티 관리 (0) | 2024.08.01 |
[C++ 게임 개발 시리즈] Day 28: 네트워크 동기화와 지연 처리 (0) | 2024.08.01 |
[C++ 게임 개발 시리즈] Day 25: 3D 게임 프로젝트 시작 (2) (0) | 2024.08.01 |
[C++ 게임 개발 시리즈] Day 26: 3D 게임 프로젝트 시작 (3) (0) | 2024.08.01 |