본문 바로가기

html5 캔버스 『마우스에 반응하는 이미지 만들기』

by Recstasy 2020. 2. 13.


캔버스로 가장 간단한 어플을 만든다면 위와 같은 타입을 생각할 수 있다. 

간단하게 보이지만 위의 예시는 캔버스 게임제작을 이해하는 기본 원리가 담겨있다. 코드 전체의 길이도 200줄 가량으로 게임제작 초보자가 암기할 만한 수준이다.


1 운전준비

캔버스 게임제작의 원리를 이해하기 위해서 자동차를 운전하는 상황을 생각해보자. 차를 운전하려면 일단 차가 있어야한다. 너무 당연한가? 그런데 가끔 뭘해야할지 모르는 상황에서는 이 당연한 것조차 생각나지 않을 때가 있다. 


캔버스 게임에서 '차'는 Game클래스다. Game클래스를 만들고, 캔버스 구성요소를 불러오자. 여기서 캔버스 구성요소는 '차량 바디'에 해당한다.

class Game{

    constructor(){

        this.canvas = document.getElementById('game')  //DOM id 'game'

        this.context = this.canvas.getContext('2d');    

    }

}


 

자, 이제 '캔버스'란 차에 탔으면 시동을 걸자. 시동을 거는 명령어는 init()으로 생각하고, 일단 시동을 걸기 위한 장치들을 불러올 필요가 있다.

class Game{

    constructor(){

        this.canvas = document.getElementById('game')  //DOM id 'game'

        this.context = this.canvas.getContext('2d');  

        this.sprites = [];  


        const game = this;

        this.spriteImg = new Image();

        this.spriteImg.src = "https://firebasestorage.googleapis.com/v0/b/webgame-786ab.appspot.com/o/flower.png?alt=media&token=076f5c0a-6733-466f-9c6b-4e8ccc6b29af";

        this.spriteImg.onload = function(){

             game.init();

        }

    }

}


this.spriteImg는 이미지 객체를 저장한다. 그리고 차로 생각하면 '연료'에 해당한다. spriteImg객체가 onload되면 game.init()명령어를 내려 시동을 거는 셈이다. 또, 위의 코드에서 this.sprites 배열은 '연료상태를 표시하는 연료 상태표시등에 해당한다.



2 시동걸기

차에는 시동을 걸기전에도 전원이 켜지는 장치들이 있다. 계기판, 내비, 비상등, 시계, 블랙박스 등..

이런 장치들은 배터리가 방전되지 않는 한, 사용자의 반응을 대기하고 있는 상태에 있다. 게임 코드에서는 시동걸기와 관련된 사항을 init()메서드에서 처리한다.


class Game{

    constructor(){

        this.canvas = document.getElementById('game')  //DOM id 'game'

        this.context = this.canvas.getContext('2d');  

        this.sprites = [];  


        const game = this;

        this.spriteImg = new Image();

        this.spriteImg.src = "https://firebasestorage.googleapis.com/v0/b/webgame-786ab.appspot.com/o/flower.png?alt=media&token=076f5c0a-6733-466f-9c6b-4e8ccc6b29af";

        this.spriteImg.onload = function(){

             game.init();

        }

    }


   init(){

       this.score = 0;

       this.lastTime = Date.now();


       this.spawn();

       this.refresh();

   }

  //Game Class end

}


init()메서드를 '시동걸기'에 비유핸다면 아래 4가지를 수행해야 한다. 


1] 주행기록 시작

2] 기름투입(엔진 내부)

3] 엔진동작

4] 전자장치 시작


위의 코드에서 this.score, this.lastTime은 "1]주행기록 시작"에 해당한다. 게임세계에서의 시간을 구하기 위해서는 this.lastTime값이 필요하고, 점수를 기록하기 위한 score변수역시 필수다. 


this.spawn()메서드는 이미지 정보를 담고있는 객체이며, 이미지는 게임세계를 움직이는 동력원이다. 차로 비유하자면 '연료'인 셈이다. this.refresh()는 재귀를 담고 있는데 자동차 엔진의 반복운동(피스톤)은 게임의 refresh()메서드와 비교할 수 있다.


위의 코드에서 남은 부분은 사용자 이벤트 처리다. 이벤트 처리는 자동차의 전자제어 장치와 비슷하다. 게임코드로 표현하면 아래와 같다.


