우수사례 - HTML5 캔버스 사용하기

Derek Detweiler
Derek Detweiler

소개

지난 봄 (2010년)에 저는 HTML5 및 관련 기술에 대한 지원이 급증하는 것에 관심을 보였습니다. 당시 한 친구는 2주간의 게임 개발 경연대회에서 서로 경쟁하며 프로그래밍 및 개발 기술을 연마하고 서로에게 끊임없이 던지는 게임 아이디어를 실현해 주었습니다. 그래서 저는 초기 HTML 사양으로는 작동 방식을 더 잘 이해하고 이전 HTML 사양으로는 거의 불가능한 작업을 할 수 있도록 자연스럽게 HTML5 요소를 경쟁 항목에 통합하기 시작했습니다.

HTML5의 여러 새로운 기능 중 캔버스 태그에 대한 지원이 늘어남에 따라 자바스크립트를 사용하여 양방향 아트를 구현할 수 있는 흥미로운 기회가 생겼고, 그 결과 현재 Entanglement라고 하는 퍼즐 게임을 구현해 봤습니다. 카탄의 세틀러 타일 뒷면을 사용해 프로토타입을 이미 만들었으므로 이를 청사진으로 사용하여 웹 플레이를 위해 HTML5 캔버스의 육각형 타일을 만드는 데 꼭 필요한 부분은 육각형 그리기, 경로 그리기, 타일 회전입니다. 아래에는 현재 형식으로 각 작업을 성취한 방법이 자세히 설명되어 있습니다.

육각형 그리기

Entanglement의 원래 버전에서 육각형을 그리는 데 여러 캔버스 그리기 메서드를 사용했지만 현재 게임 형식에서는 drawImage()를 사용하여 스프라이트 시트에서 잘린 텍스처를 그립니다.

타일 스프라이트 시트
카드 스프라이트 시트

이미지를 하나의 파일로 결합했기 때문에 이 경우에는 10개가 아닌 하나의 서버 요청만 하게 됩니다. 선택한 육각형을 캔버스에 그리려면 먼저 캔버스, 컨텍스트, 이미지를 함께 수집해야 합니다.

캔버스를 만들려면 다음과 같이 html 문서에 캔버스 태그만 있으면 됩니다.

<canvas id="myCanvas"></canvas>

스크립트로 가져올 수 있도록 ID를 제공합니다.

var cvs = document.getElementById('myCanvas');

둘째, 그리기를 시작할 수 있도록 캔버스의 2D 컨텍스트를 가져와야 합니다.

var ctx = cvs.getContext('2d');

마지막으로 이미지가 필요합니다. 웹페이지와 동일한 폴더에서 파일 이름이 'tiles.png'인 경우 다음과 같은 방법으로 가져올 수 있습니다.

var img = new Image();
img.src = 'tiles.png';

이제 세 가지 구성요소가 있으므로 ctx.drawImage()를 사용하여 스프라이트 시트에서 캔버스까지 원하는 단일 육각형을 그릴 수 있습니다.

ctx.drawImage(img, sourceX, sourceY, sourceWidth, sourceHeight,
            destinationX, destinationY, destinationWidth, destinationHeight);

여기에서는 맨 위 행의 왼쪽에서 네 번째 육각형을 사용합니다. 또한 왼쪽 상단 모서리에 있는 캔버스에 원본과 같은 크기로 그립니다. 육각형의 너비가 400픽셀이고 높이가 346픽셀이라고 가정하면 다음과 같이 표시됩니다.

var cvs = document.getElementById('myCanvas');
var ctx = cvs.getContext('2d');
var img = new Image();
img.src = 'tiles.png';
var sourceX = 1200;
var sourceY = 0;
var sourceWidth = 400;
var sourceHeight = 346;
var destinationX = 0;
var destinationY = 0;
var destinationWidth = 400;
var destinationHeight = 346;
ctx.drawImage(img, sourceX, sourceY, sourceWidth, sourceHeight,
            destinationX, destinationY, destinationWidth, destinationHeight);

그 결과 다음과 같이 이미지의 일부를 캔버스에 복사했습니다.

육각형 타일
육각형 타일

경로 그리기

이제 캔버스에 육각형을 그렸으므로 여기에 선을 몇 개 그리려고 합니다. 먼저 육각형 타일과 관련된 몇 가지 도형을 살펴보겠습니다. 다음과 같이 한 쪽의 선 끝 두 개가 각 가장자리를 따라 끝에서 1/4이 되고 가장자리의 1/2이 서로 떨어져야 합니다.

육각형 타일의 선 끝점
6각형 타일의 선 끝점

