뜯어본 것공개

Elemental Sandbox

이펙트가 가장 안 값나갑니다. 가져올 규칙은 하나 — 기록은 무차원으로, 치수는 매 프레임 해석.

이펙트 코드는 한 줄도 안 가져왔는데, 규칙 한 문장이 제 코드 43곳의 성격을 정했습니다. 이 레포의 화려한 부분은 웹 전용이라 제 엔진으로 안 넘어옵니다. 넘어온 건 “인스턴스는 seed와 비율만 기억하고, 미터·초·라디안은 매 프레임 설정에서 다시 계산한다” 한 줄입니다. 그리고 그 자로 제 프로젝트를 재보니 — 정확히 반대로 짜여 있었습니다.

“정지 화면을 보면서 숫자를 만진다” — 이게 되려면 셋이 동시에 성립해야 한다 ① 설정이 곧 API 단일 가변 객체가 유일한 상태 소유자 모든 시스템은 매 프레임 읽기만 한다 ② 기록은 무차원 비율(0~1) + 난수 seed 만 저장 미터 · 라디안 · 초는 하나도 없다 ③ 일시정지 = dt 0 루프를 멈추는 게 아니라 길이 0인 프레임을 계속 돌린다 얼음 가시 하나가 기억하는 것 전부 직선 위 위치 0.0~1.0 · 좌우 오프셋 ±0.0~1.0 · 지터 난수 전부 비율. 실제 단위는 하나도 없다 유일한 실단위 — 발생 시각 치수가 아니라 사건이라서 저장한다 ⇒ 높이 슬라이더를 당기면 이미 서 있는 얼음밭이 다시 자란다 — 시간이 1틱도 안 흘렀는데
이 레포의 모든 구조가 요구 하나에서 나왔다 — 이펙트를 한복판에서 얼려 놓고, 정지 화면을 보면서 실루엣·팔레트·타이밍을 고친다.

무엇을 만든 것인가

브라우저에서 도는 스킬샷 이펙트 놀이터다. 캐릭터가 바닥에 서 있고, 키로 스킬을 뽑으면 지면 조준 표시가 마우스를 따라 움직이고, 클릭하면 발사된다. 게임이 아니다 — 적도 체력도 없다. 목적은 “이펙트를 눈으로 보면서 숫자를 만지는 것” 하나다.

그리고 간판 주장이 사실이었다. “전부 코드로 생성” — 클론해서 전수 확인하니 바이너리는 열 개뿐이고 그중 이펙트용이 하나도 없다. 스프라이트시트도, 플립북도, 이펙트 텍스처도 0개다.

무엇 어떻게 만들었나
얼음 결정 절차적 지오메트리
번개 버텍스 셰이더가 배치하는 리본 스트립 한 장
운석 CPU에서 파쇄면으로 깎은 구체
반지름 셋으로 세 번 그린 파라메트릭 튜브
조준선·화상·용암 균열 전부 거리장 + 노이즈 셰이더
안개·불티·파편 GPU 파티클 — 실루엣도 절차적

런타임 의존성이 두 개다. 그리고 레포 나이가 5일, 커밋 18개, 기여자 1명이다.

왜 일시정지 중에 슬라이더가 먹히나

위 도판의 셋이 동시에 성립하기 때문인데, 두 번째가 핵심이다. 얼음 가시 하나가 기억하는 것에 미터도 라디안도 초도 없다. 직선 위 위치가 비율이고, 좌우 오프셋이 비율이고, 나머지는 난수 몇 개다. 저장하는 유일한 실단위는 “발생 시각” — 그건 치수가 아니라 사건이라서다.

그러니 매 프레임 update()가 설정 객체에 대고 다시 곱한다. 높이 슬라이더를 당기면 이미 서 있는 얼음밭이 다시 자란다.

표현 못 하는 값의 예외 처리도 좋다. 면 개수나 휘어짐처럼 위치·크기 곱하기로 표현할 수 없는 파라미터 넷은 지오메트리에 구워야 해서, 보통은 “재시작해야 반영되는 상수”가 된다. 저자는 그 넷만 문자열 키로 해싱해서 바뀔 때만 통째로 다시 만들고, 인스턴스의 seed와 발생 시각은 그대로 옮겨 태운다 — 모양은 새로, 정체성은 유지.

그리고 주석에 판단 근거가 적혀 있다. “결정 하나가 108삼각형이라 근사하느니 그냥 다시 만드는 게 싸다.” 비싸서 못 하던 걸 비용을 실제로 재보고 뚫은 사례다.

직접 돌려서 확인했다

