본문 바로가기
자바스크립트와 캔버스

자바스크립트와 캔버스 8, 테트리스를 만들어봐-4

1. HTML 표준 CANVAS 기술 소개 / https://itadventure.tistory.com/130

2. 자바스크립트와 CANVAS 두번째시간. 캔바스에 눈을 내리자 / https://itadventure.tistory.com/131

3. 자바스크립트와 캔버스 3번째 시간, 공튀기기 놀이 / https://itadventure.tistory.com/132

4. 자바스크립트와 캔버스 4번째 시간, 마우스의 파동을 느껴봐! | https://itadventure.tistory.com/133

5. 자바스크립트와 캔버스, 테트리스를 만들어봐-1 | https://itadventure.tistory.com/136

6. 자바스크립트와 캔버스, 테트리스를 만들어봐-2 | https://itadventure.tistory.com/139

7. 자바스크립트와 캔버스, 테트리스를 만들어봐-3 | https://itadventure.tistory.com/142

◐ 8. 자바스크립트와 캔버스, 테트리스를 만들어봐-4 

이번 시간에는 키보드 조작을 이용하여 블록조각을 쌓는 부분을 살펴보겠습니다.

우선, 좌/우/아래 방향 화살표키를 이용할텐데요. 윗방향 화살표키는 블록을 회전시키는 부분인데, 좀 대대적인 수정이 필요해서 다음 시간에 다뤄보겠습니다.

우선 먼저 이번 강좌의 결과물을 미리 살펴보실까요?

http://dreamplan7.cafe24.com/canvas/cray08.php

 

캔바스. 테트리스

 

dreamplan7.cafe24.com

웹페이지에서는 화살표 키를 입력할 때, 이벤트라는 것이 발생합니다.

마트 첫 오픈 기념 '이벤트'라고 들어보셨나요?
이벤트장에 처음 가면 막 무료 사은품도 나눠주고 SNS에 인증샷을 올려주면 먹거리를 주기도 하지요.
맞습니다. 그 이벤트입니다. 원래 이벤트란 '어떤 일이 벌어지는 것'을 의미합니다.

웹페이지도 그런 이벤트라는게 있는데요.
아무것도 없는 공간에서 단순히 화살표 키를 입력해도 이벤트란 것이 발생합니다.

하지만 이벤트가 발생해도 아무 일도 일어나지 않는 것은
이벤트에 따른 '코딩'을 해준 것이 없기 때문입니다.

화살표 키 입력시 발생하는 이벤트에 코딩을 해주면 어떤 일이 일어날까요?
코딩한 일이 그대로 일어납니다 :)

테트리스 블록조각을 움직이기 위해 화살표 키를 입력하는 경우 키보드 이벤트가 발생하는데요.
jQuery(제이쿼리) 에서는 이 이벤트를 keydown 이벤트라고 합니다.
보통 아래와 같이 정의하는 것이 일반적인데요.

	$(document).keydown(function( event ){
		  :
	});

event 라는 파라미터가 함께 따라옵니다.
이 파라미터는 무슨 이유로 함께 따라오는 것일까요?
그것은 바로 키보드로 눌려진 키의 정체를 알아내기 위해서이지요.

지금 눌려진 키보드가 왼쪽 화살표 키인지, 오른쪽 화살표 키인지를 알아야
무슨 처리를 할 것이지 않습니까? :)
그 정체가 event 파라미터 변수 안에 들어 있습니다.

본 소스에서는 onKeyDown(event)라는 함수를 다시 호출하도록 되어 있는데요.
가독성을 위한 의도입니다.

$(document).keydown(function( event ){
	onKeyDown(event);
});

onKeyDown 함수에서는 실제 키보드 입력에 대응하는 작동을 하는데요.
우선 왼쪽 키를 누르면 블럭 조각이 왼쪽으로 한칸 움직이는 동작을 하기 위해,
tetrix_block_x 변수의 좌표를 왼쪽으로 한칸 이동합니다.

event.which 항목에 눌려진 키보드의 코드값이 들어 있는데,
37인 경우 왼쪽키를 의미합니다.