또한 좋은 곡선이 필요한 것이므로 약간의 시행착오를 통해 각 끝점의 가장자리에서 수직선을 만들면 육각형의 주어진 각도에서 각 끝점의 교집합이 주어진 끝점에 대한 베지어 제어점이 됩니다.

육각형 타일의 기준점
6각형 타일의 기준점

이제 엔드포인트와 제어점을 캔버스 이미지에 해당하는 데카르트 평면에 매핑하고 코드로 다시 돌아갈 준비가 되었습니다. 간단하게 설명하기 위해 한 줄부터 시작하겠습니다. 먼저 왼쪽 상단 끝점에서 오른쪽 하단 끝점까지의 경로를 그립니다. 이전의 육각형 이미지가 400x346이었으므로 상단 엔드포인트의 가로가 150픽셀이고 아래로 0픽셀이 됩니다 (150, 0). 기준점은 (150, 86)입니다. 하단 가장자리 엔드포인트는 (250, 346)이고 제어점은 (250, 260)입니다.

첫 번째 베지어 곡선 좌표
첫 번째 베지어 곡선 좌표

좌표가 입력되었으므로 이제 그리기를 시작할 준비가 되었습니다. ctx.beginPath()로 새로 시작한 후 다음을 사용하여 첫 번째 엔드포인트로 이동합니다.

ctx.moveTo(pointX1,pointY1);

그런 다음 다음과 같이 ctx.bezierCurveTo()를 사용하여 선을 그릴 수 있습니다.

ctx.bezierCurveTo(controlX1, controlY1, controlX2, controlY2, pointX2, pointY2);

선에 멋진 테두리를 지정하려고 하므로 매번 다른 너비와 색상을 사용하여 이 경로에 두 번 스트로크를 적용합니다. 색상은 ctx.strokeStyle 속성을 사용하여 설정되고 너비는 ctx.lineWidth를 사용하여 설정됩니다. 첫 번째 선을 모두 그리면 다음과 같이 됩니다.

var pointX1 = 150;
var pointY1 = 0;
var controlX1 = 150;
var controlY1 = 86;
var controlX2 = 250;
var controlY2 = 260;
var pointX2 = 250;
var pointY2 = 346;
ctx.beginPath();
ctx.moveTo(pointX1, pointY1);
ctx.bezierCurveTo(controlX1, controlY1, controlX2, controlY2, pointX2, pointY2);
ctx.lineWidth = 15;
ctx.strokeStyle = '#ffffff';
ctx.stroke();
ctx.lineWidth = 10;
ctx.strokeStyle = '#786c44';
ctx.stroke();

이제 첫 번째 줄이 구불구불한 육각형 타일이 만들어졌습니다.

육각형 타일의 고립선
육각형 타일의 1개 선

다른 10개의 엔드포인트와 해당하는 베지어 곡선 제어점의 좌표를 입력하면 위 단계를 반복할 수 있으며 다음과 같은 카드를 만들 수 있습니다.

완성된 육각형 타일
완성된 육각형 타일

캔버스 회전

카드를 만들었으면 게임에서 다양한 경로를 선택할 수 있도록 카드를 돌릴 수 있어야 합니다. 캔버스를 사용하여 이 작업을 실행하려면 ctx.translate()ctx.rotate()를 사용합니다. 타일이 중심을 중심으로 회전해야 하므로 첫 번째 단계는 캔버스 기준점을 육각형 타일의 중심으로 이동하는 것입니다. 이를 위해 다음을 사용합니다.

ctx.translate(originX, originY);

여기서 출발지X는 육각형 타일 너비의 절반이고 원점Y는 높이의 절반이 됩니다.

var originX = 200;
var originY = 173;
ctx.translate(originX, originY);

이제 새 중심점으로 타일을 회전할 수 있습니다. 육각형은 6변으로 되어 있으므로 3으로 나눈 수학.PI의 배수로 회전하려고 합니다. 다음을 사용하여 시계 방향으로 한 번 회전해 보겠습니다.

ctx.rotate(Math.PI / 3);

그러나 육각형과 선은 이전 (0,0) 좌표를 원점으로 사용하므로 회전이 완료되면 그리기 전에 다시 변환하려고 합니다. 따라서 지금까지

var originX = 200;
var originY = 173;
ctx.translate(originX, originY);
ctx.rotate(Math.PI / 3);
ctx.translate(-originX, -originY);

위의 변환 및 회전을 렌더링 코드 앞에 배치하면 이제 회전된 타일을 렌더링합니다.

회전된 육각형 타일
회전된 육각형 타일

요약

