Published on

섹션 5: 타일바니아 2

#섹션 5: 타일바니아 - 2

Unity Prefab 시스템과 새로운 Input System을 활용한 플레이어 캐릭터 제어 구현 가이드입니다.

#📋 목차

  1. 유니티 프리팹(Prefab)
  2. Input System 설정
  3. 플레이어 움직임 구현
  4. 2D 물리 엔진 활용
  5. 캐릭터 애니메이션
  6. 점프 시스템
  7. 충돌 감지

#{ 유니티 프리팹(Prefab) }

#1. 프리팹(Prefab)이란?

**프리팹(Prefab)**은 재사용 가능한 템플릿입니다.

게임 오브젝트를 프리팹으로 만들면, 나중에 필요할 때 다시 꺼내 쓸 수 있습니다.

즉, 자주 사용하거나 반복적으로 생성할 오브젝트는 프리팹으로 만들어 두면 관리가 편합니다.

#💡 용어 정리

프리팹(Prefab)

		템플릿 원본, 하드 드라이브(Assets 폴더)에 저장됨

인스턴스(Instance)

		프리팹을 씬에 가져온 복제본(사용 버전)

#2. 왜 지금 프리팹을 배우는가?

이전까지는 프리팹이 필요하지 않았음.

이제 게임 콘텐츠를 만들기 시작하면서 프리팹이 중요해짐.

씬(Scene)이 여러 개일 때, 오브젝트를 다음 씬에서도 유지하려면 프리팹이 필요함.

#3. 실습: 원 만들기 & 복제하기

Hierarchy 패널에서 우클릭

2D Object > Sprites > Circle 선택 → 하얀 원 생성

원을 Ctrl + D 눌러 복제 (복제된 오브젝트는 이름 뒤에 (1), (2)가 붙음)

#4. 복제 후 수동 변경의 문제

원 하나를 초록색, 크기 확대 등 변경함

나머지도 똑같이 바꾸려면? → 일일이 클릭해서 또 변경해야 함 😣

오브젝트가 많아질수록 비효율적이고 실수 가능성 증가

#5. 프리팹 만들기

1. 변경한 원(Circle)을 Hierarchy에서 Project 패널로 드래그!

2. .prefab 파일 생성됨 → 이게 프리팹 원본!

3. 이제 드래그해서 프리팹을 언제든지 씬에 추가 가능함

✅ 확인 사항

		프리팹 인스턴스는 파란색 아이콘으로 표시됨

		Assets 폴더에 .prefab 파일 존재
Unity 게임 스크린샷

#6. 프리팹과 일반 오브젝트의 차이

프리팹은 여러 씬에서 재사용 가능

일반 오브젝트는 해당 씬 파일 안에만 저장됨
Unity 게임 스크린샷

#7. 인스턴스의 일괄 변경

인스턴스 하나의 색상을 변경 (예: 노란색)

상단 오른쪽 Overrides 클릭 → 변경된 요소가 표시됨

Apply All 클릭 → 모든 인스턴스에 동일 변경사항 적용됨
Unity 게임 스크린샷 Unity 게임 스크린샷

#8. 프리팹 직접 수정하기

Project 패널에서 프리팹을 더블 클릭하면 프리팹 자체를 편집 가능

편집 시 다른 오브젝트는 보이지 않고 프리팹 하나만 보임

우측 위 화살표 아이콘을 누르면 씬 뷰로 복귀
Unity 게임 스크린샷 Unity 게임 스크린샷
이런식으로 화살표를 누르고도 변경이 가능하다
Unity 게임 스크린샷
이렇게 바꾸고 돌아오면 모두 변해있다

#9. 특정 인스턴스만 다르게 설정하기

Unity 게임 스크린샷
예: Circle(3)만 보라색으로 바꿈

이후 프리팹에서 색을 바꿔도 → Circle(3)에는 적용되지 않음

💡 이유

		해당 인스턴스는 프리팹에서 "오버라이드(변형)"됨

		프리팹보다 우선순위가 높기 때문에 무시됨

#10. 오버라이드(Override) 관리

인스턴스에서 값이 바뀌면 해당 속성이 굵은 글씨로 표시됨

Overrides 메뉴에서 특정 속성만 선택해 Apply 가능

예: 색상은 적용, 크기는 적용 안 하기

#11. 인스턴스화(Instantiate)의 개념

씬에서 프리팹을 드래그하면 **인스턴스화(복제)**된 것

코드로도 인스턴스화 가능 → 나중에 배울 내용

#12. 프리팹의 장점 요약

재사용 가능

		여러 장면에서 동일한 오브젝트 사용 가능

일괄 관리

		원본만 수정해도 모든 인스턴스가 변경됨

효율성

		반복적인 수작업 없이 동일한 오브젝트 생성

인스턴스화

		코드로도 필요할 때 오브젝트 생성 가능

#{ 2D 캐릭터와 월드 충돌 처리 + 중력 설정 }

#1. 목표

캐릭터가 월드(타일 맵)에 충돌하고 떨어져 착지하도록 만든다.

중력을 적용하여 자연스럽게 땅에 착지한 채 넘어지지 않게 만든다.

#2. 프리팹 만들기 (Prefab)

프리팹(Prefab)은 유니티에서 반복 사용 가능한 오브젝트 원본입니다.

#프리팹을 만드는 이유

플레이어가 여러 레벨에서 동일하게 유지되려면 하나의 원본(프리팹)이 필요합니다.

수정이 필요할 때 프리팹만 수정하면 인스턴스에 적용됩니다.

#프리팹 만들기 순서

Assets 폴더에서 우클릭 → Create → Folder를 선택

