실습 노트

AI 캐릭터 스프라이트 애니메이션: 초기·실패·성공 비교 실습

서류를 들고 걷는 종이 로봇을 만들다가 안테나를 잃고, 크기와 다리를 몇 번씩 고친 과정을 그대로 남겼습니다.

정인효CSS 애니메이션 · AI 이미지 · 스프라이트 시트 · 웹 개발

홈페이지 히어로에 서류를 들고 걷다가 넘어지는 종이 로봇을 넣어봤습니다. 원래는 한 장짜리 이미지를 CSS로 움직이고 회전시키는 정도였는데, 아무리 봐도 걷는다기보다 그림 한 장이 이동하는 느낌이었습니다.

그래서 처음에는 걷기 4장과 넘어짐 2장으로 만들었고, 조금 더 자연스럽게 보이게 하려고 걷기 6장과 넘어짐 6장으로 늘렸습니다. 장수만 두 배로 늘리면 될 줄 알았는데 아니었습니다. 실제 화면에 올려보니 걷는 동작보다 캐릭터가 상하좌우로 덜컹거리는 게 먼저 보였습니다.

아래 세 개를 같은 10초 주기로 틀어놓으면 제가 어디서 잘못 봤는지 바로 보입니다.

초기 · 4+2프레임
걷기 네 장과 넘어짐 두 장으로 만든 종이 로봇 초기 애니메이션
처음 만든 것걷기 4장 · 넘어짐 2장
실패 · 6+6프레임
크기와 위치를 맞추기 전 종이 로봇 열두 프레임 애니메이션
장수만 늘린 실패본12장인데 위치가 흔들림
성공 · 6+6프레임
크기와 바닥선을 맞추고 뒤쪽 다리를 옅은 회색으로 구분한 종이 로봇 열두 프레임 애니메이션
최종으로 남긴 것정렬 기준 유지 · 양쪽 다리 교대

기기에서 동작 줄이기를 사용하고 있다면 세 애니메이션은 첫 프레임에서 멈춥니다.

처음에는 6장이면 될 줄 알았습니다

처음 쓰던 것은 384×384 단일 이미지였습니다. CSS로 위아래 움직이고 회전시키니 넘어지는 흉내는 낼 수 있었지만, 팔과 다리, 눈, 들고 있는 종이는 한 장 안에서 전혀 바뀌지 않았습니다.

그래서 첫 스프라이트 시트1는 걷기 4장과 넘어짐 2장을 가로 한 줄에 놓았습니다. 한 장짜리를 통째로 돌리는 것보다는 나았지만, 넘어지는 장면이 두 장뿐이라 서 있다가 갑자기 바닥에 붙는 느낌이 남았습니다.

장수를 늘렸더니 안테나가 사라졌습니다

걷기와 넘어짐을 각각 6장으로 늘리고, 넘어질 때 눈도 짧은 가로형 감긴 눈으로 바꿔달라고 했습니다. 그런데 중간 결과를 보니 멀쩡히 있던 안테나가 사라져 있었습니다.

나: 안테나 없어짐 ㅜ

저는 프레임 수와 자세만 얘기하면 나머지 캐릭터 디자인은 그대로 남을 거라고 생각했습니다. 아니었습니다. 이때부터 안테나, 머리와 몸의 비율, 들고 있는 서류, 눈 모양을 따로 적고 하나씩 확인했습니다.

12장으로 늘렸는데 더 부산스러웠습니다

안테나와 눈을 다시 고치고 이제 됐다 싶었습니다. 그런데 12프레임을 실제 히어로 크기로 재생하니 또 다른 문제가 보였습니다.

나: 됐는데 이미지 크기가 일정치가 않아서, 움직임이 부산스럽네 ㅋㅋㅋ

각 파일의 캔버스는 전부 512×512였지만, 눈으로 봐도 안에 있는 캐릭터 크기와 위치는 일정하지 않았습니다. 투명 배경을 제외한 알파 채널2의 실제 경계를 재보니 차이가 숫자로 더 확실하게 나왔습니다.

프레임 보이는 폭 왼쪽 x 바닥 y
walk-01 245px 212px 460px
walk-02 238px 153px 446px
walk-03 247px 63px 446px
walk-04 230px 215px 381px
walk-05 233px 155px 387px
walk-06 232px 81px 387px

왼쪽 좌표는 63px에서 215px, 바닥 좌표는 381px에서 460px까지 벌어져 있었습니다. 캔버스 크기만 같았지, 안에 있는 캐릭터는 서로 다른 자리에 놓여 있던 셈입니다. 이 정도면 안 흔들리는 게 더 이상합니다.

