본문 바로가기

프로젝트/팀 프로젝트

230602~230626_Team project_node.js 그림 일심동체 게임제작

728x90

 

 

 


개요

6월 2일부터 3주간의 프로젝트가 시작되었다. 이번 프로젝트의 요구사항은 다음과 같다.

[요구사항]
어드민 페이지 (유저 관리 페이지)
로그인, 회원가입
게시판 글 등록
댓글, 대댓글 작성 가능
게시판 조회수
좋아요 기능
실시간 고객 센터 문의 가능 (socket 사용)
ERD구성
API문서 작성 (추천 Swagger)
마이페이지 유저 프로필 (이미지 포함) 수정
AWS 페이지 배포 (개발 하면서 중간중간 배포)
게시글 통계 페이지 (그래프 추가)

 해당 내용을 바탕으로 만들고자 하는 페이지의 주제를 정하고자 하였다.

 

 

 


기획과정

 처음에 페이지를 만들고자 했을 때 생각나는 페이지는 SNS였다. 사실 예전부터 node를 배우고 나서 지금 우리가 배운 내용을 바탕으로 인스타그램 같은 SNS를 만들 수 있을것이라고 많이 생각을 했었기 때문에 불현듯 떠오르긴 했지만, 이 생각은 어느 누구나 할 수 있을 것이라는 생각이 들어 좀 더 참신한 주제로 제작을 했으면 했다.

 그렇게 주제를 정하던 중, 캐치마인드에 대한 얘기가 나왔고 이 게임을 만들 수 있지 않을까? 라는 생각으로 우리 조의 프로젝트는 시작되었다. 캐치마인드는 주어진 제시어를 가지고 그림을 그리면 같은 게임에 참가중인 사람이 실시간으로 그림을 그리는 과정을 보며 정답을 유추하는 게임이라, 그림판 기능을 구현할 수 있다면 그림을 그리는 과정을 socket으로 보여줄 수 있을 것이라고 생각했다.

 캐치마인드라는 의견이 나오면서 우리는 한 발짝 더 앞서가, 요즘 더 유행하는 게임인 '갈틱폰'을 참고하여 게임을 만들면 어떻겠냐는 의견이 나왔고, 이 의견으로 우리게임은 캐치마인드에서 갈틱폰으로 완전히 정착하게 되었다. 캐치마인드의 경우에는 개발자가 이미 여러개의 제시어를 입력해놓고 그 제시어를 랜덤으로 뿌려주어, 플레이어가 순서대로 그림을 그리고, 그 그림에 대한 정답을 맞추는 게임이지만, 갈틱폰은 제시어 자체를 해당 게임방에서 게임을 플레이하고 있는 유저가 제시하며, 해당 제시어를 랜덤으로 받아 그 제시어를 유추할 수 있게끔 그림을 그리고, 또 해당 그림을 보고 제시어를 유추하며 정답을 입력한다. 최종적으로 최초로 입력한 제시어부터 그림, 또 그에대한 정답을 순차적으로 보여주어 각자가 입력한 정답과 그림으로 처음의 제시어에서 얼마나 달라졌는지를 확인하는 게임이다.

 해당 게임을 기준으로 페이지를 제작하고, 위에서 언급한 요구사항을 충족시키며 페이지를 제작하고자 하였다.  따라서 우리 사이트의 페이지는  크게 메인페이지, 회원가입 페이지, 관리자 페이지, 게시판 페이지, 게임 페이지로 나누어 작업을 진행했다.

 아래 그림은 제작을 진행하기 전, 페이지에 대한 간단한 스케치이다.

 메인페이지이다.

 로그인하기 전으로, 좌측에는 로그인을 하기위한 공간과, 회원가입 버튼을 클릭하면, 회원가입 페이지로 넘어간다.

 상단의 로고를 클릭하면 다시 메인페이지로 넘어간다. 해당 로고는 게임이 진행되는 페이지를 제외한 모든 페이지에 노출된다.

 명예의 전당은 가장 인기많은 그림이 노출되고, 스와이퍼 형식으로 노출된다.

