EP02. Sprite 이미지와 에셋에 대해
2D 게임을 제작할 때 사용하는 sprite 이미지에 대해 학습합니다.
Pixi.js로 만든 픽셀 감성 러너. 장애물을 피하며 달리는 러너 루프와 충돌 처리, 간단한 파티클 효과를 담았습니다.
- 러너 루프와 배경 패럴럭스 처리
- AABB 충돌 감지와 난이도 곡선 설계
- UI/사운드 자산을 포함한 전체 빌드 파이프라인
1. 스프라이트란?
게임 개발에서 스프라이트(Sprite) 는 캐릭터, 배경, 아이템 등의 시각적 요소를 표현하는 이미지 또는 애니메이션 단위입니다.
특히 2D 게임에서는 스프라이트 시트를 활용해 하나의 이미지에 여러 프레임을 담고, 이를 빠르게 교체하며 움직이는 애니메이션처럼 보이게 만듭니다.

위 이미지는 4개의 프레임이 가로로 나열된 공격 동작 스프라이트 시트입니다.
2. 스프라이트 시트 로딩 구조
실제 게임에서는 Pixi.js의 PIXI.Assets.load를 통해 스프라이트 시트를 비동기로 불러오고, 이후 PIXI.Rectangle을 이용해 프레임 단위로 잘라서 PIXI.Texture 배열로 관리합니다.
2.1 Texture 분리 및 저장
import * as PIXI from "pixi.js";
// config 파일 기반 캐릭터와 애니메이션 정보를 사용하여 로딩
const sheetTexture = await PIXI.Assets.load(
`/assets/pixel-runner/assets/png/Fighter/Attack_1.png`
);
const textures: PIXI.Texture[] = [];
for (let i = 0; i < 4; i++) {
const frame = new PIXI.Rectangle(i * 64, 0, 64, 64);
textures.push(new PIXI.Texture({ source: sheetTexture.source, frame }));
}
PIXI.Assets.load는 내부적으로 BaseTexture를 생성하고 관리하므로, 따로PIXI.BaseTexture.from을 사용할 필요 없이 간결하게 처리할 수 있습니다.PIXI.Texture는source와frame속성을 통해 원하는 프레임을 잘라 사용할 수 있습니다.
2.2 애니메이션 생성 및 사용
const sprite = new PIXI.AnimatedSprite(textures);
sprite.animationSpeed = 0.15;
sprite.play();
app.stage.addChild(sprite);
frame을 크기에 맞춰 잘라 new PIXI.Texture()로 감싸 textures에 담았다면, 이미지 정보가 담긴 textures를 PIXI.AnimatedSprite()에 담아 sprite 애니메이션 객체를 생성합니다.
그리고 속도를 정한 뒤, play()로 실행하고, app의 stage에 추가합니다.
2.3 실제 애니메이션 예시

3. 모든 애니메이션 미리 로딩해두는 방식
실제 게임에서는 위처럼 즉시 사용하는 대신, 초기화 시 모든 캐릭터와 애니메이션을 미리 로딩해두고 필요할 때 꺼내 쓰는 구조를 갖춥니다.
Player.ts에서는 다음과 같은 방식으로 처리합니다:
const assetMap = this.config[this.character];
for (const animName of Object.keys(assetMap)) {
const animData = assetMap[animName];
const textures: PIXI.Texture[] = [];
if (animData.isSpriteSheet) {
const sheetTexture = await PIXI.Assets.load(
`/assets/pixel-runner/assets/png/${this.character}/${animName}.png`
);
for (let i = 0; i < animData.frames; i++) {
const frame = new PIXI.Rectangle(
i * animData.frameWidth,
0,
animData.frameWidth,
animData.frameHeight
);
textures.push(new PIXI.Texture({ source: sheetTexture.source, frame }));
}
} else {
const texture = await PIXI.Assets.load(
`/assets/pixel-runner/assets/png/${this.character}/${animName}.png`
);
textures.push(texture);
}
this.animations[animName] = textures;
}
이렇게 하면 switchCharacter() 단계에서 필요한 모든 애니메이션을 한번에 로딩하고 this.animations 객체에 저장해두기 때문에, 런타임에서는 setAnimation()으로 바로 꺼내 사용 가능합니다.
assetMap은 외부 JSON 파일(character-config.json)로부터 불러온 애니메이션 정의 객체이며, 다음과 같은 구조를 가집니다:
{
"Fighter": {
"Idle": {
"frames": 6,
"frameWidth": 128,
"frameHeight": 128,
"isSpriteSheet": true
},
"Run": {
"frames": 8,
"frameWidth": 128,
"frameHeight": 128,
"isSpriteSheet": true
},
...
}
}
각 캐릭터(Fighter, Samurai, Shinobi)는 여러 애니메이션 상태(Idle, Run, Walk 등)를 가지며, 각 상태마다 프레임 수와 프레임의 너비/높이, 시트 형태 여부를 지정합니다. 이를 기반으로 애니메이션을 자동 분기 처리할 수 있어 코드의 확장성과 유지보수성이 좋아집니다.
4. 왜 스프라이트를 사용할까?
- 성능: 여러 개의 이미지를 개별로 로딩하지 않고 한 장으로 구성하면 GPU 연산 효율이 높아집니다.
- 일관성: 애니메이션 타이밍과 위치가 통일되어 자연스럽게 표현됩니다.
- 간결함: 하나의 이미지로 여러 동작을 제어할 수 있어 코드가 간결해집니다.
5. 무료 스프라이트 에셋 얻는 방법 (Craftpix.net)
상용 또는 개인 프로젝트에서 사용할 수 있는 고품질 스프라이트 에셋을 찾고 있다면, Craftpix.net 같은 사이트에서 무료 또는 유료 리소스를 다운로드할 수 있습니다.
5.1 Craftpix.net에서 무료 에셋 받는 법
- Craftpix.net에 접속합니다.
- 상단 메뉴에서 “Freebies” 항목을 클릭합니다.
- 2D Game Assets 또는 Pixel Art 카테고리 중 원하는 항목을 선택합니다.
- 원하는 리소스를 클릭하면 상세 페이지로 이동합니다.
- 하단에 있는 Download 버튼을 클릭하여 압축 파일(.zip) 형태로 다운로드합니다.
- 압축을 해제하면 PNG, PSD, JSON 등 다양한 리소스를 사용할 수 있습니다.
회원 가입 없이 다운로드 가능한 무료 리소스도 있고, 일부 고급 에셋은 유료 또는 회원제(Premium) 전용입니다.
이렇게 확보한 스프라이트 시트는 Pixi.js에서 쉽게 로딩하여 애니메이션으로 사용할 수 있으며, assetMap JSON 포맷에 맞춰 구성하면 게임을 제작할 수 있습니다.
6. 정리
스프라이트 시트는 2D 게임에서 애니메이션 구현의 핵심 도구입니다.
Pixi.js에서는 이를 효율적으로 분리하고 사용할 수 있도록 다양한 유틸리티와 클래스를 제공합니다.