Unity 2D 플랫포머
0%
Player Idle
2026 Tutorial

Unity 2D 플랫포머

스프라이트 애니메이션부터 완성까지

실제 픽셀 아트 스프라이트 14종 활용 · 19장 단계별 실습

Unity 6C#PhiMir ToolsGitHub
시작하기 →
01

프로젝트 준비

1프로젝트 생성 및 환경 설정

이 튜토리얼에서는 실제 픽셀 아트 스프라이트 시트 14종(플레이어 8종 + 적 6종)을 활용하여 완성된 2D 플랫포머 게임을 만듭니다. 이동, 점프, 공격, 피격, 사망 애니메이션을 모두 구현하고, 적 AI, UI, 게임 매니저까지 완성합니다.

1.1 Unity Hub에서 프로젝트 생성

  1. Unity Hub 실행 → New Project
  2. 템플릿: 2D (URP) 선택
  3. 프로젝트 이름: PlatformerTutorial
  4. Unity Editor 버전: Unity 6 LTS 권장

1.2 필수 패키지 설치

패키지용도
Cinemachine플레이어 추적 카메라
Input System키보드/게임패드 입력
2D Tilemap Editor타일맵 레벨 디자인
TextMeshProUI 텍스트

1.3 프로젝트 폴더 구조

Assets/
├── Sprites/           ← 스프라이트 시트 (8개 파일)
├── Animations/        ← 애니메이션 클립 및 컨트롤러
├── Scripts/
│   ├── Player/
│   ├── Enemy/
│   ├── Manager/
│   └── UI/
├── Prefabs/
├── Scenes/
├── Tiles/
└── Audio/

1.4 프로젝트 세팅 체크리스트

02

스프라이트 에셋 준비

2스프라이트 시트 분석

이 튜토리얼에서 사용할 스프라이트 시트를 분석합니다. 플레이어 캐릭터 8종 + 적 캐릭터 6종 = 총 14개의 스프라이트 시트를 사용합니다.

IDLE player_idle.png
Idle

2000×496 · 4프레임 · 각 500×496px

캐릭터가 가만히 서서 좌우로 살짝 움직이는 모션. 게임 시작 시 기본 상태로 사용됩니다.

WALK player_walk.png
Walk

2000×327 · 8프레임 · 각 250×327px

좌우 이동 시 재생되는 걷기 모션. 발곱치가 교차하며 자연스러운 루프 애니메이션입니다.

JUMP player_jump.png
Jump

2000×240 · 10프레임 · 각 200×240px

점프 준비 → 상승 → 정점 → 하강 → 착지까지의 전체 점프 사이클. 공중에서는 특정 프레임에서 멈추는 방식으로 구현합니다.

ATTACK player_attack.png
Attack

2000×496 · 4프레임 · 각 500×496px

칼을 휘두르는 공격 모션. 준비 → 휘두르기 → 히트 → 복귀의 4단계. Animation Event로 히트 판정 타이밍을 잡습니다.

HURT player_hurt.png
Hurt

2000×496 · 10프레임 · 각 200×496px

적에게 맞았을 때의 피격 반응. 뒤로 밀려나는 넉백과 함께 재생합니다. 무적 시간(iFrame)과 연동하여 구현합니다.

DIE player_die.png
Die

2000×327 · 5프레임 · 각 400×327px

HP가 0이 되면 재생되는 사망 애니메이션. 쓰러지며 넘어지는 모션입니다. 루프 없이 1회 재생 후 Game Over 처리합니다.

MULTI clear_motion_spritesheet.png
Motion 1

2000×496 · 2행 멀티모션

여러 모션이 하나의 시트에 합쳐진 통합 스프라이트시트. Unity에서 Grid 슬라이싱으로 분할합니다.

MULTI clear_motion_spritesheet2.png
Motion 2

2000×496 · 2행 멀티모션

추가 모션이 포함된 두 번째 통합 시트. 점프+공격 등 복합 모션이 포함되어 있습니다.

적 캐릭터 스프라이트 시트

보라색 뿔 달린 귀여운 악마 캐릭터의 스프라이트 시트 6종입니다. 플레이어와 동일한 구조로 Idle, Walk, Attack, Hurt, Die 모션을 갖추고 있습니다.