문서를 믿지 않고 클론해서 로컬에서 돌렸다. 브라우저 창이 화면에 안 떠 있으면 프레임을 안 그려서 캡처가 계속 실패했는데 — 프레임 루프를 고정 스텝으로 직접 호출하는 방식으로 우회했다. 부수 효과로 결정론적 타임스텝을 얻었고, 그래서 아래 검증이 가능해졌다.

얼음을 쏘고 54프레임 진행한 뒤 일시정지하고, 시간을 1틱도 안 흘린 채 설정값 셋만 바꿔 세 프레임을 더 그렸다.

경과 시각 높이 기울기
BEFORE 13.95 3.1 0.42
AFTER 13.95 (동일) 7.44 0.97

주장은 참이었다. 경과 시각이 완전히 같은데 이미 서 있던 결정들이 더 높이 자라고 바깥으로 기울었다. 연기 기둥과 눈송이 입자와 캐릭터 포즈는 픽셀 단위로 그대로다 — 시뮬레이션이 다시 돈 게 아니라 같은 순간을 새 치수로 다시 해석한 것이다. 드로우콜도 두 프레임 모두 115로 같았다.

그리고 실측이 자기신고와 갈렸다. 대기 상태 드로우콜은 32로 일치했는데, 얼음밭이 서 있을 때는 제 쪽이 95~115로 자기신고(~69)보다 높았다. 캡처 시점과 해상도 차이다 — 자기신고를 그대로 인용하면 안 된다. 슬라이더 개수도 실측 918개로 주장(938)과 20 차이가 났다.

그리고 제 코드를 대봤다

내 프로젝트에서 시간을 다루는 방식 — 전수 조사 시작 시점에 굳힌다 플레이 중에 값을 바꿔도 진행 중인 것에는 반영되지 않는다 43곳 매 프레임 다시 읽는다 22곳 전조 시간이 코루틴 시작 시점에 굳는다 — 이 레포와 정확히 반대로 짜여 있었다 그래서 밸런싱 중에 전조 길이를 만져도 이미 진행 중인 전조는 그대로다 이펙트 코드는 한 줄도 안 가져왔는데, 규칙 한 문장이 이 43곳의 성격을 정했다
가져온 것은 이 한 문장뿐이고, 그것이 가리킨 자리가 43곳이었다.

넘어온 규칙은 이것 하나다.

어빌리티 인스턴스는 seed와 비율만 기억한다. 미터·초·라디안은 매 프레임 설정에서 다시 계산한다.

제 무협 게임 프로젝트는 정확히 반대로 짜여 있었다. 보스가 초식을 낼 때 전조 시간이 코루틴 시작 시점에 굳는다. 플레이 중에 전조 시간을 바꿔도 이미 진행 중인 전조에는 반영되지 않는다.

전체를 세니 시작 시점에 굳히는 방식 43곳, 매 프레임 다시 읽는 방식 22곳이었다.

이펙트 코드는 한 줄도 안 가져왔는데 제 코드 43곳의 성격이 정해졌다. 이 분류에서 기대할 건 그런 종류다.

라이선스는 두 곳을 봐야 한다

코드 포팅은 기각이다. 라이선스상 자유롭지만 웹 셰이더와 3D 평면 전제라 제 엔진의 2D 파이프라인으로 옮길 값이 없다.

그리고 통째로 가져가는 건 위험하다. 같은 레포가 라이선스 파일에는 관대한 조건을 적어 두고 README에는 “이 프로젝트 목적으로 있는 그대로 제공된다”고 다른 말을 한다. 번들된 캐릭터 모델과 환경 프로브는 원 라이선스 유지라고만 적혀 있다.

의존 대상이 아니라 교재로 다룰 것. 5일 된 레포에 기여자 한 명이다. 게임 쪽 레포는 수명이 짧아 이 구분이 특히 중요하다.

마지막으로 — 이 레포가 잘한 것 하나 더

목표로 삼은 참고 이미지 넉 장을 레포 루트에 넣어 뒀다. “무엇에 맞춰 튜닝했는지”를 남긴 것이다. 그리고 README에 “알려진 거친 부분” 일곱 건을 스스로 적었다 — 정렬 아티팩트, 평면 바닥 전제, 비어 있는 패스까지.

기법으로 가장 이식성 높은 건 무차원 기록, 길이 0 프레임, 그리고 이 마지막 것이다. 엔진과 언어에 무관한 설계 규칙이기 때문이다. 나머지는 수동 엔진이라서 직접 해야 하는 일이라 — 엔진이 이미 해 주는 환경에서는 값이 급락한다.