그래서 캐릭터 자체의 기준점을 맞췄습니다

여기서 보이는 폭과 높이를 전부 똑같이 맞추면 간단할 것 같지만, 걷는 자세는 무릎이 굽거나 몸이 펴지면서 원래 높이가 달라집니다. 높이까지 억지로 같게 만들면 자세 자체가 찌그러질 수 있어, 중심과 바닥선만 고정하기로 했습니다.

  • 걷기 6장은 보이는 폭을 240px로 맞췄습니다.
  • 가로 중심은 512px 캔버스의 중앙에 놓아 왼쪽 좌표를 136px로 맞췄습니다.
  • 발이 닿는 바닥선은 y=460px로 통일했습니다.
  • 자세 때문에 생기는 세로 높이 차이는 그대로 남겼습니다.

넘어짐 6장은 조금 다르게 봤습니다. 프레임마다 폭을 따로 맞추면 넘어지는 동안 몸이 커졌다 작아질 수 있었습니다. 첫 넘어짐 프레임의 보이는 폭 260px을 기준으로 240 ÷ 260 = 0.923077 배율을 구하고, 여섯 장 모두에 같은 92.3% 배율을 적용했습니다. 바닥선은 y=460px로 맞추되 공중에 뜨는 세 번째 프레임만 y=442px에 뒀습니다.

결국 제가 맞춘 것은 모든 숫자가 아니었습니다. 걷기는 중심과 바닥선을 고정하고 자세 차이를 남겼고, 넘어짐은 한 번 정한 배율을 끝까지 유지했습니다.

다리를 교차시키면 끝인 줄 알았습니다

크기와 위치를 맞춘 v5는 확실히 덜 흔들렸습니다. 이제 끝인가 싶었는데, 계속 보고 있으니 다리 움직임이 이상했습니다. 걷기 6장 중 여러 장의 다리 모양이 비슷했고, 긴 보폭만 이어져 걷는다기보다 제자리에서 튀는 것처럼 보였습니다.

나: 애니메이션 다리가 교차하는 게 없어서 약간 어색함

그래서 v6에서는 기존의 폭 240px·중심 x=136px·바닥 y=460px 기준은 건드리지 않고, 걷기 2·4·6번에 다리가 겹쳐 보이는 통과 자세를 넣었습니다. 긴 보폭과 교차 자세가 번갈아 나오니 이전보다는 걷는 느낌이 났습니다. 넘어짐 6장은 눈·안테나·배율이 이미 맞아 그대로 사용했습니다.

그런데 이것도 반쪽짜리 수정이었습니다. 저는 당시 빠져 있던 교차 자세를 넣고 기존 정렬을 유지하는 데만 신경 썼습니다. 정작 어느 다리가 앞으로 나오는지는 끝까지 추적하지 않았습니다.

그런데 반대 다리가 안 나왔습니다

v6를 다시 재생해보니 1·3·5번의 긴 보폭에서 같은 다리만 계속 앞으로 나와 있었습니다. 2·4·6번에 교차 자세가 있어도 다음 접지 자세에서 반대 다리가 나오지 않으니, 한쪽 다리만 반복해서 내미는 셈이었습니다.

나: 반대 다리가 앞으로 나온 그림이 없는데 되는겨?

확인해보니 진짜 그랬습니다. v6를 성공본이라고 해버리면 안 되겠더군요. 교차 자세만 추가했을 뿐, 양쪽 다리가 번갈아 전진하는 걷기까지 완성한 것은 아니었습니다.

그래서 v7에서는 뒤쪽 다리만 회색으로 구분했습니다. 예쁘게 보이려고 색을 넣은 것이 아니라, 옆모습에서도 어느 다리가 움직이는지 제가 직접 추적하기 위해서였습니다. 1번에서는 흰 다리가 앞으로 나오고, 2번에서 두 다리가 교차한 뒤, 3번에서는 회색 다리가 앞으로 나옵니다. 4번 교차 자세를 거쳐 5번에서 다시 흰 다리가 전진합니다.

여섯 장에는 모두 같은 92% 배율을 적용하고, 가로 중심은 x=256px, 바닥은 y=460px로 맞췄습니다. 긴 보폭과 교차 자세 때문에 보이는 폭은 226~285px로 달라지지만, 캐릭터 몸체 자체가 프레임마다 커졌다 작아지는 것은 아닙니다. 넘어짐 6장은 기존 성공본을 그대로 썼습니다.

그런데 다리를 구분하려고 넣은 회색이 실제 재생 화면에서는 너무 짙었습니다. 흰 다리와 회색 다리가 교대한다기보다 한쪽 다리가 프레임마다 다른 색으로 바뀌는 인상이 먼저 보였습니다.