명예의 전당 밑에는 게시글을 확인할 수 있는 영역이고 해당 영역을 이용하여, 게시판 페이지로 이동한다.

 유저 랭킹은 유저의 exp를 기준으로 삼아 유저를 순서대로 노출한다.

 메인페이지이다.

 로그인을 했을 경우에 나타나며, 이전 페이지와 달라진 점은, 로그인을 하면 해당 계정의 정보를 담아 좌측에 정보를 띄워주고, 마이페이지로 이동할 수 있는 버튼이 생성된다.

 

 

 

 

 게시판 페이지이다.

 작성된 게시글 리스트를 확인할 수 있다.

글쓰기 버튼을 클릭하면 게시글을 작성할 수 있는 페이지로 넘어가며, 페이지네이션이 있어, 게시글을 일정개수만큼 확인할 수 있다.

 

 

 

 

게시글 상세페이지이다.

해당게시글의 제목, 내용, 사진 등을 확인할 수 있다.

좋아요 개수, 조회수 등을 확인할 수 있으며, 댓글과 대댓글을 작성할 수 있다.

본인이 작성한 게시글은 수정작업이 가능하다.

글목록 페이지로 이동이 가능하다.

 

 

게시글 작성페이지이다.

제목, 이미지, 내용을 입력하여 게시글을 작성할 수 있다.

 

 

 

 

 

 회원가입 페이지이다.

 아이디, 비밀번호, 비밀번호 확인, 닉네임을 입력하여 회원가입을 진행할 수 있다.

아이디, 닉네임은 중복확인을 진행하며, 비밀번호는 비빌번호 확인과정을 거쳐 회원가입을 진행한다.

 

 

 

마이페이지 이다.

프로필 이미지와 닉네임을 변경할 수 있다.

 

 

 

 

 

 게임화면이다.

 게임방이 생성되면 게임방 영역에 하나씩 게임방이 늘어나고, 게임방 목록 밑에는 채팅영역이 있다.

우측에는 현재 접속 중인 유저 목록이 있고, 유저목록 밑에는 현재 로그인한 계정의 유저 정보가 노출된다. 해당 영역에는 홈으로 버튼이 있어, 메인페이지로 이동이 가능하다.

우측 상단의 방만들기 버튼을 이용하여 게임방을 생성할 수 있다. 방을 생성한 후, 방장의 경우 팝업창에서 게임 시작하기 버튼과 나가기 버튼으 활성화되고, 참여 유저는 나가기 버튼이 활성화된다.

 

 게임 진행 중 가장 첫 제시어를 입력하는 화면이다.

 좌측 상단에는 해당 게임방의 총인원과 각 라운드별로 입력이 완료된 인원이 표시된다.

 우측 상단에는 각 라운드별로 남은 시간이 표시된다.

 제시어 입력란으로 가장 처음의 제시어를 입력한다.

 

 

 게임 진행 화면 중, 넘겨받은 제시어에 맞춰 그림을 그리고, 넘겨받은 그림에 맞춰 정답을 입력하는 화면이다. 이 두 화면은 각각 다른 화면이지만 편의상 같이 스케치 하였다.

 가운데에 그림 그리는 곳은 마우스로 그림을 그릴 수 있는 영역이며, 색과 브러쉬 사이즈를 선택할 수 있도록 제작할 예정이다.

 

 

 

 리플레이 화면이다.

 좌측에는 해당 게임방에 참여한 유저의 목록이 나타난다.

우측에는 좌측의 플레이어 이름을 누르면, 해당 플레이어가 입력한 첫 제시어부터, 그에대한 그림, 정답이 순서대로 재생된다.

 

 

 이 페이지 중에서, 나는 이전 팀플을 진행했을 때 게임 부분을 맡아보질 않아 게임을 제작하고 싶었다. 때문에 그림을 그리고 제시어를 입력하는 게임 부분을 맡아 제작하게 되었고, 내가 제작한 부분을 위주로 프로젝트를 설명하고자 한다.

 

 

 

 


