99발을 매번 만들고 부수는 대신 돌려쓰는 설계
Ice Breaker의 발리는 10발로 시작해 +1 픽업으로 늘어나고, 밸런스는 99발을 상한으로 잡는다. 각 공은 Rigidbody2D, CircleCollider2D, TrailRenderer를 달고 있다. 이 구성을 발리마다 새로 만들고 부수는 대신 생성·할당 부담을 줄이려는 설계로, 공을 Stack<Ball> 풀에 반납해 다음 발리에서 다시 쓴다. 연결된 Unity API 문서가 이 방식이 의존하는 SetActive 스위치를 설명한다. 부담 감소가 실기기에서 어느 정도인지는 측정하지 않았다.
오브젝트가 아니라 상태가 샌다
풀링의 진짜 실패는 오브젝트가 사라지는 것이 아니라 살아 있는 상태가 다음 사용으로 새는 것이다. 소스에서 PrepareForPool이 반납 때 지우는 목록이 곧 누수 후보 목록이다.
이 목록은 코드를 읽은 관찰이고, 뒤에 나오는 테스트가 각 항목의 초기화를 하나씩 검사하는 것은 아니다. 나가는 쪽에서는 Launch가 같은 물리·발리 필드를 다시 초기화하고 SpawnBall이 델리게이트를 새로 연결하는 구조다. 재활용된 공이 새 공과 완전히 같은 상태라는 것도 테스트가 아닌 소스 관찰이다.
- 물리: 선속도·각속도·회전을 0으로 돌리고 simulated를 끄고 콜라이더를 끈다.
- 델리게이트: OnFloorHit, OnFullyRecalled, OnSideWallHit, GetDirectHitDamage, OnDirectHitResolved 다섯 개를 전부 null로 둔다. 돌파전 전용 콜백이 일반 발리에 남으면 없는 규칙이 발동한다.
- 발리 소유값: VolleyOrdinal과 돌파전 플래그 다섯 개를 초기화한다. 충전된 공이 다음 발리에 충전된 채로 나오면 안 된다.
- 시각·명단: TrailRenderer를 지우고 static AllLive 목록에서 빠진다. 반납된 공이 라이브로 세어지면 카운터가 거짓말을 한다.
테스트가 잠그는 것
NinetyNineBallsAreReusedWithoutLeakingLiveState는 비공개 SpawnBall을 리플렉션으로 99번 호출해 왕복을 강제하고, 카운터만 검사한다.
- 발사 직후: GameSession.ActiveBallCount와 Ball.AllLive가 둘 다 99다.
- 전량 회수: 99개 전부에 InstantDestroyRecalled를 부르면 둘 다 0이 되고 PooledBallCount는 99 이상이다. 부서진 공은 풀에 도달하지 못하므로 이 값이 반납 경로를 증명한다.
- 재사용 왕복: 다시 1개를 쏘면 두 카운터가 1이고, 그 공을 회수하면 다시 0이다.
소스에서 추가로 확인한 것(테스트 밖)
- InstantDestroyRecalled는 OnFullyRecalled가 없으면 Destroy로 빠진다. 테스트는 콜백이 연결된 반납 경로만 지나간다.
- RegisterLive는 등록 때 죽은 항목을 먼저 청소하고 라이브 공끼리 Physics2D.IgnoreCollision을 건다.
- PrewarmFirstVolley는 타이틀 화면에서 시작 공 수(기본 10, Clamp 상한 99)만큼 프레임당 한 개씩 미리 만들고, 발사 효과 PixelOneShotFx와 PixelVolleyStreak도 같은 수만큼 준비한다. 첫 발리의 할당을 줄이려는 의도로 읽히며 실제 할당량은 재지 않았다.
다른 프로젝트에서 따라 할 순서
- 세션 카운터와 라이브 명단처럼 서로 다른 두 기록이 스폰·회수 뒤에도 일치하는지 본다.
- 반납 함수가 지우는 상태 목록을 적고 델리게이트가 빠져 있지 않은지 확인한다.
- N개 스폰 → 전량 회수 → 풀 ≥ N → 1개 재스폰의 왕복을 테스트로 고정한다.
- 콜백이 없을 때의 파괴 경로와 콜백이 있는 반납 경로를 구분해 둘 다 확인한다.
- 예열은 몰아서 하지 말고 메뉴 화면에서 프레임당 한 개씩 한다.
이 기록이 증명하지 않는 것
9월 8일 PlayMode 30/30 실행 기록과 고정 커밋의 소스를 9월 21일에 대조했다. 테스트는 카운터 왕복 계약만 잠근다. 개별 상태 초기화, 콜백 없는 파괴 경로, 재활용 공의 동일성은 소스 관찰이지 테스트 보장이 아니다. 99라는 상한과 풀 구조의 실기기 프레임·메모리 적합성, 리플렉션 우회로 빠진 실제 발리 코루틴, 회수 후 지연 누수도 검증하지 않았다.
기존 실행기록·소스 대조
검증일
검증 환경Unity 6000.5.3f1 · Windows Editor · September 8 retained PlayMode receipts; Ball and GameSession source review on September 21
Ice Breaker PR #40의 병합 소스와 2026년 9월 8일 Unity 6000.5.3f1 PlayMode 30/30 시험 XML을 대조했다. NinetyNineBallsAreReusedWithoutLeakingLiveState는 그 실행 안에 포함된다. 글 작성 중 게임을 새로 실행하지 않았고, 99라는 상한이나 이 풀 구조가 실기기 프레임 시간·메모리에 맞는지는 검증하지 않는다.