ENEMY IDLE enemy_idle.png
Enemy Idle

2000×496 · 4프레임 · 각 500×496px

적 캐릭터의 대기 모션. 가만히 서서 좌우로 미세하게 흔들리는 동작입니다.

ENEMY ATTACK enemy_attack.png
Enemy Attack

2000×496 · 4프레임 · 각 500×496px

적의 근접 공격 모션. 날개를 펼치며 돌진하는 형태입니다.

ENEMY ATTACK 2 enemy_attack2.png
Enemy Attack 2

2000×496 · 4프레임 · 각 500×496px

적의 두 번째 공격 패턴. 날개를 이용한 원거리 공격 모션입니다.

ENEMY HURT enemy_hurt.png
Enemy Hurt

2000×496 · 4프레임 · 각 500×496px

적이 플레이어에게 맞았을 때의 피격 반응. 깜빡임 이펙트와 함께 사용합니다.

ENEMY WALK enemy_walk.png
Enemy Walk

2000×327 · 6프레임 · 각 333×327px

적이 순찰하며 이동하는 걷기 모션. EnemyPatrol 스크립트와 연동합니다.

ENEMY DIE enemy_die.png
Enemy Die

2000×327 · 5프레임 · 각 400×327px

적의 사망 애니메이션. 반짝이며 쓰러지고 파티클이 흩어지는 모션. HP가 0이 되면 1회 재생 후 Destroy됩니다.

2.2 스프라이트 시트 사양 요약

파일명해상도프레임프레임 크기용도
▸ 플레이어 캐릭터
player_idle2000×4964500×496Idle 대기
player_walk2000×3278250×327Walk 걷기
player_jump2000×24010200×240Jump 점프
player_attack2000×4964500×496Attack 공격
player_hurt2000×49610200×496Hurt 피격
player_die2000×3275400×327Die 사망
clear_motion_12000×4962행가변통합 모션
clear_motion_22000×4962행가변통합 모션
▸ 적 캐릭터
enemy_idle2000×4964500×496Idle 대기
enemy_walk2000×3276333×327Walk 순찰
enemy_attack2000×4964500×496Attack 공격1
enemy_attack22000×4964500×496Attack 공격2
enemy_hurt2000×4964500×496Hurt 피격
enemy_die2000×3275400×327Die 사망

3스프라이트 임포트 및 슬라이싱

3.1 임포트 설정

  1. Assets/Sprites 폴더 생성
  2. 14개 스프라이트 시트 파일을 드래그앤드드롭으로 임포트 (플레이어 8종 + 적 6종)
  3. 각 파일 선택 → Inspector에서 설정

3.2 Inspector 설정 (모든 스프라이트 공통)

항목설명
Sprite ModeMultiple여러 프레임이 포함된 시트
Pixels Per Unit32 또는 64픽셀 아트 크기에 맞게 조정
Filter ModePoint (no filter)픽셀 아트 선명하게
CompressionNone화질 손실 방지
Max Size2048원본 해상도 유지
💡 팁 — Pixels Per Unit은 캐릭터 크기에 직접 영향을 줍니다. 값이 클수록 캐릭터가 작아집니다.

3.3 Sprite Editor 슬라이싱

개별 스프라이트 시트마다 Sprite Editor를 열어 프레임을 분할합니다.

  1. Sprite Editor 열기 (Inspector → Sprite Editor 버튼)
  2. Slice 드롭다운 → Type: Grid By Cell Size
  3. 각 시트에 맞는 Cell Size 입력
  4. Slice 버튼 클릭 → Apply

각 시트별 슬라이싱 설정

파일Cell Size (W×H)결과 프레임
player_idle.png500 × 4964프레임
player_walk.png250 × 3278프레임
player_jump.png200 × 24010프레임
player_attack.png500 × 4964프레임
player_hurt.png200 × 49610프레임
player_die.png400 × 3275프레임

적 캐릭터 슬라이싱 설정

