이 게임의 UI는 HTML 엘레멘트와 canvas를 적절히 조합하여 만들었다. UI는 2개의 레이어로 구성되는데 canvas 레이어가 있고 그 위에 HTML 엘레멘트들이 존재하는 레이어가 있다. 기존에는 canvas만으로 UI를 만들었는데 HTML의 input과 같은 유용한 기능을 사용하기 위해 이러한 선택을 하였다. 이 두 레이어는 GameScreen 안에 존재한다.

canvas 레이어

캔버스 레이어는 게임 내에 게임 유닛의 이미지를 렌더링 하는 역할을 한다. 이러한 이미지들은 관찰자(camera)의 좌표를 기준으로 렌더링되어야 하는데 여기서 GameScreenRendererCamera가 사용된다.

class GameScreenRendererCamera{
    screenPos=[0,0]; //카메라가 화면 어디에 위치할지
    pos=[0,0]; //카메라의 위치
    zoom=1; //커질수록 확대되어 보인다
    isFollow=false; //타겟을 따라가는지
    followSpeed=0.1;//타겟을 따라가는 속도 0~1
    target=[] // 타겟(여러 타겟이 있으면 여러 타겟 위치의 가운데)
    inv_targetCount=0; //최적화를 위한 변수: 1/타겟수
    vibratePower=0; //카메라의 진동 정도
    constructor(screenPos){
        this.setScreenPos(screenPos);
        this.reset();
    }
    reset(){
        this.zoom=1;
        this.resetTarget();
        calvec(this.pos,'=',this.screenPos);
    }
    update(){
        if(this.isFollow)this.follow();
    }
    addTarget(target){
        this.isFollow=true;
        this.target.push(target);
        this.inv_targetCount=1/this.target.length;
    }
    resetTarget(){
        this.isFollow=false;
        this.followSpeed=0.1;
        this.target=[];
    }
    move(xy){
        this.pos[0]+=xy[0];
        this.pos[1]+=xy[1];
    }
    follow(){
        let followX=0;
        let followY=0;
        for(let i=this.target.length-1; i>=0; i--){
            followX+=this.target[i].midX;
            followY+=this.target[i].midY;
        }
        this.pos[0]+=(followX*this.inv_targetCount-this.pos[0])*this.followSpeed;
        this.pos[1]+=(followY*this.inv_targetCount-this.pos[1])*this.followSpeed;
        let r = (Math.random()>0.5 ? 1 : -1)
        this.move([this.vibratePower*r,this.vibratePower*r]); //카메라를 진동
        this.vibratePower=0;
    }
    getRenderPos(pos){ //카메라의 위치에 따라 pos가 캔버스에 어느 좌표에 렌더링되어야 할지 반환
        return [(pos[0]-this.pos[0])*this.zoom+this.screenPos[0], (pos[1]-this.pos[1])*this.zoom+this.screenPos[1]];
    }
    getRenderSize(size){ //카메라의 확대 정도의 따라 크기를 조정 
        return [size[0]*this.zoom, size[1]*this.zoom];
    }
    setScreenPos(screenPos){ //카메라가 화면에서 어디에 위치할지 설정
        calvec(this.screenPos,'=',screenPos);
    }
    vibrate(power){ //카메라 진동(몬스터 피격시 타격감을 위해 사용)
        if(this.vibratePower<power)this.vibratePower=power;
    }
}

HTML 레이어

HTML 레이어는 canvas 레이어 위에 위치하여 HTML 엘레멘트를 보여준다. 이때 HTML 레이어의 크기는 canvas 레이어의 크기와 동일해야 한다. 여기서는 HTML 레이어에 있는 엘레멘트들은 절대위치로 존재한다. 이를 위해 position을 다음과 같이 설정해야 한다.

아래는 GameScreenUI 클래스이며, HTML 레이어와 element들의 position을 absolute로 설정하는 것을 알 수 있다.

class GameScreenUI{
    ui; //HTML 레이어
    constructor(){
        this.ui = document.createElement("div");
        this.ui.style.position="absolute"; //HTML 레이어의 position을 absolute로 설정
        this.ui.style.overflow="hidden";
    }
    setSize(w,h){
        this.ui.style.width=Math.floor(w)+"px";
        this.ui.style.height=Math.floor(h)+"px";
        this.ui.style.fontSize=Math.floor(w*16/1200)+"px"
    };
    reset(){this.ui.innerHTML=""}
    getUI(){return this.ui;}
    create(name, pos, size, className=""){
        let ele = document.createElement(name);
        ele.style.position="absolute" //element의 position을 absolute로 설정
        this.setElementPos(ele,pos);
        this.setElementSize(ele,size);
        ele.className=className;
        return ele;
    }
    add(name, pos, size, className="", isAppend=true){ //레이어에 엘레멘트를 추가
        let ele = this.create(name, pos, size, className, isAppend);
        if(isAppend)this.ui.append(ele);
        return ele;
    }
    setElementPos(ele, pos){
        ele.style.left=pos[0]+"px";
        ele.style.bottom=pos[1]+"px";
    }
    setElementSize(ele, size){
        ele.style.width=size[0]+"px";
        ele.style.height=size[1]+"px";
    }
}

화면을 꽉채우기

몰입감을 주기 위해 게임 화면이 브라우저를 꽉 채우는 setFitSize()라는 함수를 만들었다. 여기서 window.innerWidth와 window.innerHeight를 통해 브라우저의 주소창과 개발자 도구를 제외한 화면 크기를 알 수 있다. 또한 모바일 기기는 가로로 회전해야 하기 때문에 만약 세로가 긴 경우 가로,세로 비율을 반대로 하여 화면 크기를 설정하였다.

class GameScreen{
    ...
    setSize(w,h){
        w = Math.floor(w)
        h = Math.floor(h)
        this.screen.style.width=w+"px";
        this.screen.style.height=h+"px";
        this.widthDividedBy100=w/100;
        this.heightDividedBy100=h/100;
        this.renderer.setSize(w,h); //canvas 레이어의 화면을 조정한다.
        this.ui.setSize(w,h); //HTML 레이어의 화면을 조정한다.
        console.log(`[Screen]change screen size: ${w}, ${h}`)
    };
    setFitSize(){ //화면을 꽉채우기 위한 함수
        const sw=window.innerWidth, sh=window.innerHeight;
        this.setSize(sw,sw*(sh<sw ? sh/sw : sw/sh)); //모바일과 같이 세로로 긴 화면은 반대로 적용
    }
}