홈 › 읽을거리 › 개발노트

레오니스 몬스터를 사진 스프라이트로 바꾼 기록

개발노트 · 2026-10-08 · GamePool 편집팀

레오니스의 몬스터는 원래 코드로 그린 벡터 도형이었습니다. 지금은 아군 챔피언 9종과 영웅, 아군 성채, 몬스터 18종, 모두 29개의 이미지가 /assets/units/<id>.webp 로 불러와집니다. 이 글은 그 교체 작업, 그중에서도 3장 몬스터 5종을 적용한 과정과 프레임 영역을 직접 지정하는 기능(rects)을 추가한 이유를 git 이력과 현재 소스 값으로 정리한 노트입니다. 커밋 시각은 모두 저장소에 기록된 UTC 기준입니다.

스프라이트 한 장이 게임 안에서 하는 일

각 이미지는 한 장의 시트입니다. 흰 배경이나 어두운 배경에 오른쪽을 보는 캐릭터를 행 순서(왼쪽에서 오른쪽)로 늘어놓았고, 소스 주석이 정한 기본 규칙은 서 있기 1프레임, 걷기 4프레임, 공격 3프레임 이상, 쓰러짐 2프레임입니다. 시트에서 프레임이 8개 미만으로 잡히면 클립 구성을 포기하고 모두 첫 프레임을 쓰며, 아예 시트가 없는 유닛은 예전처럼 벡터 그림으로 그립니다. 그래서 이미지를 하나씩 넣어도 게임이 깨지지 않고 몬스터 한 종류씩 교체할 수 있었습니다.

시트를 읽는 buildSheet() 는 프레임을 자른 뒤 화면용 아틀라스 캔버스에 굽습니다. 서 있는 프레임의 높이를 SPRITE_PX 150 에 맞춰 축소하되(1을 넘겨 키우지는 않음) 프레임마다 여백 2px 를 둡니다. 재생 속도는 서 있기 4fps, 걷기는 프레임 수×2fps(한 주기 0.5초), 공격 10fps, 쓰러짐은 프레임 수÷0.4(0.4초 안에 끝남)로 정해져 있어서 프레임 수가 달라도 걷는 박자와 쓰러지는 길이가 같습니다. 타격 순간은 칼끝이 가장 멀리 나간 프레임(최대 도달 거리의 85% 이상인 첫 프레임)이거나 hit 값으로 직접 지정한 프레임입니다.

SPRITES 목록과 화면 크기 SPRITE_H

SPRITES 는 이미지 id 배열을 경로로 바꾸는 한 줄입니다. 챕터마다 새 몬스터를 넣을 때 이 배열에 id 를 하나 덧붙이고, 몬스터 정의에 sprite: 'id' 를 적으면 연결됩니다. 이 두 줄만으로는 크기가 맞지 않는데, 시트마다 캐릭터 그림의 크기가 제각각이기 때문입니다. 그래서 SPRITE_H 가 id 별로 '서 있는 프레임이 화면에서 차지할 높이(논리 px)'를 정합니다. 기본값은 40, 영웅 72, 미어캣 56, 해골 40, 오크 46 이고 몬스터는 슬라임 34, 개구리 32, 눈도깨비 44, 얼음망령 44, 서리 늑대 40, 빙결 예언자 46, 프로스트 예티 52, 1장 보스 리치 60, 2장 보스 하이드라 62, 3장 보스 프로스트웜 68 입니다.

최종 크기는 SPRITE_H ÷ 서 있는 프레임 원본 높이로 계산한 배율(unit)에 몬스터의 size 와 전체 배율을 곱한 값입니다. 얼음망령은 서 있는 프레임이 266px 높이여서 배율이 44÷266, 약 0.165 이고, 같은 방식으로 프로스트웜은 316px 높이를 68로 맞춥니다. 숫자로 정한 덕분에 시트를 새로 그려서 교체해도 몬스터가 갑자기 커지거나 줄지 않습니다. 보스는 화면 높이를 60~68 로 잡아 일반 몹 32~46 보다 크게 보이도록 했습니다.

프레임 영역을 직접 지정하는 rects 를 추가한 이유

