본문 바로가기

All Categories/FE & javascript

개발일지 #2 - javascript 게임 제작 팀 프로젝트

반응형

빙고 게임

기본 코드

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        table {
            margin: auto;
        }

        td {
            width: 100px;
            height: 100px;
            border: 5px solid black;
            text-align: center;
        }
    </style>
</head>

<body>
    <table>
        <tr>
            <td id="1" onclick="colorChange(this)">수박</td>
            <td id="2" onclick="colorChange(this)">딸기</td>
            <td id="3" onclick="colorChange(this)">바나나</td>
            <td id="4" onclick="colorChange(this)">블루베리</td>
            <td id="5" onclick="colorChange(this)">복숭아</td>
        </tr>
        <tr>
            <td id="6" onclick="colorChange(this)">코코넛</td>
            <td id="7" onclick="colorChange(this)">용과</td>
            <td id="8" onclick="colorChange(this)">오렌지</td>
            <td id="9" onclick="colorChange(this)">사과</td>
            <td id="10" onclick="colorChange(this)">포도</td>
        </tr>
        <tr>
            <td id="11" onclick="colorChange(this)">귤</td>
            <td id="12" onclick="colorChange(this)">체리</td>
            <td id="13" onclick="colorChange(this)">석류</td>
            <td id="14" onclick="colorChange(this)">토마토</td>
            <td id="15" onclick="colorChange(this)">레드향</td>
        </tr>
        <tr>
            <td id="16" onclick="colorChange(this)">키위</td>
            <td id="17" onclick="colorChange(this)">한라봉</td>
            <td id="18" onclick="colorChange(this)">파프리카</td>
            <td id="19" onclick="colorChange(this)">레몬</td>
            <td id="20" onclick="colorChange(this)">망고</td>
        </tr>
        <tr>
            <td id="21" onclick="colorChange(this)">파인애플</td>
            <td id="22" onclick="colorChange(this)">배</td>
            <td id="23" onclick="colorChange(this)">감</td>
            <td id="24" onclick="colorChange(this)">자몽</td>
            <td id="25" onclick="colorChange(this)">오이</td>
        </tr>
    </table>
</body>
<script>
    let COLOR = "green";
    let BINGO_COLOR = "gold";

    function colorChange(element) {
        if (element.style.backgroundColor == COLOR) {
            element.style.backgroundColor = "white";
        } else if (element.style.backgroundColor != BINGO_COLOR) {
            element.style.backgroundColor = COLOR;
        }
        check();
    }
</script>

</html>

check()

function check() {
    widthBingo(); // 가로 빙고
    heightBingo(); // 세로 빙고 
    crossLineBingo(); // 대각선 빙고
}

widthBingo()

// 가로 빙고
function widthBingo() {
    for (let i = 0; i < 5; i++) {
        isBingo = true;
        let arr = [];
        for (let j = 1; j <= 5; j++) {
            let element = document.getElementById(String(i * 5 + j));
            arr.push(element);
            if (element.style.backgroundColor != COLOR && element.style.backgroundColor != BINGO_COLOR) isBingo = false;
        }
        if (isBingo) {
            for (let i = 0; i < 5; i++) {
                arr[i].style.backgroundColor = BINGO_COLOR;
            }
        }
    }
}

heightBingo()

// 세로 빙고
function heightBingo() {
    for (let i = 1; i <= 5; i++) {
        isBingo = true;
        let arr = [];
        for (let j = 0; j < 5; j++) {
            let element = document.getElementById(String(i + j * 5));
            arr.push(element);
            if (element.style.backgroundColor != COLOR && element.style.backgroundColor != BINGO_COLOR) isBingo = false;
        }
        if (isBingo) {
            for (let i = 0; i < 5; i++) {
                arr[i].style.backgroundColor = BINGO_COLOR;
            }
        }
    }
}

crossLineBingo()

// 대각선 빙고
function crossLineBingo() {
    // 왼 -> 오 대각선
    isBingo = true;
    arr = [];
    for (let i = 1; i < 26; i += 6) {
        let element = document.getElementById(String(i));
        arr.push(element);
        if (element.style.backgroundColor != COLOR && element.style.backgroundColor != BINGO_COLOR) isBingo = false;
    }
    if (isBingo) {
        for (let i = 0; i < 5; i++) {
            arr[i].style.backgroundColor = BINGO_COLOR;
        }
    }
    // 오 -> 왼 대각선
    isBingo = true;
    arr = [];
    for (let i = 5; i < 22; i += 4) {
        let element = document.getElementById(String(i));
        arr.push(element);
        if (element.style.backgroundColor != COLOR && element.style.backgroundColor != BINGO_COLOR) isBingo = false;
    }
    if (isBingo) {
        for (let i = 0; i < 5; i++) {
            arr[i].style.backgroundColor = BINGO_COLOR;
        }
    }
}

 


