펑션과 코딩의 행방불명

[JS Canvas] 폭죽 본문

Web Projects/Frontend

[JS Canvas] 폭죽

Function_Develop 2022. 8. 15. 13:16

오늘은 광복절로 구글에서 태극기 모양 폭죽을 만들어주었다.

저걸 보고 나는 감탄과 동시에 만들어 보고 싶었다! 그래서 바로 시작했다.

마침 내가 어제 만들었던 폭발 이펙트와 잔상(꼬리) 이펙트가 있어서 그걸 활용하였다.

 

먼저 오늘은 밤하늘이 배경이니까 #070816(진한 남색) 정도로 맞춰준다.

 

//벡터
class Vector {
    constructor(x, y) {
        this.x = x
        this.y = y
    }
    add(b) {
        this.x += b.x
        this.y += b.y
    }
    clone() {
        return new Vector(this.x, this.y)
    }
}

//색상
class RGB {
    constructor(r, g, b, a = 1) {
        this.R = r
        this.G = g
        this.B = b
        this.A = a
    }
    //fillStyle에서 사용할수 있게
    toString() {
        return `rgb(${this.R}, ${this.G}, ${this.B}, ${this.A})`
    }
    //불투명도 조절
    alpha(value) {
        let A = value
        if (value < 0) {
            A = 0
        }
        return new RGB(this.R, this.G, this.B, A)
    }
}

어제껄 이용해서 Vector와 RGB를 만들어주고

 

//입자 1개
class Particle {
    constructor(x, y, vel_x, vel_y, color) {
        this.AFTERIMG_COUNT = AFTERIMG_COUNT	//잔상이미지 갯수(길이)
	
        this.life = 1				//불투명도로 목숨 시각화
        this.pos = new Vector(x, y)		//Position
        this.vel = new Vector(vel_x, vel_y)	//Velocity
        this.color = color
        this.afterimg = []			//잔상을 담을 리스트
    }
    draw() {
        Circle(this.pos, 2, this.color)		//Circle(위치, 반지름, 색깔)
        
        //잔상 그려주기
        for (var i = 0; i < this.afterimg.length; i++) {
            let _color = this.color.alpha(i / this.AFTERIMG_COUNT - 1 + this.life)
            Circle(this.afterimg[i], 1, _color)
        }
    }
    move() {
    	//남은 목숨에 따라 불투명도 조절
        this.color = this.color.alpha(this.life)
		
        //움직이기 전 위치 저장
        this.afterimg.push(this.pos.clone())
        
        //움직이기
        this.pos.add(this.vel)
        
        //잔상 갯수(길이) 조절
        while (true) {
            if (this.afterimg.length <= this.AFTERIMG_COUNT) {
                break
            }
            this.afterimg.shift()
        }
    }
}

폭죽이 터질때와 올라갈때 필요한 입자를 만들어준다. (코드에 주석 열심히 썼으니 확인해주세요!)

 

Fireworks - CLASS

    constructor(pos_x, pos_y, speedH, color = Palette[randint(Palette.length)]) {
        this.start = new Vector(pos_x, pos_y)
        this.color = color

        //Explosion Particle Count
        this.EXP_COUNT = EXP_COUNT

        // 0 : 상승, 1: 터짐
        this.mode = 0

        //Particle
        this.elevator = new Particle(pos_x, pos_y, 0, speedH, this.color)
        this.explosion = []
    }
    draw() {
        if (this.mode === 0) {
            this.elevate()
        } else {
            this.explode()
        }
    }

Palette는 몇가지 색깔의 정보를 저장하고 있는 리스트다. start를 통해서 폭죽이 시작될 위치를 잡아준다. 그리고 이번 프로젝트의 핵심인 mode가 있다. mode는 폭죽이 상승할때와 폭죽이 터질때 따로 처리할 수 있게 도와주는 변수이고 그 밑에 elevator이 상승, explosion이 폭발할때 입자이다.

 

    elevate() {
        this.elevator.draw()
        this.elevator.move()
        this.elevator.vel.y += 0.01
        
        //최고점
        if (this.elevator.vel.y > 0) {
            const pos = this.elevator.pos
            for (var i = 0; i < this.EXP_COUNT; i++) {
                this.explosion.push(new Particle(pos.x, pos.y,
                    rand(1.5) * randSym(), rand(1.5) * randSym(), this.color))
            }
            this.mode = 1
        }
    }