처음 몬스터들(해골, 오크, 임프, 묘지 들개)은 자동 자르기로 충분했습니다. 어두운 바탕 시트는 연결된 덩어리를 찾는 blob 방식을, 흰 바탕 시트는 빈 줄과 빈 열로 구간을 나누는 방식을 썼고, 오크처럼 효과 두 개가 붙어 한 덩어리가 된 프레임은 splitN 으로 같은 폭으로 쪼갰습니다. 그런데 묘지 들개 바로 다음 작업인 레이스(2026-10-06 09:11)에서 자동 자르기가 어긋났습니다. 마법 구체나 소용돌이 같은 효과 조각이 몸에서 따로 떨어져 나가 덩어리가 다르게 나뉘었기 때문입니다.

그날의 수정은 buildSheet() 의 프레임 목록 F 를 만드는 한 줄에 조건을 하나 더하는 것이었습니다. SPRITE_CLIPS[id].rects 가 있으면 [x, y, 너비, 높이] 를 그대로 프레임으로 쓰고, 없을 때만 예전 자동 방식으로 갑니다. 커밋 제목에 '프레임 영역 직접 지정 기능 추가'라고 남겼고, 코드에는 이유를 주석으로 적었습니다. 효과 조각이 따로 갈라져 자동 자르기가 어긋나는 시트는 영역을 직접 지정한다는 내용입니다. 레이스 시트는 프레임이 14개로 서 있기 1, 걷기 4, 공격 준비 3, 소용돌이 2, 불꽃 발사 1, 피격 1, 쓰러짐 2 였습니다.

이후 몬스터 14종 전부가 이 방식을 씁니다. 지금 소스에서 rects 를 쓰는 항목은 레이스, 리치, 슬라임, 개구리, 촉수 습격자, 마녀, 역병 골렘, 하이드라, 눈도깨비, 얼음망령, 서리 늑대, 빙결 예언자, 예티, 프로스트웜으로 정확히 14개이고, 그 직전에 들어간 묘지 들개만 blob 입니다. 좌표를 직접 적는 방식은 시트마다 숫자를 열 개 이상 써야 해서 번거롭지만, 어긋나는 지점을 사람이 눈으로 보고 고정할 수 있었습니다.

3장 몬스터 5종을 넣은 순서와 걸린 시간

3장에서 눈도깨비는 2장 작업이 끝난 직후인 10월 6일 10:50 에 들어갔고, 나머지 다섯 마리는 이튿날 10월 7일에 연달아 적용되었습니다. 얼음망령 11:24, 서리 늑대 11:30, 빙결 예언자 11:38, 프로스트 예티 11:55, 빙룡 프로스트웜 12:01 입니다. 첫 커밋부터 마지막까지 37분이고 간격은 6분, 8분, 17분, 6분입니다. 17분 걸린 구간은 빙결 예언자에서 프로스트 예티로 넘어가는 사이가 가장 깁니다. 이유는 이력에 남아 있지 않습니다. 다만 예티 시트의 소스 좌표를 보면 공격 프레임 두 장의 영역이 겹칩니다. 5번 프레임이 x 1126~1445, 6번 프레임이 x 1350~1649 라서 95px 가 겹치고, 효과가 퍼지는 시트에서는 이런 겹침이 생긴다는 정도만 확인됩니다.

다섯 번의 커밋은 모양이 같습니다. 모두 파일 두 개(webp 한 장과 index.html)를 바꾸고 추가 5줄·삭제 3줄입니다. 바뀐 줄은 몬스터 정의에 sprite 필드 추가, SPRITES 배열에 id 추가, SPRITE_H 에 높이 추가, 그리고 새로 쓴 줄은 SPRITE_CLIPS 항목과 그 위의 프레임 구성 주석 한 줄입니다. 이미지 크기는 얼음망령 141,448바이트, 서리 늑대 171,946, 빙결 예언자 159,220, 예티 158,822, 프로스트웜 192,202바이트로 다섯 장 합계 823,638바이트(약 804KB)입니다.

5종의 프레임 구성