class Game{

    constructor(){

        this.canvas = document.getElementById('game')  //DOM id 'game'

        this.context = this.canvas.getContext('2d');  

        this.sprites = [];  


        const game = this;

        this.spriteImg = new Image();

        this.spriteImg.src = "https://firebasestorage.googleapis.com/v0/b/webgame-786ab.appspot.com/o/flower.png?alt=media&token=076f5c0a-6733-466f-9c6b-4e8ccc6b29af";

        this.spriteImg.onload = function(){

             game.init();

        }

    }


   init(){

       this.score = 0;

       this.lastTime = Date.now();

       const game = this;


       this.spawn();

       this.refresh();


       function tab(e){

           game.tab(e);

       }


       if('ontouchstart' in window){

           this.canvas.addEventListener('touchstart', tab);

       }else{

           this.canvas.addEventListener('mousedown', tab);

       }

       

   }

  //Game Class end

}


'ontouchstart' in window를 통해 'touchstart'와 'mousedown'이벤트가 대기상태를 유지한다. 만일 스마트폰과 같은 터치이벤트를 받는 디바이스라면 'touchstart'이벤트가 실행되고, 마우스를 사용하는 상태에서는 'mousedown'이벤트가 실행된다. 




3 터치반응

포스팅의 첫부분에 실행되고 있는 꽃 이미지를 터치해보자. 사용자가 꽃 이미지를 터치했을 때, 아래와 같은 2가지 상황이 존재다.


1] 꽃 이미지의 범위안에 사용자의 터치좌표가 있

2] 꽃 이미지의 범위안에 사용자의 터치좌표가 없다


사용자가 터치를 했을 때 tab()메서드는 재빨리 사용자의 좌표범위와 이미지의 크기를 비교해서 어느 위치에 있는지를 알려줘야 한다. 만일 사용자가 이미지를 터치했다면, 이미지는 삭제된다. 이와 관련된 마우스 터치 이벤트와 반응은 암기하는 편이 좋을 정도로 많이 사용된다.


..(중략)

tab(e){

   const mousePos = this.getMousePos(e);

     for(let sprite of this.sprites){

          if(sprite.hitTest(mousePos)){

              sprite.kill = true;

              this.score++;

          }

     }

}


getMousePos(e){

    const rect = this.canvas.getBoundingClientRect();

    const mouseX = e.targetTouches ? e.targetTouches[0].pageX : e.clientX;

    const mouseY = e.targetTouches ? e.targetTouches[0].pageY : e.clientY;

    const scale = this.canvas.width / this.canvas.offsetWidth;

    const loc = {};

    loc.x = (mouseX - rect.left) * scale;

    loc.y = (mouseY - rect.top) * scale;

    return loc

}



tab(e)메서드에서 sprites배열에 있는 sprite중에서 hitTest()메서드는 이후 생성할 Sprite클래스의 터치 감지를 알아내는 메서드다. 만일 hitTest()메서드와 관련된 부분은 Sprite메서드에서 알아보자.



캔버스의 위치값이 변하더라도 마우스의 x, y값이 변하지 않으려면 캔버스 좌측, 상단의 빈 공간을 제거해줘야한다. 캔버스의 DOM element에서 getBoundingClientRect()를 실행하면 좌측과 상단의 빈 공간값을 받을 수 있다. 그리고 canvas.offsetWidth값은 캔버스의 선과 padding을 포함하는데, 해당값을 캔버스 너비에 나눠줌으로써 일종의 캔버스 크기축적을 구할 수 있다. 


어쨌든 getMousePos()메서드를 실행하면 사용자가 선택한 마우스의 좌표값을 받을 수 있고, 해당값을 sprite.hitTest()의 파라미터값으로 전달함으로써 이미지의 범위 안에 사용자의 마우스 좌표값 여부를 알 수 있다.



사용자의 좌표값을 얻는 과정은 자동차의 시동을 켜는 상황과 비슷하다. 이후 이미지 파일을 설정하고, 캔버스 api를 이용하는 과정은 게임의 성격에 따라 달라진다. 그래서 여기까지의 과정을 기본으로 숙지하고 있어야 한다.



댓글

최신글 전체

이미지
제목
글쓴이
등록일