프로젝트 병합

  • 현재 프로젝트에 파일이 많아 병합하는 과정에서 오류가 자주 뜬다

UserInterFace 연결

  • 클라이언트 game.js에서 유저의 점수, 골드를 내부에서 관리하였는데,
    이를 사용자에게 보여주기위해 새로운 userInterface.model 을 만들면서 get/set 오류가 있었다

  • 콘솔 로그를 보며 관련 코드들을 함수를 import하여 수정하는 것으로 해결하였다

/* 이전 */
// 점수 바꾸기
if(Object.keys(Monsters.getInstance().getInfo()).length !== 0){
score = Monsters.getInstance().getInfo().score;
gold = Monsters.getInstance().getInfo().gold;
monsterLevel = Monsters.getInstance().getInfo().wave;
}
/* 이후 */
import { setScore, setUserGold } from "./model/userInterface.model.js";
// 점수 바꾸기
if(Object.keys(Monsters.getInstance().getInfo()).length !== 0){
setScore(Monsters.getInstance().getInfo().score);
setUserGold(Monsters.getInstance().getInfo().gold);
monsterLevel = Monsters.getInstance().getInfo().wave;
}

Chat Socket 공유

멀티 플레이

연결 해제

  • 현재 유저가 연결을 끊으면(새로고침, 페이지 나가기) 아무 일도 일어나지 않아 기존의 방이 남아있는 오류가 있다

  • 이를 해결하기 위해 socket.id 를 통해 유저 및 유저가 참여한 방을 삭제할 수 있도록 반영해야겠다

//연결이 끊길 시
export const handleDisconnect = (socket, io) => {
    // 유저 존재 확인
    const user = deleteUser(socket.id)
    let room = getRoom(user.userId);
    let destroyed = null;
    // 참여 방 확인 + 게임 시작 여부 확인으로 방 삭제 혹은 나가기
    if (room) {
        if (room.startTime > 0) {
            destroyed = destroyRoom(user.userId)
            io.to(room.gameId).emit('leaveRoom', { roomId: destroyed.gameId })
        }
        else {
            room = leaveRoom(room.gameId, user.userId)
            io.to(room.gameId).emit('room', { room })
        }
    }
}
// 사용된 user.model 함수
export const deleteUser = (socketId) => {
    const idx = users.findIndex((e) => e.socketId === socketId)
    // splice로 인해 배열형태로 반환되는 것을 다시 객체 형태로 변환
    if (idx !== -1 ) return Object.assign(...users.splice(idx, 1))
    else return false
}
// 사용된 gameRoom.model 함수 
export const destroyRoom = (userId) => {
    const roomIdx = gameRooms.findIndex((e) => e.userId1 === userId || e.userId2 === userId)
    if (roomIdx) return Object.assign(...gameRooms.splice(roomIdx, 1))
    else return false
}
  • 이제 leaveRoom 을 받은 클라이언트는 게임도 종료할 수 있도록 수정!
/* lobby.js = 클라이언트 플레이어 화면*/
let game = null

// 대기방 나가기
export const exitRoom = () => {
    selectedRoom = null
    roomId = null
    waitRoom.hide()
    gameOver()
    // 방 대기열 요청
    sendEvent(1002);
}

// 게임 시작 
export const gameStart = () => {
    waitRoom.hide();
    import("./src/game.js").then( module => {
        game = module
    });
    gameFrame.style.display = "block"
}

// 게임 오버,끝
export const gameOver = () => {
    game = null
    gameFrame.style.display = "none"
}

/* socket.js = 웹소켓 통신*/
// 방이 파괴되었을 시 
socket.on('leaveRoom',(data) => {
  roomId = null
  exitRoom()
  socket.emit('leaveRoom',{ roomId: data.roomId })
})

/* leaveRoom을 다시 받은 서버*/
// 삭제된 방에서 일괄 나가기
socket.on('leaveRoom', (data) => socket.leave(data.roomId))

닉네임 설정

  • 현재 방에 입장하면 아이디를 보여주는 것을 닉네임을 보여주는 형식으로 변경해준다!
// 방 입장 시 서버에서 진행되는 핸들러
export const enterRoom = (userId, payload, socket) => {

    /* 검증 로직 생략 */

    let room = getRoom(userId)
    // 아이디 대신 닉네임으로 변환
    room = { ...room, userId1: getUser(room.userId1).nickname, userId2: getUser(room.userId2)?.nickname }

    // 클라이언트에겐 변환된 값을 전달하여 id 대신 닉네임이 보이도록 조절
    socket.to(room.gameId).emit('room', room)
    socket.join(room.gameId)

    return { status: "success", room }
};
  • 또한 방의 유저값들이 업데이트 되는 부분들을 전부 수정해준다!
    ( 참가자가 나갈 때 )