게임 제작 팀 프로젝트

자유 주제로 팀원들과 함께 게임을 만드는 프로젝트를 진행했습니다.

게임 제목: 초록네모의 놀라운 여정

개요: Javascript를 활용한 장애물 피하기 게임으로, 스페이스바를 누르면 게임이 시작되고 점프를 하여 빨간색 장애물을 피할 수 있습니다. 초기 생명 5개, 0점으로 시작하고, 장애물을 넘으면 10점씩 증가합니다. 초록네모가 땅에 닿아있는 상태일 때만 점프가 가능합니다. 50점을 달성할 때 마다 속도가 증가합니다. 장애물은 랜덤 간격, 크기로 등장합니다. 생명 5개를 모두 잃으면 게임이 종료됩니다.

참고 영상: 코딩애플 Youtube

기본 코드

<!DOCTYPE html>
<html>

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <div>
        <span>하찮은 목숨: <i id="life">5</i></span>
        <span style="margin-left: 10px;">점수: <i id="score">0</i></span>
        <h2 id="nemo">■ 초록네모의 놀라운 여정 ■</h2>
        <h3>스페이스를 눌러 게임을 시작! 5O점 달성할 때 마다 속도가 증가합니다.</h3>
        <canvas id="canvas"></canvas>
    </div>
</body>
<script>
    let canvas = document.querySelector('#canvas');
    let ctx = canvas.getContext('2d'); // context 란 뜻으로 ctx

    canvas.width = window.innerWidth - 100;
    canvas.height = window.innerHeight - 100;


    // let dinoImg = new Image();
    // dinoImg.src = 'dinosaur.png';
    let dino = {
        x: 10,
        y: 200,
        width: 40,
        height: 50,
        draw() {
            ctx.fillStyle = 'green';
            ctx.fillRect(this.x, this.y, this.width, this.height);
            // ctx.drawImage(dinoImg, this.x, this.y);
        }
    }

    class Cactus {
        constructor() {
            this.width = 20 + getRandomInt(0, 50);
            this.height = 30 + getRandomInt(0, 50);
            this.x = 500;
            this.y = 250 - this.height;
        }
        draw() {
            ctx.fillStyle = 'red';
            ctx.fillRect(this.x, this.y, this.width, this.height);
        }
    }

    let timer = 0;
    let cactusArr = [];
    let gameState = 0; // 0: end, 1: start
    let jumpState = 0; // 0: default, 1: jumping
    let jumpTimer = 0;
    let animation;
    let life = 5;
    let score = 0;
    let x = getRandomInt(80, 120);
    let lastTime = 0;

    document.addEventListener('keydown', keyDownHandler);
    function keyDownHandler(e) {
        if (e.code == 'Space') {
            if (gameState == 0) {
                gameState = 1; // 게임실행
                frameAction();
                document.querySelector('h2').style.display = 'none';
            } else if (gameState == 1) { // 게임실행중일때 스페이스누르면
                if (dino.y == 200) {
                    jumpState = 1; // 점프중으로 변경
                }
            }
        }
    }
</script>

</html>

dino 변수로 초록네모의 x, y 좌표, 너비, 높이를 관리하고, Cactus 클래스를 통해 장애물을 관리합니다. 이후 코드에서 장애물들을 배열로 관리하여 화면에 보여지도록 합니다.

keydown 이벤트리스너를 등록하여, 스페이스바가 눌렀을 때 게임을 실행하고, 게임이 실행되었을 때 dino가 점프할 수 있도록 상태를 변경합니다. 이 때 바닥 좌표(200)일 때만 점프가 가능하도록 구현했습니다.

frameAction()

function frameAction() {
    animation = requestAnimationFrame(frameAction);
    ctx.clearRect(0, 0, canvas.width, canvas.height);
    timer += 1;

    if (lastTime + x == timer) {
        let cactus = new Cactus();
        cactusArr.push(cactus);
        lastTime = timer;
        x = getRandomInt(80, 120);
    }

    cactusArr.forEach((a, i, o) => {
        if (a.x < 0) {
            o.splice(i, 1);
            score += 10;
            document.querySelector('#score').innerHTML = score;
        } else if (collisionDetection(dino, a) < 0) {
            o.splice(i, 1);
        }

        a.x -= 4 + Math.floor(score / 50) * 1;
        a.draw();
    })

    if (jumpState == 1) {
        jumpTimer++;
        dino.y -= 3;
    }
    if (jumpTimer > 40) {
        jumpState = 0;
        jumpTimer = 0;
    }
    if (jumpState == 0) {
        if (dino.y < 200) {
            dino.y += 3;
        }
    }

    drawLine();
    dino.draw();
}

