본 포스팅은 패스트캠퍼스 환급 챌린지 참여를 위해 작성하였습니다.
https://fastcampus.info/4n8ztzq
Part 2. 디아블로 게임을 만들어 보며 마스터하는 시스템 구현과 그래픽
Ch 01. 플레이어 캐릭터 제작기
03. NavMeshAgent를 이용한 캐릭터 제작기
Step 1. NavMesh 사용하는 이유
NavMeshAgent는 유니티 내부의 길찾기 시스템을 사용할 수 있게 해준다.
이 방식은 장애물을 설정하고, 캐릭터끼리도 피할 수 있고 길찾기의 우선순위로 비용 개선을 할 수 있습니다.
클릭 앤 무브에 적합한 방식
NavMeshAgent를 이용한 Character GmaeObject 구성
구성 Component들 :
- Capsule Mesh
- CharacterController Component
- GameObject의 이동 / 회전 로직 포함
- Capsule Collider 를 포함함
- NavMeshAgent
- 길찾기 시스템을 이용하기 위한 Component
- AgentControllerCharacter.cs
- 사용자 입력 및 이동 처리
Step 2. NavMesh / NavMeshObstacle 빌드하기

window → AI → Navigation 을 통해 Navigation 탭을 열고 CharacterController와 동일하게 설정


object 로 들어간 후, Mesh Renderers 를 가진 오브젝트만 Hierachy 창을 통해 확인할 수 있음. 그 중 빌드를 할 오브젝트를 선택 & 옵션 설정
Navigation Static :
OffMeshLinks : 점프라던지 순간이동 아니면 다른 행동을 설정할 수 있다고함.
Navigation Area : 걸을 수 있는 영역인지 설정 (jump가 있던데 뭘까? 우리가 custom 해서 만든건데, 기본으로 있는건줄 알았습니다.)
Unity의 Navigation 시스템에서 "Area" 설정은 AI(NavMeshAgent)의 이동 가능 여부와 이동 비용을 제어하는 데 사용되죠.
여기서 말하는 "Jump" 같은 **커스텀 네비게이션 영역(Navigation Area)**은 특별한 이동 동작(ex. 점프, 수영, 기어가기 등)을 구현하기 위한 논리적 구분이에요.
✅ Navigation Area란?
- NavMesh에서 영역을 정의하고 이름을 붙여 AI가 어떻게 반응할지를 설정하는 시스템
- 기본 제공:
- Walkable (기본 걷기 가능)
- Not Walkable (이동 불가)
- Jump는 사용자가 만든 커스텀 Area일 가능성이 큽니다.
✅ "Jump" 영역이란 무엇인가?
Unity 기본 내비게이션 시스템에는 "Jump"라는 기능이 내장되어 있지 않아요.
따라서 아래 중 하나에 해당합니다:
1. 🔧 사용자가 만든 커스텀 Navigation Area
Navigation → Areas 탭에서 커스텀으로 추가한 영역일 가능성이 큽니다:
Window → AI → Navigation → Areas
여기서:
- Jump라는 이름의 Area를 추가하고
- 특정 NavMesh Surface에서 이 Area를 가진 메시를 설정한 것
이렇게 되면, AI는 Jump 영역을 지나갈 수는 있지만 이동 비용이 더 크다고 인식합니다.
🟡 단점:
NavMeshAgent는 점프 동작을 자동으로 처리하지 않습니다.
→ 직접 구현해줘야 합니다 (아래 참고)
✅ 그럼 실제로 AI가 "점프"하게 하려면?
Unity NavMeshAgent는 기본적으로 평면 이동만 가능하고, 점프는 지원하지 않습니다.
그래서 "Jump 영역"은 다음 조건부 판단을 위한 표시 역할만 합니다.
예시: 점프 지점 구현 흐름
- NavMesh에서 특정 영역을 Jump로 설정
- NavMeshAgent가 이 영역을 지나려고 하면
- 커스텀 로직으로 감지:
NavMeshHit hit;
if (NavMesh.SamplePosition(transform.position, out hit, 1.0f, NavMesh.AllAreas))
{
int areaMask = 1 << NavMesh.GetAreaFromName("Jump");
if ((hit.mask & areaMask) != 0)
{
Debug.Log("Jump 영역 진입");
PerformJump();
}
}
4. PerformJump()에서 점프 애니메이션 또는 리지드바디 점프 구현
✅ 비용(Cost) 설정은 왜 하나요?
Area마다 Cost를 다르게 주면 AI는 덜 선호하는 경로로 인식합니다.
| Walkable | 1.0 | 정상 이동 |
| Mud | 3.0 | 이동은 가능하지만 느림 (우회 가능하면 우회함) |
| Jump | 10.0 | 이동 가능하지만 "특수한 행동 필요"함 |
✅ 요약
| 질문 | 답변 |
| "Jump" 영역은 뭐야? | 커스텀 Navigation Area일 가능성이 큼 (직접 추가된 이름) |
| AI가 진짜 점프하나? | 기본 NavMeshAgent는 점프 못함. 직접 처리해야 함 |
| 어떻게 써? | "이 영역을 통과하려면 점프해야 한다"는 논리적 트리거로 사용 |
| 어디서 설정? | Navigation > Areas 탭에서 이름과 비용 지정 가능 |
✅ 보너스: 유사 시스템
Unity에선 아래 도구를 써야 실제 점프 등 고급 동작 처리가 가능합니다:
| 시스템 | 설명 |
| NavMesh OffMeshLink | 특정 지점 사이를 연결해 점프, 낙하 등 구현 |
| NavMesh Modifier Volume | 지역마다 다른 Area 자동 지정 |
| Root Motion & 애니메이션 이벤트 | 점프 등 복잡한 동작 시 사용 |
Bake에 들어간 후, Bake

Off Mesh Links 같은 속성도 궁금할 수 있으나,추후에 환경에 관한 강의에서 Navigation mesh 에 대해서 좀 더 자세하게 다룰 예정.
이전 시간에 만들었던, Player Character에 Nav Mesh Agent 컴포넌트 추가.

