본문 바로가기

캔버스 루프 애니메이팅 1편 『설계 및 최소구현』

by Recstasy 2020. 1. 31.
캔버스로 루프 애니메이션을 구현하기 위한 간략한 목표를 정리해보자. 
필요한 소스는 2가지다. 

*이미지 파일: 


*json파일: 

bucket.json


1) 요구사항  

- 시간의 흐름에 따라 움직이는 이미지가 캔버스에 표현된다.
* 유스케이스 생략

2) 도메인 & 책임 
- Game엔진 : 캔버스 API를 불러오며, 이미지 데이터를 관리한다.
- Sprite : 이미지에 관한 정보를 게임엔진에 제공한다.


3) 역할
 3-1) Game Class 
- loadJSON, ajax를 통해 서버에서 이미지 데이터를 불러온다.
- init, 캔버스 환경을 구축한다.
- spawn, 이미지를 생성한다.
- render, 캔버스 화면에 각 이미지를 표현한다.
- update, 시간흐름에 따라 변화되는 부분을 점검한다. 

 3-2) Sprite Class
- get offset, 이미지의 스팩을 설정한다.
- update, 이미지의 동작을 업데이트한다.
- render, 캔버스의 랜더링 api를 설정한다. 

위의 간략명세서에는 생략되었지만 유스케이스의 '메시지'가 가장 중요하다. 
유스케이스에 따라 순서를 정리하자면 다음과 같다. 

1) 브라우저를 켠다. => DOMContentLoaded 이벤트, new Game()실행

2) 캔버스가 실행된다.(고객은 모름) => Game엔진에서 캔버스 API실행

3) 이미지가 캔버스에 나타난다. => Render, update, Sprite 클래스 실행 

4) 이미지가 반복적으로 움직인다. => sprite 설정하기


빠른 구현을 위해서는 프로토타입 방식과 비슷하지만 기초에서 점차 살을 붙여나가는 예광탄 방식(테스트주도)이 좋을 듯하다. 참고로 예광탄 방식은 최소한의 기능만 구현되게끔 A-Z까지 빠르게 진행한 뒤에 살을 조금씩 붙여나가는 기법이다. 이를 위해, 아래와 같은 코스로 진행해보자. 


"최소 클래스 생성" => "최소 기능 구현" => "살 붙이기" => "완성"

 
1 최소 클래스 생성 
요구사항을 충족시키기 위한 최소한의 도메인을 생각해보자. 일단 캔버스가 있어야 하니깐 전체를 관리하는 Game클래스는 기본적으로 필요하다. 그리고 이미지에 대한 각종 정보를 담고 있는 이미지 관리 클래스가 필요하다. Game클래스가 이미지에 종속되어 있으면 안 되니깐 개별 이미지에 대한 정보는 이미지 클래스가 따로 관리할 필요가 있다. 또, 고객의 정보 및 GUI와 데이터베이스와 관련된 설정을 관리하는 클래스가 필요하다. 

이 중에서 블로그에 구현하는 데에 필요한 최소한의 클래스는 Game과 Sprite클래스다. 고객정보와 데이터베이스, GUI는 굳이 필요하지 않다.
class Game{

   constructor(){

       this.canvas = document.querySelector('#canvas');

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

       const game = this;

       this.loadJSON();

   }

                     //게임엔진 메서드 시작

    loadJSON(확장자, callback함수){ }

