코딩 강의/컨텐츠를 만들어 볼까요

테트리스 게임 개발 #8 - 보드 및 블럭 스타일 추가

아미넴 2020. 9. 28.

목차

     

    지난 강의 리뷰

    테트리스 게임 개발 #7 - 최고 점수 표시, 블럭 색깔 추가, 배경음악 및 효과음 적용

     

    테트리스 게임 개발 #7 - 최고 점수 표시, 블럭 색깔 추가, 배경음악 및 효과음 적용

    목차 지난 강의 리뷰 테트리스 게임 개발 #6 - 시작, 종료, 일시 정지 및 레벨, 라인, 점수 계산 기능 구현 테트리스 게임 개발 #6 - 시작, 종료, 일시 정지 및 레벨, 라인, 점수 계산 기능 구현 목차

    sangminem.tistory.com

     

    이제 테트리스는 저희 손 안에 있습니다.

    원하는대로 바꾸도록 해 볼거예요.

     

    1. main-board와 next-board에 밝은 색 무늬도 추가하여 1자 블럭이 잘 보이도록 할 겁니다.

    2. 그리고 블럭에 스타일 옵션을 추가하여 블럭을 좀 더 고급스럽게 만들어 보겠습니다.

     

    보드 격자 무늬 적용

    먼저 보드판 무늬를 어떻게 넣을 지 생각해 볼게요.

    보통 격자 무늬가 있어야 블럭의 위치 판단도 용이하므로 괜찮아 보입니다.

    function drawLattice(board, ctx) {
        board.forEach((row, y) => {
            row.forEach((value, x) => {
                if((x%2 == 0 && y%2 == 0) || (x%2 == 1 && y%2 == 1)){
                    ctx.fillStyle = '#f9f9ff';
                } else {
                    ctx.fillStyle = '#eeeeff';
                }
                ctx.fillRect(x, y, 1, 1);
            });
        });
    }

    draw.js 파일에 위와 같은 함수를 하나 작성했습니다.

    x, y 좌표가 둘 다 홀수이거나 둘 다 짝수일 때 같은 색을 drawing하도록 하였고

    그 외에 다른 색을 drawing하여 격자 무늬를 생성하는 로직을 구현했습니다.

     

    그리고 main.js 파일에 rebuild 함수를 수정해 보겠습니다.

    function rebuild() {
        resize();
        drawLattice(matrixMainBoard, ctxMainBoard);
        drawLattice((new Array(4)).fill((new Array(4)).fill(0)), ctxNextBoard);
        //... 생략
    }

    resize 함수 호출 후에 바로 격자 무늬 생성 함수를 호출하였습니다.

    그 위에 조작 가능한 블럭과 쌓인 블럭을 drawing 해야 표현이 되기 때문에 순서도 중요합니다.

     

     

    중간 확인 1

    저장하고 실행을 한 번 해볼게요.

    격자 무늬가 잘 표시되었네요.

    뭔가 더 고급스러워진 느낌입니다.

     

    게임을 실행해 보니

    격자가 사라졌어요.

    무슨 일일까요?

    function drawBlock(block, ctx, colorSet) {
        // ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height); //제거 필요
        //... 생략
    }

    살펴보니 draw.js 파일에 drawBlock 함수의 수정이 필요합니다.

    기존에는 상관없었던 블럭을 그리기 위해 clearRect 메서드를 호출했던 부분이

    격자 무늬까지 지워버리는 효과를 줬습니다.

    어차피 rebuild 함수를 지속적으로 호출하면서 새로 drawing하므로 clearRect를 하지 않아도 됩니다.

     

     

     

    저장하고 다시 실행해 볼게요.

    이제 격자 무늬도 잘 표현될 뿐더러 1자 모양 블럭도 화면에 잘 보이게 됐습니다.

     

    블럭 스타일 변경

    이어서 블럭의 느낌을 좀 더 살리는 로직을 생성해 보려고 합니다.

    블럭 한 조각마다 옵션을 추가할 수 있도록 약간 수정을 해보겠습니다.

    function randomNextBlockMatrix() {
        //... 생략
    
        const BLOCK_SET_OPTIONS = [ // 0: 0, 1~5: 1, 6: 2
            [
                [{},{}],
                [{},{}]
            ],
            [
                [{},{},{}],
                [{},{},{}],
                [{},{},{}]
            ],
            [
                [{},{},{},{}],
                [{},{},{},{}],
                [{},{},{},{}],
                [{},{},{},{}]
            ]
        ];
    
        const index = getRandomIndex(BLOCK_SET.length);
    
        let result = {
            block_set: BLOCK_SET[index],
            block_set_options: BLOCK_SET_OPTIONS[(index===0)?0:((index===6)?2:1)]
        }
    
        return result;
    }
    
    function initMatrix(rows, cols) {
        let matrix = {
            board: [],
            options: []
        };
        for(let y=0; y < rows; y++) {
            matrix.board.push(new Array(cols).fill(0));
            matrix.options.push(new Array(cols).fill({}));
        }
        return matrix;
    }

    먼저 ㅁ자 블럭과 1자 블럭은 각각 2x2, 4x4, 나머지는 3x3의 빈 오브젝트를 갖는

    새로운 배열 변수를 추가하여 블럭을 생성합니다.

     

    그리고 보드에 쌓이는 블럭에 대해서도 옵션을 주어야 하므로

    같은 방식으로 빈 오브젝트를 할당해 주었습니다.

     

    matrix가 배열에서 오브젝트 형태로 바뀌었기 때문에 기존 matrix를 사용했던 로직을 약간 손 보아야 합니다.

    예를 들어 다음과 같은 부분이 있는데요.

    function drawRemovingLines(ctx, matrix, lineIndexes, colorSet) {
        lineIndexes.forEach((y, i) => {
            for(let x=0; x < matrix.board[y].length; x++) { //변경된 부분
                //... 생략
            }
        });
    }

    matrix[y].length 였던 부분을 matrix.board[y].length로 변경해야 합니다.

    조금 번거롭긴 하지만 어렵지 않게 변경할 수 있습니다.

     

    다 변경을 했다면 블럭 스타일에 opacity 옵션을 주어 좀 더 입체스럽게 만들어 볼게요.

    function drawBlock(block, ctx, colorSet) {
        let globalAlpha = 1.0; //1, 0.95, 0.9, 0.85
        ctx.globalAlpha = 1.0;
        block.shape.forEach((row, y) => {
            row.forEach((value, x) => {
                if(value > 0) {
                    block.shape_options[y][x].opacity = globalAlpha;
                    ctx.globalAlpha = block.shape_options[y][x].opacity;
                    ctx.fillStyle = colorSet[value-1];
                    ctx.fillRect(x + block.x, y + block.y, 1, 1);
                    globalAlpha -= 0.05;
                }
            });
        });
        ctx.globalAlpha = 1.0;
    }

    블럭을 그릴 때 opacity 값(ctx의 globalAlpha)을 1.0에서 0.05씩 감소시켜

    블럭 cell마다 약간의 구분이 되도록 시각적인 효과를 주었습니다.

     

    그리고 matrix.js 파일에 작성되어 있는 stack 함수의 수정이 필요해 보입니다.

    function stack(block, matrix) {
        block.shape.forEach((row, y) => {
            row.forEach((value, x) => {
                if(value > 0) {
                    matrix.board[y+block.y][x+block.x] = block.shape[y][x];
                    matrix.options[y+block.y][x+block.x] = block.shape_options[y][x]; // 추가
                }
            });
        });
    }

    블럭이 가지고 있는 옵션 변수를 matrix에도 대입을 시켜 주었습니다.

     

    그 다음 마찬가지로 main-board에 쌓인 블럭에도 같은 효과를 줘 보겠습니다.

    function drawBoard(matrix, ctx, colorSet) {
        ctx.globalAlpha = 1;
        matrix.board.forEach((row, y) => {
            row.forEach((value, x) => {
                if(value > 0) {
                    ctx.globalAlpha = matrix.options[y][x].opacity;
                    ctx.fillStyle = colorSet[matrix.board[y][x]-1];
                    ctx.fillRect(x, y, 1, 1);
                }
            });
        });
        ctx.globalAlpha = 1.0;
    }

    더 어려울 것이 없습니다.

    쌓인 블럭이 갖고 있는 opacity 값을 그대로 대입만 시켜 주었습니다.

     

     

    중간 확인 2

    저장하고 실행해 보면

    어떤가요.

    블럭에 뭔가 생기가 좀 더 도는 느낌이지 않나요.

    저는 일단 마음에 듭니다.

     

    블럭 스타일 추가 수정

    이왕 수정한 김에 블럭이 제거될 때도 약간 더 고급스러운 느낌을 줘 보겠습니다.

    function drawRemovingLines(ctx, matrix, lineIndexes, colorSet) {
        lineIndexes.forEach((y, i) => {
            for(let x=0; x < matrix.board[y].length; x++) {
                ctx.globalAlpha = 1.0;
                ctx.fillStyle = 'white';
                ctx.fillRect(x, y, 1, 1);
                ctx.globalAlpha = matrix.options[y][x].opacity - 0.5;
                ctx.fillStyle = colorSet[matrix.board[y][x]-1];
                ctx.fillRect(x, y, 1, 1);
            }
        });
        ctx.globalAlpha = 1.0;
    }

    먼저 red로 처리했던 부분을 white로 바꾸어 제거될 라인에 깔고

    블럭이 갖고 있는 opacity 값을 0.5 감소시킨 뒤 해당 블럭 색깔을 입혀서

    제거될 라인을 흐릿하게 표현하는 방법을 사용하였습니다.

     

    결과 확인

    실행을 해 보면

    이와 같이 표현이 됩니다.

    순간 캡쳐로는 느낌이 잘 안 살지만 실제 0.1초만에 사라지게 하면 좀 더 고급스러운 분위기가 연출 됩니다.

     

    완성도가 높아지는 모습을 보니 매우 뿌듯합니다.

     

    다음 시간에는

    아래에서 한 줄씩 올라오는 로직과

    콤보 기능까지 만들어 보겠습니다.

     

    제가 다 기대 되네요!

     

    #다음강의

    테트리스 게임 개발 #9 - 콤보 기능 및 라인 한 줄 씩 추가

     

    테트리스 게임 개발 #9 - 콤보 기능 및 라인 한 줄 씩 추가

    목차 지난 강의 리뷰 테트리스 게임 개발 #8 - 보드 및 블럭 스타일 추가 테트리스 게임 개발 #8 - 보드 및 블럭 스타일 추가 목차 지난 강의 리뷰 테트리스 게임 개발 #7 - 최고 점수 표시, 블럭 색

    sangminem.tistory.com

     

    반응형

    댓글1

    💲 추천 글