파일Cell Size (W×H)결과 프레임
enemy_idle.png500 × 4964프레임
enemy_walk.png333 × 3276프레임
enemy_attack.png500 × 4964프레임
enemy_attack2.png500 × 4964프레임
enemy_hurt.png500 × 4964프레임
enemy_die.png400 × 3275프레임
⚠️ 주의 — clear_motion_spritesheet 파일들은 프레임 크기가 불균일하므로 Automatic 모드로 슬라이싱하거나, 개별 시트를 사용하는 것을 권장합니다.
03

애니메이션 시스템

4애니메이션 클립 생성

4.1 애니메이션 클립 생성 방법

  1. Assets/Animations 폴더 생성
  2. Project 창에서 player_idle 스프라이트 전체 선택 (idle_0 ~ idle_3)
  3. 선택한 스프라이트를 Scene 뷰에 드래그앤드드롭
  4. 저장 대화상자에서 Animations/Player_Idle.anim으로 저장
  5. 나머지 5개 애니메이션도 동일하게 생성

4.2 애니메이션 클립 설정

클립명프레임 수Sample RateLoop
Player_Idle48✔ Yes
Player_Walk812✔ Yes
Player_Jump1012✘ No
Player_Attack410✘ No
Player_Hurt1012✘ No
Player_Die58✘ No
💡 — Idle와 Walk만 Loop Time을 체크합니다. 나머지는 1회 재생 후 다른 상태로 전환됩니다.

5Animator Controller 구성

5.1 State Machine 설계

Window > Animation > Animator를 열어 상태 머신을 구성합니다.

Idle기본 상태
Walk좌우 이동
Jump점프 중
Attack공격
Hurt피격
Die사망

5.2 파라미터 설정

파라미터타입기본값설명
SpeedFloat0이동 속도 (0=정지, >0.1=걷기)
IsGroundedBooltrue바닥 접촉 여부
IsJumpingBoolfalse점프 중 여부
AttackTrigger-공격 트리거
HurtTrigger-피격 트리거
DieTrigger-사망 트리거

5.3 전환(Transition) 규칙

출발도착조건Has Exit Time
IdleWalkSpeed > 0.1No
WalkIdleSpeed < 0.1No
Any StateJumpIsJumping = trueNo
JumpIdleIsGrounded = trueNo
Any StateAttackAttack (trigger)No
AttackIdle-Yes (1.0)
Any StateHurtHurt (trigger)No
HurtIdle-Yes (1.0)
Any StateDieDie (trigger)No
💡 — Any State에서의 전환은 어떤 상태에서든 즉시 발동됩니다. Attack/Hurt/Die는 우선순위가 높아야 합니다.
04

플레이어 스크립팅

6플레이어 이동 스크립트

6.1 PlayerController.cs

플레이어의 이동, 점프, 방향 전환을 처리하는 핵심 스크립트입니다.

using UnityEngine;

public class PlayerController : MonoBehaviour
{
    [Header("Movement")]
    [SerializeField] private float moveSpeed = 5f;
    [SerializeField] private float jumpForce = 12f;

    [Header("Ground Check")]
    [SerializeField] private Transform groundCheck;
    [SerializeField] private float groundRadius = 0.2f;
    [SerializeField] private LayerMask groundLayer;

    private Rigidbody2D rb;
    private Animator animator;
    private SpriteRenderer spriteRenderer;
    private float moveInput;
    private bool isGrounded;

    void Awake()
    {
        rb = GetComponent<Rigidbody2D>();
        animator = GetComponent<Animator>();
        spriteRenderer = GetComponent<SpriteRenderer>();
    }

    void Update()
    {
        // 입력 처리
        moveInput = Input.GetAxisRaw("Horizontal");

        // 바닥 체크
        isGrounded = Physics2D.OverlapCircle(
            groundCheck.position, groundRadius, groundLayer);

        // 점프
        if (Input.GetButtonDown("Jump") && isGrounded)
            rb.velocity = new Vector2(rb.velocity.x, jumpForce);

        // 방향 전환 (Sprite Flip)
        if (moveInput > 0) spriteRenderer.flipX = false;
        else if (moveInput < 0) spriteRenderer.flipX = true;

        // 애니메이터 파라미터 업데이트
        animator.SetFloat("Speed", Mathf.Abs(moveInput));
        animator.SetBool("IsGrounded", isGrounded);
        animator.SetBool("IsJumping", !isGrounded);
    }

