

3개로 지정.게임은 <canvas> 엘리먼트에 렌더링된다.<style> * { padding: 0; margin: 0; } canvas { background: #eee; display: block; margin: 0 auto; } </style> <canvas id="myCanvas" width="480" height="320"></canvas> <script> // JavaScript 코드가 여기에 들어갈 것이다. </script> </body>캔버스 속성으로 id값을 myCanvas를 하고 너비 480, 높이 320으로 지정했다.
id와height속성을 canvas엘리먼트에서 정의한다.
var함수getElementById로 아이디값을 넣어준다.
그리고 나서는 캔버스에 그리기 위해 실질적으로 사용되는 도구인 2Drendering context를ctx변수에 저장해야한다.

ctx.beginPath();
ctx.rect(20, 40, 50, 50); //직사각형 정의(처음 두값은 좌상단 좌표 의미, 나머지 두 값은 너비와 높이를 의미)
ctx.fillStyle = "#FF0000"; //사각형 빨간색으로
ctx.fill(); //색상 값을 갖는다.
ctx.closePath();
ctx.beginPath(); ctx.arc(240, 160, 20, 0, Math.PI*2, false); ctx.fillStyle = "green"; ctx.fill(); ctx.closePath();위 코드에서
arc()메서드:6개의 파라미터를 갖는다.
- 원의 중심을 가리키는
x와y좌표- 시작 각도와 끝 각도
- 그리는 방향(
false를 넣으면 시계방향 / 기본 값이나true를 넣으면 반시계 방향)
ctx.beginPath(); ctx.rect(160, 10, 100, 40); ctx.strokeStyle = "rgba(0, 0, 255, 0.5)"; ctx.stroke(); ctx.closePath();
fill()을 사용해서 원에 색상을 채울 수 있다면,stroke()를 이용하면 원의 외곽선에 색상을 부여할 수 있다.

나는 캔버스의 속성을 알게되었다. 두 번째 챕터로 넘어가서 게임에서 공을 움직이기위해 어떻게 해야하는지 계속 알아보겠다.
캔버스에 그리는것을 지속적으로 갱신하기 위해서는, 계속해서 그리는 것을 반복하게 만들어주는 함수가 필요하다. 매 프레임마다 위치를 바꿔주기 위한 몇가지 변수들을 포함한다.
JavaScript 타이밍 함수인 setInterval() 이나 requestAnimationFrame() 를 이용하면 함수를 몇번이고 계속해서 반복 할 수 있다고 한다.
다시 script태그에서 처음 두 줄만 제외하고 나머지는 모두 지운다.
function draw() { // drawing code } setInterval(draw, 10);무한히 작동되는
setInterval함수 덕에, 여기서draw()함수는 우리가 멈추기 전 까지10밀리초마다 영원히 호출된다.
ctx.beginPath(); ctx.arc(50, 50, 10, 0, Math.PI*2); ctx.fillStyle = "#0095DD"; ctx.fill(); ctx.closePath();이제 위에서 이 코드로 수정해본다. 그럼 공은 매 프레임마다 다시 그려지게 될 것이다.
ctx.arc(50, 50, 10, 0, Math.PI*2);
(50,50)이라는 좌표 대신에, x와 y라는 변수를 이용하여 하단 중앙에서 그려지게 하겠다.
x와 y를 정의하기 위해서 다음 두 줄을 추가 한다.
var x = canvas.width/2; var y = canvas.height-30;그 다음
draw()함수를 갱신 할 것이다.arc()메소드안에서x와y변수를 사용.function draw() { ctx.beginPath(); ctx.arc(x, y, 10, 0, Math.PI*2); ctx.fillStyle = "#0095DD"; ctx.fill(); ctx.closePath(); }공을 움직이는 것을 표현하기 위해
x와y에 작은 값을 매 프레임마다 더해줄 것이다.
그 작은 값을dx와dy라 정의하고, 각각2와-2로 값을 정해보겠다.var dx = 2; var dy = -2;
dx와dy변수를 이용해서 매 프레임마다x와y변수를 갱신해 준다.draw()함수에 추가하면function draw() { ctx.beginPath(); ctx.arc(x, y, 10, 0, Math.PI*2); ctx.fillStyle = "#0095DD"; ctx.fill(); ctx.closePath(); x += dx; y += dy; }여기까지 하면 공을 움직이는데는 이상이 없다. 하지만 뒤에 흔적을 지우기 위해서 코드를 수정해야할것이다.
clearRect() 이 메소드는 네 개의 파라미터가 필요하다. 직사각형의 좌상단 모서리를 표시할 x와 y좌표, 우하단 모서리를 표시할 x와 y좌표가 있다.function draw() { ctx.clearRect(0, 0, canvas.width, canvas.height); //0,0 =좌상단, width.height는 우하단 모서리 ctx.beginPath(); ctx.arc(x, y, 10, 0, Math.PI*2); ctx.fillStyle = "#0095DD"; ctx.fill(); ctx.closePath(); x += dx; y += dy; }
draw()함수와 공을 움직이는 코드를 분리시킨다.draw()함수를 두개로 쪼개볼까?👉🏻 자바스크립트 태그안에 총 정리를 하면 다음과 같다.
<script> var canvas = document.getElementById("myCanvas"); var ctx = canvas.getContext("2d"); var x = canvas.width/2; var y = canvas.height-30; var dx = 2; var dy = -2; function drawBall() { ctx.beginPath(); ctx.arc(x, y, 10, 0, Math.PI*2); ctx.fillStyle = "#0095DD"; ctx.fill(); ctx.closePath(); } function draw() { ctx.clearRect(0, 0, canvas.width, canvas.height); drawBall(); x += dx; y += dy; } setInterval(draw, 10); </script>
결과 🎬

공을 움직이는 코드를 일단 성공해봤다. 이게 첫시작이다...
내용이 너무 길면 보는사람도 지루해 할 수 있기에 중요부분만 정리해야겠다.
( ͡° ͜ʖ ͡°)_/¯

canvas 엘리먼트는 단지 그래픽들을 위한 컨테이너id와 height 속성을 canvas 엘리먼트로 정의한다.canvas의 X Y좌표는 캔바스에서 그리는데 있어서 위치를 나타내는데 사용한다.setInterval()함수 : 지정한 밀리초마다 영원히 호출 시킴. (타이밍 함수 라는것)
fill()을 사용해서 원에 색상을 채울 수 있다면,stroke()를 이용하면 원의 외곽선에 색상을 부여
arc()메서드: 6개의 파라미터를 갖는다.
x와 y좌표false를 넣으면 시계방향 / 기본 값이나 true를 넣으면 반시계 방향)캔버스의 내용들을 지워주기 위한 메소드인 clearRect()
clearRect()는 매 프레임마다 그릴때 이전 프레임을 지워주지 않았을 경우에 사용됨.
- 충돌을 감지하기 위해서는 공이 벽에 닿았는지 확인
- 그에 따라 움직이는 방향 수정
변수를 하나 선언한다.ballRadius= 10; 원의 반지름 값을 대입하여 계산하는데 사용
drawBall()func에서ballRadius로 수정
캔버스에는 총4개의 벽이 있다.
캔버스 내 위치 구조는 좌상단부터 시작
- 공의 위치에서 좌상단부터
y값을 측정하기 때문에
상단 모서리에서의y값은0, 하단에서의y값은480, 즉 캔버스의 높이값.
y축 움직임의 반대 방향으로 튕겨낸다.
if(y + dy > canvas.height || y + dy < 0) { dy = -dy; }
이번엔 상,하단이 아닌 좌우 모서리이다.
y값 대신 x값을 대입하여 반복
if(x + dx > canvas.width || x + dx < 0) { dx = -dx; } if(y + dy > canvas.height || y + dy < 0) { dy = -dy; }

if(x + dx > canvas.width-ballRadius || x + dx < ballRadius) { dx = -dx; } if(y + dy > canvas.height-ballRadius || y + dy < ballRadius) { dy = -dy; }

게임을 만들면서 점점 형태가 보여서 신기하기도 하다🙄
4개의 벽으로 부터 내 위치 구조는 좌상단부터 시작된다는점!!y축 움직임의 반대 방향으로 튕긴다?y값 대신 x값을 대입하여 반복한다.
paddle의 높이와 너비,x축의 위에 시작 지점을 정의하자!!
paddle을 스크린에 그리는 함수를 만들자!!var paddleHeight = 10; var paddleWidth = 75; var paddleX = (canvas.width-paddleWidth)/2;
변수keydown과 keyup 이벤트를 사용해 패들의 움직임을 조종할 수 있는 코드가 실행되어야함.keydown과 keyup 이벤트를 핸들링하는 두개의 함수버튼을 누르는 것은
boolean변수로 정의하고 초기화.
처음엔 버튼이 눌려지지 않은 상태이므로 기본값은false이다.var rightPressed = false; var leftPressed = false;키가 눌렸음을 인식하기 위해
이벤트 리스너를 설정하겠다.document.addEventListener("keydown", keyDownHandler, false); document.addEventListener("keyup", keyUpHandler, false);
function keyDownHandler(e) { //키보드 중 어떤 키 하나를 누르면 keydown 이벤트 발생 -> keyDownHandler() 함수가 실행 if(e.keyCode == 39) { rightPressed = true; } else if(e.keyCode == 37) { leftPressed = true; } } function keyUpHandler(e) {//키에서 손을 때면 keyup이벤트 -> keyupHandler()함수 실행 if(e.keyCode == 39) { rightPressed = false; } else if(e.keyCode == 37) { leftPressed = false; } }
✔ 각 변수에 키를 누르면 true / 키에서 손을 때면 false가 됨
이때 function 값에 e변수 를 적용해서 이벤트를 파라미터로 사용해보자.
✔ keycode는 눌려진 키에 대한 정보를 가지고 있다. 예시로 왼쪽 방향이 37 오른쪽이 39이다. 만약에 왼쪽키를 누르면 leftPreseed 변수가 true 가 설정이 되고,
✔ 반대 오른쪽키를 누르면 rightPressed 변수에도 동일하게 적용된다.
각각의 프레임이 렌더링 될때마다 왼쪽 오른쪽 방향키가 눌렸는지 확인.
if(rightPressed && paddleX < canvas.width-paddleWidth) { paddleX += 7; } else if(leftPressed && paddleX > 0) { paddleX -= 7; }
위 코드에서는 왼쪽키를 눌르면 패들은 좌측으로 7픽셀 움직이고,
반대로, 오른쪽키를 눌러도 7픽셀 움직인다. 키를 너무 오래 누를 경우에
패들이 캔버스 밖으로 사라질 수 있어 캔버스의 width값과 패들width를 계산해줘야한다.
paddleX 의 위치는 캔버스 왼쪽 끝 0 위치와 오른쪽 canvas.width-paddleWidth 에서 움직인다.
draw() 함수 안에서 drawPaddle()을 호출해준다.

👏👏👏👏👏👏👏👏👏👏👏
벽에 공을 반사시키는 코드에서 수정
if(x + dx > canvas.width - ballRadius || x + dx < ballRadius){ dx = -dx; } if(y + dy > canvas.height - ballRadius || y + dx < balllRadius){ dy = -dy; }
사면 모두 공을 좌우,위쪽에만 적용시켜주고 아래에 닿는다면 게임over되게 해야한다.
그래서 if문을 수정해보자!
우선 경고 메시지를 주려면 alert("경고창")을 해야될텐데
경고메시지를 보여주고 페이지를 리로딩하게 게임을 다시 시작할 것이다.
그 과정에서는 location.reload()함수를 써보자 !
if(y + dy < ballRadius) { dy = -dy; } else if(y + dy > canvas.height-ballRadius) { alert("GAME OVER"); document.location.reload(); ``` }
공이 밑면에 닿는 순간, 공이 패들의 안쪽에 있는지 확인해야한다.
만약 그렇다면, 공은 튕겨진다. 그게 아니라면, 게임의 전과 같이 끝내야함.
if(y + dy < ballRadius) { dy = -dy; } else if(y + dy > canvas.height-ballRadius) { if(x > paddleX && x < paddleX + paddleWidth) { dy = -dy; } else { alert("GAME OVER"); document.location.reload(); } }

벽돌을 위한 코드를 2차원 배열 동작하는 반복문을 사용할것이다.
var brickRowCount = 3; var brickColumnCount = 5; var brickWidth = 75; var brickHeight = 20; var brickPadding = 10; var brickOffsetTop = 30; var brickOffsetLeft = 30;
var bricks = []; for(var c=0; c<brickColumnCount; c++) { bricks[c] = []; for(var r=0; r<brickRowCount; r++) { bricks[c][r] = { x: 0, y: 0 }; } }배열을 담을 수 있는
bricks함수를 만들었다.
그리고 이중for문으로 행과 열 수만큼 반복되는 벽돌을 만든다.
배열안의 모든 벽돌을 반복해 화면에 그려줄 함수를 만들어보자.
function drawBricks() { for(var c=0; c<brickColumnCount; c++) { for(var r=0; r<brickRowCount; r++) { bricks[c][r].x = 0; bricks[c][r].y = 0; ctx.beginPath(); ctx.rect(0, 0, brickWidth, brickHeight); ctx.fillStyle = "#0095DD"; ctx.fill(); ctx.closePath(); } } }행,열 반복함으로써
x,y값을 설정, 캔버스에brickwidth*brickHeight크기의 벽돌을 그린다. 좌표는0,0위치해 있고, 연산을 통해 각 벽돌의x,y값을 계산해야함
var brickX = (c*(brickWidth+brickPadding))+brickOffsetLeft; var brickY = (r*(brickHeight+brickPadding))+brickOffsetTop;
**brickX는brickWidth + brickPadding에 c를 곱하고, brickOffsetLeft를 더한 값
brickY도 동일합니다. 이제 모든 벽돌들이 올바른 위치에, 알맞은 간격을 두었다.
캔버스 모서리부터 오프셋 값만큼 그릴 수 있게되었다.
brickX와 brickY 값을 (0, 0) 대신 갑으로 할당하고 drawpaddle함수 아래에 추가.

여기까지 벽돌도 만들어보았다.
어느정도 틀이 잡힌 느낌이 든다. 이제 동적인 것과
유저의 컨트롤만 남았다. 화이팅하자 !!
코드의 가독성을 향상시키기 위해 충돌 감지 반복문에서 b변수를 정의
function collisionDetection() { for(var c=0; c<brickColumnCount; c++) { for(var r=0; r<brickRowCount; r++) { var b = bricks[c][r]; // calculations } } }
function collisionDetection(){ for(var c = 0; c<brickColumCount; c++){ for(var r=0; r<brickRowCount; r++){ var b = brick[c][r]; if(x > b.x && x < b.x+brickWidth && y > b.y && y < b.y+brickHight){ dy = -dy; } } } }
아직까지 공이 충돌시 벽돌이 제거되지 않는다.
변수를 선언해서 벽돌을 초기화를 코드에, status 속성을 각 벽돌 객체에 추가해보자
var bricks = []; for(var c=0; c<brickColumnCount; c++) { bricks[c] = []; for(var r=0; r<brickRowCount; r++) { bricks[c][r] = { x: 0, y: 0, status: 1 }; } }
status 속성을 확인해야 한다. 만약 status가 1이라면 벽돌을 그리고, 만약 0이라면 이미 공이 치고간 벽돌이므로 그릴 필요가 없다.
function drawBricks() { for(var c=0; c<brickColumnCount; c++) { for(var r=0; r<brickRowCount; r++) { if(bricks[c][r].status == 1) { var brickX = (c*(brickWidth+brickPadding))+brickOffsetLeft; var brickY = (r*(brickHeight+brickPadding))+brickOffsetTop; bricks[c][r].x = brickX; bricks[c][r].y = brickY; ctx.beginPath(); ctx.rect(brickX, brickY, brickWidth, brickHeight); ctx.fillStyle = "#0095DD"; ctx.fill(); ctx.closePath(); } } } }
collisonDectection 함수에 벽돌 status 속성을 포함 시키자.
만약 벽돌 활성 상태시 (status 1)이라면 추우돌이 일어났는지 확인해야한다.
만약 충돌이 발생했다면 다시 그리지 않게 벽돌의 속성을 0으로 변경function collisionDetection(){ for(var = c=0; c<brickCoulumCount; c++){ for(var = r=0; r<brickRowCount; r++){ var b = bricks[c][r]; if(b.status == 1){ if(x > b.x && x < b.x+brickWidth && y > b.y && y < b.y+brickHeight){ dy = -dy; b.status = 0; } } } } }
draw함수에서collisionDetection()함수를 호출

점수를 기록하려고 또 하나의 변수를 생성 할 것이다.
var score = 0;
drawScore()점수 표시를 만들고 업데이트 하는 기능 도 필요하다.
collistionDetection() 함수 뒤에 다음을 추가하자.fucntion drawScore(){ ctx.font = "16px Arial"; ctx.fillStyle = "#0095DD"; ctx.fillText("Score:" +score, 8, 20); }
fileText()설정하고 캔버스에 배치 할 실제 텍스트와 배치 할 위치를 설정
첫번째 매개 변수는 텍스트 자체. 현재 포인트 수를 보여준다, 마지막 두 매개 변수는 텍스트가 캔버스에 배치 될 좌표
drawPaddle()호출 바로 아래에 추가한다.
function collisionDetection() { for(var c=0; c<brickColumnCount; c++) { for(var r=0; r<brickRowCount; r++) { var b = bricks[c][r]; if(b.status == 1) { if(x > b.x && x < b.x+brickWidth && y > b.y && y < b.y+brickHeight) { dy = -dy; b.status = 0; score++; } } } } }
function collisionDetection() { for(var c=0; c<brickColumnCount; c++) { for(var r=0; r<brickRowCount; r++) { var b = bricks[c][r]; if(b.status == 1) { if(x > b.x && x < b.x+brickWidth && y > b.y && y < b.y+brickHeight) { dy = -dy; b.status = 0; score++; if(score == brickRowCount*brickColumnCount) { alert("YOU WIN, CONGRATULATIONS!"); document.location.reload(); clearInterval(interval); // Needed for Chrome to end game } } } } } }여기서
document.location.reload()기능은 페이지를 다시로드하고 경고 버튼을 클릭하면 게임을 다시 시작

이미 키보드 컨트롤을 추가했지만, 마우스 컨트롤도 쉽게 추가 할 수 있다.
mousemove. 이벤트에 대한 리스너 생성하기!
document.addEventListener("mousemove", mouseMoveHandler, false);
function mouseMoveHandler(e) { var relativeX = e.clientX - canvas.offsetLeft; if(relativeX > 0 && relativeX < canvas.width) { paddleX = relativeX - paddleWidth/2; } }
relativeX 뷰포트의 수평 마우스 위치
(e.clientX)에서 캔버스의 왼쪽 가장자리와 뷰포트의 왼쪽 가장자리 사이의 거리()를 뺀 값을 계산
canvas.offsetLeft 사실상 이것은 사이의 거리와 같다. 캔버스 왼쪽 가장자리와 마우스 포인터.
상대 x포인터 위치가 0보다 크고 Canvas 너비보다 낮으면 포인터가 Canvas 경계 내에 있고 paddleX위치가 패들 relativeX 너비의 절반을 뺀 값으로 설정 움직임이 실제로 패들의 중앙을 기준으로 한다.
패들은 이제 마우스 커서의 위치를 따르지만 캔버스의 크기로 이동을 제한하고 있습니다.
var lives = 3; //3개의 목숨을 준다.
수명 카운터를 그리는 것은 점수 카운터를 그리는 것과 같다.
function drawLives() { ctx.font = "16px Arial"; ctx.fillStyle = "#0095DD"; ctx.fillText("Lives: "+lives, canvas.width-65, 20); }
게임을 즉시 종료하는 대신 더 몫이 끝날때까지 생명 수를 줄인다.
다음 생을 시작하면 공과 패들 위치를 재설정 할 수도 있다.
lives--; if(!lives) { alert("GAME OVER"); document.location.reload(); clearInterval(interval); // Needed for Chrome to end game } else { x = canvas.width/2; y = canvas.height-30; dx = 2; dy = -2; paddleX = (canvas.width-paddleWidth)/2; }공이 화면의 하단 가장자리에 닿으면 live변수에서 생명 1개를 뺀다. 남은 생명이 0이면 GameOver/ 아직 목숨이 남아 있으면 다시 재설정 됨
requestAnimationFram()으로 렌더링 개선
requestAnimationFrame브라우저가 현재 setInterval(). 다음 줄을 바꾼다.
var interval = setInterval(draw, 10)
requestAnimationFrame(draw);
draw();

내 자기소개 페이지에 게임을 저장했다.
녹화중 렉이 생겼네..
2D게임이지만 이번 과제를 진행하면서 자바스크립트에 대해서
많이 알아가고 공부한것 같다. 추후엔 기능이 더 추가되는 멋진 게임을
만들고싶다.
노래추가 + 난이도 추가 + 랭킹등록 등,, 생각해 본것들은 많다.
그러기 위해서는 자바스크립트 공부를 열심히 해야겠다.