폴더 이름을 Prefabs로 지정

Hierarchy(계층) 창에서 플레이어를 드래그하여 Prefabs 폴더 안으로 이동

이렇게 하면 플레이어 프리팹이 생성됩니다
Unity 게임 스크린샷

#3. 타일 맵 충돌 설정하기

타일맵(Tilemap)은 2D 게임의 배경, 땅, 벽 등을 그릴 때 사용되는 기능입니다.

#목표

플레이어가 땅에 닿고 멈출 수 있도록, 땅(타일맵)에 충돌(Collider)을 추가

#순서

1. Hierarchy 창에서 플랫폼 Tilemap을 선택

2. Inspector 창에서 [Add Component] 클릭 → Tilemap Collider 2D 추가

		이 컴포넌트는 타일 하나하나에 충돌 영역을 부여함

3. 다시 Add Component → Composite Collider 2D 추가

		여러 타일의 충돌을 하나로 묶어서 "틈" 문제를 해결

4. Composite Collider를 추가하면 Unity가 자동으로 Rigidbody 2D도 추가함

5. Rigidbody 2D의 Body Type을 ‘Static’으로 설정

		Static은 움직이지 않는 오브젝트 설정 (ex. 바닥, 벽 등)
Unity 게임 스크린샷 Unity 게임 스크린샷 Unity 게임 스크린샷 Unity 게임 스크린샷
하나로 된 충돌 영역이 생성

#

Tilemap Collider 2D의 옵션 중 **"Used By Composite"**를 체크하면 타일들이 하나의 충돌 덩어리로 묶입니다

Tilemap Renderer를 끄면 충돌 박스 시각화만 확인 가능

#4. 플레이어에 충돌과 중력 적용

#목표

플레이어에 충돌 설정 및 중력을 적용해 땅에 자연스럽게 떨어지고 착지하도록 만들기

#플레이어 충돌 설정

프리팹 플레이어 선택

[Add Component] 클릭 → Capsule Collider 2D 추가

		플레이어 캐릭터의 모양에 맞는 충돌 영역을 설정함

Edit Collider 클릭 → 크기 및 위치 조절

		충돌 영역이 캐릭터를 딱 맞게 감싸도록 조정
Unity 게임 스크린샷

#플레이어 중력 및 물리 적용

[Add Component] → Rigidbody 2D 추가

		중력 적용, 물리 반응 가능

		Body Type은 기본적으로 Dynamic (움직이는 객체용)

Constraints 항목 설정

		Freeze Rotation → Z 체크

		Z축 회전을 막아 몸이 구르거나 넘어지지 않게 설정
Unity 게임 스크린샷 Unity 게임 스크린샷

#{ 새로운 Input System 설정하기 }

#개념: Unity Input System이란?

Unity에는 입력을 처리하는 시스템이 2가지 있습니다.

기존(Input Manager): 오래된 방식. 키보드, 마우스 정도만 처리에 적합.

새로운(Input System): 현대식 입력 처리. 게임패드, 터치, 다양한 디바이스 지원.

#2. Input System 설치하기

1. Package Manager 열기

		Unity 상단 메뉴에서 Window > Package Manager 클릭

		Packages: Unity Registry로 설정

2. Input System 설치

		Input System 검색 → Install 클릭

		Unity가 "재시작할까요?" 묻는 경고창 뜸 → Yes

		Unity가 자동으로 재시작됨
Unity 게임 스크린샷

#3. Player 오브젝트에 Input 설정

1. Player 선택

		Hierarchy에서 Player 오브젝트 선택

2. Player Input 컴포넌트 추가

		Inspector에서 Add Component 클릭 → Player Input 추가
Unity 게임 스크린샷

#4. Input Actions 생성 및 저장

1. Create Actions 버튼 클릭

		Input Actions 에셋을 만들기 위한 버튼

		에셋이란? → Unity에서 설정을 파일로 저장하는 것

2. 저장할 폴더 생성

		새 폴더: Input System

		Input 설정 에셋(.inputactions)을 이 폴더에 저장
Unity 게임 스크린샷

#5. Input Action 설정

기본적으로 들어있는 Action

Move

Look

Fire

→ 여기서 우리는 Jump 액션을 추가할 거예요

#6. Jump 액션 추가

1. + 버튼으로 Jump 액션 추가

		이름: Jump

		Type: Button (눌렀다 뗐다만 판단)

2. Jump 키 지정 (키보드)

		Binding → Keyboard 선택

		Listen 클릭 → Space 키 누르기 → Space 등록됨

3. Jump 키 지정 (Gamepad)

		Binding 추가 → Gamepad 선택

		Listen 클릭 → Gamepad의 Button South (일반적으로 A 버튼) 선택
Unity 게임 스크린샷 Unity 게임 스크린샷

#7. 설정 저장 및 마무리

✅ 1. Input Actions 에셋 연결

		.inputactions 파일을 Player 오브젝트의 Player Input 컴포넌트에 드래그해서 연결.

		→ 이걸 연결해야 키보드/패드 입력을 받을 수 있어요.


✅ 2. Create Settings Asset

		Player Input 하단의 "Create Settings Asset" 버튼 클릭.

		→ 인풋 시스템의 전체 설정을 저장하는 전역 설정 파일을 만들어줘요.

		한 번만 만들면 프로젝트 전체에서 사용됨.
Unity 게임 스크린샷 Unity 게임 스크린샷 Unity 게임 스크린샷
인풋 세팅 생성 완료
Unity 게임 스크린샷

#8. PlayerMovement 스크립트 생성

1. Scripts 폴더 만들기

		Assets 우클릭 → Create > Folder → 이름은 Scripts