    void FixedUpdate()
    {
        rb.velocity = new Vector2(moveInput * moveSpeed, rb.velocity.y);
    }
}
💡 — FixedUpdate에서 velocity를 설정하는 이유: 물리 연산은 FixedUpdate에서 처리해야 일관된 결과를 얻습니다.

6.2 GameObject 구성

  1. Hierarchy에서 Player 오브젝트 생성
  2. 컴포넌트 추가: SpriteRenderer, Rigidbody2D, BoxCollider2D, Animator
  3. Rigidbody2D 설정: Freeze Rotation Z 체크
  4. 빈 자식 오브젝트 GroundCheck 생성 → 발 위치에 배치
  5. PlayerController.cs 컴포넌트 붙이고 Inspector에서 설정

7공격 시스템

7.1 PlayerCombat.cs

using UnityEngine;

public class PlayerCombat : MonoBehaviour
{
    [SerializeField] private Transform attackPoint;
    [SerializeField] private float attackRange = 0.8f;
    [SerializeField] private int attackDamage = 1;
    [SerializeField] private LayerMask enemyLayer;
    [SerializeField] private float attackCooldown = 0.4f;

    private Animator animator;
    private float nextAttackTime;

    void Awake() => animator = GetComponent<Animator>();

    void Update()
    {
        if (Input.GetButtonDown("Fire1") && Time.time >= nextAttackTime)
        {
            Attack();
            nextAttackTime = Time.time + attackCooldown;
        }
    }

    void Attack()
    {
        animator.SetTrigger("Attack");
        Collider2D[] enemies = Physics2D.OverlapCircleAll(
            attackPoint.position, attackRange, enemyLayer);
        foreach (var enemy in enemies)
            enemy.GetComponent<IDamageable>()?.TakeDamage(attackDamage);
    }

    void OnDrawGizmosSelected()
    {
        if (attackPoint == null) return;
        Gizmos.color = Color.red;
        Gizmos.DrawWireSphere(attackPoint.position, attackRange);
    }
}

7.2 IDamageable 인터페이스

public interface IDamageable
{
    void TakeDamage(int damage);
}
💡 — 인터페이스를 사용하면 적, 파괴 가능한 오브젝트 등에 동일한 데미지 시스템을 적용할 수 있습니다.

8체력 및 피격 시스템

8.1 PlayerHealth.cs

using UnityEngine;
using System;

public class PlayerHealth : MonoBehaviour, IDamageable
{
    [SerializeField] private int maxHP = 5;
    [SerializeField] private float iFrameDuration = 1f;
    [SerializeField] private float knockbackForce = 5f;

    public int CurrentHP { get; private set; }
    public event Action<int, int> OnHPChanged;
    public event Action OnDeath;

    private Animator animator;
    private Rigidbody2D rb;
    private SpriteRenderer sr;
    private bool isInvincible, isDead;

    void Awake()
    {
        animator = GetComponent<Animator>();
        rb = GetComponent<Rigidbody2D>();
        sr = GetComponent<SpriteRenderer>();
        CurrentHP = maxHP;
    }

    public void TakeDamage(int damage)
    {
        if (isInvincible || isDead) return;
        CurrentHP -= damage;
        OnHPChanged?.Invoke(CurrentHP, maxHP);
        if (CurrentHP <= 0) { Die(); return; }

        animator.SetTrigger("Hurt");
        float dir = sr.flipX ? 1f : -1f;
        rb.velocity = new Vector2(dir * knockbackForce, knockbackForce * 0.5f);
        StartCoroutine(InvincibilityCoroutine());
    }

    private System.Collections.IEnumerator InvincibilityCoroutine()
    {
        isInvincible = true;
        float end = Time.time + iFrameDuration;
        while (Time.time < end)
        {
            sr.enabled = !sr.enabled;
            yield return new WaitForSeconds(0.1f);
        }
        sr.enabled = true;
        isInvincible = false;
    }

    private void Die()
    {
        isDead = true;
        animator.SetTrigger("Die");
        rb.velocity = Vector2.zero;
        GetComponent<PlayerController>().enabled = false;
        GetComponent<PlayerCombat>().enabled = false;
        OnDeath?.Invoke();
    }
}
05

