안녕하세요. 온라인 Snake 게임 개발 교육과정의 세 번째 시간입니다. 오늘은 Socket.io를 이용해 실시간 멀티플레이어 기능을 구현하는 과정을 살펴보겠습니다.
1. Socket.io 설정
먼저 서버와 클라이언트에 Socket.io를 설정합니다.
서버 측 (server.js):
const express = require('express');
const app = express();
const http = require('http').createServer(app);
const io = require('socket.io')(http);
app.use(express.static('public'));
io.on('connection', (socket) => {
console.log('A user connected');
// 여기에 소켓 이벤트 핸들러를 추가할 예정입니다.
});
const PORT = process.env.PORT || 3000;
http.listen(PORT, () => {
console.log(`Server is running on port ${PORT}`);
});
클라이언트 측 (game.js):
const socket = io();
2. 방 생성 및 참여 기능
플레이어들이 게임 방을 만들고 참여할 수 있도록 구현합니다.
서버 측:
const rooms = new Map();
io.on('connection', (socket) => {
socket.on('createRoom', () => {
const roomCode = generateRoomCode();
rooms.set(roomCode, { players: [], gameStarted: false });
socket.join(roomCode);
socket.emit('roomCreated', roomCode);
});
socket.on('joinRoom', (roomCode) => {
if (rooms.has(roomCode) && !rooms.get(roomCode).gameStarted) {
socket.join(roomCode);
socket.emit('joinedRoom', roomCode);
} else {
socket.emit('roomNotFound');
}
});
});
function generateRoomCode() {
return Math.random().toString(36).substring(2, 8).toUpperCase();
}
클라이언트 측:
document.getElementById('createRoom').addEventListener('click', () => {
socket.emit('createRoom');
});
document.getElementById('joinRoom').addEventListener('click', () => {
const roomCode = document.getElementById('roomCodeInput').value;
socket.emit('joinRoom', roomCode);
});
socket.on('roomCreated', (roomCode) => {
console.log(`Room created with code: ${roomCode}`);
// 방 코드를 화면에 표시
});
socket.on('joinedRoom', (roomCode) => {
console.log(`Joined room: ${roomCode}`);
// 게임 준비 화면으로 전환
});
3. 게임 상태 동기화
서버에서 게임 상태를 관리하고 모든 클라이언트에게 주기적으로 전송합니다.
서버 측:
function gameLoop(roomCode) {
const room = rooms.get(roomCode);
// 게임 상태 업데이트 로직
// ...
io.to(roomCode).emit('gameState', room.gameState);
setTimeout(() => gameLoop(roomCode), 100);
}
io.on('connection', (socket) => {
// ...
socket.on('startGame', (roomCode) => {
if (rooms.has(roomCode)) {
rooms.get(roomCode).gameStarted = true;
gameLoop(roomCode);
}
});
});
클라이언트 측:
socket.on('gameState', (gameState) => {
// 받은 게임 상태로 화면 업데이트
updateGameScreen(gameState);
});
function updateGameScreen(gameState) {
// 캔버스에 게임 상태 그리기
// ...
}
4. 플레이어 입력 처리
플레이어의 입력을 서버로 전송하고 서버에서 처리합니다.
클라이언트 측:
document.addEventListener('keydown', (event) => {
let direction;
switch(event.key) {
case 'ArrowUp': direction = 'UP'; break;
case 'ArrowDown': direction = 'DOWN'; break;
case 'ArrowLeft': direction = 'LEFT'; break;
case 'ArrowRight': direction = 'RIGHT'; break;
}
if (direction) {
socket.emit('changeDirection', { roomCode, direction });
}
});
서버 측:
io.on('connection', (socket) => {
// ...
socket.on('changeDirection', ({ roomCode, direction }) => {
if (rooms.has(roomCode)) {
const player = rooms.get(roomCode).players.find(p => p.id === socket.id);
if (player) {
player.direction = direction;
}
}
});
});
5. 네트워크 지연 보정
클라이언트 사이드 예측을 구현하여 네트워크 지연에 의한 불편함을 줄입니다.
클라이언트 측:
let lastProcessedInput = 0;
function predictGameState(gameState) {
const player = gameState.players.find(p => p.id === socket.id);
while (lastProcessedInput < currentInput) {
player.move();
lastProcessedInput++;
}
return gameState;
}
socket.on('gameState', (gameState) => {
gameState = predictGameState(gameState);
updateGameScreen(gameState);
});
이렇게 구현하면 서버로부터 새로운 게임 상태를 받기 전에 클라이언트에서 먼저 예측하여 화면을 업데이트할 수 있습니다.
결론
이번 포스팅에서는 Socket.io를 이용하여 실시간 멀티플레이어 기능을 구현하는 방법을 살펴보았습니다. 방 생성 및 참여, 게임 상태 동기화, 플레이어 입력 처리, 그리고 네트워크 지연 보정까지 다루었습니다.
다음 포스팅에서는 프론트엔드 개발에 대해 더 자세히 다루겠습니다. Canvas를 이용한 게임 렌더링과 사용자 인터페이스 디자인에 대해 알아보겠습니다. 감사합니다!
관련 포스팅
결과물: 지렁이 게임 멀티 6인용(ver. 2.0) – CSAI
1. 프로젝트 개요 및 기획(지렁이 게임 온라인) – CSAI
2. 게임 로직 설계(지렁이 온라인 게임) – CSAI