timer 변수는 현재까지 지난 시간을 저장하는 변수입니다. lastTime 변수에 마지막 선인장이 나타난 시간을 저장하고, 랜덤 값 x를 더한 값과 비교하여 계속해서 새로운 장애물을 만들어 배열에 push 합니다.

배열 내부를 forEach문을 통해 반복하여 장애물을 넘었으면 점수를 10 증가하고, 부딪히면 collisionDetection 함수를 통해 충돌 처리를 해줍니다. a.x -= 4 + Math.floor(score / 50) * i를 통해 50점을 달성할 때 마다 1씩 속도를 증가시킵니다.

사용자가 스페이스바를 눌러 점프를 하면 jumpState 값이 1이 되고, jumpTimer 값이 40이 될 때 까지 dino의 y 좌표를 감소시켜 위로 올라가도록 합니다. jumpTimer의 값이 40에 도달하면 jumpTimerjumpState 값을 0으로 초기화하여, dino가 다시 바닥으로 향하도록 y 좌표를 바닥 좌표(200)까지 증가시킵니다.

getRandomInt(Number, Number)

function getRandomInt(min, max) {
    return Math.floor(Math.random() * (max - min + 1)) + min;
}

최솟값과 최댓값을 매개변수로 받아 최솟값 <= 값 <= 최댓값 사이의 랜덤 값을 도출합니다.

collisionDetection(공룡, 장애물)

function collisionDetection(dino, cactus) {
    let xValue = cactus.x - (dino.x + dino.width);
    let yValue = cactus.y - (dino.y + dino.height);
    if (xValue < 0 && yValue < 0) { // 충돌!
        // 충돌 시 실행되는 코드
        life--;
        document.querySelector('#life').innerHTML = life;
        if (life == 0) {
            alert('게임오버');
            gameState = 0;
            cancelAnimationFrame(animation);
            // ctx.clearRect(0, 0, canvas.width, canvas.height); // 작동이 안되서 새로고침으로 대체
            location.reload();
        }
        return -1;
    } else {
        return 1;
    }
}

dino와 장애물이 충돌 시 생명을 감소시키고, 0이 되었을 때 게임을 종료합니다.

drawLine()

function drawLine() {
    ctx.beginPath();
    ctx.moveTo(0, 250);
    ctx.lineTo(600, 250);
    ctx.stroke();
}

바닥에 그려지는 라인을 그리는 함수입니다.


배운 점

빙고판에서 가로, 세로, 대각선 한 줄이 모두 채워졌을 때(빙고가 되었을 때) 배경색을 바꿔 빙고임을 표시하는 코드를 구현했습니다. 각 테이블 아이템의 id의 값이 1부터 25까지의 5 X 5 빙고인 점을 참고하여 이중 for문을 활용하여 코드를 작성하였습니다.

가로 빙고 구현 시 (1, 2, 3, 4, 5) / (6, 7, 8, 9, 10) / ... / (21, 22, 23, 24, 25) 묶음이 각각 한 줄이 되기 때문에, 이를 식으로 나타내면 5 * 몫 + (1 ~ 5)가 됩니다. 이를 구현하기 위해 바깥 반복문의 변수를 0부터 4까지 증가시키고, 안쪽 반복문의 변수를 1부터 5까지 증가시켜 id가 i * 5 + j인 테이블 아이템이 선택되었는지 판단하였습니다. 한줄씩 배열에 넣고, 빙고라면 배경색을 변경하였습니다.

세로 빙고 구현 시 (1, 6, 11, 16, 21) / ... / (5, 10, 15, 20, 25) 묶음이 각각 한 줄이 됩니다. 이를 식으로 나타내면 (1 ~ 5) + (0 ~ 4) * 5가 됩니다. 바깥 반복문의 변수를 1부터 5까지 증가시키고, 안쪽 반복문의 변수를 0부터 4까지 증가시켜 id가 i + j * 5인 테이블 아이템이 선택되었는지 판단하였습니다.

대각선 빙고규칙성을 발견하여 반복문으로 작성하였습니다. 왼쪽 상단에서 오른쪽 하단으로 향하는 대각선의 id 값은 (1, 7, 13, 19, 25) 한 줄이기 때문에, 6씩 증가합니다. 오른쪽 상단에서 왼쪽 하단으로 향하는 대각선의 id 값은 (5, 9, 13, 17, 2) 한 줄로, 4씩 증가하는 특징을 발견할 수 있습니다. 따라서 이를 반복문으로 구현하여 빙고인지 판단하였습니다.

빙고를 구현하면서 반복문과 알고리즘 해결 능력을 키울 수 있었습니다. 또한 다른 풀이법도 익혔습니다. 간단하게 하나의 포문을 활용하여 다음과 같이 구현할 수 있습니다.