🔷 Unity 인스펙터에 표시되는 NavMeshAgent 속성 설명
💡 버전에 따라 약간의 차이가 있지만, 대부분의 항목은 동일합니다.
### ✅ Agent Size
항목 설명| Radius | 에이전트의 반지름입니다. 좁은 통로를 지나갈 수 있을지, 장애물을 얼마나 피해갈지 결정할 때 사용됩니다. |
| Height | NavMeshAgent의 높이입니다. 너무 낮으면 장애물을 통과하지 못하고, 너무 높으면 불필요하게 경로가 제한될 수 있습니다. |
| Base Offset | 에이전트의 기준 위치가 지면에서 얼마나 떨어져 있는지 설정합니다. 일반적으로 캐릭터 모델의 중심이 바닥에서 어느 정도 위에 위치할 때 이 값을 조절합니다. |
✅ Steering
항목 설명| Speed | 에이전트의 최대 이동 속도입니다. 단위는 m/s(미터/초). 걷기 속도 = 1 |
| Angular Speed | 회전 속도입니다. 캐릭터가 방향을 바꿀 때 얼마나 빠르게 회전하는지를 결정합니다. 단위는 도(degree)/초입니다. |
| Acceleration | 가속도입니다. 정지 상태에서 얼마나 빠르게 목표 속도까지 도달할지를 결정합니다. 단위는 m/s². |
| Stopping Distance | 목적지에 얼마나 가까이 도달하면 정지할지 설정합니다. 이 값을 너무 작게 하면 목표를 향해 왔다 갔다할 수 있습니다. |
✅ Obstacle Avoidance
항목 설명| Auto Braking | 목적지 근처에 다가갈 때 자동으로 감속할지를 설정합니다. 연속된 경로 이동에서는 꺼두는 게 좋습니다. |
| Obstacle Avoidance Type | 장애물 회피 알고리즘의 품질을 설정합니다.옵션: None, LowQuality, MediumQuality, GoodQuality, HighQuality.높을수록 더 자연스러운 회피를 하지만 CPU 비용이 커집니다. |
| Avoidance Priority | 다른 에이전트와의 충돌 회피 우선순위 (0~99). 숫자가 낮을수록 회피 대상이 되고, 숫자가 높을수록 회피를 덜 함. |
✅ Pathfinding
항목 설명| Auto Traverse Off Mesh Link | 점프, 클라임 등 OffMeshLink를 자동으로 통과할지 여부입니다. |
| Auto Repath | 경로가 막혔을 때 자동으로 새로운 경로를 계산할지 설정합니다. NavMesh가 변경되는 환경에서는 true로 두는 게 좋습니다. |
✅ Advanced
항목 설명| Area Mask | 에이전트가 이동 가능한 NavMesh Area를 마스크(비트 필터)로 지정합니다. 예를 들어 Walkable, Not Walkable, Jump 등으로 구분됩니다. |
🔍 인스펙터에 보이지 않지만 스크립트에서 자주 쓰는 속성
이 항목들은 Inspector에서는 안 보이지만, 코드에서 많이 사용됩니다:
- destination (목표 위치)
- velocity, remainingDistance, hasPath, pathStatus
- nextPosition, steeringTarget
💡 팁: NavMeshAgent 튜닝할 때 주의할 점
항목 체크포인트| Stopping Distance | 너무 작으면 목적지 근처에서 에이전트가 계속 움직일 수 있음 |
| Auto Braking | 여러 목적지를 순차적으로 이동할 경우 꺼두는 게 자연스럽습니다 |
| Avoidance Priority | 여러 에이전트가 몰릴 경우 충돌 방지를 위해 값 분산 필요 |
| Base Offset | 모델의 pivot이 발에 없을 경우 조절 필요 (ex. 공중에 떠 보일 수 있음) |
Step 3. NavMeshAgentCharacter.cs 구현하기

클릭 앤 무브를 사용하고, NavMeshAgent의 이동 수치를 사용할 것이기 때문에 위의 부분은 이제 필요가 없음.

