Project/01 Cannon Game

Cannon Game_day8: (2021.10.04) 컨셉 변경과 애니메이션 추가, 조준 가속도, 각도 대칭값, 둥근 사각형, 텔레포트 효과

devyoseph 2021. 10. 5. 05:47
canvas내에서 애니메이션 동작을 직접 구현한다
gif 파일을 다 찾아놓았지만 canvas 내에서 활용할 방법이 구글 내에서는 없었다
또 이미지를 따로 로드하면 용량도 너무 커지고 각각 다른 이미지를 프레임마다 적용하려니 이게 맞나 싶었다

*핀터레스트(pinterest)에서 이미지를 무료로 제공해준다

결국 이미지 파일만 준비하고 나머지 애니메이션은 직접 그리기로 했다. 최소한의 부분만 준비한다
마법사의 부유효과
단순히 sin함수의 진동값을 사용하면된다

 

조준 방향키를 유지시 가속도가 붙는다
draw를 조금씩 이해하면서 자연스레 만들어냈다
하지만 다음 조건을 명심해야했다
1) 조준 방향이 이전 방향과 달라지면 가속도가 초기화된다
2) 한 방향으로 계속 유지해야만 가속도가 붙는다
이를 위해 timer함수와 조건문 그리고 이전 방향과 현재 방향을 변수화했다
그래서 이전 방향이 전 방향과 같으면  timer가 증가하고
timer가 일정량 증가하면 조건이 만족해 각도변수가 가파르게 증가한다
animate(t){
//에임 가속도: on_Aim은 이전 방향과 현재 방향이 같을 때만 켜진다
        if(on_Aim == true){
            angle_timer ++;
            console.log(angle_timer);
            on_Aim =false;
	}
}
    cannonAiming(e){
        if(e.code === 'ArrowUp' && onGame ==true){
            last_angle = present_angle;
            present_angle = 1;
            if(last_angle*present_angle==1){
                on_Aim =  true;}
            if(last_angle*present_angle==-1){
                angle_timer = 0;}
            ball_angle += PI/180;
            
            if(angle_timer >=3 ){
                ball_angle += PI/90;
            }
            if(angle_timer >=6 ){
                ball_angle += PI/45;
            }
            if(angle_timer >=9 ){
                ball_angle += PI/20;
            }
        }
        if(e.code === 'ArrowDown' && onGame ==true){
            last_angle = present_angle;
            present_angle = -1;
            if(last_angle*present_angle==1){
            on_Aim =  true;}
            if(last_angle*present_angle==-1){
                angle_timer = 0;}
            ball_angle -= PI/180;
            
            if(angle_timer >=3 ){
                ball_angle -= PI/90;
            }
            if(angle_timer >=6 ){
                ball_angle -= PI/45;
            }
            if(angle_timer >=9 ){
                ball_angle -= PI/20;
            }
        }
    }
//저장되어있는 현재값을 이전값에 덮어씌우고 현재값을 다시 넣어준다
//왼쪽 방향은 -1 오른쪽 방향은 1로 구분한다
//이전 방향(past)과 현재 방향(present)이 같으면 그 곱은 언제나 1이된다
//곱이 1일 때만 on_Aim이 켜지고 timer가 발동한다
//timer가 어느 정도 쌓이면 각도 변화량이 커진다
//곱이 -1이 되면 timer가 초기화되고 가속도는 다시 없어진다

 

현재 방향과 다른 방향키를 누르면 각도가 대칭값으로 변한다
좌우 방향키를 누르면 움직이는 이벤트가 본래 존재했다
해당 메소드 안에서 각도 변경을 같이 할 수 있는지 테스트했지만 그렇게 했더니
각도 연산자로 인해 각도 방향이 달라져 복잡하게 되었다. 이벤트를 새로 만들었다
또한 아무 조건이 없는 채 움직이기 위해 앞으로 누르게 되면 계속해서 각도가 대칭이된다
이런 문제를 해결하기 위해 이전 방향과 현재 방향을 담는 변수를 만들었고
이전 방향과 현재 방향이 달라지는 경우에만 각도의 대칭이 일어나도록 했다
angleTurn(e){
        if(e.code === 'ArrowLeft' && onGame ==true){
            past_move = present_move;
            present_move = -1;  
            if(past_move == 1 && present_move == -1){
            ball_angle = Math.PI - ball_angle;}
        }
        if(e.code === 'ArrowRight' && onGame ==true){
            past_move = present_move;
            present_move = 1;
            if(past_move == -1 && present_move == 1){
            ball_angle = Math.PI - ball_angle;}
        }
    }

 

현재 공의 상태 정보창 만들기: 투명화(opacity), 둥근 사각형
투명화의 경우 ctx.fillStyle단계에서 사용할 수 있다
현재 캔버스는 둥근 사각형을 지원하지 않는다고 한다
직접 그려야 하는데 원리를 이해하니 간단했다(코드리뷰 작성)
둥근 사각형을 적용할 자리를 clearRect로 삭제하고 그 부분을 1/4원으로 채워넣으면 된다
원을 그릴 때는 앞에 패스를 켜고 뒤에 패스를 꺼주는 것을 꼭 기억한다
안 그러면 앞 뒤로 패스들이 꼬여버린다

