DEVLOG

HTML5 캔버스(Canvas) 화면에 튕기는 공 만들기 본문

dev log/html canvas

HTML5 캔버스(Canvas) 화면에 튕기는 공 만들기

meroriiDev 2020. 12. 15. 17:44
728x90
반응형

mesonia.tistory.com/70

 

HTML5 캔버스(Canvas)기초, 캔버스로 그림판만들기

눈으로 보여지는걸 만드는게 좋아서 선택한 길이다보니 이왕 하는거 제대로 만들어보자!하는 생각이 들었다. 그래서 인터렉티브한 웹을 만드는데 기본이 되는 html5 캔버스에 대해서 공부해보려

mesonia.tistory.com

Interaction

지금까지 공부해본 것들을 이용해서 하나의 예제를 따라해보았다.

flow

먼저 캔버스로 하나의 네모박스를 그리고

박스가 오른쪽으로 이동하도록 하고

박스가 오른쪽에 닿으면 다시 왼쪽에서 나타나도록 설정하고

정상적으로 움직이면 오른쪽으로 이동하는 값, 가장 처음에 위치할 x, y좌표를 랜덤으로 주고

박스를 클릭하는 것을 인지하도록 클릭이벤트를 추가한 후

하나의 박스를 배열에 담아 여러개를 만드는 순서로 진행하면 된다.

 

영상에서 따라한거라 따로 설명은 넣지않을 예정이다...!

 

이런 예제를 따라하다가 혼자서 디자인을 변경해보았다.

그랬더니 갑자기 공이 그냥 흐르는게 아니라 화면의 끝에 닿으면 튕기면 좋겠다는 생각을 했다.

그래서 화면에서 공이 튕기는 예제를 만들어보았다.

이미 공이 튀기는 예제도 만들어져있지만 전부 class형으로 만드는 코드가 너무 어려워서

이전에 했던 예제와 공 튀기는 예제를 참고해서 내가 이해할 수 있는 코드로 직접 만들었다.

 

● 화면에 튕기는 공 예제

flow

이 예제도 역시

먼저 캔버스로 하나의 공을 그리고

공이 대각선으로 이동하게 하고

공이 화면에 끝에 닿으면 45도로 튕기도록 설정하고

정상적으로 움직이면 가장 처음에 위치할 x, y좌표, 이동하는 속도, 색상, 크기등을 랜덤으로 주고

하나의 공을 배열에 담아 여러개를 만든 후, 

공이 클릭되는 것을 인지하는 클릭이벤트를 추가하는 순서로 진행할 것이다.

 

+ 여기에 스크린 크기가 조절돼도 그 값을 따라 작동하는 반응형으로 작업할 것

 

○ 캔버스에 하나의 공 그리기

let stageWidth, stageHeight;
const canvas = document.createElement('canvas');
document.body.appendChild(canvas);
const ctx = canvas.getContext('2d');

window.addEventListener('resize', resize.bind(), false);
resize();
function resize(){
    stageWidth = document.body.clientWidth;
    stageHeight = document.body.clientHeight;
    canvas.width = stageWidth;
    canvas.height = stageHeight;
}

class Ball{
    constructor(x, y, radius, speed, color){
        this.x = x;
        this.y = y;
        this.vx = speed;
        this.vy = speed;
        this.radius = radius;
        this.color = color;
        
        this.draw();
    }
    draw(){
        ctx.beginPath();
        ctx.fillStyle=this.color;
        ctx.arc(this.x, this.y, this.radius, Math.PI*2, false);
        ctx.fill();
    }
}

const box = new Ball(50, 50, 20, 5, 'red');
function render(){
    ctx.clearRect(0, 0, canvas.width, canvas.height);
    box.draw();
    requestAnimationFrame(render);
}

render();

 

+ resize이벤트

window.addEventListener('resize', resize.bind(), false);
resize();
function resize(){
    stageWidth = document.body.clientWidth;
    stageHeight = document.body.clientHeight;
    canvas.width = stageWidth;
    canvas.height = stageHeight;
}