function onKeyDown(event)
{
	if(event.which==37)	// 왼쪽키
	{
		tetrix_block_x--;
               :

그러면 실제 떨어지고 있는 블록조각도 x좌표가 바뀌었기 때문에 왼쪽으로 한칸 이동합니다.

그런데 주의사항이 있습니다.
왼쪽으로 이동하는 동작을 항상 허용하면 될까요?
왼쪽도 경계가 있기 때문에 경계를 넘어가는 동작을 허용해서는 안됩니다.

이를 위해 왼쪽 경계를 넘어설라치면,
"안된다, 거긴 위험해"라고 이동을 무산시키는 부분이 필요한데요.
웬지 지난 번에 화면 아래로 무한대로 하강을 막는 부분과 비슷하지요?
이 경우 다른 이미 쌓여 있는 블록과 충돌이 발생하는 경우에도 동일한 처리를 해주어야 합니다.

오른쪽 방향 이동도 마찬가지입니다.
그러면 이 모든 경우에 대해서 비슷한 충돌을 검사하는 코드를 한땀 한땀(?) 넣어서 다시 좌표를 원래 위치로 되돌리는 방법으로 코딩을 짜면 될까요?

네 그렇게 해도 됩니다 :)

하지만 그보다 더 효율적인 방법이 있습니다.
위의 방법은 중복된 코드가 너무 많이 생깁니다.
나중에 블럭을 회전시킬 때도 또 충돌검사를 할텐데요.
똑같은 코드가 여기저기 산재해 있으면 나중에 수정하려고 보면,
여기 저기 너무 많은 부분을 수정해야 해서 매우 복잡한 문제가 발생합니다.

효율적인 방법은 무엇이 있을까요?
그것은 바로 충돌을 검사하는 부분을 함수로 정의하는 방법입니다.
블럭조각이 충돌하는지 하지 않는지를 검사하고
그 결과를 마치 자바스크립트 명령어를 사용하듯이 쓸 수 있는 기능입니다.

지난번의 충돌판정 부분을 아래와 같이 함수화시켜보았습니다.

function CheckConflict()
{
	var size=(tetrix_block[tetrix_block_number]).length;
	for(k=0;k<size;k+=2)
	{
		check_y = tetrix_block_y + tetrix_block[tetrix_block_number][k];
		check_x = tetrix_block_x + tetrix_block[tetrix_block_number][k+1];
		// 겹치는 경우
		if(check_x < 0 || check_x >=10 || check_y >= 20 || tetrix_blockbox[check_y][check_x]!=0)return true;
	}
	return false;
}

이 소스가 있으면 중간 중간 블록이 경계를 넘어가거나, 다른 블록과 충돌하는지 여부는 CheckConflict() 함수를 호출하는 것으로 확인할 수 있습니다.

아래처럼 말이지요. 참고로 이 함수는 아랫쪽 뿐만 아니라 위쪽 좌, 우 경계까지도 완벽하게 검사해서 충돌 여부를 true 또는 false 로 반환합니다.

if(CheckConflict()){ ... }

예전의 코드가 어떻게 바뀌었는지 볼까요?
자동으로 떨어지는 부분은 충돌검사하는 부분이 이렇게 바뀌었습니다,
충돌검사하는 부분이 if문 한줄로 바뀌어 매우 간결해졌지요

	// 블럭을 한칸 떨어뜨리고
	tetrix_block_y++;

	// 겹침검사	
	if(CheckConflict())
	{
		// 다시 위로 이동시킨 다음
		tetrix_block_y--;
                :

이 함수를 키보드를 통해, 블록 조각을 움직일 때도 그대로 사용합니다.

왼쪽으로 이동하는 작동을 하면서 만일 충돌하는 좌표라면 tetrix_block_x 변수값를 증가시켜 다시 오른쪽으로 한칸 이동해줍니다. 하지만 정상적으로 이동한 경우 onDraw() 함수를 호출해서 결과를 즉시 화면에 그려주는데요. 0.5 초 후에 그려주면 속도가 매우 느리기 때문입니다.

	if(event.which==37)	// 왼쪽키
	{
		tetrix_block_x--;
		if(CheckConflict())tetrix_block_x++;
		else onDraw();
	}

오른쪽 키 조작도 마찬가지입니다. 충돌이 발생하면 다시 왼쪽으로 이동해 줍니다.

	if(event.which==39)	// 오른쪽키
	{
		tetrix_block_x++;
		if(CheckConflict())tetrix_block_x--;
		else onDraw();
	}

아랫방향키와 스페이스키를 동일한 용도로 사용하도록 했는데요.
누르고 있으면 바닥으로 빠르게 하강합니다.
이 기능은 키보드 하나를 계속 누르고 있으면 같은 글자가 계속 새겨지는 것과 동일한 원리인데 이를 Auto key repeat ( 오토 키 리피트 = 자동 키 반복 ) 라고 부릅니다.

	if(event.which==40 || event.which==32)	// 아래쪽키, 스페이스키 
	{
		tetrix_block_y++;
		if(CheckConflict())tetrix_block_y--;
		else onDraw();
	}

윗방향키를 누르면 어떻게 할까요?
이 부분은 아직 공란으로 기능을 비워놓았습니다.

	if(event.which==38)	// 위쪽키(회전)
	{
		
	}

사실 윗방향 키를 누르면, 블럭조각을 회전시킬 겁니다.
하지만 회전을 해야 할 경우 구조가 일부 바뀌어야 하기에 블럭조각의 회전은 다음 시간에 또 다루도록 하지요.

전체 소스는 아래와 같습니다 :)

<!DOCTYPE html>
<html lang="ko">
<head>
	<meta charset="UTF-8">
	<title>캔바스. 테트리스</title>
</head>
<body>
https://blog.naver.com/ephraimdrlee 
/ 크레이의 세컨드라이프 탐구생활 네이버 블로그<br/>
<script src="https://code.jquery.com/jquery-3.4.1.min.js"></script>
<script>
// 기본 초기화
var init=false;
var myCanvas;
var Context;

var tetrix_blockbox_boxsize=25;
var tetrix_blockbox_top=50;
var tetrix_blockbox_left=280;

// tetrix_blockbox[row][col]; 20행 10열
var tetrix_blockbox;

// 테트리스 블럭박스 초기화
function tetrix_blockbox_init()
{
	// 20행 10열의 박스 생성
	tetrix_blockbox=new Array();
	for(i=0;i<20;++i)
	{
		tetrix_blockbox.push(new Array(10));
		// 모두 0으로 채운다
		for(j=0;j<10;++j)tetrix_blockbox[i][j]=0;
	}
}

// 5가지 타입 블록
var tetrix_block;

// 5가지 블럭 초기화
function tetrix_block_init()
{
	tetrix_block=new Array();

	// 첫번째 블럭
	// □□□□
	tmp=new Array();
	tmp.push(0,0); tmp.push(0,1); tmp.push(0,2); tmp.push(0,3);
	tetrix_block.push(tmp);

	// 두번째 블럭
	// □□□
	//  □
	tmp=new Array();
	tmp.push(0,0); tmp.push(0,1); tmp.push(0,2); tmp.push(1,1);
	tetrix_block.push(tmp);

	// 세번째 블럭
	// □□
	//  □□
	tmp=new Array();
	tmp.push(0,0); tmp.push(0,1); tmp.push(1,1); tmp.push(1,2);
	tetrix_block.push(tmp);

	// 네번째 블럭
	//  □□
	// □□
	tmp=new Array();
	tmp.push(0,1); tmp.push(0,2); tmp.push(1,0); tmp.push(1,1);
	tetrix_block.push(tmp);

	// 다섯번째 블럭
	// □□
	// □□
	tmp=new Array();
	tmp.push(0,0); tmp.push(0,1); tmp.push(1,0); tmp.push(1,1);
	tetrix_block.push(tmp);
}

// 현재 떨어지는 블록번호와 좌표
var tetrix_block_number=1;
var tetrix_block_x=3;
var tetrix_block_y=0;

// 초기화
function Init()
{
	if(init==false)
	{
		myCanvas=document.getElementById("MyCanvas");
		Context=myCanvas.getContext("2d");		
		init=true;
		tetrix_block_init();	// 5가지 블럭 모양 초기화
		tetrix_blockbox_init();	// 블럭상자 초기화
	}
}

function CheckConflict()
{
	var size=(tetrix_block[tetrix_block_number]).length;
	for(k=0;k<size;k+=2)
	{
		check_y = tetrix_block_y + tetrix_block[tetrix_block_number][k];
		check_x = tetrix_block_x + tetrix_block[tetrix_block_number][k+1];
		// 겹치는 경우
		if(check_x < 0 || check_x >=10 || check_y >= 20 || tetrix_blockbox[check_y][check_x]!=0)return true;
	}
	return false;
}

function Run()
{
	var size=(tetrix_block[tetrix_block_number]).length;

	// 블럭을 한칸 떨어뜨리고
	tetrix_block_y++;

	// 겹침검사	
	if(CheckConflict())
	{
		// 다시 위로 이동시킨 다음
		tetrix_block_y--;
		// 블럭을 블럭판에 박는다
		for(k=0;k<size;k+=2)
		{
			check_y = tetrix_block_y + tetrix_block[tetrix_block_number][k];
			check_x = tetrix_block_x + tetrix_block[tetrix_block_number][k+1];
			tetrix_blockbox[check_y][check_x]=1;
		}

		// 블럭을 다시 제일 위로 생성시키고
		tetrix_block_y=0;
		// 블럭번호도 바꿔 주자
		tetrix_block_number++;
		if(tetrix_block_number>=5)tetrix_block_number=0;			
	}

	// 그리기 이벤트
	onDraw();
}

// 키입력
function onKeyDown(event)
{
	if(event.which==37)	// 왼쪽키
	{
		tetrix_block_x--;
		if(CheckConflict())tetrix_block_x++;
		else onDraw();
	}
	if(event.which==39)	// 오른쪽키
	{
		tetrix_block_x++;
		if(CheckConflict())tetrix_block_x--;
		else onDraw();
	}
	if(event.which==40 || event.which==32)	// 아래쪽키, 스페이스키 
	{
		tetrix_block_y++;
		if(CheckConflict())tetrix_block_y--;
		else onDraw();
	}
	if(event.which==38)	// 위쪽키(회전)
	{
		
	}
}

// draw 이벤트
function onDraw()
{
	if(init==false)return;
	// 전체 테두리
	Context.strokeStyle="#000";
	Context.lineWidth=1;
	Context.strokeRect(0, 0, myCanvas.width-1, myCanvas.height-1);
	// 블럭 표시
	for(i=0;i<20;++i)
		for(j=0;j<10;++j)
		{
			if(tetrix_blockbox[i][j]==0)
				Context.fillStyle="#ccc";
			else
				Context.fillStyle="green";
			
			// 떨어지는 블럭표시
			var size=(tetrix_block[tetrix_block_number]).length;
			for(k=0;k<size;k+=2)
			{
				if(tetrix_block_y+tetrix_block[tetrix_block_number][k]==i
				   && tetrix_block_x+tetrix_block[tetrix_block_number][k+1]==j)
					Context.fillStyle="blue"; 
			}

			x=tetrix_blockbox_left + j*tetrix_blockbox_boxsize;
			y=tetrix_blockbox_top + i*tetrix_blockbox_boxsize;
			Context.fillRect(x, y, tetrix_blockbox_boxsize-2, tetrix_blockbox_boxsize-2);
		}
}

$(document).ready(function(){
	Init();
	setInterval(Run, 500);
});
$(document).keydown(function( event ){
	onKeyDown(event);		
});

</script>

<canvas id="MyCanvas" width=800 height=600>
Canvas is not supported.
</canvas>
방향키 좌, 우, 하(또는 스페이스키) 조작 추가<br/>

<span id=debug></span>
</body>
</html>

이제 조금씩 난이도가 올라가는 추세입니다.
반복집중해서 보셔야 뛰어넘을 수 있는데요.
이해가 어려운 일부 부분을 문의하시면 답변드리겠습니다.

도전하시는 모든 분들 성공하시길 바랍니다 :)
오늘도 읽어주셔서 감사합니다.

 

다음강좌 보러가기 / https://itadventure.tistory.com/148

 

자바스크립트와 캔버스 9, 테트리스를 만들어봐-5

1. HTML 표준 CANVAS 기술 소개 / https://itadventure.tistory.com/130 2. 자바스크립트와 CANVAS 두번째시간. 캔바스에 눈을 내리자 / https://itadventure.tistory.com/131 3. 자바스크립트와 캔버스 3번째 시..

itadventure.tistory.com