    init(){        //게임설정 세팅   }

    update(){   //이미지의 상태변화 점검  }

    spawn(){     //이미지 생성하기  }

    refresh(){    //시간변화, 반복설정하기  }

    render(){     //이미지 캔버스에 띄우기  }

                      //게임 클래스 End

}


class Sprite{

     constructor(options){

     }

     get offset(){   //게임엔진이 정한 이미지 세부사항 설정  }

     update(dt){     //시간변화에 따른 이미지 움직임 구현  }

     render(){         //캔버스API 구현    }

}


프로퍼티 설정같은 경우, 굳이 초기에 작성할 필요가 없다. 요구사항을 충족하는 메서드를 하나씩 작성하다보면 프로퍼티는 자동으로 늘어난다. 따라서 최소 구현을 위한 요구사항의 메시지에 집중할 필요가 있다. 위에서는 2개 클래스로부터 7~8개의 메서드만 붙이는 것만으로 최소 도메인을 설계했다.


2-1 최소 구현
도메인을 어느정도 만들었다면 이제 캔버스에 이미지를 띄우고, 움직이는 최소한의 구현을 만들어보자. 요구사항의 핵심부분(최소한의 구현)을 완성하고 난 이후부터는 살붙이기 작업일 뿐이다. 

일단 고객의 요구사항 "이미지가 캔버스에서 실행된다"라는 메시지를 구현해보자.

class Game{

   constructor(){

       this.canvas = document.querySelector('#canvas');

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

       const game = this;

       this.sprites = [];

       this.lastTime;

       this.loadJSON('이미지 경로',function(data,game){

             game.spriteData = JSON.parse(data);  --- ②

             game.spriteImage = new Image(); --- ③

             game.spriteImage.src = game.spriteData.meta.image;

             game.spriteImage.onload = function(){

                     game.init();  --- ④

             }

      });

   }

                     //게임엔진 메서드 시작

    loadJSON('이미지 경로', callback){

        let url = '확장자';

        const game = this; 

        var xobj = new XMLHttpRequest();

             xobj.overrideMimetype("application/json");

             xobj.open('GET', url+'.json', true );

             xobj.onreadystatechange = function(){

                    if(xobj.readyState == 4 && xobj.status == '200'){

                              callback(xobj.responseText, game);  ---- ①

                     }

             }

             xobj.send(null);

    }

                                     (..중략) 


자체서버를 사용하고 있다면 loadJSON 메서드가 필요하지 않겠지만 클라우드 서버를 사용하고 있다면 ajax를 통해 이미지 파일을 불러들여야 한다. 위의 코드 에서는 loadJSON메서드를 통해 이미지 경로를 받고, 결과값을 callback함수로 return하고 있다. 


Game클래스는 loadJSON메서드에게 다음 3가지 할일을 지시한다. 


1) ajax로 받아온 text데이터를 json형태로 파싱한 뒤 저장하라. ---

2) 자바스크립트 내장객체 new Image()를 통해 이미지 객체를 생성하라. --- 

3) 게임을 실행하라.  ---


2-2 Game Class 구현


loadJSON()메서드를 통해 실행된 game.init()메서드는 어떤 역할을 해야할까?


                        (....중략)

    init(){        //게임설정 세팅

this.lastTime = Date.now();

this.spawn();

this.refresh();

}


init()메서드는 initialize란 의미 그대로 실행을 해야한다. 캔버스에 이미지를 띄우고, 이미지 정보를 이미지에 보내야하고, 시간이 바뀔 때마다 이미지를 갱신해줘야 한다. 


1) 이미지 띄우기 => render()메서드

2) 이미지 정보전달 => spawn()메서드

3) 이미지 갱신 => update(), refresh()메서드


init()메서드의 역할은 Game클래스의 책임과 같다. 시간이 바뀔 때마다 화면을 갱신하고, 화면이 갱신될 때 이미지를 띄우며, 또 바뀐 정보를 이미지객체에 전달한다. 해당 프로젝트처럼 간단한 프로그램에서는 따로 view역할이 없지만 만일 view가 있다면 인터페이스 클래스를 따로 만들고 추상화를 진행하는 역할까지 Game클래스가 담당한다. 


update(delta){   

        //이미지의 상태변화 점검  

        for(let sprite of this.sprites){

            if(sprite == null) continue;

            sprite.update(delta);

        }

    }


    spawn(){     

       //이미지 생성하기

       const game = this;

       let options = {

            context : this.context,

            image : this.spriteImage

       }

       let sprite = new Sprite(options);

       this.sprites.push(sprite);      

  }


    refresh(){    

    //시간변화, 반복설정하기  

        const game = this;

        let now = new Date();

        let delta = (now - this.sinceTime)/1000.0;


        this.update(delta);

        this.render();

        this.lastTime = now;

        requestAnimationFrame(function(){

             game.refresh();

        })

}

    render(){     

   //이미지 캔버스에 띄우기

     this.context.clearRect(0, 0, this.canvas.width, this.canvas.height )

     for(let sprite of this.sprites.sprite){

           sprite.render();

     }

  }

   //게임 클래스 End

}


3 Sprite Class 구현

Sprite클래스의 역할은 Game엔진이 지시한 이미지 세팅값에 따라 캔버스에 이미지를 구현해내는 것이다. 최소한의 구현을 위해서는 canvas의 drawImage()메서드를 실행하는 동작이 가장 중요하다.이를 위해서 게임 클래스에서 받은 options객체값을 통해 drawImage()를 구현하는 부분이 핵심이다. 


class Sprite{

     constructor(options){

           this.context = options.context;

           this.image = options.image;

     }


     update(dt){     //시간변화에 따른 이미지 움직임 구현

           this.currentTime = dt;

           

    }


     render(){         //캔버스API 구현

           this.context.drawImage(this.image, 10, 10, 450, 220);

    }

}


가장 기본적인 구현을 위해서 drawImage의 설정값은 this.image를 제외하고 임의로 설정했다. 최소구현 단계에서는 무엇보다도 '구현'이 가장 중요하기 때문에 세부적인 부분은 모두 '살 붙이기'단계로 넘긴다. 


Game클래스의 loadJSON에 파이어베이스에 올려놓은 이미지경로를 넣으면 아래와 같은 반복되는 이미지가 그려져 있는 이미지를 로딩할 수 있다. 여기까지 진행을 마무리했다면 '살 붙이기' 단계에서는 시간변화(프레임)에 따라 특정동작이 그려진 영역만 캔버스에 표현하는 작업을 진행한다. 



//최소한의 구현


댓글

최신글 전체

이미지
제목
글쓴이
등록일