제작과정

 먼저, 그림을 그리는 기능과, 그 그림을 비디오로 재생이 가능한지의 여부를 확인해야 했다. 확인해보니, canvas를 활용하면 웹페이지로 그림을 그릴 수 있고, 그 그림을 그리는 과정을 녹화하여 재생이 가능하다고 해서 그 기능을 먼저 파악해야 했다. 이 과정을 통해 canvas에 대해 알게 된 점을 몇 가지 작성하고자 한다.

 

💡 Canvas의 사이즈는 js에서 조정해야 한다!

그림을 그릴 때, 마우스와 canvas상에서의 좌표가 맞지 않을 수 있으므로, js에서 사이즈를 설정하도록 한다.

const canvas = document.getElementById("canvas");

canvas.width = 700;
canvas.height = 700;

 

💡 captureStream 메소드와 MediaRecorder메소드

  • captureStream: canvas 요소의 현재 상태를 스트림 형태로 캡쳐한다.
  • MediaRecorder: 웹 브라우저 상에서 미디어 스트림을 녹화하고 저장하는 메소드이다.
    • 해당 메소드를 사용하기 위해서는 녹화할 미디어 스트림(예. captureStream)을 제공해야 한다.

 

💡 Canvas에서 녹화된 데이터는 Blob 형식으로 제공된다.

  • MediaRecoder 객체에서 데이터를 사용할 수 있을 때마다 mediaRecorder.ondataavailable을 사용하여 녹화된 미디어 데이터를 나타내는 Blob 객체를 반환한다.
  • 해당 데이터를 배열에 순서대로 push하여, video에서 재생하고자 할때 createObjectURL로 그림에 대한 데이터를 담은 URL을 생성한다.

 

 테스트 시, 그림 데이터를 배열에 담아 해당 데이터로 URL을 생성하면 해당 그림을 비디오로 확인할 수 있었으니, 우리가 원하는 게임을 제작할 수 있다는 확인을 받은 것이다. 그래서 본격적인 작업을 진행하고자 하였다.

 

 먼저 게임 대기방 페이지를 제작하였다. 내가 관여한 부분은 게임방을 생성하면 해당 정보다 db에 저장되고, 생성된 게임방이 노출되도록 구성하였다. 또, 현재 로그인한 유저의 정보를 보여줄 수 있도록 제작하였다. 먼저 게임방 생성 부분에 대해 설명하고자 한다.

서버단과 클라이언트단의 연결을 위해서 클라이언트 단에서는 axios를 사용해야 했다. axios를 사용하기 위해서 클라이언트 단의 상단(style 위)에 해당 코드를 넣어주어야 한다.

<script src="https://unpkg.com/axios/dist/axios.min.js"></script>

