1. HTML 표준 CANVAS 기술 소개 / https://itadventure.tistory.com/130
2. 자바스크립트와 CANVAS 두번째시간. 캔바스에 눈을 내리자 / https://itadventure.tistory.com/131
◐ 3. 자바스크립트와 캔버스 3번째 시간, 공튀기기 놀이 ◑
매력덩어리 캔버스 3번째 시간입니다 :)
오늘은 공놀이를 할까 합니다.
떨어지는 공을 받아치며 벽돌을 깨뜨리는 게임을 해보신 적이 있다면 아주 친근하실텐데요.
벽돌까지는 안 나오고 공만 나옵니다.
사방이 꽉 막힌 공간이 있습니다.
그 속에서 하나의 공이 등장하며 여기저기를 떠돌아 다닙니다.
벽에 닿을때마다 반사되어 반대쪽으로 튀어다니긴 하지만 마찰력이 없기 때문에
튀어다니는 힘은 전혀 줄어듬이 없습니다.
아래 URL에서 미리보기를 하실 수 있습니다.
PC든 모바일이든 모두 잘 작동합니다.
http://dreamplan7.cafe24.com/canvas/cray03.php
우선 공이 3개 튀어다니는 소스를 먼저 살펴보실까요?
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<title>캔바스 샘플#3</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 ball_max =1;
var balls=Array();
var ball_image = new Image();
// 초기화
function Init()
{
if(init==false)
{
myCanvas=document.getElementById("MyCanvas");
Context=myCanvas.getContext("2d");
ball_image.src = "https://i.imgur.com/wPPFCbU.png";
for(i=0;i<ball_max;++i)
{
var obj=new Object();
obj.x=Math.random()*myCanvas.width;
obj.y=Math.random()*myCanvas.height;
obj.size=Math.random()*32+16;
obj.xdir=Math.random()*16+3;
obj.ydir=Math.random()*16+3;
balls.push(obj);
}
init=true;
}
}
function Run()
{
for(i=0;i<ball_max;++i)
{
balls[i].x+=balls[i].xdir;
if(balls[i].x<balls[i].size/2)
{
balls[i].x=balls[i].size/2;
balls[i].xdir=-balls[i].xdir;
}
if(balls[i].x>myCanvas.width-balls[i].size/2)
{
balls[i].x=myCanvas.width-balls[i].size/2;
balls[i].xdir=-balls[i].xdir;
}
balls[i].y+=balls[i].ydir;
if(balls[i].y<balls[i].size/2)
{
balls[i].y=balls[i].size/2;
balls[i].ydir=-balls[i].ydir;
}
if(balls[i].y>myCanvas.height-balls[i].size/2)
{
balls[i].y=myCanvas.height-balls[i].size/2;
balls[i].ydir=-balls[i].ydir;
}
}
onDraw();
}
// draw 이벤트
function onDraw()
{
if(init==false)return;
Context.strokeStyle="#000";
Context.lineWidth=1;
Context.strokeRect(0, 0, myCanvas.width-1, myCanvas.height-1);
Context.fillStyle="#fcfcfc";
Context.fillRect(1, 1, myCanvas.width-2, myCanvas.height-2);
for(i=0;i<ball_max;++i)
{
Context.drawImage(ball_image,
balls[i].x-balls[i].size/2,
balls[i].y-balls[i].size/2,
balls[i].size,
balls[i].size
);
}
Context.filter = "none";
}
$(document).ready(function(){
Init();
setInterval(Run, 20);
});
</script>
<canvas id="MyCanvas" width=800 height=600>
Canvas is not supported.
</canvas>
</body>
</html>
지난번 소스와 거의 비슷합니다.
거의 3단계인데요. 아래와 같습니다.
첫번째 / 공의 속성 정보 초기화,
두번째 / 시간에 따른 공의 이동 시뮬레이션 처리
세번째 / 실제적인 공들의 화면 표시
우선 소스를 살펴보기 전에 먼저 상상을 해보실까요?
공들이 각각 하나의 작은 살아있는 생명체라고 생각한다면
공이 튀어다니려면 공은 어떤 속성들을 가지고 있어야 할까요?
전체적으로는 공의 갯수를 생각해볼 수 있습니다.
공이 몇개나 튀어다니면 될까요?
그건 자유롭게 변경할 수 있도록 전역변수를 생성해놓으면 됩니다.
ball_max 라고 이름지어보도록 할까요?
기본값은 3 정도로 주어보도록 하겠지만 영상에서 보셨듯이 자유롭게 변경할 수 있습니다.
그 다음으로 각각의 공들을 생각함에 있어서 쉽게 생각하려면 하나의 공을 먼저 생각해야 합니다.
우선, 하나의 공은 캔바스 내에서의 위치값을 가지고 있어야 합니다.
공 자신이 어느 위치에 있는지를 알아야 캔바스 안에서 표현이 가능하겠지요.
캔바스는 2차원 좌표를 가지고 있기 때문에 이 좌표값을 x, y 로 표현할 수 있습니다.
또한 공들마다 크기가 다른 것을 보실 수 있지요.
각각의 공들은 크기 값을 가지고 있습니다.
이 크기를 size 라는 이름으로 주어보도록 하지요
아울러 공들은 이동을 하는데 방향값을 가지고 있습니다.
이를 벡터라고 생각해볼까요? x 방향으로의 이동량과 y 방향으로의 이동량을 가지고 있는데 한번턴마다 그 이동량만큼 동시에 이동하는 것이지요.
x방향으로의 이동량을 xdir, y방향으로의 이동량을 ydir 로 표현해보겠습니다.
그 밖에도 욕심을 내자면 공이 벽에 튕길 때 바로 튕기는 것이 아니라
약간의 지연시간과 함께 공이 찌그러지는 모습을 표현할 수도 있고,
공끼리 부딪힐 때 서로 반대쪽으로 튕긴다든가 다양한 상황을 생각할 수도 있겠지만,
오늘은 이 정도 선까지만 생각하겠습니다 :)
우선 페이지 로딩이 완료되면,
jquery 로 Init() 함수를 1회 실행, setInterval 기능으로 Run() 함수를 지속 실행해주는 부분은 지난 시간과 동일합니다.
$(document).ready(function(){
Init();
setInterval(Run, 20);
});
전역변수로는 공의 총 갯수를 선언하고 공 이미지를 담을 ball_image 변수,
그리고 각각의 공을 담을 배열을 미리 정의해 놓습니다.
// 기본 초기화
var init=false;
var myCanvas;
var Context;
// 공의 갯수
var ball_max =3;
var balls=Array();
var ball_image = new Image();
초기화 함수 Init 에서는 캔바스에 대한 기본 정보를 받아오는 부분과 함께
// 초기화
function Init()
{
if(init==false)
{
myCanvas=document.getElementById("MyCanvas");
Context=myCanvas.getContext("2d");
공의 이미지 정보를 받아 옵니다.
공의 이미지는 주소는 크레이가 준비한 전용 url 을 사용해도 되지만 독자 여러분께서 준비한 이미지를 사용하셔서 구현하셔도 더 뿌듯하시겠지요 :)
ball_image.src = "https://i.imgur.com/wPPFCbU.png";
이제 공의 갯수만큼 오브젝트를 만들어서 공 배열에 집어넣을 차례입니다.
공의 갯수만큼 for 문을 이용하여 반복루프를 만들어 준 다음에,
for(i=0;i<ball_max;++i)
{
:
}
반복 루프안에서 공 오브젝트를 하나 생성하고 속성을 정해줍니다.
var obj=new Object();
x, y좌표는 캔바스 내의 임의의 위치로 랜덤하게 정해주고
obj.x=Math.random()*myCanvas.width;
obj.y=Math.random()*myCanvas.height;
공의 크기를 정해줍니다. 너무 작으면 좀 이상하니까 최소 16에서 48까지의 크기로 랜덤하게 정해줍니다. 0~32의 랜덤 수치를 발생하고 거기에 16을 더해주는 방법을 사용합니다.
obj.size=Math.random()*32+16;
그리고 공의 속도를 정해주는데, X방향 3~19, Y방향 3~19의 벡터로 정해줍니다.
obj.xdir=Math.random()*16+3;
obj.ydir=Math.random()*16+3;
이렇게 생성된 이후 속성들이 정해진 공 오브젝트를 공 배열에 집어넣는 것으로 하나 단위의 공 생성이 반복 루프 안에서 완성됩니다.
balls.push(obj);
반복문이 완료된 후 초기화가 끝났다는 표식으로,
init 값을 true 로 바꾸어 줍니다.
init=true;
다음으로 0.02초 단위로 실행되는 Run 함수를 살펴보면,
공들 각각을 시뮬레이션 처리하기 위해 반복문을 수행합니다.
for(i=0;i<ball_max;++i)
{
:
}
먼저 공의 x좌표를 xdir 방향만큼 이동하고 y좌표 또한 ydir 방향만큼 이동합니다.
xdir 는 양수일 수도 있고 음수일 수도 있는데, 처음 초기값은 양수이기 때문에 양수 방향으로 이동합니다.
하지만 중간에 음수가 되기도 하고 다시 양수가 되기도 합니다.
balls[i].x+=balls[i].xdir;
:
balls[i].y+=balls[i].ydir;
그러면서 만약 공이 화면의 경계에 닿거나 넘칠 경우 공의 방향을 바꿔줍니다.
이 때 약간의 예외 처리 또한 병행됩니다.
if(balls[i].x<balls[i].size/2)
{
balls[i].x=balls[i].size/2;
balls[i].xdir=-balls[i].xdir;
}
if(balls[i].x>myCanvas.width-balls[i].size/2)
{
balls[i].x=myCanvas.width-balls[i].size/2;
balls[i].xdir=-balls[i].xdir;
}
만일 공의 끝이 캔버스의 왼쪽 경계를 넘어가는지 여부를 판단하려면,
공의 중심좌표를 기준으로 공의 크기의 절반을 빼준 위치가 캔버스 왼쪽 경계선을 벗어나는지를 판단해주면 됩니다.
이 부분을 판단하는 비교문은 아래와 같은데, 처음에는 은근 헷갈릴수 있습니다만
반복해서 보다 보면 어느 순간 이해가 됩니다 :)
if(balls[i].x<balls[i].size/2)
:
if(balls[i].x>myCanvas.width-balls[i].size/2)
:
if(balls[i].y<balls[i].size/2)
:
if(balls[i].y>myCanvas.height-balls[i].size/2)
공이 너무 캔버스 바깥쪽으로 깊이 벗어나면 느낌이 이상하기 때문에
경계를 넘을 때마다 공의 위치를 벽에 딱 붙입니다.
각각 4개의 if문에 따라 각각 붙이는 위치가 다르지요.
balls[i].x=balls[i].size/2;
:
balls[i].x=myCanvas.width-balls[i].size/2;
:
balls[i].y=balls[i].size/2;
:
balls[i].y=myCanvas.height-balls[i].size/2;
그리고 다음번에는 공이 반대 방향로 이동하도록 방향을 바꾸어 줍니다.
balls[i].xdir=-balls[i].xdir;
:
balls[i].xdir=-balls[i].xdir;
:
balls[i].ydir=-balls[i].ydir;
:
balls[i].ydir=-balls[i].ydir;
이 과정 전체를 합쳐서 보면 아래와 같지요
for(i=0;i<ball_max;++i)
{
// 공의 x좌표를 x방향벡터만큼 이동합니다.
balls[i].x+=balls[i].xdir;
// 공이 왼쪽 경계를 넘은 경우
if(balls[i].x<balls[i].size/2)
{
// 왼쪽 경계에 딱 붙이고
balls[i].x=balls[i].size/2;
// 다음번 이동 방향을 반대쪽(오른쪽)으로 바꿉니다.
balls[i].xdir=-balls[i].xdir;
}
// 역시 공이 오른쪽 경계를 넘은 경우
if(balls[i].x>myCanvas.width-balls[i].size/2)
{
// 공을 오른쪽 경계선에 딱 붙이고
balls[i].x=myCanvas.width-balls[i].size/2;
// 다음번에는 왼쪽으로 이동하게 합니다.
balls[i].xdir=-balls[i].xdir;
}
// 공의 y좌표 또한 y방향 벡터만큼 이동합니다.
balls[i].y+=balls[i].ydir;
// 공이 위쪽 경계선을 넘으면
if(balls[i].y<balls[i].size/2)
{
// 위쪽 경계선에 붙이고
balls[i].y=balls[i].size/2;
// 다음번 이동할때는 공이 아랫쪽으로 향하게 합니다.
balls[i].ydir=-balls[i].ydir;
}
// 반대로 공이 아랫쪽 경계선을 넘는다면
if(balls[i].y>myCanvas.height-balls[i].size/2)
{
// 아랫쪽 경계선에 붙이고
balls[i].y=myCanvas.height-balls[i].size/2;
// 다음번에는 위쪽으로 이동하도록 합니다.
balls[i].ydir=-balls[i].ydir;
}
}
그 다음으로는 공을 실제 캔버스에 그려주는 부분인데요.
// draw 이벤트
function onDraw()
{
if(init==false)return;
Context.strokeStyle="#000";
Context.lineWidth=1;
Context.strokeRect(0, 0, myCanvas.width-1, myCanvas.height-1);
Context.fillStyle="#fcfcfc";
Context.fillRect(1, 1, myCanvas.width-2, myCanvas.height-2);
for(i=0;i<ball_max;++i)
{
Context.drawImage(ball_image,
balls[i].x-balls[i].size/2,
balls[i].y-balls[i].size/2,
balls[i].size,
balls[i].size
);
}
}
첫번째 시간과 크게 다를 건 없고,
유심히 보실 부분은 공을 캔바스에 그릴때, 약간의 계산식이 들어간다는 점이지요.
왜냐하면 캔바스는 공 이미지의 왼쪽 상단 좌표를 기준으로 기본적으로 그리기 때문에
공의 중심좌표를 기준으로 그리려면 아래와 같은 소스를 사용하여야 합니다.
Context.drawImage(ball_image, // 공 이미지
// 공의 x좌표에서 공의 크기의 절반만큼을 뺀 x좌표
balls[i].x-balls[i].size/2,
// 공의 y좌표에서 공의 크기의 절반만큼을 뺀 y좌표
balls[i].y-balls[i].size/2,
// 공의 x크기
balls[i].size,
// 공의 y크기
balls[i].size
);
대략적인 소스 흐름은 위와 같습니다.
초심자분에게는 난이도가 꽤 있는 편이기 때문에 여러번 반복해서 읽어주셔야 어느정도 감이 잡히실 겁니다.
여기까지 읽어 주셔서 감사합니다 :)
다음강좌 / https://itadventure.tistory.com/133
'자바스크립트와 캔버스' 카테고리의 다른 글
자바스크립트와 캔버스, 5번째 시간. 테트리스를 만들어봐-1 (2) | 2019.09.23 |
---|---|
자바스크립트와 캔바스 4번째 시간. 마우스의 파동을 느껴봐! (2) | 2019.09.21 |
자바스크립트와 CANVAS 두번째시간. 캔바스에 눈을 내리자 (0) | 2019.09.21 |
HTML 표준 CANVAS 기술 소개 (2) | 2019.09.20 |
jQuery 함수 ( prototype ) 추가 (1) | 2019.08.25 |