레벨 및 적 시스템

9타일맵 레벨 디자인

9.1 타일맵 기초

  1. Hierarchy → 2D Object → Tilemap → Rectangular
  2. Tile Palette 창 열기 (Window > 2D > Tile Palette)
  3. Create New Palette → 타일 에셋 드래그
  4. 플랫폼, 벽, 바닥 타일로 레벨 구성

9.2 충돌 설정

  • Tilemap에 TilemapCollider2D 추가
  • CompositeCollider2D 추가 (충돌 최적화)
  • Rigidbody2D → Body Type: Static
  • Layer를 "Ground"로 설정

10카메라 시스템 (Cinemachine)

  1. Hierarchy → Cinemachine → 2D Camera
  2. Virtual Camera의 Follow에 Player 할당
  3. Body → Framing Transposer → Dead Zone 조정
  4. Confiner 2D로 카메라 범위 제한

11적 AI

11.0 적 애니메이션 설정

적 캐릭터도 플레이어와 동일한 방식으로 Animator Controller를 구성합니다.

ENEMY 적 캐릭터 미리보기
Enemy Idle Enemy Walk

보라색 뿔 달린 악마 캐릭터 — Idle(4F), Walk(6F), Attack(4F×2종), Hurt(4F), Die(5F)

적 애니메이션 클립 설정

클립명프레임 수Sample RateLoop
Enemy_Idle48✔ Yes
Enemy_Walk610✔ Yes
Enemy_Attack410✘ No
Enemy_Attack2410✘ No
Enemy_Hurt412✘ No
Enemy_Die58✘ No

적 Animator 파라미터

파라미터타입설명
SpeedFloat이동 속도
AttackTrigger공격 (랜덤으로 Attack1/Attack2 선택)
HurtTrigger피격
DieTrigger사망

11.1 EnemyPatrol.cs

using UnityEngine;

public class EnemyPatrol : MonoBehaviour
{
    [SerializeField] private float speed = 2f;
    [SerializeField] private Transform pointA, pointB;
    private SpriteRenderer sr;
    private Transform target;

    void Awake() { sr = GetComponent<SpriteRenderer>(); target = pointB; }

    void Update()
    {
        transform.position = Vector2.MoveTowards(
            transform.position, target.position, speed * Time.deltaTime);
        if (Vector2.Distance(transform.position, target.position) < 0.1f)
            target = (target == pointA) ? pointB : pointA;
        sr.flipX = (target.position.x < transform.position.x);
    }
}

11.2 EnemyHealth.cs

public class EnemyHealth : MonoBehaviour, IDamageable
{
    [SerializeField] private int maxHP = 3;
    private int currentHP;
    void Awake() => currentHP = maxHP;

    public void TakeDamage(int damage)
    {
        currentHP -= damage;
        StartCoroutine(FlashCoroutine());
        if (currentHP <= 0) Destroy(gameObject);
    }

    System.Collections.IEnumerator FlashCoroutine()
    {
        var sr = GetComponent<SpriteRenderer>();
        sr.color = Color.red;
        yield return new WaitForSeconds(0.1f);
        sr.color = Color.white;
    }
}
06

UI 및 게임 매니저

12HP 바 UI

12.1 Canvas 구성

  1. Hierarchy → UI → Canvas 생성
  2. Canvas Scaler → Scale With Screen Size → 1920×1080
  3. UI → Slider 추가 → HP바로 구성
  4. 배경 색상: 빨간색, Fill 색상: 초록색

12.2 HPBar.cs

using UnityEngine;
using UnityEngine.UI;

public class HPBar : MonoBehaviour
{
    [SerializeField] private Slider slider;
    [SerializeField] private PlayerHealth playerHealth;

    void Start()
    {
        slider.maxValue = 1; slider.value = 1;
        playerHealth.OnHPChanged += UpdateHP;
    }
    void UpdateHP(int cur, int max) => slider.value = (float)cur / max;
    void OnDestroy() => playerHealth.OnHPChanged -= UpdateHP;
}

13게임 매니저

13.1 GameManager.cs

using UnityEngine;
using UnityEngine.SceneManagement;

