WWolves Den

개발 · 출시 · 약 5분

브라우저 게임 스프라이트 깜빡임: 두 이미지 버퍼와 비동기 디코딩

던전즈 앤 나이츠에서 다음 PNG를 준비하는 동안 현재 그림을 유지하고, 동작이 바뀌어도 이전 이미지와 새 크기가 섞이지 않도록 만든 표시 구조를 살펴봅니다.

Wolfgadeok · 업데이트

그림을 요청한 시점과 보여줄 수 있는 시점은 다르다

던전즈 앤 나이츠의 캐릭터는 동작별 이미지 프레임을 순서대로 표시합니다. 여기서 다음 이미지 주소를 얻었다는 사실과 브라우저가 그 그림을 표시할 준비를 마쳤다는 사실은 다릅니다. 현재 보이는 이미지의 주소를 바로 교체하면 준비되지 않은 프레임이 끼어들 수 있습니다. 이를 해결하려고 게임 시간이나 이동을 멈추면 그림의 문제를 전투 규칙의 문제로 바꾸게 됩니다.

이 게임에서는 DOM 이미지 두 개를 고정된 슬롯으로 두고, 하나는 표시하고 다른 하나는 다음 그림을 준비합니다. 여기서 말하는 이중 버퍼는 GPU의 스왑 체인이 아니라 두 이미지 요소를 번갈아 노출하는 표시 계층입니다. MDN의 HTMLImageElement.decode() 설명처럼 디코딩 완료를 기다린 뒤 교체하는 것이 핵심이며, 캐시에 이미지가 있다는 이유만으로 준비 과정을 생략하지 않습니다.

  1. 현재 표시 슬롯과 다음 그림을 준비하는 슬롯을 구분합니다. 새 요청이 와도 표시 슬롯의 이미지는 그대로 둡니다.
  2. 숨겨진 이미지에서 decode() 완료와 유효한 이미지 크기를 확인한 뒤 표시 슬롯을 교체합니다.
  3. 로드나 디코딩이 실패하면 마지막으로 준비된 그림을 유지합니다. 아직 첫 그림이 없는 시작 구간은 별도로 검사합니다.

이미지만 보관하면 동작 전환에서 크기가 튄다

공백을 없앴다고 표시가 안정된 것은 아닙니다. 오크의 걷기와 공격은 그림 크기와 발 기준점이 다릅니다. 이전 걷기 그림을 유지하면서 공격의 크기·앵커를 먼저 적용하면 캐릭터가 순간적으로 커지거나 위치가 튀어 보입니다. 이 문제는 이미지가 항상 보이는지만 검사해서는 놓치기 쉽습니다.

따라서 버퍼에는 주소뿐 아니라 해당 프레임의 geometry, 동작 인스턴스 키, 보행 표시 여부와 검기 이미지 주소를 함께 넣습니다. 앞쪽 슬롯으로 승격할 때 이 정보도 같이 바뀝니다. 반면 캐릭터의 월드 위치, 바라보는 방향과 화면 배율은 디코딩 대기열에 가두지 않습니다. 그림이 준비되는 동안에도 이동과 화면 크기 변경을 즉시 따라야 하기 때문입니다. 같은 이미지 주소를 공유하는 걷기와 달리기도 보정값이 다를 수 있어 주소만으로 동일한 표시라고 판단하지 않습니다.

  1. 전환 직전의 이미지와 그 이미지의 크기·앵커가 같은 동작에서 나온 한 묶음인지 기록합니다.
  2. 다음 동작의 로딩을 지연시킨 상태에서 이전 이미지의 크기가 먼저 바뀌지 않는지 확인합니다.
  3. 로딩 중 캐릭터를 이동시키고 좌우 반전·화면 배율을 바꾸어, 표시 준비와 월드 변환이 서로 막지 않는지 확인합니다.

늦게 끝난 과거 요청을 현재 동작으로 올리지 않는다

걷기 이미지를 준비하는 도중 공격으로 바뀌고, 다시 걷기로 돌아오는 상황을 생각해 봅시다. 처음 걷기의 디코딩이 뒤늦게 끝났다고 그 결과를 새 걷기의 프레임으로 인정하면 이미 취소된 동작이 잠깐 끼어듭니다. 현재 구현은 요청마다 증가하는 requestId를 붙이고, 완료 이벤트의 번호가 현재 대기 요청과 일치할 때만 승격합니다. 동작 이름뿐 아니라 동작의 실행 인스턴스를 구분하는 animationKey도 사용합니다.

반대로 같은 동작 안에서 프레임 요청이 빠르다고 매번 준비 중인 작업을 갈아치우면 어떤 그림도 끝까지 준비되지 못할 수 있습니다. 같은 동작의 요청은 최신 요청을 기억하며 묶어서 처리하고, 준비가 끝나면 필요한 최신 프레임을 이어서 준비합니다. 동작이 실제로 바뀌었을 때의 취소와 같은 동작 안에서의 요청 병합을 구분하는 이유입니다.

  1. 걷기 → 공격 → 걷기 순서를 빠르게 요청하고 첫 걷기·취소된 공격의 완료 이벤트를 나중에 보내 봅니다.
  2. 같은 동작의 여러 프레임을 준비 시간보다 빠르게 요청해도 표시가 영구히 정지하지 않는지 확인합니다.
  3. 이미지 주소가 같아도 새 동작 인스턴스와 보정값을 반영하는지 확인합니다.

검증할 것은 공백, 형상 일치, 그리고 게임 규칙 보존이다

버퍼 회귀 테스트는 두 슬롯 교대, 실패 시 앞 그림 유지, 취소된 요청 거부, 빠른 요청의 진행, 오크별 표시 크기와 같은 주소의 보정값 변경을 나누어 검사합니다. 실제 오크 설정을 쓰는 테스트에서는 걷기 그림이 남아 있는 동안 걷기 geometry가 유지되고, 공격 그림 준비 후에만 공격 geometry로 바뀌는지 확인합니다. 이처럼 문제를 만든 순서를 작은 테스트로 남기면 리팩터링 때 같은 결함이 되돌아오는 것을 찾기 쉽습니다.

이 구조의 목적은 준비되지 않은 그림과 서로 다른 동작의 표시 정보를 섞지 않는 것입니다. 두 이미지가 있다고 모든 렌더링 문제가 사라지거나 전체 게임 FPS가 일정 비율로 높아지는 것은 아닙니다. 메모리 사용량, 느린 네트워크, 실제 브라우저의 디코딩과 장시간 플레이는 별도로 살펴야 합니다. 무엇보다 표시를 안정시키는 수정이 공격 시점·이동 속도·충돌 판정을 변경하지 않았는지 함께 확인해야 합니다.

참고 자료