나: 회색 다리가 너무 짙고 흰색에서 바뀌는 게 티가 많이 나는데?

v8에서는 캐릭터를 다시 생성하지 않고 v7의 좌표와 투명도를 그대로 둔 채 뒤쪽 다리의 중심색만 #ADADAD 부근에서 #E2E2E2 부근으로 낮췄습니다. 넘어짐 6장은 PNG 픽셀을 그대로 유지했습니다. 96~148px 크기에서는 다리의 겹침 순서만 구분되고, 별도의 회색 색면이 번쩍이는 느낌은 줄었습니다.

다음에는 이렇게 프롬프트를 쓰려고 합니다

여기까지 몇 번을 고치고 나니 자연스럽게 걷게 해줘 같은 말이 생각보다 아무 기준도 되지 않는다는 걸 제대로 봤습니다. 프롬프트를 무조건 길게 쓰는 것보다, 절대 바뀌면 안 되는 요소와 프레임별 자세, 마지막에 확인할 조건을 따로 적는 편이 훨씬 나았습니다.

실제 실패 프롬프트에 넣을 지시 그래도 필요한 확인
안테나 소실 안테나·눈·서류·몸체 비율을 변경 금지 요소로 열거 여섯 셀을 각각 확대해 누락 확인
프레임마다 캐릭터 변화 한 장씩 따로 만들지 말고 3×2 전체 시트를 한 번에 생성 종이 장수·선 굵기·몸체 비율 비교
같은 다리만 전진 흰 다리와 회색 다리를 구분하고 프레임별 앞발을 지정 1번 앞발은 흰색, 3번 앞발은 회색인지 확인
크기와 위치 흔들림 같은 카메라 거리·몸체 배율을 요구하고, 전체 실루엣이 아닌 몸통 중심·접지선을 정렬 기준으로 지정 배경 제거 후 알파 경계를 측정해 다시 정렬

다음에 비슷한 6프레임 걷기 시트를 만들면 아래 뼈대부터 넣을 생각입니다.

목표:
서류를 들고 왼쪽으로 걷는 종이 로봇의 6프레임 걷기 시트를 만든다.

출력 구성:
- 정확히 3열×2행, 같은 크기의 정사각형 셀 6개
- 왼쪽 위부터 오른쪽 아래까지 1~6번 순서
- 셀 경계선, 번호, 글자, 잘린 캐릭터 없음

캐릭터 고정 요소:
- 머리 위 짧은 줄기와 원형 끝의 안테나 유지
- 한쪽 검은 눈, 땀방울, 큰 서류더미, 두 팔과 두 손 유지
- 머리·몸체·서류의 비율과 검은 선 굵기를 모든 셀에서 동일하게 유지
- 다리 자세 외의 요소는 바꾸지 않음

프레임 순서:
1. 흰 다리가 앞으로 뻗은 접지 자세, 회색 다리는 뒤
2. 회색 다리가 몸 아래를 통과하는 교차 자세
3. 회색 다리가 앞으로 뻗은 반대 접지 자세, 흰 다리는 뒤
4. 흰 다리가 몸 아래를 통과하는 반대 교차 자세
5. 흰 다리가 다시 앞으로 뻗은 접지 자세
6. 회색 다리가 다음 동작을 준비하는 교차 자세

정렬 조건:
- 모든 프레임에서 같은 카메라 거리와 같은 몸체 배율
- 몸통 기준점은 각 셀의 가로 50% 선에 놓기
- 접지한 발바닥은 각 셀 높이의 90% 선에 놓기
- 보폭의 양끝을 기준으로 그림 전체를 가운데 맞추지 않기
- 보폭 때문에 달라지는 전체 폭과 자세 높이는 억지로 같게 만들지 않음
- 모든 셀에 충분한 배경 여백을 남기고 캐릭터를 자르지 않음

배경:
- 배경 제거용 단일 #00ff00 색상
- 그림자, 질감, 그라데이션, 바닥면 없음

검수 조건:
- 1번과 3번에서 서로 반대 다리가 앞으로 나와야 함
- 1번 앞발은 흰색, 3번 앞발은 회색이어야 함
- 안테나는 6개 프레임에 모두 있어야 함
- 팔, 다리, 발은 각 프레임에 정확히 2개씩 있어야 함

다만 이 프롬프트를 넣는다고 그림이 픽셀 단위로 딱 맞는 것은 아니었습니다. 실제로 회색 다리 규칙까지 적었는데도 3번 앞발이 다시 흰색으로 나온 결과가 있었습니다. 결국 최종본은 배경을 제거한 뒤 여섯 장에 같은 92% 배율을 적용하고, 알파 경계의 중심을 x=256px, 접지선을 y=460px로 다시 맞췄습니다.