resize이벤트로 화면의 크기가 조절됨을 감지하여 캔버스 사이즈를 알아서 조절해준다.

설정하지 않으면 화면의 크기를 조절할 때 일부가 잘려서 보이게 될 것이다.

 

class Ball{
    constructor(x, y, radius, speed, color){
        this.x = x;
        this.y = y;
        this.vx = speed;
        this.vy = speed;
        this.radius = radius;
        this.color = color;
        
        this.draw();
    }
    draw(){
        ctx.beginPath();
        ctx.fillStyle=this.color;
        ctx.arc(this.x, this.y, this.radius, Math.PI*2, false);
        ctx.fill();
    }
}

클래스 생성자에 관련해서 디테일하게 알지 못해서 설명을 할 수는 없지만..

constructor부분에서는 전달된 공의 정보를 받아오는 용도로, draw함수는 공을 그려주는 용도로 사용한다.

 

const box = new Ball(50, 50, 20, 5, 'red');
function render(){
    ctx.clearRect(0, 0, canvas.width, canvas.height);
    box.draw();
    requestAnimationFrame(render);
}

render();

box라는 변수에 Ball생성자를 만들어준다.

이때 안에 값들은 나중에 랜덤값을 뽑아서 변수로 보낼 것이지만 일단 정해진 값으로 넣어준다!

render함수 안에서 draw()함수를 실행하고 이 render가 무한루프로 돌아갈 수 있도록 계속 호출해준다.

움직이는 듯한 애니메이션 효과를 위해서 draw하기전에 clearRect로 화면을 지워준다.

 

○ 공이 대각선으로 이동하게 만들기

draw(){
            this.x += this.vx;
            this.y += this.vy;
            
            ...
            
 }

Ball을 만드는 constructor아래 draw함수에 위와 같은 코드 두줄이면 된다.

x,y 모두 같은 값을 플러스하도록 설정하면 대각선으로 이동하게 할 수 있다.

얼만큼 값을 더해주느냐에 따라 각도 조절을 가능할 것 같다.

 

○ 공이 화면에 끝에 닿으면 45도로 튕기도록 설정

draw(){
        const minX = this.radius;
        const maxX = stageWidth - this.radius;
        const minY = this.radius;
        const maxY = stageHeight - this.radius;

        if(this.x<=minX || this.x>=maxX){
            this.vx *= -1;
            this.x += this.vx;
        }else if(this.y<=minY || this.y>=maxY){
            this.vy *= -1;
            this.y += this.vy;
        }else{
            this.x += this.vx;
            this.y += this.vy;
        }
        
        ...
}

바로 위에서 만들어주었던 두줄을 수정해주어야한다.

무작정 vx, vy를 더하기만 하면 화면 밖으로 사라져도 계~~속 같은 방향으로 이동하고 있을 것이다.

이 공을 화면 바깥에 닿았을때 튕기도록 해주려면 먼저 이동범위를 체크해야한다.

이 그림처럼 minX~maxX / minY~maxY만큼만 가능하도록 하고 이 범위가 넘어가면 튕기도록 해주어야한다.

이때 그림을 보면 minX와 minY는 공의 반지름만큼이고

maxX와  maxY는 캔버스 크기에서 반지름을 뺀 만큼이다.

 

그런데 이렇게만 실행하면 화면 끝에 걸쳐있는 애들이 벽에 낀 것처럼 움직이지 못하고 그 자리에서만 움찔움찔거리는 오류가 나타나게 된다.

시작 x,y값이랑 이동하는 x,y이 랜덤으로 돌아가다보니 범위에 해당하지 못하는 애들한테 발생하는 오류인데 

이 오류를 해결했을 때 희열.....쾌감........ㅠ,ㅠ 못잊어 ㅠ,ㅠ

 

이런 오류가 발생하는 이유는 범위 바깥에 해당하는 값을 부여받았을 때

범위에 해당하지 못하니 하라는대로 vx에 -1을 곱해서 -vx을 했는데?