네비 메시 에이전트의 이동 시스템을 사용하지 않고 캐릭터 컨트롤러의 이동 시스템을 사용할 것이기 때문 에이전트의 업데이트 포지션을 false 로 지정
회전값은 네브메시 에이전트에 있는 것을 사용할 것이기 때문에 true 로 설정
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AI;
public class AgentControllerCharacterTest : MonoBehaviour
{
#region Variables
private CharacterController characterController;
private NavMeshAgent agent;
private Camera camera;
private bool isGrounded = false;
public LayerMask groundLayerMask;
public float groundCheckDistance = 0.2f;
public float dashDistance = 5f;
private Vector3 calcVelocity = Vector3.zero;
#endregion
#region Unity Methods
// Start is called before the first frame update
void Start()
{
characterController = GetComponent<CharacterController>();
agent = GetComponent<NavMeshAgent>();
agent.updatePosition = false;
agent.updateRotation = true;
camera = Camera.main;
}
// Update is called once per frame
void Update()
{
// Process mouse left button input
if (Input.GetMouseButtonDown(0))
{
// Make ray from screen to world
Ray ray = camera.ScreenPointToRay(Input.mousePosition);
// Check hit from ray
RaycastHit hit;
if (Physics.Raycast(ray, out hit, 100, groundLayerMask))
{
Debug.Log("We hit" + hit.collider.name + " " + hit.point);
// Move our character to what we hit
agent.SetDestination(hit.point);
}
}
// 이 부분이 굳이 필요한가? 어차피 LateUpdate 에서 위치를 맞춰주는데?
// => 하지만 CharacterController.Move()는 단순한 위치 이동이 아니라 충돌 처리 포함
if (agent.remainingDistance > agent.stoppingDistance)
{
characterController.Move(agent.velocity * Time.deltaTime);
}
else
{
characterController.Move(Vector3.zero);
}
}
private void LateUpdate()
{
transform.position = agent.nextPosition;
}
#endregion Unity Methods
}
주의할 점 : 강의에서 강사가 if 문의 위치를 잘못 넣음.
Update 문의 characterController.Move 를 사용하는 if 문의 위치를 밖으로 빼둬야함.
난 이 코드를 처음 볼 때, 어 characterContorller.Move 는 사실 상 위치 이동에 대해서는 관여를 하지 않는다고 생각함. 그래서 쓸모없는 코드가 아닌가? 라는 생각을 했는데 chatgpt 에 질문을 하니,
해당 코드는 물리적 충돌 및 상태 판정을 위해 꼭 필요한 코드였음을 알 수 있었습니다. (접은 글은 Chatgpt 답변)
그 의문은 충분히 합리적입니다. 결론부터 말하면:
🔹 **그 코드(CharacterController.Move(agent.velocity * Time.deltaTime))는 "쓸모없어 보이지만, 실제로는 꼭 필요한 경우가 많다"**고 판단됩니다.
🎯 핵심 요점 요약
항목 요약 설명| LateUpdate()에서 위치를 agent.nextPosition으로 맞추고 있음 | ✔ 캐릭터와 NavMeshAgent의 위치를 동기화하기 위한 부분 |
| 하지만 CharacterController.Move()는 단순한 위치 이동이 아니라 충돌 처리 포함 | ✔ 즉, 이동을 하지 않으면 지면 판정, 슬로프 이동, 중력 적용 등이 모두 동작하지 않음 |
| 그래서 Update에서 Move()로 실제 물리적 이동을 처리한 후, LateUpdate에서 보정하는 방식 | ✔ 에이전트 경로 추적 + 물리 이동을 동시에 성립시키기 위한 구조 |
🔍 왜 CharacterController.Move()가 필요할까?
1. CharacterController.Move()는 단순 위치 변경이 아님
- 내부적으로 충돌 처리, 경사로 처리, 바닥 감지 등이 모두 포함되어 있습니다.
- transform.position = agent.nextPosition은 물리 처리를 무시한 단순 이동입니다.
- → 충돌 X, 슬로프 감지 X, 지면 체크 X
2. NavMeshAgent는 이동 처리만 하지만, 물리 처리는 못 함
- NavMeshAgent는 경로 계산과 목표점 이동만 담당.
- 이동 결과로 얻는 agent.velocity는 방향 + 속도 벡터지만, 직접 캐릭터를 움직이지 않음.
- 그래서 그 이동값을 CharacterController.Move()에 넘겨줘야 실제로 충돌 반응, 경사 반응, 중력 반응 등이 작동.
3. LateUpdate는 위치 보정용
- NavMeshAgent.updatePosition = false로 했기 때문에, NavMeshAgent는 직접 transform을 이동시키지 않음.
- 대신 agent.nextPosition을 계산해주는데,
- transform.position = agent.nextPosition으로 보정하지 않으면 NavMeshAgent와 캐릭터가 점점 어긋남
🎯 그래서 두 코드의 역할은 다름
위치 코드 목적| Update() | characterController.Move(...) | 실제 물리 이동 처리 (충돌, 슬로프, 중력 등 포함) |
| LateUpdate() | transform.position = agent.nextPosition | NavMeshAgent와 캐릭터 위치를 일치시키기 위한 보정 |
❗ 네가 느낀 "쓸모없음"의 이유는?
맞아요. 언뜻 보면 LateUpdate에서 위치를 덮어쓰기 때문에, Update의 Move()가 무효화되는 것처럼 보입니다.
하지만 중요한 건:
- Move()는 내부적으로 충돌 계산, 지면 체크 등을 수행해서 그 결과를 CharacterController에 전달합니다.
- 그래서 isGrounded 같은 판정이나, 슬로프 감지 같은 것도 정상 작동하려면 Move()가 꼭 필요합니다.
✅ 결론
if (agent.remainingDistance > agent.stoppingDistance)
{
characterController.Move(agent.velocity * Time.deltaTime);
}
else
{
characterController.Move(Vector3.zero);
}
이 코드는 단순히 위치 이동이 아니라,
물리적 충돌 및 상태 판정을 위해 꼭 필요한 코드입니다.
단순 위치 보정(LateUpdate)만으로는 캐릭터컨트롤러의 기능이 동작하지 않아요.
필요하다면, 이 구조를 NavMeshAgent 없이 CharacterController만으로 재작성하거나,
NavMeshAgent만으로 이동 처리하는 구조로도 비교해서 설명드릴 수 있어요.
04. 캐릭터 모델과 애니메이션 설정하기
이번 시간에는 무료로 사용할 수 있는 Mixamo 사이트에서 캐릭터 모델하고 애니메이션드릉ㄹ 다운로드 받아서 설정해보도록 하겠습니다.
Mixamo 사이트 같은 경우, 회원 가입을 하면 무료로 다양한 캐릭터 모델과 애니메이션들을 사용할 수 있습니다.
프로토타입을 만들 때 아주 유용합니다.
Mixamo
www.mixamo.com
Idle과 Walk 애니메이션을 다운로드 받아서, Animator 상태 머신을 구성해보고,
이를 가지고 플레이어 캐릭터와 연결을 해보겠습니다.
또 간단하게 애니메이션 스테이트 머신 behavior 를 구성하여 애니메이터의 구성을 확장해 보도록 하겠습니다.
Step 1. Mixamo 에서 모델 / 애니메이션 가져오기

Format : FBX for Unity 로 다운받기
이렇게 다운받은 에셋을 가지고 유니티에서 어떻게 세팅하는지 알아보겠습니다.
Unity에 Import 한 모델의 인스펙터 설정을 확인해보자. (기본 모델인 ybot 부터 살펴보자)