2. PlayerMovement.cs 생성

		Scripts 폴더 안에 C# 스크립트 생성

		이름: PlayerMovement

3. Player 오브젝트에 스크립트 연결

		Player 오브젝트에 PlayerMovement 스크립트 드래그
Unity 게임 스크린샷

#9. PlayerMovement 코드 작성

using System.Collections;
using System.Collections.Generic;
using UnityEngine.InputSystem; // 새로운 인풋 시스템을 사용하기 위한 네임스페이스
using UnityEngine;

public class PlayerMovement : MonoBehaviour
{
    // Vector2는 2차원 벡터를 표현하는 구조체예요.
    // X축(좌우)과 Y축(상하)의 입력값을 저장하는 데 사용됩니다.
    // 예: 왼쪽 화살표를 누르면 (-1, 0), 오른쪽은 (1, 0), 위는 (0, 1), 아래는 (0, -1)
    Vector2 moveInput;

    // 이 메서드는 Input System에서 "Move" 액션이 발생할 때 자동으로 호출됩니다.
    // 키보드나 게임패드로 입력이 들어오면 그 값이 InputValue로 전달돼요.
    void OnMove(InputValue value)
    {
        // 입력된 값을 Vector2 형식으로 변환해서 moveInput에 저장합니다.
        moveInput = value.Get<Vector2>();

        // 현재 입력된 벡터 값을 콘솔에 출력해서 입력이 잘 들어오는지 확인할 수 있어요.
        // 예: (1.0, 0.0) = 오른쪽 입력, (-1.0, 0.0) = 왼쪽 입력
        Debug.Log(moveInput);
    }
}

#{ 플레이어 움직임 구현 (하늘에서 떨어지고 좌우로 이동하기) }

#목표

플레이어가 중력의 영향을 받아 하늘에서 떨어지도록 만들기

키보드 입력에 따라 좌우로 이동할 수 있도록 만들기

#핵심 개념: `Rigidbody2D`

Rigidbody2D란?

Unity에서 물리 엔진을 적용하고 싶을 때 사용하는 컴포넌트입니다.

중력, 속도, 충돌 등을 다룰 수 있게 해주는 물리 속성 컨트롤러라고 생각하면 됩니다.

플레이어가 중력에 따라 떨어지고, 힘을 받아 움직이는 동작을 하려면 Rigidbody2D가 꼭 필요합니다!

#1단계: Rigidbody2D 참조 가져오기 (캐싱)

Rigidbody2D myRigidbody;
Rigidbody2D 타입의 변수를 하나 선언합니다.

변수 이름은 myRigidbody라고 했는데, rigidbody라고 하면 Unity 내부 예약어와 헷갈릴 수 있어요.
void Start()
{
    myRigidbody = GetComponent<Rigidbody2D>();
}
게임이 시작될 때(Start 함수), 현재 GameObject에서 Rigidbody2D 컴포넌트를 찾아서 myRigidbody에 저장합니다.

이 과정을 **"참조를 캐싱한다"**고 합니다. 계속 GetComponent를 호출하지 않아도 되니까 성능상 더 좋아요.

#2단계: 플레이어 입력 받기

void OnMove(InputValue value)
{
    moveInput = value.Get<Vector2>();
    Debug.Log(moveInput);
}
OnMove()는 Unity의 Input System에서 키 입력이 있을 때 자동으로 호출되는 함수입니다.

키보드나 게임패드의 입력값은 Vector2 (x, y) 형태로 들어옵니다.

예: A 키 → (-1, 0), D 키 → (1, 0)

moveInput은 플레이어가 누르는 이동 방향 입력을 저장하는 변수입니다.

#3단계: 좌우로 달리기 (Run 함수 만들기)

#왜 Run 함수가 필요할까?

매 프레임마다 플레이어의 움직임을 계산해서 적용해야 하기 때문입니다.
void Update()
{
    Run();
}
Unity의 Update() 함수는 매 프레임마다 자동으로 호출됩니다.

이 안에서 만든 Run() 함수가 계속 실행되어 플레이어를 이동시킵니다.

#Run 함수 코드 설명

void Run()
{
    Vector2 playerVelocity = new Vector2(moveInput.x * runSpeed, myRigidbody.velocity.y);
    myRigidbody.velocity = playerVelocity;
}

#🧠 개념 설명:

velocity는 Rigidbody2D의 속도입니다.

속도는 방향과 세기를 모두 갖는 Vector2 형태입니다.

moveInput.x는 -1(왼쪽), 0(가만히), 1(오른쪽) 값을 가집니다.

여기에 runSpeed를 곱하면 원하는 속도로 이동할 수 있습니다.

y는 현재 속도를 그대로 유지합니다. 이유는? 중력에 의해 떨어지는 속도는 유지하고 싶기 때문입니다.

#🎛️ 속도 조절

[SerializeField] float runSpeed = 10f;
runSpeed는 외부에서 수정할 수 있도록 SerializeField로 선언하고 초기값을 10f로 설정했습니다.

Inspector 창에서 직접 숫자를 조정하며 속도를 테스트할 수 있어요.
🚫 실수 방지 팁

Vector2 playerVelocity = new Vector2(1, 3); 이렇게 하려다 에러가 날 수 있어요.

반드시 new Vector2(x값, y값)처럼 두 개의 숫자를 정확히 지정해줘야 해요.

#최종 흐름 정리

Rigidbody2D 참조 가져오기

		myRigidbody = GetComponent<Rigidbody2D>();

키보드 입력 받아 저장

		moveInput = value.Get<Vector2>();

입력에 따라 좌우 속도 적용

		Vector2 playerVelocity = new Vector2(moveInput.x * runSpeed, myRigidbody.velocity.y);

		myRigidbody.velocity = playerVelocity;