// 연결이 끊기면
export const handleDisconnect = (socket, io) => {
    /* 경우의 수에 따른 로직 생략 */
    // 방에서 참가자가 떠나게 되었을 때
    left = leaveRoom(room.gameId, user.userId)
    if (left) {
        // 남아있는(호스트) 인원의 아이디를 닉네임으로 변환
        left.userId1 = getUser(left.userId1).nickname
        // 업뎃 정보 공유
        io.to(room.gameId).emit('room', left )
        // 참가자가 나갔을 시 참가자만 제외
        socket.emit('leaveRoom', { roomId: room.gameId })
        // 호스트가 나갈 시 + 오류 시 인원 전부 삭제하도록 요구
    } else io.to(room.gameId).emit('leaveRoom', { roomId: room.gameId })
}

// 나가기 또는 강퇴 당할 시
export const exitRoom = (userId, payload, socket, io) => {
    const room = leaveRoom(payload.roomId, userId)
    if (room) {
        room.userId1 = getUser(room.userId1).nickname
        // 업뎃 정보 공유
        io.to(payload.roomId).emit('room', room )
        // 참가자가 나갔을 시 참가자만 제외
        socket.emit('leaveRoom', { roomId: payload.roomId })
    // 호스트가 나갈 시 + 오류 시 인원 전부 삭제하도록 요구
    } else io.to(payload.roomId).emit('leaveRoom', { roomId: payload.roomId })

    return { status: "success" }
}
export const kickUser = (userId, payload, socket) => {
    const room = kick(payload.roomId)
    // 참가자에게 방을 나가도록 요구
    socket.to(payload.roomId).emit('leaveRoom', { roomId: payload.roomId })
    // 닉네임 변환
    room.userId1 = getUser(room.userId1).nickname
    // 업데이트된 값 적용
    socket.emit('room', { room })

    return { status: "success" }
}

오류 발생!

  • 위의 과정을 수행하다 서버의 id까지 오염되는 오류를 확인했다

  • 콘솔로그를 통해 파고들어가본 결과 유저가 나갔을 때(leaveRoom 함수)가 문제인 것 같았다

// 원래 코드
export const leaveRoom = (gameId, userId) => {
    const roomIdx = gameRooms.findIndex((e) => e.gameId === gameId)
    // 방이 서버에 있는 확인
    if (roomIdx === -1) return false
    
    // 호스트가 나갈 시 방 삭제
    if (gameRooms[roomIdx].userId1 === userId) {
        gameRooms.splice(roomIdx,1)
        return false
    // 참가자가 나갈 시 userId2를 비우고 값을 반환
    } else if (gameRooms[roomIdx].userId2 === userId) {
        gameRooms[roomIdx].userId2 = null
        return gameRooms[roomIdx]
    } else return false  
}
  • 그래서 유심히 살펴보니 반환 값이 얕은 복사로 인해 반환된 값을 변경하면..
    서버 세션 값 까지 변경이 되버리는 것이다!
// 얕은 복사 방지
return {...gameRooms[roomIdx]}
  • 위는 오류 부분을 수정한 코드다

랭킹 기능 구현

  • 랭킹 기능을 구현하기 전 사용자에게 표현될 인터페이스를 만들어준다!

image

  • 빨간색 부분은 유저의 정보를, 노란색 부분엔 서버에 저장된 랭킹을 불러올 수 있도록 하였다

  • socket을 이용한 첫 연결 시, 유저 정보를 서버에서 읽어와 전달할 수 있도록 설계해주었다

// 첫 연결 시 
const loginUser = await prisma.users.findUnique({ where: { id: decoded.id } });
/* 검증 로직 생략*/

//유저와 연결되면 클라이언트에게 인터페이스 용 값 전달
socket.emit('connection', [loginUser.id, loginUser.nickname, loginUser.highScoreS, loginUser.highScoreM, rooms])
  • 원래 랭킹 기능을 더 구현할 예정이었으나 트러블 슈팅으로 인해 보류되었다

한줄 평 + 개선점

  • 하루마다 병합을 하면서 나온 오류들을 해결하는 과정을 너무 담지 못해서 아쉬웠다