vx값마저 너무 작은 랜덤값을 부여받아서 여전히 해당하지 못하고 그러다 보니

또 범위에 해당하지 못해서 vx에 -1을 곱해서 또 +vx를 하고

-vx를 하고 +vx를 하고 ...

이게 무한반복이 되다보니까 그 자리를 못벗어나는거다...

그래서 지금 x값에 vx를 뺐는데도 범위에 해당하지 못하면

아예 최소값을 x에 넣어버리면 그 감옥을 벗어날 수 있게 되는 것이다.

if((this.x-this.vx)>=maxX){
	this.x = maxX;
}else if((this.x-this.vx)<=minX){
	this.x = minX;
}

 

오류 수정한 튕기기코드

        const minX = this.radius;
        const maxX = stageWidth - this.radius;
        const minY = this.radius;
        const maxY = stageHeight - this.radius;

        if(this.x<=minX || this.x>=maxX){
            if((this.x-this.vx)>=maxX){
                this.x = maxX;
            }else if((this.x-this.vx)<=minX){
                this.x = minX;
            }
            this.vx *= -1;
            this.x += this.vx;
        }else if(this.y<=minY || this.y>=maxY){
            if((this.y-this.vy)>=maxY){
                this.y = maxY;
            }else if((this.y-this.vy)<=minY){
                this.y = minY;
            }
            this.vy *= -1;
            this.y += this.vy;
        }else{
            this.x += this.vx;
            this.y += this.vy;
        }

공이 튕기도록 설정해주려면 좌우에 닿았을때는 vx값에 -1을 곱해 y이동은 그대로, x이동만 반대로 움직이도록 하고

위아래에 닿았을때는 vy에 -1을 곱해 x이동은 그대로, y이동만 반대로 움직이도록하면 된다.

 

지금까지 코드

(하나의 공이 대각선으로 움직이는데 화면의 끝에 닿을때 튕기기까지)

let stageWidth, stageHeight;
const canvas = document.createElement('canvas');
document.body.appendChild(canvas);
const ctx = canvas.getContext('2d');


window.addEventListener('resize', resize.bind(), false);
resize();
function resize(){
    stageWidth = document.body.clientWidth;
    stageHeight = document.body.clientHeight;
    canvas.width = stageWidth;
    canvas.height = stageHeight;
}

class Ball{
    constructor(x, y, radius, speed, color){
        this.x = x;
        this.y = y;
        this.vx = speed;
        this.vy = speed;
        this.radius = radius;
        this.color = color;
        
        this.draw();
    }
    draw(){
        const minX = this.radius;
        const maxX = stageWidth - this.radius;
        const minY = this.radius;
        const maxY = stageHeight - this.radius;

        if(this.x<=minX || this.x>=maxX){
            if((this.x-this.vx)>=maxX){
                this.x = maxX;
            }else if((this.x-this.vx)<=minX){
                this.x = minX;
            }
            this.vx *= -1;
            this.x += this.vx;
        }else if(this.y<=minY || this.y>=maxY){
            if((this.y-this.vy)>=maxY){
                this.y = maxY;
            }else if((this.y-this.vy)<=minY){
                this.y = minY;
            }
            this.vy *= -1;
            this.y += this.vy;
        }else{
            this.x += this.vx;
            this.y += this.vy;
        }

        ctx.beginPath();
        ctx.fillStyle=this.color;
        ctx.arc(this.x, this.y, this.radius, Math.PI*2, false);
        ctx.fill();
    }
}



const box = new Ball(50, 50, 20, 5, 'red');
function render(){
    ctx.clearRect(0, 0, canvas.width, canvas.height);
    box.draw();
    requestAnimationFrame(render);
}

render();

 

x, y좌표, 이동하는 속도, 색상, 크기등 랜덤값 넣기

const box = new Ball(50, 50, 20, 5, 'red');

render함수 위에서 값을 지정하여 공을 생성하던 한줄을