#{ 플레이어가 이동 방향에 따라 캐릭터 스프라이트 뒤집기 }

#목표

플레이어가 오른쪽으로 이동하면 오른쪽을 보고, 왼쪽으로 이동하면 왼쪽을 보는 캐릭터를 만들기

#캐릭터를 뒤집는 개념 소개

캐릭터는 오른쪽을 향한 기본 모습을 가지고 있어요.

왼쪽으로 움직이면 스프라이트를 반대로 뒤집어야 해요.

이때 localScale의 x값을 +1 또는 -1로 바꿔주면 캐릭터가 반대 방향을 보게 돼요.
오른쪽 → x = +1 (기본)

왼쪽 → x = -1 (반전)

#이동 방향에 따라 뒤집기

#수학 개념 사용:

이동 입력 값은 -1 ~ +1 사이예요:

		왼쪽 입력: -1

		오른쪽 입력: +1

이를 속도와 곱하면 velocity.x 값이 나와요:

		왼쪽 이동 → 음수

		오른쪽 이동 → 양수

그 방향을 알아내기 위해 Mathf.Sign() 함수를 사용:

		음수 → -1

		양수 → +1

		0 → 1 (Unity에서는 0을 양수로 취급)

#Unity 코드 설명

transform.localScale = new Vector2(Mathf.Sign(myRigidbody.velocity.x), 1f);
Mathf.Sign()을 통해 x방향 속도의 부호를 가져옴

transform.localScale의 x에 적용 → 왼쪽은 -1, 오른쪽은 +1

y축은 그대로 1f 유지 (세로 크기 변경 없음)

#속도가 0일 땐 안 뒤집도록 하기

#문제 발생:

정지 상태에서도 velocity.x = 0인데도 Mathf.Sign(0)이 +1로 처리돼서 스프라이트가 다시 오른쪽으로 뒤집힘.

#해결 방법:

캐릭터가 실제로 이동 중일 때만 방향을 바꾸도록 조건을 추가
bool playerHasHorizontalSpeed = Mathf.Abs(myRigidbody.velocity.x) > Mathf.Epsilon;
Mathf.Abs() : 절댓값 (음수 → 양수로 변환)

Mathf.Epsilon : 아주 작은 값 (0보다 큰 최소 부동소수점 값)

		이걸로 "속도가 0보다 아주 조금 클 때만" 판단

#조건문:

if (playerHasHorizontalSpeed)
{
    transform.localScale = new Vector2(Mathf.Sign(myRigidbody.velocity.x), 1f);
}

#전체 코드 흐름

void Update()
{
    Run();
    FlipSprite();  // 방향 바꾸기 로직 실행
}

void Run() {
    // 입력 방향에 따라 속도 적용
    Vector2 playerVelocity = new Vector2(moveInput.x * runSpeed, myRigidbody.velocity.y);
    myRigidbody.velocity = playerVelocity;
}

void FlipSprite()
{
    // 속도가 아주 작더라도 0이 아니면 방향 반영
    bool playerHasHorizontalSpeed = Mathf.Abs(myRigidbody.velocity.x) > Mathf.Epsilon;

    if (playerHasHorizontalSpeed)
    {
        // velocity.x가 양수 → 오른쪽, 음수 → 왼쪽
        transform.localScale = new Vector2(Mathf.Sign(myRigidbody.velocity.x), 1f);
    }
}

#Mathf.Sign(x)

"이 값이 양수인지 음수인지를 알려줘!"

x가 양수면 → 1 반환

x가 음수면 → -1 반환

x가 0이면 → 0 반환

어떤 값의 방향만 필요할 때 사용해요.

예: 캐릭터가 왼쪽으로 달리면 -1, 오른쪽이면 1을 얻어서 방향을 정할 수 있어요.
Mathf.Sign(5);   // 결과: 1
Mathf.Sign(-3);  // 결과: -1
Mathf.Sign(0);   // 결과: 0

#Mathf.Abs(x)

"이 값의 절댓값(부호를 빼고 크기만)을 알려줘!"

x가 양수 → 그대로

x가 음수 → 양수로 바꿈

값이 음수든 양수든 상관없이 "얼마나 빠르게 움직이는가?" 같은 속도의 크기를 알고 싶을 때 사용해요.
Mathf.Abs(5);    // 결과: 5
Mathf.Abs(-3);   // 결과: 3
Mathf.Abs(0);    // 결과: 0

#Mathf.Epsilon

"0보다 약간 더 큰, 매우 작은 수"

Mathf.Epsilon은 유니티가 정의한 가장 작은 실수값이에요 (거의 0처럼 보일 만큼 작음).

0을 직접 비교하는 건 위험할 수 있어요. 대신 Epsilon을 사용해서 "정말 0에 가깝다"는 걸 체크해요.

속도 == 0 으로 비교하면 작동이 이상할 수 있어요 (컴퓨터의 부동소수점 처리 방식 때문에).

그래서 속도가 Epsilon보다 크다로 체크하면 더 안전하게 정지 상태를 확인할 수 있어요.
if (Mathf.Abs(속도) > Mathf.Epsilon)
{
    // 움직이고 있다
}

#transform.localScale

"이 오브젝트의 크기와 방향을 바꿔줘!"

transform.localScale은 오브젝트의 크기(scale) 를 x, y, z 방향으로 나타내요.

x값이 양수 → 오른쪽 보기

x값이 음수 → 왼쪽 보기

y나 z는 일반적으로 스프라이트에서는 잘 안 바꿔요.