Rig 탭에서는 Animation Type을 Humanoid 로 설정.
휴머노이드는 3D Max 에서 사람 형태의 캐릭터 모델을 구성하는 뼈대로 설저오디어 있는 것들을 가져오는 것입니다.
(다만, 사람의 형태가 아닌 것들은 Generic 으로 설정해서 사용한다고 합니다)
Skin Weight 같은 경우는 캐릭터 모델이 애니메이션을 할 때 각 버텍스가 주변의 몇 개의 뼈들에 영향을 받을 것이냐를 결정. (주변에 영향을 받는 뼈들의 수가 많아질수록, 부하도 커질 것입니다) (예전같은 경우, 이런 Skin wieght의 부하 때문에 모바일 같은 경우는 두 개의 본 정도로 설정하는 경우도 꽤 많았습니다, 하지만 요새는 4개를 많이 사용하는 추세이다. 컴퓨터 성능이 좋아졌기 때문에…)
Animation 탭에서는 기본 모델로 사용할 것이기 때문에 import Animation을 꺼준다
Unity에서 3D 모델의 Import Setting에 대해서 알아보기
좋아요! Mixamo에서 가져온 3D 캐릭터 모델을 Unity에 임포트하면, Inspector 창에서 Model, Rig, Animation, Materials 네 개의 탭이 보이는데, 이 각각이 모델의 다른 속성들을 제어하는 영역이에요.
2D 프로젝트만 해오셨다면 익숙하지 않을 수 있지만, 각 탭은 논리적으로 잘 나눠져 있고, 특히 Rig와 Animation 탭은 애니메이션 시스템에서 매우 중요한 부분이에요.
🔹 전반적인 탭 개요
| 탭 이름 | 역할 요약 |
| Model | 모델의 기본 구조, 크기, 메시 처리 방식 등 설정 |
| Rig | 본(뼈대) 구조 및 애니메이션의 유형 설정 (Humanoid 등) |
| Animation | 애니메이션 클립 가져오기, 압축, 에러 허용 설정 |
| Materials | 모델에 할당된 머티리얼(재질) 관리 및 교체 설정 |
✅ 각 탭 자세히 설명
🔷 1. Model 탭
3D 모델 자체의 속성을 다루는 곳입니다.
주요 속성 설명:
| 항목 | 설명 |
| Scale Factor | 모델의 기본 크기 배율. Mixamo는 1로 두면 적당함. |
| Convert Units | cm → m 자동 변환. Mixamo는 1cm 단위라 체크 필요 (기본 체크됨). |
| Import Cameras/Lights | FBX에 포함된 카메라/조명을 Unity에 가져올지 여부. 보통 꺼둡니다. |
| Preserve Hierarchy | 원본 모델의 계층 구조를 유지할지 여부. 보통은 해제. |
| Mesh Compression | 메시 데이터 압축. 낮을수록 정확하고, 높을수록 성능 향상 (손실 있음). |
| Read/Write | 메시를 스크립트에서 읽거나 수정하려면 켜야 함. 안 쓸 땐 꺼두세요. |
| Optimize Mesh | 메시 최적화. 일반적으로 Everything으로 두면 성능에 좋음. |
| Generate Colliders | 자동으로 콜라이더 생성. 보통 애니메이션 모델엔 꺼두는 게 좋음. |
✅ 개발자가 자주 건드는 항목: Scale Factor, Read/Write, Mesh Compression, Generate Colliders
🔷 2. Rig 탭
애니메이션을 사용할 수 있도록 본 구조를 정의하는 부분입니다.
주요 속성 설명:
| 항목 | 설명 |
| Animation Type | 애니메이션 시스템 타입 설정Generic: 기계, 동물 등✅ Humanoid: 사람형 (Mixamo는 무조건 이걸로!) |
| Avatar Definition | 본 구조(Avatar)를 어디서 생성할지 선택Create From This Model: 이 모델 자체로 생성 |
| Skin Weights | 한 버텍스가 영향을 받는 본 개수Standard(4 Bones)가 대부분의 경우 적당 |
| Strip Bones | 사용하지 않는 본을 제거하여 최적화 |
| Optimize Game Objects | 최적화를 위해 트랜스폼을 숨김. 디버깅 중엔 꺼두고, 릴리즈할 땐 켜도 됨. |
✅ 가장 중요한 항목: Animation Type = Humanoid 설정 (Humanoid 애니메이션을 공유하려면 반드시 필요)
🔷 3. Animation 탭
FBX 내에 포함된 애니메이션 클립을 어떻게 가져올지 설정하는 부분입니다.
주요 속성 설명:
| 항목 | 설명 |
| Import Animation | 애니메이션 가져오기 여부 |
| Resample Curves | 키프레임을 일정하게 리샘플링. 대부분 켜져 있어야 부드러운 애니메이션 처리 가능 |
| Anim. Compression | 애니메이션 데이터 압축 정도Optimal: 일반적으로 가장 좋은 선택 |
| Rotation / Position / Scale Error | 압축 허용 오차. 낮을수록 품질 좋고, 높을수록 성능 향상 |
| Clips | 가져온 애니메이션 클립 목록. 시작/끝 시간 설정 가능 |
✅ 개발자가 자주 조절하는 항목: Import Animation, Compression, Clips 범위 설정
🔷 4. Materials 탭
- *모델에 적용된 머티리얼(재질)**을 어떻게 Unity에 적용할지 설정하는 곳입니다.
주요 속성 설명:
| 항목 | 설명 |
| Material Creation Mode | 머티리얼을 어떻게 처리할지 결정Import via Material Description 또는 None 등 |
| Location | 머티리얼 저장 위치Use Embedded Materials: FBX 안에 포함된 것을 사용 |
| Extract Materials | FBX 내부 머티리얼을 Unity 프로젝트 폴더로 추출 |
| Remapped Materials | Unity에서 해당 머티리얼을 수동으로 교체하고 싶을 때 사용 |
✅ 개발자가 손대는 부분: Extract Materials, 머티리얼 교체
💡 Mixamo 캐릭터를 Unity에 잘 가져오려면?
| 항목 | 설정 |
| Model 탭 | Scale Factor = 1, Convert Units 체크, Read/Write는 필요시만 |
| Rig 탭 | ✅ Animation Type = Humanoid, Avatar = Create From This Model |
| Animation 탭 | Import Animation 체크, Clips에서 애니메이션 잘라 쓰기 가능 |
| Materials 탭 | Extract Materials 후, Unity 머티리얼로 교체 가능 |
✨ 실전 팁
- Humanoid로 설정하면 Mixamo에서 받은 다양한 사람형 애니메이션들을 공유해서 재사용할 수 있어요.
- Clips를 잘라서 Idle, Run, Attack 등의 클립으로 만들면 Animator에서 쉽게 활용 가능.
- FBX에 포함된 머티리얼은 대개 추출해서 Unity에서 수정하는 게 더 좋습니다.
그럼 이제 Idle & Walk 애니메이션 같은 경우는,
Model 탭 : 기본 모델하고 돌일하게 설정 (기본값)
Rig 탭 : Animation Tpye - Humanoid / Avatar Definition : Copy From Other Avatar 로 설정

Animaiton 탭 :
import Animation 활성화 & loop 활성화

Model 탭 :
기본 모델하고 돌일하게 설정 (기본값)
이렇게 설정을 바친 뒤, YBot 모델을 Scene 에 드레그 & 드랍
그리고 Idle 애니메이션을 해당 오브젝트에 드래그 & 드랍

그러면 위 사진과 같이 Animation Controller 가 생성이 됩니다.
(직접 생성하고 Idle Animation을 Animator 에서 추가해도 됩니다 )
Step 2. Animator 구성하기
Step 3. Animator 확장하기
저희는 Idle 애니메이션을 3가지 정도를 랜덤하게 플레이 할 것임.

이런 Idle Animation들을 따로 처리하기 위해 Sub Station을 생성.
해당 SubStation Machine에 Idle 애니메이션 추가 후, Transition 연결 & 설정.
RandomIdle 파라미터 추가