let tempX, tempY, tempRadius, tempSpeed, tempColor, r, g, b;
tempRadius = Math.random() * 20 + 20;
tempX = Math.random() * stageWidth;
tempY = Math.random() * stageHeight;
tempSpeed = Math.random() * 1 + 2;
r = Math.random() * 255;
g = Math.random() * 255;
b = Math.random() * 255;
tempColor="rgba("+r+","+g+","+b+",0.5)";
const box = new Ball(tempX, tempY, tempRadius, tempSpeed, tempColor);

각각에 값에 랜덤값을 뽑아서 그 변수를 넣어주면 된다.

랜덤값을 설정하는 메소드는 Math.random()을 사용하면 되고 이값에 곱하는 만큼 범위가 지정될 것이다.

Math.random() * 범위 + 범위 시작값; //범위시작값(없으면 0) ~ 범위

 

○ 공 여러개 만들기

let tempX, tempY, tempRadius, tempSpeed, tempColor, r, g, b;
for(let i=0; i<20; i++){
    tempRadius = Math.random() * 20 + 20;
    tempX = Math.random() * stageWidth;
    tempY = Math.random() * stageHeight;
    tempSpeed = Math.random() * 1 + 2;
    r = Math.random() * 255;
    g = Math.random() * 255;
    b = Math.random() * 255;
    tempColor="rgba("+r+","+g+","+b+",0.5)";
    boxes.push(new Ball(tempX, tempY, tempRadius, tempSpeed, tempColor));
}

방금전 랜덤값을 넣어서 Ball을 생성해서 변수에 담은 부분을

반복문을 사용해서 배열에 push하도록 변경해주면 된다.

그러면 만들어진 ball하나하나가 변수가 아니라 배열에 차곡차곡 쌓이는 것이다.

 

function render(){
    ctx.clearRect(0, 0, canvas.width, canvas.height);
    for(let i=0; i<20; i++){
        boxes[i].draw();
    }
    requestAnimationFrame(render);
}

마지막으로 render함수에서 box.draw()로 변수를 draw해주는게 아니라

역시 반복문와 배열을 사용해서 배열 모두를 draw해주도록 변경하면 끝~!!!!

 

 

○ 공이 클릭되는 것을 인지하는 클릭이벤트

각각의 공에 번호를 부여한 후 그 공이 눌렸을 때 그 공을 인지하는 클릭이벤트다

각각의 공에 부여된 번호는 배열의 index이므로 배열을 생성할 때 index필드를 추가하면된다.

boxes.push(new Ball(i, tempX, tempY, tempRadius, tempSpeed, tempColor));

 

const mousePos = {x:0, y:0};
let selectedBox; //클릭된 박스 

const pop = document.createElement('div');
pop.className='pop';
document.body.appendChild(pop);

canvas.addEventListener('click', function(e){
    mousePos.x = e.layerX;
    mousePos.y = e.layerY;
    // console.log(mousePos);
    let box;
    for(let i=0; i<boxes.length; i++){
      box = boxes[i];
      if (mousePos.x >= (box.x - box.radius) &&
          mousePos.x < (box.x + box.radius) &&
          mousePos.y >= (box.y - box.radius)&&
          mousePos.y < (box.y + box.radius))
          {
            selectedBox = box;
          }else{
            pop.style.display='none';
          }
    }
    if(selectedBox) {
        console.log(selectedBox.index);
        pop.textContent=selectedBox.index;
        pop.style.display='block';
        selectedBox = null;
    }

})

클릭이벤트에서는 이벤트객체의 layerX프로퍼티를 사용해 클릭한 좌표를 읽어오고

박스 사이즈내에 좌표가 위치하는지 읽어오면 된다.

 

재밌넹 :)

 

 

 

 

www.youtube.com/watch?v=hZCHBqpjFDc&t=2698s

geonlee.tistory.com/230

 

HTML5 Canvas Tutorial : 화면에 튕기는 공 만들기

 

geonlee.tistory.com

 

728x90
반응형
Comments