플레이어가 왼쪽/오른쪽으로 달릴 때 모습도 해당 방향으로 바꿔야 할 때 사용해요.
transform.localScale = new Vector2(1, 1);    // 오른쪽 보기
transform.localScale = new Vector2(-1, 1);   // 왼쪽 보기 (좌우 반전)

#Rigidbody2D.velocity.x

"지금 이 오브젝트가 x축(좌우)으로 얼마나 빠르게 움직이고 있는가?"

velocity는 오브젝트의 현재 속도를 나타내는 속성이에요.

.x는 그 중 가로 방향(왼쪽/오른쪽) 속도를 의미해요.

양수 → 오른쪽으로 이동 중

음수 → 왼쪽으로 이동 중

0 → 정지 상태
float speed = myRigidbody.velocity.x;

#{ 캐릭터 애니메이션 상태 전환 }

#1. **애니메이터(Animator) 창 확인**

Unity 게임 스크린샷
애니메이터는 애니메이션 상태(state) 간 전환을 시각적으로 보여주는 상태 머신입니다.

예: Idle 상태, Run 상태, Jump 상태 등 다양한 상태들이 존재할 수 있으며,

이들 간 전환은 **조건(conditions)**에 따라 일어납니다.

#2. **기본 상태: Idle**

플레이 버튼을 누르면 기본 상태인 Idle 애니메이션이 실행됩니다.

캐릭터는 특별한 입력 없이 가만히 서 있는 상태를 반복 애니메이션으로 보여줍니다.

#3. **애니메이션 전환 조건 설정**

상태 전환을 위해서 조건이 필요합니다. 예: isRunning이 true일 경우 Run 애니메이션으로 전환

이 조건은 Animator 창에서 정의한 파라미터 이름과 일치해야 합니다.

		예: Animator 창에 isRunning이라는 bool 파라미터가 있어야 합니다.

#4. **Animator 참조 캐싱하기**

애니메이터를 사용하려면 Animator 컴포넌트의 참조가 필요합니다.

Start 메서드에서 한 번만 가져와서 사용하는 것이 좋습니다 (이걸 캐싱이라 부름).

같은 방식으로 Rigidbody도 참조해 사용 중입니다.
Animator myAnimator;

void Start()
{
    myAnimator = GetComponent<Animator>();
}

#5. **애니메이션 실행 위치 선정**

캐릭터가 달리는지 판단하는 메서드는 Run().

따라서 애니메이션의 전환도 Run 메서드 안에서 수행합니다.

#6. **실제 애니메이션 전환 구현**

bool playerHasHorizontalSpeed = Mathf.Abs(myRigidbody.velocity.x) > Mathf.Epsilon;
myAnimator.SetBool("Running", playerHasHorizontalSpeed);
Mathf.Abs는 속도의 절댓값을 계산하고, Mathf.Epsilon은 거의 0에 가까운 작은 값.

움직임이 있을 경우 Running 파라미터를 true로 설정 → 애니메이션 전환 발생

움직임이 없을 경우 false → Idle 상태로 돌아감

( SetBool	Animator 파라미터를 제어해 상태 전환 유도 )

#7. **불리언 파라미터 이름 주의**

애니메이터 파라미터 이름("Running")은 문자열이기 때문에 오타에 취약합니다.

Animator 창에서 복사해서 붙여넣는 게 좋습니다.

		혹은 Animator.StringToHash("Running")처럼 해시로 쓰면 성능상 유리

#8. **스프라이트 뒤집기 로직**

void FlipSprite()
{
    bool playerHasHorizontalSpeed = Mathf.Abs(myRigidbody.velocity.x) > Mathf.Epsilon;

    if (playerHasHorizontalSpeed)
    {
        transform.localScale = new Vector2(Mathf.Sign(myRigidbody.velocity.x), 1f);
    }
}
좌우 이동 방향에 따라 캐릭터의 x축 스케일을 반전

이 조건도 애니메이션 전환 조건과 동일하게 사용할 수 있음

#최종 완성된 흐름

1. Start에서 myAnimator를 캐싱한다.

2. OnMove로 입력을 받아 moveInput에 저장한다.

3. Update에서 Run()과 FlipSprite()를 호출한다.

4. Run()에서:

		Rigidbody에 속도를 적용

		이동 속도 기반으로 playerHasHorizontalSpeed를 계산

		myAnimator.SetBool("Running", playerHasHorizontalSpeed)로 애니메이션 상태 전환

5. FlipSprite()에서 캐릭터 방향을 설정

#{ 캐릭터의 점프 기능 }

#1. 점프 동작 개요 및 개념 설명

점프 애니메이션은 필수가 아님: 달리기 애니메이션을 그대로 사용해도 무방합니다.

중요한 것은 기능 구현: 캐릭터가 점프할 수 있어야 하며,

이 기능이 제대로 작동하도록 물리 엔진과 입력 시스템을 연동해야 합니다.

#2. Unity 입력 시스템과 점프 처리

#점프 동작 생성 및 입력 설정

Unity의 New Input System에서는 Player Input 컴포넌트에서 Jump라는 액션을 생성하고,

이를 키보드의 스페이스바(Space) 또는 게임패드의 **South 버튼(A)**에 매핑합니다.

해당 입력이 발생했을 때 호출될 함수 OnJump()를 스크립트에 생성합니다.

#`OnJump` 메서드 구성

void OnJump(InputValue value)
{
    if(value.isPressed) {
        myRigidbody.velocity += new Vector2(0f, jumpSpeed);
    }
}
InputValue.isPressed를 통해 버튼이 눌렸는지 확인합니다.

Rigidbody2D의 velocity에 Y축 방향으로 점프 속도(jumpSpeed) 를 더해 캐릭터를 위로 점프시킵니다.