axios를 사용할 때는 내가 원하는 동작을 하고있는 함수의 router에서 설정한 주소를 입력해야한다. 주소 입력 시, cors에서 입력한 도메인 주소는 생략하고 그 뒤부터 입력을 해주면 된다. (예. 완전한 URL - http://127.0.0.1:8080/gameready/hi => /gameready/hi)

 아래의 코드는 db에 저장된 방 중 방의 상태를 나타나는 play 컬럼의 값이 2(게임종료)상태가 아닌 방을 제외하고 클라이언트단에 방을 그려준다. 해당 방의 리스트는 socket으로 동시에 생성된 방을 확인할 수 있고, 방을 선택하면 게임방장은 방에 들어온 유저를 socket으로 확인할 수 있다.

 

서버 단 코드

// 만들어진 게임방을 보여주는 함수
exports.RoomViews = async (req, res) => {
    try {
        const data = await Room.findAll({where: { play: {[Op.ne] : 2} }});
        for (const el of data) {
            //-------- 방장의 이름을 보여주는 구문 ------------------------------------------------
            const userId = el.dataValues.room_manager;
            const userleader = await User.findOne({ attributes: ['username'], where: { id: userId } })
            const userName = userleader.dataValues.username;
            el.dataValues.room_manager = userName;

            //-------- 게임 대기 팝업에서 멤버의 이름을 보여주는 구문 (여기가 아닌, 방을 선택하는 팝업에서 보여야 한다.)-------------------------------
            const usermem = el.dataValues.users_in_room;        // db의 멤버 값을 받는다.
            if (usermem == null) {          // 들어온 멤버가 없으면
            }
            else {      // 들어온 멤버가 있으면
                const usermem2 = usermem.indexOf(",")

                if (usermem2 == -1 && usermem2 != null) {   // 들어온 유저가 1명이면
                    const RoomMem = await User.findOne({ attributes: ['username'], where: { id: usermem } })
                    const RoomMem2 = RoomMem.dataValues.username;
                    el.dataValues.users_in_room = RoomMem2;

                }
                else if (usermem2 != -1 && usermem2 != null) {  // 들어온 유저가 여러명이면

                    let roommem = usermem.split(',');   // 현재 방의 유저의 아이디 값을 배열로 담는다.

                    let roommem3 = [];      // 멤버의 네임 값을 받을 배열
                    for (const elm of roommem) {
                        const roommem2 = elm;
                        let finalMem = "";  // 멤버의 이름값을 받을 strig객체. el에 들어갈 값
                        const RoomMem = await User.findOne({ attributes: ['username'], where: { id: roommem2 } })
                        const userName2 = RoomMem.dataValues.username;
                        roommem3.push(userName2);
                        finalMem = roommem3.toString();
                        el.dataValues.users_in_room = finalMem;
                    }
                }
            }
        }
        res.json(data);

    } catch (error) {
        console.log(error)
    }
}


// 게임방을 선택하는 함수
exports.RoomChoice = async (req, res) => {
    try {
        const { id } = req.body;
        const { decode } = req;

        const room = await Room.findOne({where : {id : id}});
        
        const currentMem = room.users_in_room; // 현재 users_in_room 값 가져오기
        const newMem = [];
        if(currentMem != null){
            const isuser = currentMem.indexOf(decode.id);
            if(isuser == -1){
                newMem.push(currentMem);
                newMem.push(decode.id);    // 새로운 title 값 생성
                const newMem2 = newMem.toString();
                await Room.update({users_in_room : newMem2}, {where : {id : id}});
            }
        }
        else{
            newMem.push(decode.id);    // 새로운 title 값 생성
            const newMem2 = newMem.toString();
            await Room.update({users_in_room : newMem2}, {where : {id : id}});
        }
            
    } catch (error) {        
        console.log(error)
    }
}

 

클라이언트 단 코드

async function RoomView() {
        try {
            const data = await axios.get('/gameready/hi', {
                withCredentials: true
            })
            const roomArr = data.data;
            const roomList = document.querySelector('.roomList');
            roomArr.forEach((el, index) => {
                // el의 play값을 받아, 해당 방이 게임대기인지, 게임중인지를 표시한다.
                const play2 = "";
                if (el.play == '0') {
                    play2 = "시작 대기 중"
                }
                else if (el.play == '1') {
                    play2 = "게임 중"
                }
                roomList.innerHTML +=
                    `<div class="roomlists">
                        <div class="room_num">${el.id}</div>
                            <div class="indoor">
                                <div class="room_manager">${el.room_manager}</div>
                                <div class="room_play">${play2}</div>
                            </div>
                            <div class="door">
                                
                                <div class="room_title">${el.title}</div>
                            </div>
                        </div>`
                //----방을 선택해서 참가하는 함수 ---------------------------------------------------------------------
                let rooms = document.querySelectorAll(".roomlists");
                rooms.forEach((el2) => {
                    el2.onclick = () => {
                        const num = el2.querySelector('.room_num').textContent;
                        const title = el2.querySelector('.room_title').textContent;
                        const play = el2.querySelector('.room_play').textContent;

                        axios.post('/gameready/roomchoice', { id: num }, {
                            withCredentials: true,
                            "Content-Type": "application/json"
                        }).then((e) => {

                        }).catch((err) => {
                            console.log(err);
                        })
                        const popReady = document.querySelector('.roompopup_ready');
                        popReady.classList.toggle('pop');
                        popReady.classList.toggle('test')
                        popReady.innerHTML = `
                        <div class="roomTitle">${title}</div>
                        <div class="roomBtn_member pop">
                            <div class="roomExit">나가기</div>
                        </div>
                        `
                        document.querySelector('.roomExit').onclick = () => {
                            popReady.classList.toggle('pop')
                        }

                    }
                });
            }

            );
            const roomNumElements = document.getElementsByClassName('room_num');
            Array.from(roomNumElements).forEach((element) => {
                element.addEventListener('click', () => {
                    const roomNum = element.textContent;
                    nowRoomNUm=roomNum;
                    joinRoom(roomNum);
                   
                 
                })
            })
        } catch (error) {
            console.log(error);
        }
    }
    RoomView();

 

 아래의 코드는 현재 로그인한 유저의 정보를 확인할 수 있는 코드이다.

서버 단 코드

exports.UserView = async (req, res) => {
    const { decode } = req;
    res.json(decode);
}

 

클라이언트 단 코드

    axios.get('/gameready/userInfo', {
        withCredentials: true
    }).then((e) => {
        const userInfo = e.data;
        const myProf = document.querySelector('.myProfile2');
        myProf.innerHTML = `
            <img class="pro_img" src="${userInfo.profile_img}" alt="유저프로필">
            <div class="profile3">
                <div class="pro_name">${userInfo.name}</div>
                <div class="pro_exp">${userInfo.experience}</div>
                <a href="main.html">홈으로</a>
            </div>
        `
    }).catch((err) => {
        console.log(err)
    })

 

그 후 게임 페이지를 제작하였다. 게임은 제시어를 입력하고, 그림을 그리고, 다시 정답을 입력하고, 그림을 그리고, 최종정답을 입력하는 순으로, 총 5라운드로 진행이 된다. 게임의 로직을 설계하기 위해 게임은 다음과 같은 과정을 진행이 되었다.

    const startGame = () => {
        if (gamestep === 1) {
            // 첫 번째 제시어를 입력받는다.
            StepOne();
        }
        else if (gamestep === 2) {
            // 첫 번째 제시어로 그림을 그린다.
            setTimeout(() => {
                StepTwo();
                console.log("조금 기다려");
            }, 1000);
        }
        else if (gamestep === 3) {
            // 그린 그림을 보고 제시어를 입력한다.
            setTimeout(() => {
                StepThree();
                console.log("조금 기다려");
            }, 1000);
        }
        else if (gamestep === 4) {
            // 입력한 제시어로 그림을 그린다.
            setTimeout(() => {
                // StepFour();
                StepTwo();
                console.log("조금 기다려");
            }, 1000);
        }
        else if (gamestep === 5) {
            // 최종적으로 그린 그림으로 정답을 입력한다.
            setTimeout(() => {
                StepFive();
                console.log("조금 기다려");
            }, 1000);
        }
        else {
            setTimeout(() => {
                gameEnd();
                console.log("조금 기다려");
            }, 1000);
        }
    }
    startGame();

 

 게임 코드 중, 그림을 입력하는 단계인 StepTwo와 그려진 그림을 보여주고 정답을 입력하는 StepThree만 코드를 확인하고자 한다.

 

서버 단 코드

//제시어를 보여주는 함수
exports.QuestionView = async (req, res) => {
    try {
        const { room, decode } = req;
        const isroomQue = await Question.findAll({ where: { room_primaryKey: room.id } })
        const roomManager = room.room_manager;
        const usersInRoom = room.users_in_room.split(",").map(Number);

        const questionData = isroomQue
            .map((i) => {
                const id = i.dataValues.id;
                const content = i.dataValues.content;
                const user_primaryKey = i.dataValues.user_primaryKey;
                return [id, content, user_primaryKey];
            })
            .sort((a, b) => {
                const aIndex = usersInRoom.indexOf(a[2]);
                const bIndex = usersInRoom.indexOf(b[2]);

                if (a[2] === roomManager) return -1; // room_manager를 가장 앞에 위치
                if (b[2] === roomManager) return 1; // room_manager를 가장 앞에 위치

                return aIndex - bIndex; // users_in_room 순서대로 정렬
            });


        const resultData = { questionData: questionData, userID : decode.id};

        res.send(resultData);
    } catch (error) {
        console.log(error);
    }
}


// 그린 그림을 db에 저장한다.
exports.DrawingAdd = async (req, res) => {
    try {
        const { room, decode } = req;
        // console.log("지금 방 정보를 확인할 수 있다고???",room)

        const videoData = req.file.buffer;
        const drawing = await Drawing.create({
            content: videoData,
            user_primaryKey: decode.id,
            room_primaryKey: room.id
        });
        const lastDrawing = drawing.id;
        const drawing2 = await Drawing.findOne({where : {id : lastDrawing}});
        // res.sendStatus(200);
        res.status(200).json({ drawing, lastDrawing });

    } catch (error) {
        console.log(error);
        res.sendStatus(500);
    }
}


// 그림 저장 시, 저장한 그림의 id 값을 question테이블에 저장하는 함수
exports.DrawQueUpdate = async (req, res) => {
    try {
        const { decode, room } = req;
        const { painter, lastDrawing, viewIndex } = req.body;


        // 해당룸, 해당유저의 row를 찾아 제시어에 추가함
        const Insert = [];
        Insert.push(painter);
        Insert.push(lastDrawing);
        const queNum = viewIndex[0];
        const queContent =[];
        queContent.push(viewIndex[1]);
        queContent.push(Insert.toString());

        await Question.update({content: queContent.toString()}, {where: {id: queNum}})
        res.send();


    } catch (error) {
        console.log(error)
    }
}

 

클라이언트 단 코드

 // 비디오 녹화 시작
    async function startRecording() {
        const canvasStream = canvas.captureStream();
        mediaRecorder = new MediaRecorder(canvasStream, { mimeType: 'video/webm' });

        mediaRecorder.ondataavailable = (e) => {
            recordedFrames.push(e.data);
        };

        mediaRecorder.onstop = () => {
            // stop() 메서드 호출이 완료된 후에 실행되는 비동기 작업 처리
            clearCanvas();
            createVideoURL().then(() => {
            }).catch((error) => {
                console.log(error);
                // 에러 처리
            });
        };
        mediaRecorder.start();
    }



// 녹화를 중지하는 함수
async function stopRecording() {
    console.log("stopRecording");
    mediaRecorder.stop();
}

// 비디오 url을 생성하는 함수
    async function createVideoURL() {

        try {
            const videoBlob = new Blob(recordedFrames, { type: 'video/webm' });
            // FormData 객체를 사용하여 videoBlob을 전송하도록 수정
            const formData = new FormData();
            formData.append('file', videoBlob);

            const response = await axios.post('/gameplay', formData, {
                withCredentials: true,
                "Content-Type": "multipart/form-data"
            });
            const { drawing, lastDrawing } = response.data;
            anggimo = lastDrawing;

            await axios.post('/gameplay/DrawQueUpdate', { painter: drawing.user_primaryKey, lastDrawing: lastDrawing, viewIndex: viewIndex }, {
                withCredentials: true
            });


        } catch (error) {
            console.log(error);
        }
    }







//-----------게임 로직에 따른 라운드 별 함수 --------------------------
const StepTwo = async () => {
        let time = 15;
        await axios.get('/gameplay/QuestionView', {
            responseType: 'json',
            withCredentials: true
        }).then((e) => {
            // 제시어가 배열형태로 불러와진다. 순서는 room에 들어온 유저 순서대로 [제시어id, 제시어, 유저id]가 불러와짐
            const MyID = e.data.userID;
            const questions = e.data.questionData;


            const MyIndex = questions.findIndex((i) => i[i.length - 1] == MyID);
            const lastIndex = questions.length - 1

            // 2단계이면
            if (gamestep === 2) {
                getNextUser(MyIndex,questions);
                startRecording()
                    viewIndex = questions[targetIndex];
                    document.querySelector('.view_question').innerHTML = viewIndex[1];
                
        
            }
            // 4단계이면
            else if (gamestep === 4) {
                getNextUser(MyIndex,questions) 
                startRecording()
                const isTarget = questions[targetIndex];

                viewIndex = isTarget;


                let isContent = isTarget[1];
                isContent = isContent.split(',')
                const isQue = isContent[isContent.length - 1];
                document.querySelector('.view_question').innerHTML = isQue;
            }


        }).catch((err) => {
            console.log(err)
        })

        let DrawingTimer = setInterval(async () => {
            document.querySelector('.draw_time').innerHTML = time + "초";
            time--;
            if (time == 0) {
                // 그린 그림이 db에 저장된다.
                clearInterval(DrawingTimer);

                await stopRecording();
                document.querySelector('.draw_time').innerHTML = "시간종료";
                //--------------------------------------------------------------------

                if (gamestep === 2) {
                    document.querySelector(".drawings").classList.toggle('pop');
                    document.querySelector(".questions").classList.toggle('pop');
                    document.querySelector(".question1").classList.toggle('pop');
                    document.querySelector(".question2").classList.toggle('pop');
                    gamestep = 3;
                    startGame();
                }
                else if (gamestep === 4) {
                    document.querySelector(".drawings").classList.toggle('pop');
                    document.querySelector(".questions").classList.toggle('pop');
                    gamestep = 5;
                    startGame();
                }

            }
        }, 1000);
    }





    const StepThree = () => {
        // myindex-2 제시어의 마지막 그림을 노출시킨다.

        // 내 아이디 값
        let MyID;
        // 타겟 제시어 아이디 값
        let targetID;

        axios.get('/gameplay/QuestionView', {
            responseType: 'json',
            withCredentials: true
        }).then((e) => {
            MyID = e.data.userID;
            const questions = e.data.questionData;

            const MyIndex = questions.findIndex((i) => i[i.length - 1] == MyID);
            const lastIndex = questions.length - 1
            getNextUser(MyIndex,questions)

            viewIndex = questions[targetIndex];

            let viewContent = viewIndex[1];
            targetID = viewIndex[0];
            viewContent = viewContent.split(',');
            const isDraw = viewContent[viewContent.length - 1]
            playVideo(isDraw);
        })

        let time = 20;
        let QuestionTimer = setInterval(() => {
            document.querySelector('.time').innerHTML = time + "초";
            time--;

            if (time < 0) {
                clearInterval(QuestionTimer);
                document.querySelector('.time').innerHTML = "시간종료";
                document.querySelector(".drawings").classList.toggle('pop');
                document.querySelector(".questions").classList.toggle('pop');

                const queInput = document.getElementById('question');
                const queInputV = queInput.value;
                TwoQuestionInput(MyID, queInputV, targetID);
                //--------------------------------------------------------------------
                gamestep = 4;
                startGame();
            }
        }, 1000);
    };

 

 

 


이슈사항

 

❗ createObjectURL메소드로 생성한 URL을 다른 window에서 사용불가

 canvas에서 생성한 데이터를 녹화하여 createObjectURL를 사용해 녹화된 영상을 socket으로 게임을 참여한 각 유저에게 보여주기 위해 생성한 url을 db에 담아 get으로 꺼내쓰려고 하였으나, 다른 window의 document에서는 접근을 할 수 없는 이슈가 있었다.
 canvas의 데이터를 db에 Blob 형식으로 담아, canvas의 그림그리는 과정을 담은 영상을 보여주기 위한 playVideo함수를 실행했을 때 해당 데이터를 꺼내 createObjectURL을 사용하여 비디오를 재생시켰다.

 

❗ await 등의 비동기를 무작위로 작성하여 데이터를 불러오는 순서에 오류 발생

 게임 코드 개발 시, 수업 때 사용했던 대로 axios 등을 사용할 때 async를 사용하던 것이 버릇이 들어, 무분별하게 async await 구문을 남발했었다. 결국 혼자 해결하진 못했지만 다음에는 고려해서 깔끔하게 코드를 짜보도록 할 것이다.


결과물

🎨 회원가입, 승인

 

🎨 로그인, 마이페이지

 

🎨 게시글_등록, 수정, 좋아요, 댓글, 대댓글

 

🎨 게시글_페이지네이션, 남의 글 수정 불가

 

🎨 게임방 생성, 채팅

 

🎨 게임진행

 

728x90