이제 애니메이션이 RandomIdle에 따라서 Idle Animation을 재생하는 것을 자동으로 하기 위해서,
애니메이터 state machine behavior를 추가하겠습니다

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class IdleRandomStateMachineBehaviorEx : StateMachineBehaviour
{
#region Variables
public int numberOfStates = 3; // 애니메이션 개수
public float minNormTime = 0f; // 최소 플레이 시간
public float maxNormTime = 5f; // 최대 플레이 시간
public float randomNormalTime; // 계산을 위한 변수
readonly int hashRandomIdle = Animator.StringToHash("RandomIdle");
#endregion Variables
// OnStateEnter is called when a transition starts and the state machine starts to evaluate this state
override public void OnStateEnter(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
{
// Readonly decide a time at which to transition.
randomNormalTime = Random.Range(minNormTime, maxNormTime);
}
// OnStateUpdate is called on each Update frame between OnStateEnter and OnStateExit callbacks
override public void OnStateUpdate(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
{
// if transitioning away from this state reset the random idle parameter to -1.
if (animator.IsInTransition(0) && animator.GetCurrentAnimatorStateInfo(0).fullPathHash == stateInfo.fullPathHash)
{
animator.SetInteger(hashRandomIdle, -1);
}
// if the state is beyond the randomly decide normalised time and noy yet transitioning
if (stateInfo.normalizedTime > randomNormalTime && !animator.IsInTransition(0))
{
animator.SetInteger(hashRandomIdle, Random.Range(0, numberOfStates));
}
}
// OnStateExit is called when a transition ends and the state machine finishes evaluating this state
//override public void OnStateExit(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
//{
//
//}
// OnStateMove is called right after Animator.OnAnimatorMove()
//override public void OnStateMove(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
//{
// // Implement code that processes and affects root motion
//}
// OnStateIK is called right after Animator.OnAnimatorIK()
//override public void OnStateIK(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
//{
// // Implement code that sets up animation IK (inverse kinematics)
//}
}
파라미터로 설정한 RandomIdle에 접근하기 위해서 string hash 값을 추가해주겠습니다.
string 값을 그대로 사용해도 되지만, 이 string 값을 그대로 사용하게 되면 string 을 비교하는 연산이 일어남.
이 string 비교 연산과 int 형 비교 연산에 대한 비용은 int형 연산 비용이 훨씬 작기 때문에, 되도록이면dms string hash 값을 사용하면 좋습니다.
string hash 사용 이유에 대해서 자세히 알아보자. (chatgpt 답변)
Unity의 Animator 파라미터 최적화에서 굉장히 중요한 부분입니다.
핵심적으로 요약하면:
✅ Animator.StringToHash("파라미터 이름")을 써서 int 해시값으로 접근하면, 성능 면에서 유리하고 안전한 방식입니다.
아래에서 왜 그렇게 하는지, 어떻게 작동하는지, 그리고 실전에서의 이점까지 차근차근 설명드릴게요.
✅ 1. 왜 string 대신 int hash를 쓰는가?
🎯 이유: 성능 + 안정성
항목 설명
| 항목 | 설명 |
| string 비교 | Animator.SetTrigger("RandomIdle")처럼 문자열로 접근하면 런타임에서 매번 문자열 해시 연산 및 비교가 발생합니다. |
| int 비교 | Animator.SetTrigger(hashRandomIdle)처럼 미리 계산된 정수 해시값을 쓰면, 런타임에서 단순한 int 비교만 하므로 더 빠릅니다. |
| 오타 방지 | 문자열은 오타나 이름 변경 시 컴파일러가 잡지 못함. 해시를 미리 정의해두면 컴파일 타임 오류로 예방 가능 (리팩토링에 유리). |
✅ 2. Animator.StringToHash()는 무엇을 하나요?
Animator.StringToHash("RandomIdle")는 주어진 문자열(파라미터 이름)을 Unity 내부에서 사용하는 정수 해시값으로 바꿔줍니다.
- 이 값은 Animator 컨트롤러 내부에서 문자열로 파라미터를 찾을 때 쓰는 키입니다.
- 일종의 ID 값으로 보면 됩니다.
- 한 번만 계산해두면 런타임에서 반복 계산하지 않아도 돼서 효율적이에요.
✅ 3. 사용 예시 코드
csharp
복사편집
public class CharacterAnimator : MonoBehaviour
{
private Animator animator;
// 해시 값을 readonly로 선언해두기
private readonly int hashRandomIdle = Animator.StringToHash("RandomIdle");
void Start()
{
animator = GetComponent<Animator>();
}
void PlayRandomIdle()
{
animator.SetTrigger(hashRandomIdle); // 정수 해시 값으로 접근
}
}
이렇게 하면 "RandomIdle"이라는 문자열을 매번 비교하지 않고, 정수 ID로 빠르게 접근할 수 있습니다.
✅ 4. 자주 사용하는 Animator 메서드에 해시를 적용할 수 있는 것들
| Animator 메서드 | 문자열 버전 | 해시 버전 |
| SetTrigger() | SetTrigger("Jump") | SetTrigger(hashJump) |
| ResetTrigger() | ResetTrigger("Jump") | ResetTrigger(hashJump) |
| SetBool() | SetBool("IsWalking", true) | SetBool(hashIsWalking, true) |
| SetInteger() | SetInteger("State", 2) | SetInteger(hashState, 2) |
| SetFloat() | SetFloat("Speed", 1.5f) | SetFloat(hashSpeed, 1.5f) |
| GetBool() 등도 동일하게 사용 가능 |
✅ 5. 성능 차이 정말 클까?
- 일반적인 게임에서는 큰 차이가 안 느껴질 수 있어요.
- 하지만 Animator 파라미터를 자주 변경하는 경우 (프레임마다 호출), 문자열 접근은 GC 할당 + 연산 비용이 발생합니다.
- 특히 모바일이나 저사양 환경, 또는 수십~수백 개의 캐릭터가 동시에 애니메이션을 사용하는 경우에는 누적 비용이 커집니다.
✅ 6. Tip: 해시 관리 잘하는 법
코드가 커질수록 해시 값이 많아질 수 있는데, 아래처럼 정리해두면 유지보수가 좋아요:
csharp
복사편집
public static class AnimatorHashes
{
public static readonly int RandomIdle = Animator.StringToHash("RandomIdle");
public static readonly int IsWalking = Animator.StringToHash("IsWalking");
public static readonly int Speed = Animator.StringToHash("Speed");
// 등등...
}
사용 시:
csharp
복사편집
animator.SetTrigger(AnimatorHashes.RandomIdle);
✅ 정리
| 항목 | 설명 |
| StringToHash() | 문자열을 정수 해시로 변환 |
| 이점 | 빠른 비교, 오타 방지, 런타임 성능 향상 |
| 권장 | 애니메이션 파라미터가 많은 경우, 대규모 프로젝트일수록 필수에 가깝게 사용 |
int vs string 비교 연산의 실제 성능 차이
이건 게임 개발에서는 중요한 최적화 포인트 중 하나입니다. 아래에 정확한 개념, 측정 예시, 그리고 Unity에서의 영향까지 차례로 설명드릴게요.
✅ 1. 개념부터 정리: int vs string 비교 연산
| 항목 | int 비교 | string 비교 |
| 연산 방식 | 값 직접 비교 (==) | 포인터/길이 확인 후 문자-by-문자 비교 |
| 연산 속도 | O(1) (매우 빠름) | O(n) (문자 길이에 비례) |
| GC 할당 | 없음 | 경우에 따라 GC 발생 가능 |
| 예외 발생 | 거의 없음 | null, 오타 등 관리 필요 |
✅ 2. C# 기준 성능 테스트 예시
.NET 환경에서 간단한 벤치마크 테스트 (단순 비교 1억 번 반복)
csharp
복사편집
// Int 비교
int a = 123;
int b = 123;
Stopwatch sw1 = Stopwatch.StartNew();
for (int i = 0; i < 100_000_000; i++)
{
bool result = a == b;
}
sw1.Stop();
Console.WriteLine("Int 비교: " + sw1.ElapsedMilliseconds + "ms");
// String 비교
string s1 = "RandomIdle";
string s2 = "RandomIdle";
Stopwatch sw2 = Stopwatch.StartNew();
for (int i = 0; i < 100_000_000; i++)
{
bool result = s1 == s2;
}
sw2.Stop();
Console.WriteLine("String 비교: " + sw2.ElapsedMilliseconds + "ms");
📌 측정 결과 예시 (환경에 따라 다르지만)
- int 비교 → 약 10~30ms
- string 비교 → 약 300~600ms 이상
즉, 10~20배 이상 느릴 수 있습니다.
✅ 3. Unity에서의 차이점은?
Unity에서도 Animator를 사용할 때 다음과 같이 작동합니다:
| 사용 방식 | 내부 동작 |
| animator.SetTrigger("Idle") | 매번 문자열 → 해시 변환 → 비교 |
| animator.SetTrigger(hashIdle) | 이미 계산된 해시 값 비교만 수행 |
특히, Animator.SetXXX("StringName") 형태를 매 프레임 반복하게 되면,
- GC Alloc이 발생
- 프레임 드롭의 원인이 될 수 있습니다.
✅ 4. Unity Profiler에서 측정 예
Unity Profiler를 켜고 아래 두 가지를 각각 테스트해보세요:
1. 문자열 비교 방식
csharp
복사편집
animator.SetTrigger("RandomIdle"); // 매 프레임 호출
- Profiler → GC Alloc 발생 확인 가능
- CPU 사용량 증가 (GC, 문자열 해시 연산)
2. 정수 해시 방식
csharp
복사편집
readonly int hashRandomIdle = Animator.StringToHash("RandomIdle");
animator.SetTrigger(hashRandomIdle); // 매 프레임 호출
- GC 할당 없음
- 연산 시간 더 짧음
✅ 5. 결론: 왜 int 해시를 써야 하나?
| 이유 | 설명 |
| ✔ 성능 최적화 | 단순한 정수 비교로 처리되므로 빠르고 비용이 적음 |
| ✔ GC 방지 | 문자열 연산은 GC 할당 유발 가능성이 높음 |
| ✔ 유지보수성 | 오타를 줄이고 리팩토링에 안전함 (특히 readonly static으로 묶어두면 좋음) |
✅ 요약
- string == string 비교는 문자열 길이에 따라 O(n) 복잡도
- int == int 비교는 O(1), 매우 빠름
- Unity에서 Animator 파라미터 접근 시 StringToHash()를 써서 int로 바꿔 쓰는 건 매우 권장되는 베스트 프랙티스
🧠 코드 설명
✅ 목적
- Idle 상태에서 일정 시간이 지난 뒤
- RandomIdle이라는 파라미터 값을 랜덤하게 설정해
- Animator가 다른 Idle 애니메이션으로 전환되도록 유도하는 코드입니다.
🔍 주요 포인트
| AnimatorStateInfo stateInfo | 현재 상태의 재생 정보 (시간, 이름 등) |
| stateInfo.normalizedTime | 애니메이션 재생 비율 (0.0 ~ ∞) |
| animator.IsInTransition(0) | 현재 상태가 전이 중인지 여부 |
| GetCurrentAnimatorStateInfo(0).fullPathHash | 현재 상태의 고유 해시값 |
| SetInteger(hashRandomIdle, n) | Animator 파라미터에 랜덤 정수 값 설정 |
🧩 Animator 전이 조건 구성 예시
Idle
├── [RandomIdle == 0] → Idle_Variant1
├── [RandomIdle == 1] → Idle_Variant2
└── [RandomIdle == 2] → Idle_Variant3
그리고 RandomIdle 값이 전환 후에도 유지되면 안 되기 때문에
전이 중일 때 -1로 초기화하여 중복 전환을 방지합니다.
✨ 정리
- StateMachineBehaviour를 활용하면 Animator 상태 안에서 직접 로직을 제어할 수 있습니다.
- normalizedTime, IsInTransition(), fullPathHash 등을 활용하면 상태의 흐름을 정밀하게 제어할 수 있어요.
- 이 코드는 Idle 애니메이션을 랜덤하게 변화시켜 캐릭터를 더욱 자연스럽고 생동감 있게 표현할 수 있도록 도와줍니다.
StateMachineBehaviour 에 대해 알아보기 & Animator 시스템 API 알아보기
이 코드에서는 StateMachineBehaviour라는 Unity 애니메이션 시스템의 기능을 사용해서 Idle 상태 중 랜덤한 애니메이션 전환을 제어하고 있어요.
이걸 제대로 이해하려면 아래 3가지 핵심 개념부터 정리할게요:
🧠 1. StateMachineBehaviour란?
Animator Controller에 붙이는 스크립트형 상태 제어기입니다.
즉, 애니메이터의 특정 상태(State) 안에 로직을 직접 넣을 수 있는 스크립트에요.
📌 Animator의 상태(State)를 클릭하면, Behaviour 추가(Add Behaviour) 버튼이 있습니다.
그걸 눌러서 이 IdleRandomStateMachineBehaviorEx를 붙인 거예요.
🔄 일반적인 MonoBehaviour 스크립트와는 다르게, Animator State 안에서만 실행됩니다.
🧩 2. 이 코드가 하는 일 요약
당신이 준 코드는 다음과 같은 흐름으로 작동합니다:
순서 동작| 1 | Idle 상태에 진입하면, 랜덤한 시간 randomNormalTime을 정함 |
| 2 | Idle 상태에서 매 프레임마다 시간이 흐르는 걸 감시함 |
| 3 | 시간이 randomNormalTime을 지나면, RandomIdle 파라미터에 0~(numberOfStates-1) 사이 숫자 설정 |
| 4 | Animator에서 이 값을 읽고 랜덤한 애니메이션으로 전환 (조건이 RandomIdle == x일 때 재생) |
| 5 | 상태 전환 중이라면 RandomIdle 값을 -1로 리셋함 (재진입 방지용) |
📦 코드 분석 – 각 부분 설명
🔹 변수 선언부
public int numberOfStates = 3;
public float minNormTime = 0f;
public float maxNormTime = 5f;
public float randomNormalTime;
readonly int hashRandomIdle = Animator.StringToHash("RandomIdle");
- numberOfStates: 사용할 랜덤 애니메이션 개수
- minNormTime, maxNormTime: 애니메이션 상태의 normalized time (0~1 이상 값)을 기준으로 전환 시점 랜덤 범위
- hashRandomIdle: Animator 파라미터 이름을 해시값으로 저장 (성능 향상용)
🔹 OnStateEnter()
override public void OnStateEnter(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
{
randomNormalTime = Random.Range(minNormTime, maxNormTime);
}
- 상태가 시작될 때마다 랜덤한 타이밍을 설정
- normalizedTime은 0.0부터 시작 → 애니메이션 재생이 얼마나 진행됐는지 알려주는 값
🔹 OnStateUpdate()
if (animator.IsInTransition(0) && animator.GetCurrentAnimatorStateInfo(0).fullPathHash == stateInfo.fullPathHash)
{
animator.SetInteger(hashRandomIdle, -1);
}
- 만약 현재 상태가 전환 중이면 → RandomIdle 값을 -1로 리셋 (중복 실행 방지)
if (stateInfo.normalizedTime > randomNormalTime && !animator.IsInTransition(0))
{
animator.SetInteger(hashRandomIdle, Random.Range(0, numberOfStates));
}
- 지정된 시간이 지난 후 상태 전환 중이 아니라면,
- RandomIdle 파라미터에 0~(numberOfStates - 1) 사이의 랜덤 값을 설정
🎬 이걸 Animator Controller에서 어떻게 써야 하냐면:
- Animator Controller 안에 Idle 상태가 있어야 함
- Idle 상태 안에 이 IdleRandomStateMachineBehaviorEx Behavior가 붙어야 함
- RandomIdle이라는 int 파라미터가 Animator에 있어야 함
- Animator에 다음처럼 조건 설정된 상태 전이가 있어야 함:
Idle
├── [RandomIdle == 0] → Idle_Variant1
├── [RandomIdle == 1] → Idle_Variant2
└── [RandomIdle == 2] → Idle_Variant3
→ 이렇게 해서 일정 시간이 지나면 랜덤하게 Idle 애니메이션이 변경되는 효과를 낼 수 있어요.
🔧 이런 방식이 유용한 이유
- 비슷한 애니메이션 반복을 줄이고, 자연스럽게 보이는 Idle을 만들 수 있음
- Animator 안에서 직접 로직을 넣지 않고 코드를 깔끔하게 상태별로 관리할 수 있음
- StateMachineBehaviour는 상태 전환의 진입, 대기, 종료 시점에 정확하게 개입할 수 있어서 유용함
✅ 요약
개념 설명| StateMachineBehaviour | Animator 상태(State)에 직접 붙일 수 있는 전용 스크립트 |
| OnStateEnter() | 상태 시작 시 실행됨 (초기화용) |
| OnStateUpdate() | 상태 유지 중 매 프레임 실행됨 |
| normalizedTime | 현재 애니메이션이 얼마나 재생되었는지 (0.0~∞) |
| animator.SetInteger("RandomIdle", n) | Animator에서 조건으로 사용되는 파라미터를 설정 |
Unity의 Animator 시스템에서 stateInfo, IsInTransition(), GetCurrentAnimatorStateInfo() 같은 메서드는 애니메이션 상태의 흐름과 정보를 파악하기 위해 핵심적으로 사용되는 API입니다.
아래에서 개념 + 실제 예시 + 코드 흐름을 함께 설명드릴게요.
✅ 먼저 전체 개념 요약
개념 의미| stateInfo | 현재 실행 중인 애니메이션 상태 정보 (AnimatorStateInfo) |
| animator.IsInTransition(layer) | 현재 상태가 다른 상태로 전이 중인지 여부를 반환 |
| animator.GetCurrentAnimatorStateInfo(layer) | **현재 재생 중인 상태(State)**에 대한 정보를 가져옴 |
| fullPathHash | 상태의 고유 ID (이름 대신 정수 값으로 비교하기 위해 사용) |
🎯 1. AnimatorStateInfo (stateInfo)란?
Animator에서 재생 중인 "현재 애니메이션 상태"의 정보를 담고 있는 구조체입니다.
csharp
복사편집
public struct AnimatorStateInfo
{
public float normalizedTime;
public int fullPathHash;
public bool IsName(string name);
...
}
📌 주요 속성:
속성 설명| normalizedTime | 현재 애니메이션이 얼마나 진행됐는지 (0.0 = 시작, 1.0 = 끝) |
| fullPathHash | 상태의 고유 식별값. 애니메이션 상태 이름 대신 비교용으로 사용 |
| IsName("Idle") | 이름으로 상태 비교 |
🎯 2. animator.IsInTransition(layerIndex)
현재 상태가 **전이 중(transition 중)**인지 확인합니다.
if (animator.IsInTransition(0))
{
Debug.Log("전이 중입니다.");
}
애니메이터는 상태 A → B로 넘어갈 때 Transition을 설정합니다. 이때 둘 다 실행되며 blend가 발생해요. 그 시간 동안 true가 됩니다.
🎯 3. animator.GetCurrentAnimatorStateInfo(layerIndex)
현재 재생 중인 애니메이션 상태의 정보를 가져오는 메서드입니다.
AnimatorStateInfo info = animator.GetCurrentAnimatorStateInfo(0)
주의: 이건 상태가 바뀌더라도 아직 transition이 완료되지 않으면 이전 상태 정보를 반환할 수도 있음
→ 그래서 IsInTransition()와 함께 쓰는 경우가 많아요!
🎯 4. fullPathHash란?
AnimatorController > 상태 이름의 전체 경로를 고유한 정수 해시 값으로 변환한 값입니다.
예: "Base Layer.Idle" → 정수 해시로 변환됨
- 상태 비교 시 "Idle"처럼 문자열로 비교하는 것보다 훨씬 빠릅니다.
- 아래처럼 비교합니다:
if (animator.GetCurrentAnimatorStateInfo(0).fullPathHash == stateInfo.fullPathHash)
{
// 같은 상태인지 확인
}
📦 원래 코드 다시 보기
// 상태 전이 중이고, 여전히 현재 상태가 우리가 들어온 그 상태라면
if (animator.IsInTransition(0) && animator.GetCurrentAnimatorStateInfo(0).fullPathHash == stateInfo.fullPathHash)
{
animator.SetInteger(hashRandomIdle, -1); // 랜덤 파라미터 초기화
}
- IsInTransition(0): 상태가 다른 상태로 넘어가고 있음
- GetCurrentAnimatorStateInfo(0) == stateInfo: 아직 우리가 들어왔던 상태의 애니메이션이 적용 중
즉,
"우리가 있던 상태에서 다른 상태로 넘어가고는 있지만, 아직 그 상태가 완전히 종료되지 않았을 때, 변수 초기화한다"는 의미입니다.
🧠 그림으로 이해해 보기
시간 흐름 →
[Idle 상태] -----------------> [Transition 중] -----> [Idle_Variant 실행]
OnStateEnter() OnStateUpdate() OnStateExit()
↑ ↑ ↑
│ └── 여기서 "Transition 중인가?" 확인
└── randomNormalTime 설정
✅ 요약 정리
항목 설명| stateInfo | 현재 StateMachineBehaviour에 연결된 상태의 정보 |
| IsInTransition(0) | 현재 상태가 다른 상태로 넘어가는 중인지 |
| GetCurrentAnimatorStateInfo(0) | Animator가 지금 재생 중인 상태 정보 |
| fullPathHash | 상태를 정수 해시로 표현한 값 (빠르고 안전한 비교용) |
✅ 실전 팁
- 상태 비교할 때는 이름보다 fullPathHash가 안전하고 빠릅니다.
- transition 도중에 처리할 게 있다면 IsInTransition()를 꼭 써야 합니다.
- 상태 진입/진행/종료를 정확히 구분하고 싶으면 OnStateEnter, OnStateUpdate, OnStateExit 세 함수 다 써야 해요.
Animator에 Walk 애니메이션 추가 & Transition 설정.
Move 파라미터 추가


PlayerCharacter 오브젝트 (우리가 추가한 Ybot 모델 ) 에 PlayerCharacter 스크립트 추가.
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AI;
using UnityEngine.EventSystems;
namespace FastCampus.Characters
{
[RequireComponent(typeof(NavMeshAgent)), RequireComponent(typeof(CharacterController)), RequireComponent(typeof(Animator))]
public class PlayerCharacter : MonoBehaviour
{
#region Variables
private CharacterController controller;
[SerializeField]
private LayerMask groundLayerMask;
private NavMeshAgent agent;
private Camera camera;
[SerializeField]
private Animator animator;
readonly int moveHash = Animator.StringToHash("Move");
readonly int fallingHash = Animator.StringToHash("Falling");
#endregion
#region Main Methods
// Start is called before the first frame update
void Start()
{
controller = GetComponent<CharacterController>();
agent = GetComponent<NavMeshAgent>();
agent.updatePosition = false;
agent.updateRotation = true;
camera = Camera.main;
}
// Update is called once per frame
void Update()
{
// Process mouse left button input
if (Input.GetMouseButtonDown(0))
{
// Make ray from screen to world
Ray ray = camera.ScreenPointToRay(Input.mousePosition);
// Check hit from ray
RaycastHit hit;
if (Physics.Raycast(ray, out hit, 100, groundLayerMask))
{
Debug.Log("We hit " + hit.collider.name + " " + hit.point);
// Move our player to what we hit
agent.SetDestination(hit.point);
}
}
if (agent.remainingDistance > agent.stoppingDistance)
{
controller.Move(agent.velocity * Time.deltaTime);
animator.SetBool(moveHash, true);
}
else
{
controller.Move(Vector3.zero);
animator.SetBool(moveHash, false);
}
if (agent.isOnOffMeshLink)
{
animator.SetBool(fallingHash, agent.velocity.y != 0.0f);
}
else
{
animator.SetBool(fallingHash, false);
}
}
private void OnAnimatorMove()
{
Vector3 position = agent.nextPosition;
animator.rootPosition = agent.nextPosition;
transform.position = position;
}
#endregion Main Methods
#region Helper Methods
#endregion Helper Methods
}
}
저번 시간에 작성했던 코드에, Animator 를 통해 Aniamtion 을 재생하기 위한 파라미터를 설정하는 코드들이 추가됨.
( 참고로 위의 스크립트를 PlayerCharacter에 추가하면, 필요로하는 컴포넌트들이 자동으로 추가되는데 RequrieComponent 속성 때문이다.)
RequireCompoent 란?
✅ RequireComponent란?
Unity의 클래스 상단에 붙이는 **Attribute(속성)**으로,
해당 스크립트를 오브젝트에 추가할 때 자동으로 필요한 컴포넌트들도 함께 추가되게 합니다.
📌 이 스크립트 상단 부분:
[RequireComponent(typeof(NavMeshAgent)),
RequireComponent(typeof(CharacterController)),
RequireComponent(typeof(Animator))]
🔽 이 PlayerCharacter 스크립트를 오브젝트에 붙이면...
Unity는 자동으로 아래 컴포넌트들을 추가합니다:
- NavMeshAgent
- CharacterController
- Animator
💡 왜 쓰는가?
- 누락 방지
필수 컴포넌트가 빠져 있으면 실행 중 오류가 나거나 NullReferenceException이 발생할 수 있음. - 자동화 및 실수 방지
다른 사람이 이 스크립트를 붙일 때도 컴포넌트 누락 걱정 없이 바로 사용할 수 있음. - 협업에 유용
여러 개발자가 협업하는 프로젝트에서 구조적 안전장치 역할을 함.
🧠 예시: 없이 붙이면?
만약 RequireComponent 없이 스크립트를 오브젝트에 추가했다면,
controller = GetComponent<CharacterController>();
이런 코드에서 controller는 null이 되고, 이후 .Move()에서 에러가 납니다.
✅ 요약
| 항목 | 내용 |
| 자동 추가된 이유 | RequireComponent 때문 |
| 어떤 컴포넌트들? | NavMeshAgent, CharacterController, Animator |
| 언제 실행됨? | 스크립트를 오브젝트에 처음 추가할 때만 |
| 왜 중요한가? | 필수 의존 컴포넌트 자동 보장, 협업 시 안정성 확보 |
추가로 궁금하신 내용 (예: RequireComponent의 제한, 실행 시점, 제거 여부 등) 있으면 바로 설명드릴게요!
학습 인증 사진
① 오늘자 날짜, 공부 시작 시각 포함 사진 1장
② 오늘자 날짜, 공부 종료 시각 포함 사진 1장


③ 1개 클립 수강 인증 사진 (강의장 목록 캡쳐, 강의 내용이 담긴 수강화면이 보이지 않도록) 1장

④ 학습 인증샷 1장 이상 (ex. 필기 촬영, 작업물, 등)







⑤ 마이페이지 - 학습통계 - 챌린지 대상 강의 완강률 캡쳐 사진 1장 (완료 클립 수가 보이도록)
