본문 바로가기
카테고리 없음

til - 게임서버와 클라이언트 구현

by 젤러비 2024. 7. 9.

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;