위에서 이미지 렌더링, 베지어 곡선 그리기, 캔버스 회전 등 캔버스 태그를 사용하여 HTML5가 제공하는 몇 가지 기능을 강조했습니다. Entanglement에 HTML5 캔버스 태그와 JavaScript 그리기 도구를 사용하는 것은 즐거운 경험임이 증명되었습니다. 다른 사람들이 이처럼 새롭게 떠오르는 기술을 통해 만들 수 있는 많은 새로운 애플리케이션과 게임을 기대하고 있습니다.

코드 참조

위에 제공된 모든 코드 예는 참조로 아래에 나와 있습니다.

var cvs = document.getElementById('myCanvas');
var ctx = cvs.getContext('2d');
var img = new Image();
img.src = 'tiles.png';

var originX = 200;
var originY = 173;
ctx.translate(originX, originY);
ctx.rotate(Math.PI / 3);
ctx.translate(-originX, -originY);

var sourceX = 1200;
var sourceY = 0;
var sourceWidth = 400;
var sourceHeight = 346;
var destinationX = 0;
var destinationY = 0;
var destinationWidth = 400;
var destinationHeight = 346;
ctx.drawImage(img, sourceX, sourceY, sourceWidth, sourceHeight,
            destinationX, destinationY, destinationWidth, destinationHeight);

ctx.beginPath();
var pointX1 = 150;
var pointY1 = 0;
var controlX1 = 150;
var controlY1 = 86;
var controlX2 = 250;
var controlY2 = 260;
var pointX2 = 250;
var pointY2 = 346;
ctx.moveTo(pointX1, pointY1);
ctx.bezierCurveTo(controlX1, controlY1, controlX2, controlY2, pointX2, pointY2);
ctx.lineWidth = 15;
ctx.strokeStyle = '#ffffff';
ctx.stroke();
ctx.lineWidth = 10;
ctx.strokeStyle = '#786c44';
ctx.stroke();

ctx.beginPath();
pointX1 = 250;
pointY1 = 0;
controlX1 = 250;
controlY1 = 86;
controlX2 = 150;
controlY2 = 86;
pointX2 = 75;
pointY2 = 43;
ctx.moveTo(pointX1, pointY1);
ctx.bezierCurveTo(controlX1, controlY1, controlX2, controlY2, pointX2, pointY2);
ctx.lineWidth = 15;
ctx.strokeStyle = '#ffffff';
ctx.stroke();
ctx.lineWidth = 10;
ctx.strokeStyle = '#786c44';
ctx.stroke();

ctx.beginPath();
pointX1 = 150;
pointY1 = 346;
controlX1 = 150;
controlY1 = 260;
controlX2 = 300;
controlY2 = 173;
pointX2 = 375;
pointY2 = 213;
ctx.moveTo(pointX1, pointY1);
ctx.bezierCurveTo(controlX1, controlY1, controlX2, controlY2, pointX2, pointY2);
ctx.lineWidth = 15;
ctx.strokeStyle = '#ffffff';
ctx.stroke();
ctx.lineWidth = 10;
ctx.strokeStyle = '#786c44';
ctx.stroke();

ctx.beginPath();
pointX1 = 325;
pointY1 = 43;
controlX1 = 250;
controlY1 = 86;
controlX2 = 300;
controlY2 = 173;
pointX2 = 375;
pointY2 = 130;
ctx.moveTo(pointX1, pointY1);
ctx.bezierCurveTo(controlX1, controlY1, controlX2, controlY2, pointX2, pointY2);
ctx.lineWidth = 15;
ctx.strokeStyle = '#ffffff';
ctx.stroke();
ctx.lineWidth = 10;
ctx.strokeStyle = '#786c44';
ctx.stroke();

ctx.beginPath();
pointX1 = 25;
pointY1 = 130;
controlX1 = 100;
controlY1 = 173;
controlX2 = 100;
controlY2 = 173;
pointX2 = 25;
pointY2 = 213;
ctx.moveTo(pointX1, pointY1);
ctx.bezierCurveTo(controlX1, controlY1, controlX2, controlY2, pointX2, pointY2);
ctx.lineWidth = 15;
ctx.strokeStyle = '#ffffff';
ctx.stroke();
ctx.lineWidth = 10;
ctx.strokeStyle = '#786c44';
ctx.stroke();

ctx.beginPath();
pointX1 = 325;
pointY1 = 303;
controlX1 = 250;
controlY1 = 260;
controlX2 = 150;
controlY2 = 260;
pointX2 = 75;
pointY2 = 303;
ctx.moveTo(pointX1, pointY1);
ctx.bezierCurveTo(controlX1, controlY1, controlX2, controlY2, pointX2, pointY2);
ctx.lineWidth = 15;
ctx.strokeStyle = '#ffffff';
ctx.stroke();
ctx.lineWidth = 10;
ctx.strokeStyle = '#786c44';
ctx.stroke();