#3. 점프 관련 코드 전체 흐름 요약

[SerializeField] float jumpSpeed = 10f;
점프 속도는 인스펙터에서 조절 가능하도록 SerializeField로 선언.

점프는 프레임마다 실행할 필요가 없고, 입력이 들어올 때 한 번만 처리하면 됩니다.

#4. 중력과 점프 속도 튜닝 방법

#두 가지 중력 설정 방법

1. 프로젝트 전체 중력 설정:

		Edit > Project Settings > Physics 2D > Gravity

		전체 게임의 중력을 변경합니다.

2. 개별 객체의 중력 조절:

		Rigidbody2D의 Gravity Scale 속성을 변경하여 플레이어만 따로 중력 조절이 가능합니다.
Unity 게임 스크린샷

#튜닝 목표

플레이어가 3개의 타일 높이를 점프해서 넘을 수 있어야 함.

점프 속도와 중력 값을 실험하면서 적절한 조합을 찾는 것이 목표입니다.

#5. 충돌 처리 문제 해결

점프 속도가 빠를수록 충돌 처리 실패가 발생할 수 있음.

Rigidbody2D의 Collision Detection을 Discreet → Continuous로 변경해야 빠른 속도에서도 충돌이 정확히 감지됩니다.
Unity 게임 스크린샷

#개념 요약

InputValue

		입력 시스템에서 버튼/축 등의 값을 읽어오는 구조체

isPressed

		버튼이 눌렸는지를 확인하는 속성

velocity

		물리적으로 물체의 현재 속도 벡터

new Vector2(0f, jumpSpeed)

		수직 방향으로 속도를 더하는 벡터

gravityScale

		해당 오브젝트에만 적용되는 중력 배율

continuous

		빠른 움직임에도 정확하게 충돌을 감지하는 모드

#{ 플레이어가 공중에서 계속 점프하는 것을 방지 }

플레이어가 공중에서 계속 점프하는 것을 방지하고, 바닥에 닿았을 때만 점프할 수 있도록 제한하는 기능을 구현하는 내용을 다룹니다.

이 과정에서 **레이어(Layer)**와 충돌 검사(Collider2D),

그리고 컬링 마스크(Culling Mask), LayerMask.GetMask(), Collider2D.IsTouchingLayers() 등의 개념을 설명합니다.

#목표

플레이어가 공중에서 계속 점프하지 못하게 제한.

바닥(Ground) 레이어에 닿아 있을 때만 점프 가능하게 만들기.

#개념 정리

#1. **레이어(Layer)**

유니티에서는 레이어를 사용해 객체들을 구분하고, 카메라나 물리 시스템에서 필터링할 수 있습니다.

이미 정렬 레이어(Sorting Layer)를 사용해 본 적이 있으며,

이번에는 **충돌 레이어(Collision Layer)**로 사용합니다.

#2. **정렬 레이어 vs 물리 레이어**

정렬 레이어: 렌더링 순서를 결정 (앞/뒤 배치).

물리 레이어: 충돌, 카메라 필터링 등에 사용.

#3. **컬링 마스크(Culling Mask)**

카메라가 어떤 레이어를 보여줄지/숨길지 결정하는 마스크.

예: 배경만 보이게 설정 가능.
Unity 게임 스크린샷

#4. **충돌 매트릭스 (Collision Matrix)**

메뉴: Edit > Project Settings > Physics 2D

어떤 레이어가 어떤 레이어와 충돌할지를 설정.

예: 적(Enemy)과 바닥(Ground)이 충돌하도록 설정해야 함.
Unity 게임 스크린샷

#5. **Collider2D.IsTouchingLayers()**

현재 콜라이더가 특정 레이어 마스크에 접촉 중인지 여부 반환 (bool).
myCapsuleCollider.IsTouchingLayers(LayerMask.GetMask("Ground"))

#6. **LayerMask.GetMask()**

문자열로 된 레이어 이름을 받아 **레이어 마스크(int)**로 변환.
LayerMask.GetMask("Ground")

#구현 순서 정리

#1. **새 레이어 생성**

이름: Ground (대문자 G 사용한 예)

플랫폼(Tilemap)의 레이어를 Ground로 설정
Unity 게임 스크린샷 Unity 게임 스크린샷 Unity 게임 스크린샷

#2. **플레이어에 콜라이더 컴포넌트 확인**

CapsuleCollider2D 사용

Start()에서 컴포넌트 가져오기
myCapsuleCollider = GetComponent<CapsuleCollider2D>();

#3. **점프 제한 로직 추가**

OnJump() 안에서 공중인지 체크 후 리턴 처리
void OnJump(InputValue value)
{
    if (!myCapsuleCollider.IsTouchingLayers(LayerMask.GetMask("Ground")))
    {
        return;
    }
    if(value.isPressed) {
        myRigidbody.velocity += new Vector2(0f, jumpSpeed);
    }
}

#4. **충돌 매트릭스 확인**

Ground 레이어가 Default, Player 등과 충돌하도록 설정되어 있는지 확인.

#{ 사다리 클라이밍 기능 구현 }

#1. 사다리 타일맵 설정 – "사다리를 배치할 기반 만들기”

#1.1 새로운 타일맵 생성

Unity의 Hierarchy 창에서 우클릭 → [2D Object] → [Tilemap] → [Rectangle]을 선택하여 새로운 타일맵을 생성합니다.

이 타일맵의 이름을 ClimbingTilemap으로 지정합니다.
개념: Unity 2D에서는 Tilemap을 사용하여 반복되는 구조(땅, 벽, 사다리 등)를 효율적으로 구성합니다.

