[Unit] Player character and movement > [Tutorial] Create Input Actions for player character movement

🔎 적 애니메이션 설정

  1. 적(Enemy) 애니메이터 설정

    • 계층 구조(Hierarchy)에서 ‘Enemy’ 선택.
    • 인스펙터(Inspector) 맨 아래 [Add Component] 클릭 → Animator 검색해서 추가.
    • 프로젝트(Project) 창 빈 곳에 마우스 우클릭 → Create > Animator Controller 클릭 (이름: Enemy).
    • 만든 Enemy 파일을 아까 만든 Animator 컴포넌트의 Controller 칸으로 드래그해서 넣기.
  2. 블렌드 트리(Blend Tree) 만들기

    • 상단 메뉴 Window > Animation > Animator 클릭 (창 열기).
    • 애니메이터 창 빈 공간에 마우스 우클릭 > Create State > From New Blend Tree 클릭.
    • 생성된 주황색 ‘Blend Tree’ 노드를 더블 클릭해서 안으로 입장.
    • 오른쪽 인스펙터에서 Blend Type을 2D Simple Directional로 변경.
    • Parameters 탭(왼쪽 상단)에서 [+] 버튼 눌러서 Move X, Move Y (Float) 두 개 만들기.
    • 인스펙터 중간의 Parameters 칸에서 첫 번째는 Move X, 두 번째는 Move Y 선택.

## ❓블렌드 트리가 무엇인가요?

  1. 핵심 작동 원리

    블렌드 트리는 우리가 설정한 좌표(Pos X, Pos Y)와 실제 게임의 입력값을 비교합니다.

    • 좌표 설정: EnemyLeft는 (-1, 0), EnemyUp은 (0, 1) 식으로 위치를 지정합니다.
    • 혼합(Blending): 입력값이 (-0.7, 0.7)처럼 대각선 방향이라면, 유니티는 왼쪽 걷기와 위쪽 걷기 애니메이션을 적절한 비율로 섞어서 보여줍니다.
  2. 블렌드 트리의 종류

    가장 자주 쓰이는 것은 다음 두 가지입니다.

    • 1D Blend: 변수 하나만 사용 (예: 이동 속도에 따라 대기 → 걷기 → 달리기)
    • 2D Simple Directional: 변수 두 개 사용 (예: 상하좌우 이동 방향에 따른 걷기)
  3. 애니메이션 클립 넣기 (Motion)

  • 인스펙터의 Motion 리스트 아래에 있는 [+] 버튼을 4번 눌러서 칸 만들기.
  • 각 칸의 ⊙(동그라미 버튼)을 눌러서 EnemyLeft, EnemyRight, EnemyUp, EnemyDown을 하나씩 선택.
  • 그 옆의 Pos X, Pos Y 숫자를 아래처럼 직접 타이핑해서 입력:

Left: -0.5, 0 / Right: 0.5, 0 / Down: 0, -0.5 / Up: 0, 0.5

  1. 적 스크립트
using UnityEngine;

public class EnemyController : MonoBehaviour
{
    // Public variables
    public float speed;
    public bool vertical;
    public float changeTime = 3.0f;

    // Private variables
    Rigidbody2D rigidbody2d;
    Animator animator;
    float timer;
    int direction = 1;

    // Start is called once before the first execution of Update after the MonoBehaviour is created

    void Start()
    {
        rigidbody2d = GetComponent<Rigidbody2D>();
        animator = GetComponent<Animator>();
        timer = changeTime;
    }

    // Update is called every frame
    void Update()
    {
        timer -= Time.deltaTime;

        if (timer < 0)
        {
            direction = -direction;
            timer = changeTime;
        }
    }

    // FixedUpdate has the same call rate as the physics system
    void FixedUpdate()
    {
        Vector2 position = rigidbody2d.position;

        if (vertical)
        {
            position.y = position.y + speed * direction * Time.deltaTime;
            animator.SetFloat("Move X", 0); // "Move X"라는 변수 값을 0으로 만듦
            animator.SetFloat("Move Y", direction);
        }
        else
        {
            position.x = position.x + speed * direction * Time.deltaTime;
            animator.SetFloat("Move X", direction);
            animator.SetFloat("Move Y", 0);
        }
        
        rigidbody2d.MovePosition(position);
    }