제가 얻은 결론은 이렇습니다. 프롬프트에는 몸통 중심은 셀 가로 50%, 접지선은 셀 높이 90%처럼 기준점을 적되, 정확한 좌표는 생성 후 직접 재서 맞추는 편이 안전했습니다. 보폭에 따라 달라지는 전체 그림을 기준으로 가운데 맞추면 몸통이 좌우로 흔들릴 수 있기 때문입니다. 큰 시트만 보고 끝내지 않고 실제 표시 크기인 124px와 96px에서도 한 사이클을 틀어봐야 했습니다.

마지막에는 12장을 한 파일로 묶었습니다

최종 12장은 4열×3행, 2048×1536 크기의 WebP 한 장으로 묶었습니다. 웹에서는 124×124 영역만 열어두고 큰 이미지를 이동시켜 한 프레임씩 보여주게 했습니다.

버전 배열 파일
초기 v3 6 6×1 약 152KB
실패 v4 12 4×3 약 252KB
정렬 v5 12 4×3 약 264KB
교차 보정 v6 12 4×3 약 243KB
교대 확인 v7 12 4×3 약 230KB
최종 v8 12 4×3 약 236KB
.paper-bot-sprite {
  width: 400%;
  height: 300%;
  animation: paper-bot-frames 10s steps(1, end) infinite;
}

가로로 한 칸 옮길 때는 translateX(-25%), 세로로 한 줄 옮길 때는 translateY(-33.3333%)를 사용했습니다. 화면에서는 Cloudflare R23의 WebP 한 장만 불러오고, 프레임 전환은 CSS가 처리합니다. PNG 원본과 실행용 WebP는 실패본도 지우지 않고 v3부터 v8까지 버전별로 나눠뒀습니다.

다음에 또 만들면 이 순서로 볼 생각입니다

  1. 생성 전에 안테나, 눈, 들고 있는 물건처럼 캐릭터를 구분하는 요소부터 적습니다.
  2. 배경을 투명하게 만든 뒤 각 프레임의 알파 경계를 잽니다.
  3. 걷기는 보이는 폭, 가로 중심, 바닥선을 맞추고 자세에 따른 높이 차이는 남깁니다.
  4. 긴 보폭 사이에 다리가 겹치는 통과 자세가 있는지 봅니다.
  5. 옆모습의 두 다리가 같은 모양이면 뒤쪽 다리에만 옅은 색을 써서 어느 다리가 앞으로 나오는지 추적합니다.
  6. 첫 접지 자세와 다음 접지 자세에서 서로 반대 다리가 나왔는지 확인합니다.
  7. 넘어짐은 첫 프레임에서 구한 하나의 배율을 전체에 적용합니다.
  8. 스프라이트로 묶기 전에 124px 데스크톱과 96px 모바일 크기로 먼저 틀어봅니다.
  9. 실패 파일도 덮어쓰지 않고 버전별로 남깁니다.
  10. prefers-reduced-motion에서는 애니메이션을 끄고 첫 프레임만 보여줍니다.

처음에는 그림 장수만 늘리면 자연스러워질 거라고 생각했습니다. 막상 해보니 장수를 늘리는 것, 위치를 맞추는 것, 다리 교차 자세를 넣는 것, 양쪽 다리를 실제로 번갈아 내미는 것은 전부 다른 문제였습니다.

그래서 위치가 흔들리는 v4, 정렬만 마친 v5, 교차 자세만 넣은 v6, 반대 다리 전진까지 확인한 v7을 전부 남겼습니다. 성공본 하나만 보면 별것 없어 보이지만, 실패본을 같이 놓고 보니 제가 뭘 놓쳤는지가 훨씬 잘 보였습니다.

홈 히어로에서 최종 애니메이션 보기

(1) 스프라이트 시트(Sprite Sheet) — 애니메이션에 필요한 여러 프레임을 한 이미지 안에 배열한 파일입니다. 화면에서는 정해진 영역만 잘라 보여주고 이미지 위치를 바꿔 다음 프레임으로 전환합니다.

(2) 알파 채널(Alpha Channel) — 이미지 픽셀의 투명도를 담는 정보입니다. 투명 픽셀을 제외한 경계를 측정하면 캔버스 크기가 아니라 실제 캐릭터가 차지하는 영역을 구할 수 있습니다.

(3) Cloudflare R2 — 이미지 같은 파일을 저장하고 웹 주소로 제공할 수 있는 객체 스토리지입니다.