사다리도 일종의 "지형"이므로 타일로 다룰 수 있습니다.

왜 필요한가?

사다리를 눈에 보이게 배치하고, 나중에 사다리에 닿았는지 판단하려면, Tilemap으로 분리해 관리해야 합니다.
Unity 게임 스크린샷

#1.2 레이어 및 정렬 설정

ClimbingTilemap의 Sorting Layer를 Climbing으로 설정합니다.

Tilemap Renderer의 Sorting Layer도 동일하게 Climbing으로 설정하여 렌더링 순서를 조정합니다.
개념: Sorting Layer는 2D에서 어떤 오브젝트가 앞/뒤에 그려질지를 결정합니다.

왜 필요한가?

캐릭터보다 사다리가 뒤에 그려지게 하려면 적절한 레이어 설정이 필요합니다. 그렇지 않으면 사다리가 플레이어를 가릴 수 있습니다.

#1.3 사다리 타일 추가

사다리 스프라이트를 타일 팔레트에 추가하고, 이를 ClimbingTilemap에 배치합니다.

사다리 타일의 이름을 SPA_Ladder로 지정합니다.
개념: 타일 팔레트에서 사다리 이미지를 등록하고, 원하는 위치에 타일로 찍어넣습니다.

왜 필요한가?

게임에서 플레이어가 오를 수 있는 구역을 직관적으로 배치할 수 있어 개발이 효율적입니다.
Unity 게임 스크린샷 Unity 게임 스크린샷

#2. 충돌 설정

#2.1 콜라이더 추가

ClimbingTilemap에 Tilemap Collider 2D와 Composite Collider 2D를 추가합니다.

Tilemap Collider 2D의 Used by Composite 옵션을 활성화하여 Composite Collider 2D와 함께 작동하도록 설정합니다.
개념: Unity에서 Collider는 물리 충돌을 감지하는 구성 요소입니다. 사다리가 존재하는 위치를 감지하려면 콜라이더가 있어야 합니다.

왜 필요한가?

플레이어가 사다리와 닿았는지 알아야 사다리 동작을 활성화할 수 있습니다.

Tilemap Collider는 각 타일마다 콜라이더를 생성하고,

Composite Collider는 그 여러 콜라이더를 하나로 묶어 최적화합니다.
Unity 게임 스크린샷

#2.2 리지드바디 설정

Unity 게임 스크린샷
ClimbingTilemap에 Rigidbody2D를 추가하고, Body Type을 Static으로 설정하여 움직이지 않도록 합니다.

#2.3 트리거 설정

Unity 게임 스크린샷
Tilemap Collider 2D의 Is Trigger 옵션을 활성화하여

플레이어가 사다리와 충돌할 때 물리적인 반응이 아닌 트리거 이벤트가 발생하도록 합니다.
개념: Is Trigger를 켜면 물리 충돌은 무시되고, 대신 이벤트를 발생시킬 수 있습니다 (닿았는지 여부만 판단).

왜 필요한가?

사다리는 플레이어를 막는 것이 아니라, "접촉 여부"만 판단하기 때문에 트리거로 설정합니다.

#3. 사다리 충돌 영역 조정

사다리 스프라이트의 콜라이더 모양을 조정하여 플레이어가 사다리 중앙에 위치하도록 합니다.

이를 위해 스프라이트 에디터에서 Custom Physics Shape을 사용하여 콜라이더의 모양을 수정합니다.
Unity 게임 스크린샷 Unity 게임 스크린샷 Unity 게임 스크린샷
이때 주의해야할게 sprite에디터 탭이 아니라 피직스 탭으로 전환해서 범위를 지정해주고

다시 팔레트에 추가해야 적용된다.

#4. 플레이어 이동 스크립트 수정

#4.1 변수 선언

[SerializeField] float climbSpeed = 5f;
Vector2 moveInput;
Rigidbody2D myRigidbody;
Animator myAnimator;
CapsuleCollider2D myCapsuleCollider;

#4.2 입력 처리

void OnMove(InputValue value)
{
    moveInput = value.Get<Vector2>();
}

#4.3 사다리 오르기 메서드

void ClimbLadder()
{
    // 1. 만약 플레이어의 캡슐 콜라이더가 "Climbing" 레이어(= 사다리)에 닿지 않았다면
    //    즉, 사다리에 닿지 않은 상태라면 climbing 동작을 실행하지 않음
    if (!myCapsuleCollider.IsTouchingLayers(LayerMask.GetMask("Climbing")))
    {
        // climbing 동작을 종료하고 빠져나감
        return;
    }

    // 2. 사다리에 닿아 있는 경우 → 수직 방향으로 움직일 수 있게 속도 지정
    //    현재 x축 속도는 유지하고, y축 속도는 "입력값 * 사다리 속도"로 변경함
    //    moveInput.y는 위(1), 아래(-1), 입력 없음(0)의 값을 가짐
    Vector2 climbVelocity = new Vector2(myRigidbody.velocity.x, moveInput.y * climbSpeed);

    // 3. Rigidbody2D의 속도에 climbVelocity를 할당하여 실제로 움직이게 함
    //    이로 인해 플레이어가 위/아래로 이동하게 됨
    myRigidbody.velocity = climbVelocity;
}

#4.4 Update 메서드에 추가

void Update()
{
    Run();
    FlipSprite();
    ClimbLadder();
}

#{ 사다리를 오를 때 중력의 영향을 받지 않게 하기 }

#기본 문제 인식

문제점:

		플레이어가 사다리 위에 있을 때 중력 때문에 천천히 아래로 미끄러지는 현상이 발생합니다.

