Node.js와 Unity를 사용하여 멀티플레이어 게임 서버와 클라이언트를 구현하는 과정과
주요 학습 내용 정리
네트워크 통신에 tcp 사용, 데이터 직렬화에 protobuf 를 사용하며 게임 상태 동기화를 관리하는데 중점을 뒀다.
서버 초기화 / server.js
TCP 서버를 설정하여 클라이언트 연결을 수신
들어오는 데이터와 클라이언트 연결 해제를 처리
const net = require('net');
const { Game, User } = require('../classes/gameInstance');
const { handleMessage } = require('../handlers/messageHandler');
const HOST = process.env.SERVER_HOST || '0.0.0.0';
const PORT = process.env.SERVER_PORT || 8080;
const game = new Game();
const server = net.createServer((socket) => {
console.log('클라이언트 연결됨:', socket.remoteAddress, socket.remotePort);
socket.on('data', (data) => {
handleMessage(socket, data, game); // 패킷 처리 함수 호출
});
socket.on('end', () => {
console.log('클라이언트 연결 종료');
game.removeUser(socket.userId); // 사용자 제거
});
socket.on('error', (err) => {
console.error('소켓 에러:', err);
});
});
server.listen(PORT, HOST, () => {
console.log(`서버가 ${HOST}:${PORT}에서 시작됨`);
});
// 조건 || '0.0.0.0'; // 모든 사용자의 접근을 허용
메시지 핸들러 messageHandler.js
수신된 데이터를 분석하고 적절한 게임 인스턴스 로직을 호출
각 메시지 타입 InitialPayload, LocationUpdatePayload 에 따른 처리를 정의
// server/handlers/messageHandler.js
const protobuf = require('../protobuf/compiled');
const InitialPayload = protobuf.lookupType("InitialPayload");
const LocationUpdatePayload = protobuf.lookupType("LocationUpdatePayload");
function decodeMessage(data) {
// 적절한 디코딩 로직을 구현
return InitialPayload.decode(data); // 예시로 InitialPayload 디코딩
}
function handleMessage(socket, data, game) {
const message = decodeMessage(data); // 프로토콜 버퍼 메시지 디코딩
switch (message.$type.name) { // message.type 대신 message.$type.name 사용
case 'InitialPayload':
handleInitialPayload(socket, message, game);
break;
case 'LocationUpdatePayload':
handleLocationUpdatePayload(socket, message, game);
break;
// 추가적인 패킷 타입 처리
}
}
function handleInitialPayload(socket, message, game) {
const user = new User(message.deviceId, message.playerId);
game.addUser(user);
socket.userId = message.deviceId;
// 응답 패킷 전송 로직 추가
}
function handleLocationUpdatePayload(socket, message, game) {
game.updateUserLocation(socket.userId, message.x, message.y);
const users = game.getUsers();
// 위치 업데이트 응답 패킷 전송 로직 추가
}
module.exports = { handleMessage };
게임 인스턴스 관리 gameInstance.js
플레이어 추가, 제거 및 위치 업데이트 관리
모든 클라이언트에 플레이어 위치 방송
class GameInstance {
constructor() {
this.players = new Map();
}
addPlayer(socket, initPayload) {
const player = {
socket: socket,
id: initPayload.deviceId,
position: { x: 0, y: 0 },
};
this.players.set(socket, player);
console.log(`Player ${player.id} added`);
}
removePlayer(socket) {
const player = this.players.get(socket);
if (player) {
console.log(`Player ${player.id} removed`);
this.players.delete(socket);
}
}
updatePlayerLocation(socket, locationPayload) {
const player = this.players.get(socket);
if (player) {
player.position.x = locationPayload.x;
player.position.y = locationPayload.y;
this.broadcastPlayerLocation();
}
}
broadcastPlayerLocation() {
const locations = Array.from(this.players.values()).map(player => ({
id: player.id,
x: player.position.x,
y: player.position.y
}));
const payload = { users: locations };
const message = root.lookupType('LocationUpdate').encode(payload).finish();
for (const player of this.players.values()) {
player.socket.write(message);
}
}
}
module.exports = GameInstance;