public class GameManager : MonoBehaviour
{
    public static GameManager Instance { get; private set; }
    [SerializeField] private GameObject gameOverPanel;
    [SerializeField] private PlayerHealth playerHealth;

    void Awake()
    {
        if (Instance == null) Instance = this;
        else Destroy(gameObject);
    }

    void Start()
    {
        playerHealth.OnDeath += HandleGameOver;
        gameOverPanel.SetActive(false);
    }

    void HandleGameOver()
    {
        gameOverPanel.SetActive(true);
        Time.timeScale = 0;
    }

    public void Restart()
    {
        Time.timeScale = 1;
        SceneManager.LoadScene(SceneManager.GetActiveScene().buildIndex);
    }
}
07

사운드 및 이펙트

14오디오 시스템

사운드트리거 시점비고
발걸음Walk 애니메이션 재생 중루프 재생
점프점프 입력 시1회 재생
착지isGrounded true 될 때1회 재생
공격 휘두르기Attack 트리거1회 재생
피격 효과음Hurt 트리거1회 재생
적 피격적 TakeDamage1회 재생
사망Die 트리거1회 재생
BGM게임 시작 시루프 재생

15파티클 이펙트

  • 착지 먼지 — 발 위치에 Particle System
  • 피격 이펙트 — 빨간 파티클 투박
  • 적 사망 — 폭발 파티클 + 아이템 드롭
  • 배경 패럴랙스 — 비할의 나무, 구름
08

빌드 및 배포

16빌드 설정

플랫폼핵심 설정
Windows/MacFile > Build Settings > PC → Build
WebGLCompression: Brotli, 메모리 제한 확인
AndroidPlayer Settings > Minimum API Level 24+
iOSXcode 프로젝트 생성 → 서명

17PhiMir Tools 활용

PhiMir Tools(phimirtools.com)를 활용하여 게임 기획부터 출시까지 AI 보조를 받을 수 있습니다.

  • Game Process Tool — 플랫포머 스토리 기획, 레벨 밸런스 설계
  • Unity Coding Supporter — 씬 구조 자동생성, 스프라이트 분석
  • Last Work — Steam/Google Play/App Store 스토어 페이지 자동 생성
PhiMir Tools 바로가기 →

18GitHub 공유

18.1 프로젝트 업로드

git init
git remote add origin https://github.com/YOUR_ID/PlatformerTutorial.git
git add .
git commit -m "Initial commit: Platformer Tutorial"
git branch -M main
git push -u origin main

18.2 Git LFS 설정

git lfs install
git lfs track "*.png"
git lfs track "*.psd"
git lfs track "*.wav"
git lfs track "*.mp3"
git lfs track "*.fbx"
git add .gitattributes
git commit -m "Add LFS tracking"
git push
⚠️ — GitHub Free는 LFS 저장소 1GB, 대역폭 1GB/월 제한. 팀 프로젝트라면 Pro 고려.
09

부록

부록 A: 전체 스크립트 목록

스크립트역할오브젝트
PlayerController.cs이동 + 점프 + 방향전환Player
PlayerCombat.cs공격 입력 + 히트 판정Player
PlayerHealth.csHP 관리 + 피격 + 사망Player
IDamageable.cs데미지 인터페이스(인터페이스)
EnemyPatrol.cs적 순찰 이동Enemy
EnemyHealth.cs적 HP + 사망Enemy
HPBar.csHP UI 업데이트Canvas/HPBar
GameManager.cs게임 상태 관리GameManager

부록 B: 조작키 설정

동작
← / → (A/D)좌우 이동
Space점프
좌클릭 / Z공격
Esc일시정지

부록 C: 확장 아이디어

  • 더블 점프 — 공중에서 1회 추가 점프
  • 벽타기 — 벽 충돌 시 반대 방향 점프
  • 대시 — 공중에서 빠르게 내려가기
  • 아이템 시스템 — 동전, 회복 아이템
  • 보스 전투 — 스테이지 끝 보스 전투
  • 세이브/로드 — PlayerPrefs 또는 JSON 저장
  • 모바일 조작 — 터치 버튼 UI 추가
💡 — PhiMir Tools의 Game Process Tool을 사용하면 이러한 확장 기능의 기획서를 AI와 함께 작성할 수 있습니다.