draw(ctx, ball_type, ball_magnitude){
        let corner_radius = this.stageWidth/40;
		//사각형: 만들어주고 둥근 적용할 부분을 제외한다
        ctx.fillStyle = 'rgba(0,0,0,0.3)';
        ctx.fillRect(this.x, this.y, this.width, this.height);
        ctx.clearRect(this.x, this.y, corner_radius, corner_radius);
       
       //4분원: 채워넣었으나 일부분이 누락되는 현상
        ctx.fillStyle = 'rgba(0,0,0,0.3)';
        ctx.beginPath();
        ctx.arc(this.x+corner_radius, this.y+corner_radius, corner_radius, Math.PI, 3/2*Math.PI);
        ctx.closePath();
        ctx.fill();

		//패스로 빈 부분을 채우고 투명도를 0으로 한다
        ctx.fillStyle = 'rgba(0,0,0,0.0)';
        ctx.beginPath();
        ctx.moveTo(this.x+corner_radius, this.y);
        ctx.lineTo(this.x+corner_radius, this.y+corner_radius);
        ctx.lineTo(this.x, this.y+corner_radius);
        ctx.lineTo(this.x+corner_radius, this.y);
        ctx.closePath();
        ctx.fill();
    }

 

각 공의 중심점을 계산하고 공을 그린다
완전 긴 작업이었다. 코드의 복잡성이 증가하는 시간이었다

 

context: ctx.setLineDash([gap1, gap2...]);
패스를 점선으로 그릴 때 setLineDash를 사용한다
그러나 현재 js파일이 아닌 다른 stroke를 사용하는 그림(선, 도형)에도 점선이 적용된다.
한 번 점선 옵션을 사용하면, 다른 패스에서 ctx.setLineDash([0]);을 다시 적용해 선으로 다시 되돌려준다
ctx.strokeStyle = 'rgba(230,222,203,0.5)';
ctx.beginPath();
//다른 곳에서 점선을 설정했지만 현재 파일에서도 점선이 적용되어
//초기 설정을 덮어씌워야 한다
ctx.setLineDash([0]);
ctx.lineWidth = "5";
ctx.strokeRect(center_X1-small_width/2,center_Y-this.height/2, small_width, this.height);
ctx.closePath();

 

현재 공의 상태창 설명하기
인스턴스끼리 엮어주는 변수를 사용하면 된다. 시간이 좀 걸릴 뿐 앞 내용들의 연장선이었다

 

텔레포트 효과: 이미지 이용의 한계, 타이머를 사용한 애니메이션 효과
텔레포트는 다음과 같이 작동한다
1) x키를 누르면 마법사는 이동하며 해당 자리에 텔레포트 잔상이 발생한다
2) 얼마 후 자기 자신이 나온 뒤 스스로 사라진다
timer를 draw내부 변수를 이용해 구현했다
내부 변수로 구현하기 위해서 constructor에 먼저 변수를 선언해주어야 이후 animate가 순환하는 중에 변동이 없다.
그 이후 draw에서 계속 증가하며 조건문을 통해 증가하는 타이머에 따라 애니메이션이 발생하도록 했다
    draw(ctx, teleportX, cannon_X,cannon_Y, cannon_W, cannon_H){
  //위치조정 부분     
        this.width = cannon_W;
        this.height = cannon_H/2;
        this.x = teleportX + this.width/2;
        this.y = cannon_Y+this.height*5/6;
        this.cannon_X = cannon_X;
  //타이머가 7의 배수일 때 원들이 사라지기 시작한다
        if(this.teleport_timer <=7){
        ctx.beginPath();
        ctx.fillStyle = 'rgba(156,222,234,0.2)';
        ctx.arc(this.x, this.y, this.width, 0, Math.PI*2);
        ctx.fill();
        ctx.closePath();
        }
        
        if(this.teleport_timer<=14){
        ctx.beginPath();
        ctx.fillStyle = 'rgba(93,133,192,0.3)';
        ctx.arc(this.x, this.y, this.width*2/3, 0, Math.PI*2);
        ctx.fill();
        ctx.closePath();
        }

        if(this.teleport_timer <=21){
        ctx.beginPath();
        ctx.fillStyle = 'rgba(53,99,203,0.4)';
        ctx.arc(this.x, this.y, this.width*1/3, 0, Math.PI*2);
        ctx.fill();
        ctx.closePath();
        }
        
        //타이머는 계속 돌아간다
        this.teleport_timer++;
    }
app.js에서는 배열을 통해 관리했다
//x를 누르면 이벤트가 발생하고 on_teleport가 켜진다
if(on_teleport == true){
            var teleport = new Teleport(this.stageWidth, this.stageHeight);
            // this.teleport.draw(this.ctx, teleport_timer, this.cannon.x, this.cannon.y);
            teleports.push(teleport);
            on_teleport = false;
        }
//타이머가 21에 도달하면 자동삭제
        teleports.forEach((tele_each, i, o) =>{
            tele_each.draw(this.ctx, teleportX, this.cannon.x, this.cannon.y, this.cannon.cannon_width, this.cannon.cannon_height);
            if(tele_each.teleport_timer >= 21){
                o.splice(i,1);}
        })