소리꽃 KeyBloom TIL 23
파티클 나선 이펙트에 “나선형(토네이도)”과 “나선 기둥” 두 모션이 있었다. 둘을 조절 가능한 하나로 합치려다, 회전을 시간이 아니라 공간(오른 높이) 기준으로 계산해야 코일 간격을 원하는 대로 다룰 수 있다는 걸 배웠다.
두 모션은 같은 물리의 프리셋
파티클 모션은 spawn(파티클이 새로 생겨나는 순간) 시점에 물리값(속도·중력·흔들림 진폭·주파수·성장률·수명)으로 환원되고 update(매 프레임 갱신 함수)는 하나의 제네릭 물리로 돈다. 나선형과 나선 기둥은 이 상수만 달랐다 — 나선형은 흔들림 진폭이 시간에 따라 커지고(swayGrow>0, 원뿔), 나선 기둥은 안 커진다(swayGrow=0, 원통). 그래서 상수를 슬라이더로 노출하면 둘은 하나의 “나선”의 두 프리셋이 된다.
motionPhysics가 speed만 받던 걸 params 전체를 받게 바꿔 이 값들을 읽는다.
// ❌ 전: 케이스마다 상수 고정
function motionPhysics(mode, speed) { /* spiral·helix 별개 case */ }
// ✅ 후: 파라미터를 읽어 하나의 나선으로
function motionPhysics(params) {
const speed = params.speed;
// swayAmp: 반경, swayGrow: params.spiralSpread (0=기둥, >0=토네이도)
}
시간 기반 회전의 한계
처음엔 회전을 시간 기준으로 그렸다. x = 중심 + 반경 × sin(회전빈도 × 나이). 이러면 코일 간격(코일 사이 세로 거리)이 상승 속도·회전빈도에 함께 묶인다.
// 시간 기반 — 코일 간격 = 상승속도 × (2π / 회전빈도)
p.x += (p.vx + swayAmp * Math.sin(p.swayFreq * p.age + p.swayPhase)) * dt;
그래서 “코일 간격만” 넓히려 해도 상승 속도나 회전빈도 중 하나가 딸려 움직였다.
코일 간격 = 상승 속도 ÷ 회전 수
이게 핵심 물리다. 이 셋은 한 식으로 묶여 있어 둘을 정하면 나머지는 자동이다.
- 코일 간격을 넓히며 회전 속도를 유지 → 상승이 빨라진다(날아감)
- 코일 간격을 넓히며 상승을 유지 → 회전이 느려진다
“넓은 간격 + 빠른 회전 + 느린 상승”은 동시에 불가능하다. 이걸 모르고 슬라이더를 서로 반대로 올려 상쇄시키는 삽질을 반복했다.
공간 회전으로 코일 간격 분리
회전 위상을 시간이 아니라 “오른 높이(climbed)”에 비례시키면 코일 간격이 공간상 고정된다 — 얼마나 빨리 오르든 같은 높이 간격마다 한 바퀴.
const climbed = SPAWN_Y - p.y; // 스폰 높이에서 얼마나 올랐나
p.x = cx + radius * Math.sin(p.coilK * climbed + p.swayPhase);
// coilK = 2π / 코일간격 → 간격이 넓으면 coilK가 작아 한 바퀴에 더 높이
이러면 코일 간격을 넓혀도 상승 속도와 무관해 “천천히 올라오는 넓은 나선”이 된다. update에서 y를 먼저 갱신한 뒤 climbed로 x를 구하는 순서가 중요하다(x가 현재 높이에 의존하므로).
중복 파라미터를 덜어내기
공간 회전으로 바꾸니 “회전 속도” 슬라이더가 결국 상승 속도(vy)와 같은 걸 조절하고 있었다 — 공용 속도/높이와 중복. 슬라이더를 늘리기보다 중복을 제거하는 게 정리였다. 나선 슬라이더는 회전 반경·퍼짐·코일 간격 셋으로 줄고, 상승·회전 빠르기는 공용 속도/높이가 담당한다.
요약
- 나선형·나선 기둥은 같은 제네릭 물리의 상수 차이(퍼짐 swayGrow)라 하나로 합쳐 프리셋으로.
motionPhysics를 params 전체를 받게 바꿔 상수를 슬라이더로 노출.- 코일 간격 = 상승속도 ÷ 회전수 — 셋이 묶여 독립 불가.
- 회전을 시간→공간(오른 높이) 기준으로 바꾸면 코일 간격이 공간상 고정돼 상승과 분리.
- 공간 회전은 update에서 y를 먼저 갱신한 뒤 climbed로 x 산출.
- 파라미터가 중복되면(회전 속도=vy) 늘리지 말고 덜어낸다.