원하는 행동:

		플레이어가 사다리 위에 있을 때는 가만히 머무르도록 만들고 싶습니다.

		즉, 중력의 영향을 받지 않도록 해야 합니다.

#해결 아이디어: 중력 조절하기

사다리 위에 있을 때만 중력 스케일을 0으로 설정.

사다리에서 내려오면 원래의 중력 스케일로 되돌리기.

#코드 구현 순서와 개념 설명

#1. **필요한 변수 선언**

float gravityScaleAtStart;
이 변수는 게임이 시작될 때 플레이어의 기본 중력 스케일을 저장해 둡니다.

#2. **Start()에서 초기 중력 값 저장**

void Start()
{
    myRigidbody = GetComponent<Rigidbody2D>();
    ...
    gravityScaleAtStart = myRigidbody.gravityScale;
}
게임이 시작될 때 한 번만 실행됨.

현재 Rigidbody2D.gravityScale 값을 저장합니다.

예: 기본값이 8이라면 gravityScaleAtStart = 8

#3. **ClimbLadder()에서 사다리 감지 및 중력 설정**

void ClimbLadder()
{
    if (!myCapsuleCollider.IsTouchingLayers(LayerMask.GetMask("Climbing")))
    {
        myRigidbody.gravityScale = gravityScaleAtStart;
        return;
    }

    Vector2 climbVelocity = new Vector2(myRigidbody.velocity.x, moveInput.y * climbSpeed);
    myRigidbody.velocity = climbVelocity;
    myRigidbody.gravityScale = 0f;
}
IsTouchingLayers("Climbing") → 현재 플레이어가 사다리 레이어에 닿아 있는지 확인.

닿아 있지 않으면:

		gravityScale을 원래 값(gravityScaleAtStart)으로 복원.

		return으로 함수 종료.

닿아 있으면:

		수직 속도(y)를 조절해 사다리를 위/아래로 이동.

		gravityScale = 0f로 설정해 중력 제거 → 더 이상 아래로 떨어지지 않음.

#{ `Climbing` 애니메이션을 재생 }

플레이어가 사다리에 오르거나 내릴 때는 Climbing 애니메이션을 재생하고,

가만히 멈춰 있거나, 사다리를 벗어나면 애니메이션을 멈추도록 구현하는 것입니다.

#1. 🎬 기본 애니메이션 준비

애니메이션 컨트롤러에는 다음과 같은 상태(state)가 있다고 가정합니다:

		Idle: 아무것도 안 하고 있을 때

		Running: 달릴 때

		Climbing: 사다리를 오르거나 내릴 때

애니메이터 파라미터(Parameters)에는 다음과 같은 불리언(Boolean)이 있어야 합니다:

		Running (bool)

		Climbing (bool)

이제 스크립트에서 이 애니메이션 상태를 조건에 따라 전환할 수 있도록 설정할 거예요.

#2. 🧠 개념 이해: Rigidbody, 중력, 이동 속도

🔧 Rigidbody

		Unity의 물리 엔진을 사용하는 오브젝트에 붙는 컴포넌트.

		velocity: 오브젝트의 현재 속도를 의미함 (x는 좌우, y는 위아래).

🔽 중력(Gravity)

		일반적으로 캐릭터는 아래로 떨어지는데 (gravityScale > 0),

		사다리에서 오를 때는 떨어지지 않아야 하므로 중력을 0으로 꺼야 함.

#3. 🧍‍♂️ 움직임 처리 - `Run()`

void Run()
{
    Vector2 playerVelocity = new Vector2(moveInput.x * runSpeed, myRigidbody.velocity.y);
    myRigidbody.velocity = playerVelocity;

    bool playerHasHorizontalSpeed = Mathf.Abs(myRigidbody.velocity.x) > Mathf.Epsilon;
    myAnimator.SetBool("Running", playerHasHorizontalSpeed);
}
moveInput.x는 좌우 키 입력.

velocity.x 값에 따라 달리는지 판단해 "Running" 애니메이션을 켜거나 끔.

#4. 🧗 사다리 오르기 처리 - `ClimbLadder()`

#4-1. **사다리를 감지하기**

if (!myCapsuleCollider.IsTouchingLayers(LayerMask.GetMask("Climbing")))
{
    myRigidbody.gravityScale = gravityScaleAtStart;
    myAnimator.SetBool("Climbing", false);
    return;
}
myCapsuleCollider.IsTouchingLayers(...): 플레이어가 특정 레이어(여기선 "Climbing")와 닿았는지 확인.

닿지 않았다면:

		중력을 원래대로 돌리고

		"Climbing" 애니메이션 끄고

		함수 끝냄 (return)

#4-2. **사다리에 닿았을 때 처리**

Vector2 climbVelocity = new Vector2(myRigidbody.velocity.x, moveInput.y * climbSpeed);
myRigidbody.velocity = climbVelocity;
myRigidbody.gravityScale = 0f;
입력 값의 y 방향(moveInput.y)에 따라 위아래 속도를 설정.

중력을 0으로 설정하여 떨어지지 않도록 함.

#5. 🎥 애니메이션 제어: 올라갈 때만 Climbing 상태로

bool playerHasVerticalSpeed = Mathf.Abs(myRigidbody.velocity.y) > Mathf.Epsilon;
myAnimator.SetBool("Climbing", playerHasVerticalSpeed);
y속도가 0보다 크면 (즉, 위아래로 움직이고 있다면) true

그렇지 않으면 false → 즉 움직일 때만 등반 애니메이션이 재생됨

✅ 정리하면:

		움직이면 Climbing 애니메이션 ON

		가만히 멈춰있으면 Climbing 애니메이션 OFF (Idle 상태 유지)