3장 몬스터 5종은 모두 10프레임입니다. 번호 0이 서 있기, 1~4가 걷기, 5~7이 공격, 8~9가 쓰러짐이고 타격 프레임을 가리키는 hit 값은 전부 2, 곧 공격 세 프레임 중 마지막 장입니다. 공격 3프레임의 내용은 몬스터마다 다르게 주석으로 남겼습니다.

  • 얼음망령(원거리): 손 뻗기, 얼음 구체 생성, 발사
  • 서리 늑대(빠른 몹): 뒷발로 일어남, 얼음 참격, 돌진하며 얼음 파편
  • 빙결 예언자(특수): 지팡이 들어올림, 얼음 마법진, 얼음 결정 발사
  • 프로스트 예티(엘리트): 두 팔 치켜듦, 얼음 휘두름, 얼음 파편 발사
  • 빙룡 프로스트웜(보스): 날개 펴고 포효, 얼음 소용돌이, 얼음 브레스

1장과 2장의 몬스터는 구성이 조금 다릅니다. 레이스는 14프레임이라 공격이 [5, 6, 7, 10], 쓰러짐이 [12, 13] 으로 중간 프레임을 건너뛰고, 하이드라는 12프레임에 걷기 1~5(5프레임)와 공격 6~9(4프레임)입니다. 시트마다 프레임 번호로 클립을 지정할 수 있게 해 두었기 때문에, 규칙을 어긴 시트도 코드를 바꾸지 않고 숫자 몇 개로 받아들일 수 있었습니다.

남은 한계와 다음에 하려는 일

이력으로 확인되는 한계를 적어 둡니다. 좌표를 사람이 적기 때문에 시트를 다시 그려 교체하면 rects 숫자도 다시 잡아야 합니다. 유닛 이미지 29장의 용량 합계는 현재 5,785,444바이트(약 5.5MB)이고, 로딩 화면에는 '유닛 그림 불러오는 중…' 퍼센트 표시가 따로 있습니다. 현재 소스에서 sprite 필드가 있는 몬스터는 1~3장의 18종뿐이고, 4장 이후 몬스터는 벡터 그림으로 남아 있습니다.

다음 작업이 있다면 같은 순서를 따를 것입니다. 시트 한 장과 id 를 정하고, SPRITES 에 덧붙이고, 자동 자르기가 맞는지 먼저 보고, 어긋나면 rects 로 고정하고, 마지막에 SPRITE_H 로 크기를 맞춥니다. 이 절차가 이미 15번의 '스프라이트 적용' 커밋으로 반복되며 다듬어진 방식입니다.

관련 게임 플레이하기

다른 읽을거리

개발노트게임 소개 문구와 소스가 어긋난 열세 곳 정정 기록게임 소개 문구를 소스 코드와 하나씩 맞춰 보며 바로잡은 열세 가지 사례를 변경 전후 문장, 근거가 된 소스 값, 어긋난 이유, 점검 방식과 함께 적었습니다.개발노트솔저 러쉬 개발 노트: 넓은 도로와 난이도 조정도로를 9에서 15로 넓히자 게이트 글씨가 늘어났고, 쉬워졌다는 의견에 체력 곡선과 무리 크기를 다시 맞춘 과정을 변경 전후 수치로 적었습니다.개발노트레오니스 개발 노트: 요새 위기 연출과 퍽 누적 재설계위기 시 등장 속도 2배 요청을 잘못 해석해 구현했다가 긴급 호위병 7체로 바로잡은 과정, 퍽 영구 누적과 몬스터 40% 강화의 수치 근거를 정리했습니다.개발노트챔피언 방어전 개발 노트: 3D 번들 한계를 우회한 기록Box3 없이 건물 크기를 재고, Float32BufferAttribute와 AdditiveBlending 없이 연출을 만든 방법, 건설 연출과 공중 몬스터를 구현하며 부딪힌 문제를 정리했습니다.개발노트소리를 직접 합성하며 키운 값들: 긴장 연출과 발사음녹음 파일 없이 코드로 만든 효과음·배경음악 구조와, 레오니스의 긴장 연출·챔피언 울음, 군단 점령전 발사음을 키울 때 바꾼 숫자를 이력으로 정리했습니다.가이드디펜스 기본기: 병력 표와 업그레이드 봇 실험왕국 수비대의 병사 표·스테이지 곡선을 소스에서 계산하고 봇으로 골드 사용처를 비교했습니다. 레오니스 조정 이력과 왕관 수호자의 예금 규칙도 정리했습니다.