관리 메뉴

cyphen156

유니티 2D 스프라이트 애니메이션 만들기 본문

프로젝트/유니티

유니티 2D 스프라이트 애니메이션 만들기

cyphen156 2025. 2. 20. 12:56

※ 공부하는 도중에 작성된 글입니다. 처음부터 설명하는 내용이 아니니 적당히 걸러서 보세요.

 그냥 이런 흐름으로 애니메이션을 작업하는구나 정도로만 받아들여주세요※

유니티에서 2D 게임을 만들때 가장 중요한 작업들 중 하나가 애니메이션을 만드는 것이다.

먼저 여러 개의 스프라이트 이미지가 필요하다.

가장 먼저 설정할 것은 이미지들의 피벗과 스프라이트 모드를 설정하는 것이다. 만약 이 피벗 위치가 달라진다면 애니메이션의 재생 위치도 달라질 것이기 때문에 애니메이션을 구성하는 모든 스프라이트 들은 같은 피벗 위치와 모드를 가지고 있어야 한다.

피벗과 모드를 전부 설정했다면 다음과 같이 프로젝트 윈도우 아무데나 이미지들을 드래그 하면 유니티 엔진이 자동으로 애니메이션을 만들어 준다.

애니메이션 자동 생성 기능 사용하기

그리고 나서 플레이어에 애니메이터 컴포넌트를 추가하고 생성된 컨트롤러를 배치한다.

추가로 해야 할 작업은 Player_Run의 애니메이션의 1초당 재생 횟수를 설정하는 것이다. 취향에 따라 적절하게 조절한다면 뛰는 모션과 걷는 모션 등 다양한 연출을 할 수 있다.

그런데 이렇게 자동추가하면 만든 애니메이션마다 플레이어 컨트롤러가 계속해서 생긴다.

다음과 같이 애니메이션만 추가할 수 있다.

그리고 나서 플레이어가 가지고 있는 컴퍼넌트들의 프로퍼티를 애니메이션에 연결 할 수 있다. 

이것을 통해 플레이어가 죽었을 때 캐릭터가 사라지는 연출을 해보겠다.

다음에 할 작업은 만들어진 여러 애니메이션들을 캐릭터의 동작에 따라 변경되어 재생 될 수 있도록 연결해주는 작업이다.

Enum클래스로 자료형을 선언해주고

public enum ANIME_STATE
{
    Player_Idle,
    Player_Run,
    Player_Jump,
    Player_Clear,
    Player_GameOver
}

필요한 변수들을 선언해준다. 

Animator animator;
string currentAnim;
string previousAnim;

Rigidbody2D rb;

float axisH;
public float speed;
public float jumpForce;
public LayerMask layerMask;
bool isJump;
bool isGround;

public static string state = "Playing";

그리고 애니메이션 상태 변화를 위해 다음과 같이 코드로직을 작성한다.

Start()

animator = GetComponent<Animator>();
currentAnim = Enum.GetName(typeof(ANIME_STATE), 0);
previousAnim = currentAnim;

FixedUpdate()

if (isGround)
{
    // 땅 위에 있고
    if (axisH == 0)
    {
        // 멈춰 있는 경우
        currentAnim = Enum.GetName(typeof(ANIME_STATE), 0);
    }
    else
    {
        // 움직이고 있는 경우
        currentAnim = Enum.GetName(typeof(ANIME_STATE), 1);
    }
}
else
{
    // 공중에 있는 경우
    currentAnim = Enum.GetName(typeof(ANIME_STATE), 2);
}

if (currentAnim != previousAnim)
{
    previousAnim = currentAnim;

    animator.Play(currentAnim);
}

충돌로직

private void OnTriggerEnter2D(Collider2D collision)
{
    if (collision.gameObject.tag == "Goal")
    {
        Goal();
    }
    if (collision.gameObject.tag == "Dead")
    {
        GameOver();
    }
}

OnTriggerEnter2D()

    private void Goal()
    {
        animator.Play(Enum.GetName(typeof(ANIME_STATE), 3));
        state = "GameClear";
        Debug.Log("1");
        GameStop();
    }
    private void GameOver()
    {
        animator.Play(Enum.GetName(typeof(ANIME_STATE), 4));
        state = "GameOver";
        GameStop();
        GetComponent<CapsuleCollider2D>().enabled = false;
        rb.AddForce(new Vector2(0, 5), ForceMode2D.Impulse);
    }
    private void GameStop()
    {
        rb.linearVelocity = new Vector2(0, 0);
    }

Stage1의 최종 코드는 다음과 같다.

PlayerController.cs

using System;
using UnityEngine;