    void OnTriggerEnter2D(Collider2D other)
    {
        PlayerController player = other.gameObject.GetComponent<PlayerController>();

        if (player != null)
        {
            player.ChangeHealth(-1);
        }
    }
}

🔎 플레이어(Player) 설정 (State Machine)

  • Player 프리팹 열기.
  • 인스펙터에서 Animator 추가하고, 제공된 Player 컨트롤러 연결.
  • 애니메이터 창에서 ‘Moving’에서 ‘Idle’로 가는 화살표 클릭.
  • 인스펙터에서 Has Exit Time 체크 해제 (즉시 멈추게 하기).
  • 그 아래 Conditions의 [+] 버튼 클릭 → Speed / Less / 0.1로 설정.

    Has Exit Time (비활성화): “움직임이 멈추면(조건 충족) 현재 걷기 애니메이션이 끝날 때까지 기다리지 말고 즉시 대기(Idle) 상태로 넘어가라”는 뜻입니다. 조작감을 빠릿하게 만듭니다.

    Trigger (Hit, Launch): Float처럼 숫자가 아니라, “방금 맞았어!” 하고 스위치를 한 번 딸깍 누르는 것과 같습니다. 일회성 동작에 사용합니다.

플레이어 스크립트

using UnityEngine;
using UnityEngine.InputSystem;

public class PlayerController : MonoBehaviour
{
    // Variables related to player character movement
    public InputAction MoveAction;
    Rigidbody2D rigidbody2d;
    Vector2 move;
    public float speed = 3.0f;

    // Variables related to the health system
    public int maxHealth = 5;
    int currentHealth;
    // get: 데이터를 요청받았을 때 실행되는 '입구(함수)'
    // return: 그 입구로 들어온 사람에게 들려보낼 '결과물'
    public int health { get { return currentHealth; } }

    // Variables related to temporary invincibility 무적
    public float timeInvincible = 2.0f;
    bool isInvincible;
    float damageCooldown; // 무적 쿨타임

    // Variables related to animation
    Animator animator;
    Vector2 moveDirection = new Vector2(1, 0); // (X, Y)

    // Start is called once before the first execution of Update after the MonoBehaviour is created
    void Start()
    {
        MoveAction.Enable();
        rigidbody2d = GetComponent<Rigidbody2D>();
        animator = GetComponent<Animator>();
        currentHealth = maxHealth;
    }

    // Update is called once per frame
    void Update()
    {
        move = MoveAction.ReadValue<Vector2>();

        // 플레이어가 움직이고 있다면 (0이 아니라면), 부동소수점문제 해결 위해 approximately 사용
        if (!Mathf.Approximately(move.x, 0.0f) || !Mathf.Approximately(move.y, 0.0f))
        {
            moveDirection.Set(move.x, move.y); // 현재 방향을 기억
            moveDirection.Normalize(); // 길이 1로 정규화
        }

        animator.SetFloat("Look X", moveDirection.x);
        animator.SetFloat("Look Y", moveDirection.y);
        animator.SetFloat("Speed", move.magnitude);

        if (isInvincible)
        {
            damageCooldown -= Time.deltaTime;
            if (damageCooldown < 0)
            {
                isInvincible = false;
            }
        }
    }

    // FixedUpdate has the same call rate as the physics system
    void FixedUpdate()
    {
        Vector2 position = (Vector2)rigidbody2d.position + move * speed * Time.deltaTime;
        rigidbody2d.MovePosition(position);
    }

    // 외부에서 데미지를 주거나(amount가 음수), 힐을 줄 때(amount가 양수) 호출하는 함수
    public void ChangeHealth(int amount)
    {
        if (amount < 0) // 데미지 줄 때
        {
            if (isInvincible)
            {
                return;
            }
            isInvincible = true;
            damageCooldown = timeInvincible;
            animator.SetTrigger("Hit"); // Hit(피격) 애니메이션을 딱 한 번만 실행
        }

        /* Mathf.Clamp 설명
           현재 체력에 받은 양을 더하되, 그 결과가 0보다 작아지거나 maxHealth보다 커지지 않게 '고정'합니다.
           예: 체력이 5인데 힐을 100 받아도 최대치인 5로 유지됨! 
        */
        currentHealth = Mathf.Clamp(currentHealth + amount, 0, maxHealth);
        Debug.Log(currentHealth + "/" + maxHealth);
    }
}

📌 출처: [Unity Learn] 2D Adventure: Robot Repair