function widthCheck(num){
    const b =Math.floor((num-1)/5);
    console.log("b = " + b);
    let check = false;

    for(let i=0; i<25; i++){
        if(b == Math.floor(i/5)){
            console.log(Math.floor(i/5));
            if(document.getElementById(i + 1).style.backgroundColor != "blue"){
                check = true; //해당 줄에 "blue"가 아닌 것이 있다면 true 
            }
        }
    }
    if(!check){ // 해당 세로줄이 다 파란색으로 바뀌었다.
        for(let i=0; i<25; i++){
            if(b == Math.floor(i/5)){
                document.getElementById(i + 1).style.backgroundColor = "gold";
            }
        }
    }
}
function heightCheck(num){
    const a = num % 5;
    let check = false;

    for(let i=1; i<=25; i++){
        if(a == i%5){
            if(document.getElementById(i).style.backgroundColor != "blue"){
                check = true; //해당 줄에 "blue"가 아닌 것이 있다면 true 
            }
        }
    }
    if(!check){ // 해당 세로줄이 다 파란색으로 바뀌었다.
        for(let i=1; i<=25; i++){
            if(a == i%5){
                document.getElementById(i).style.backgroundColor = "gold";
            }
        }
    }
}

이를 통해 다양한 반복문의 활용법을 학습하고, 특정 수로 나눈 몫과 나머지를 활용하는 법을 익힐 수 있는 시간이었습니다.


마지막 기초 교육 시간으로, 팀원들과 javascript를 활용하여 자유 주제의 게임을 만드는 팀 프로젝트 시간을 가졌습니다.

1. 미로 찾기 2. 지뢰 찾기 3. 크롬 공룡 게임 4. 스네이크 게임 5. 유령 게임

이렇게 총 5개의 아이디어를 도출하였고, 그 중 지난 시간에 배운 공 튀기기 게임과 가장 연관성이 있는 크롬 공룡 게임으로 선정하였습니다. 이미지 대신 초록, 빨강 사각형을 활용하여 캐릭터를 구성하였고, 사용자의 캐릭터가 초록, 장애물을 빨강으로 설정하였습니다. 따라서 이름을 초록 네모의 놀라운 여정으로 정하였습니다. 코딩애플 Youtube에서 제공하는 강의를 토대로 기본 틀을 잡고, 추가적인 기능을 구현하였습니다.

첫째로, 50점을 달성할 때 마다 장애물의 속도를 증가시켰습니다. a.x -= 4 + Math.floor(score / 50) * 1;와 같이 점수를 50으로 나눈 값의 몫 만큼 장애물 x 좌표의 변화 폭을 증가시켜 이를 구현하였습니다.

둘째로, 장애물이 랜덤 시간으로 등장하도록 구현하였습니다. x = getRandomInt(80, 120) 변수를 통해 최솟값과 최댓값을 설정한 후 그 사이에 있는 랜덤 시간으로 장애물이 등장하도록 하였습니다. if (lastTime + x == timer) 조건문을 통해 마지막 장애물 등장 후 다시 랜덤 값을 할당하여, 장애물이 너무 이른 시간에 중복되어 나타나지 않도록 하였습니다.

셋째로, 장애물이 랜덤 크기로 등장하도록 구현하였습니다.

class Cactus {
    constructor() {
        this.width = 20 + getRandomInt(0, 50);
        this.height = 30 + getRandomInt(0, 50);
        this.x = 500;
        this.y = 250 - this.height;
    }
    draw() {
        ctx.fillStyle = 'red';
        ctx.fillRect(this.x, this.y, this.width, this.height);
    }
}

위 코드와 같이 장애물 클래스에서 너비와 높이가 0부터 50 사이의 값으로 랜덤으로 생성될 수 있도록 구현하여, 게임의 재미 요소를 더하고자 하였습니다.

마지막으로, 초록네모(dino, 공룡)가 땅에 닿아 있을 경우에만 점프가 가능하도록 하였습니다. 기존 코드는 점프가 중복으로 눌려 연타를 할 경우 하늘 높이 날아 캐릭터가 캔버스에서 사라지는 버그가 있었습니다. 이를 해결하기 위해 스페이스바가 눌렸을 때 y 좌표를 체크하여 200(땅 좌표)일 때만 점프를 할 수 있도록 구현하였습니다.

이 프로젝트를 통해 팀원들과 아이디어를 도출하고 협업하여 시간 안에 코드를 완성할 수 있었습니다. 서로의 코드와 생각을 공유하면서 함께 만들어낸 게임이라 더 의미가 깊었습니다. 팀원 모두 프로젝트에 열심히 임해주었고, 다양한 도전과 시도를 통해 성장할 수 있는 시간이었습니다.

반응형

'All Categories > FE & javascript' 카테고리의 다른 글

개발일지 #1 - javascript 활용  (1) 2023.11.28