[RequireComponent(typeof(Rigidbody2D))] 
public class PlayerController : MonoBehaviour
{
    /// <summary>
    /// 플레이어 애니메이션 
    /// 0 : Player_Idle,
    /// 1 : Player_Run,
    /// 2 : Player_Jump,
    /// 3 : Player_Clear,
    /// 4 : Player_GameOver
    /// </summary>
    public enum ANIME_STATE
    {
        Player_Idle,
        Player_Run,
        Player_Jump,
        Player_Clear,
        Player_GameOver
    }
    Animator animator;
    string currentAnim;
    string previousAnim;

    Rigidbody2D rb;

    float axisH;
    public float speed;
    public float jumpForce;
    public LayerMask layerMask;
    bool isJump;
    bool isGround;


    public static string state = "Playing";
    private void Start()
    {
        rb = GetComponent<Rigidbody2D>();
        axisH = 0.0f;
        speed = 3.0f;
        jumpForce = 5.0f;
        isJump = false;
        isGround = true;
        state = "Playing";
        animator = GetComponent<Animator>();
        currentAnim = Enum.GetName(typeof(ANIME_STATE), 0);
        previousAnim = currentAnim;
    }

    private void Update()
    {
        if (state != "Playing")
        {
            return;
        }
        axisH = Input.GetAxisRaw("Horizontal"); //  수평이동

        // 이미지 플립
        if (axisH > 0.0f)
        {
            transform.localScale = new Vector2(1, 1);
        }
        else if (axisH < 0.0f)
        {
            transform.localScale = new Vector2(-1, 1);
        }
        if (Input.GetKeyDown(KeyCode.Space))
        {
            Jump();
        }
    }

    private void FixedUpdate()
    {
        if (state != "Playing")
        {
            return;
        }
        isGround = Physics2D.Linecast(transform.position, transform.position - (transform.up * 0.1f), layerMask);
        // 지정한 두 점을 연결하는 가상의 선에 게임 오브젝트가 접촉하는지?
        if (isGround || (axisH != 0))
        {
            rb.linearVelocity = new Vector2(speed * axisH, rb.linearVelocityY);
        }

        if (isGround && isJump)
        {
            // 플레이어 점프 수치만큼 벡터 설계
            Vector2 jumpPW = new Vector2(0, jumpForce);
            rb.AddForce(jumpPW, ForceMode2D.Impulse);
            isJump = false;
        }

        if (isGround)
        {
            // 땅 위에 있고
            if (axisH == 0)
            {
                // 멈춰 있는 경우
                currentAnim = Enum.GetName(typeof(ANIME_STATE), 0);
            }
            else
            {
                // 움직이고 있는 경우
                currentAnim = Enum.GetName(typeof(ANIME_STATE), 1);
            }
        }
        else
        {
            // 공중에 있는 경우
            currentAnim = Enum.GetName(typeof(ANIME_STATE), 2);
        }

        if (currentAnim != previousAnim)
        {
            previousAnim = currentAnim;

            animator.Play(currentAnim);
        }
    }
    private void Jump()
    {
        isJump = true;

        if (isJump)
        {

        }
    }

    private void OnTriggerEnter2D(Collider2D collision)
    {
        if (collision.gameObject.tag == "Goal")
        {
            Goal();
        }
        if (collision.gameObject.tag == "Dead")
        {
            GameOver();
        }
    }

    //private void ChangeAnim(string AnimName)
    //{
    //    animator.Play(Enum.GetName(typeof(ANIME_STATE), AnimName));
    //}

    private void Goal()
    {
        animator.Play(Enum.GetName(typeof(ANIME_STATE), 3));
        state = "GameClear";
        Debug.Log("1");
        GameStop();
    }
    private void GameOver()
    {
        animator.Play(Enum.GetName(typeof(ANIME_STATE), 4));
        state = "GameOver";
        GameStop();
        GetComponent<CapsuleCollider2D>().enabled = false;
        rb.AddForce(new Vector2(0, 5), ForceMode2D.Impulse);
    }
    private void GameStop()
    {
        rb.linearVelocity = new Vector2(0, 0);
    }
}

애셋은 다음 링크에 존재합니다.

https://drive.google.com/file/d/1xflptVnXd1ACAkABSRlKKNq8XkfsuW4y/view?usp=drive_link

 

UniSideGame_Assets.zip

 

drive.google.com

예제 링크는 다음 주소에 있습니다.

https://github.com/cyphen156/UnityBootCamp/tree/main/UnityBasic/25.02.20_2D_PlatFormemr_Game

 

UnityBootCamp/UnityBasic/25.02.20_2D_PlatFormemr_Game at main · cyphen156/UnityBootCamp

25.01.20 ~ 25.07.17. Contribute to cyphen156/UnityBootCamp development by creating an account on GitHub.

github.com