올라갈때 호출하는 함수인 elevate이다. elevator의 축의 속력을 더하여(속력이 음수이면 올라가는 처럼 보이니까) 중력이 작용하는거 처럼 보이게 한다. 그리고 속력이 0에 도달하면 최고점이니까 최고점에서 터지게 해준다. 여기서 터질때 입자들을 생성하는 이유는 위치를 기록하기 위해서 이다. 속도하고 가속도만 알면 적분하여 시간과 위치를 구할수 있지만 너무 복잡해서 여기다가 해줬다. 그리고 ±(0 ~ 1.499)로 속도를 준다.

 

 
    explode() {
        this.explosion.forEach(i => {
            i.vel.y += 0.002
            i.life -= 0.004
            i.draw()
            i.move()
        })
    }
    
 class Particle{
     ...
    move() {
        this.color = this.color.alpha(this.life)
    ...
 }

그리고 터질때 함수이다. 이놈도 마찬가지로 y 방향의 속력을 더하여 아래로 향하는거 같이 보이게 해주고(미미하지만)  life를 줄여준다. life를 줄여주면 아까 나왔던 Particle의 move 함수에서  Alpha(불투명도) 값을 줄여줄것이다. 그러면 점점 사라지는 효과를 연출할 수 있다.

 

function makeFireworks() {
    return new Fireworks(randint(canvas.width),
        FLAT, -rand(2) - 2)
}

function createFireworks() {
    RenderList.push(makeFireworks())
}

makeFireworks에서는 폭죽을 생성한다. randint(canvas.width)를 이용하여 canvas내부의 랜덤한 x좌표에서 생성해준다. FLAT 폭죽이 시작할 y 값이다. - rand(2) - 2를 통해서 폭죽의 y 방향의 속도를 -2 ~ -4 조절해 준다.

 

let frame = 0
function render() {
    ctx.clearRect(0, 0, canvas.width, canvas.height)
    //처음에 폭죽을 일정시간 마다 생성
    if (RenderList.length < FIREWORKS_COUNT && frame % 20 == 0) {
        createFireworks()
    }
    
    //폭죽이 터지는게 끝나면 재 생성
    for (var i = 0; i < RenderList.length; i++) {
        let count = RenderList[i].explosion.length
        if (RenderList[i].mode == 1) {
            if (RenderList[i].explosion[count - 1].life <= 0) {
                RenderList[i] = makeFireworks()
            }
        }
    }
    
    //폭죽 시각화
    RenderList.forEach(i => {
        i.draw()
    });

    frame++
    requestAnimationFrame(render)
}

"//처음에 폭죽을 일정시간 마다 생성" 은 처음에는 폭죽이 있어야하니까 만들어 준것이다. 그런데 한번에 터지면 렉이 걸리거나 안예쁘니까 딜레이를 줘서 터지게 해준다. 그리고 "//폭죽이 터지는게 끝나면 재 생성"에서는 아까 언급했던 life, 즉 불투명도가 0이 되면 다시 쏴주는 알고리즘이다.

 

그럼 이렇게 완성 된다! 생각보다 렉이 걸리는 부분이 있어서 갯수를 좀 줄였지만 그래도 이쁘다 ㅎㅎ

 

4일 동안 Javascript에서 Canvas를 이용하여 몇개의 프로젝트를 완성하였다. 4일 동안 나는 확실하게 성장하는걸 느낄 수 있었다. 그리고 최근들어 내가 코딩 공부를 어떻게 했나 싶었는데 코딩 공부를 어떻게 했는지 상기 시킬 수 있는 시간이었다.  내가 이렇게 열심히 한 이유는 내일이 개학이기 때문이다. 앞으로는 글을 못 올릴것 같다... 그래도 값진 시간을 보냈으니 상관없다.

 

모두들 좋은 하루 보내시길

 

소스코드 : https://github.com/Function1790/Fireworks

 

GitHub - Function1790/Fireworks: with JS

with JS. Contribute to Function1790/Fireworks development by creating an account on GitHub.

github.com

 

Comments