GURU_unity_4주차 FPS 게임 제작(6)

2023. 7. 15. 09:17SWU_프로젝트/GURU - unity

플레이어 폴리싱
길찾기 (네비 메시)

 

플레이어 폴리싱

 

➡ 플레이어 모델링 추가

 

목표 : 플레이어의 외형을 군인 캐릭터로 교체하고 싶다

순서:
1. 에셋 스토어에서 원하는 형태의 모델링 데이터 임포트하기
2. 개발 환경에 맞춰 모델링 데이터의 임포트 설정 완료하기
3. 임포트된 모델링 사용하기

 

 

  • 에셋 스토어 창을 열고, 검색 창에 ‘Soldier’를 입력 
  • Pricing의 Free Assets 항목에 체크
  • 검색된 에셋 중에서 ‘Low Poly Soldiers Demo’를 선택
애니메이션 타입 리타깃팅 기능 비주얼 스크립팅
Legacy X X
Generic X O
Humanoid O O

 

Soldier_demo.FBX 파일은 [Rig] 탭의 [Animation Type] 항목이 Generic으로 돼 있지만 리타깃팅 기능을 사용하지 않을 예정이므로 변경하지 않고 그대로 사용한다.

 

Soldier_demo.FBX 를 플레이어 오브젝트의 자식 오브젝트로 등록한다.

 

  • [Model] 탭의 Scale Factor 항목의 값을 ‘0.7’로 변경하고 하단의 [Apply] 버튼을 클릭한다.
  • 메시 렌더러 컴포넌트를 비활성화한다.
  • 캐릭터 콘트롤러 컴포넌트의 충돌 영역의 크기를 캐릭터 크기에 맞게 조정한다.

캐릭터 컨트롤러의 Skin Width

 

눈 역할을 담당하는 메인 카메라의 위치가 군인 캐릭터의 눈높이에 맞춰지도록 CamPosition 오브젝트의 위치 값을 그림과 같이 조정한다.

 

 

➡ 애니메이터 설정

목표 : 플레이어의 캐릭터의 조작에 맞춰 애니메이션을 적용하고 싶다.

순서:
1. 애니메이터 컨트롤러 구성하기
2. 대기 동작에 대한 애니메이션 적용하기
3. 이동 동작에 대한 애니메이션 적용하기
4. 공격 동작에 대한 애니메이션 적용하기

  • 프로젝트 뷰에서 [+]버튼 – [Animator Controller]를 선택한 후 이름을 ‘Soldier Anim’으로 변경한다.
  • 군인 캐릭터의 애니메이터 컴포넌트의 Controller 항목에 Soldier Anim 애니메이터 컨트롤러를 추가한다.

demo_combat_idle.FBX 파일을 애니메이터 뷰 쪽으로 드래그해서 넣어준다.

 

  • 게임 뷰에서 보면 총의 모습이 조금씩 뚫려 보이는 문제 확인
  • 총의 모습이 잘 보이도록 메인 카메라의 카메라 컴포넌트에서 Near 값을 0.22로 변경한다.

 

➡ 블랜드 트리 활용

  • 유니티에는 플레이어의 이동 속도에 따라 애니메이션이 자동으로 블랜딩되면서 전환되는 기능 존재하낟.
  • 애니메이터 뷰에서 마우스 오른쪽 버튼을 클릭한 후 [Create State–From New Blend Tree]를 선택해 새로운 블랜드 트리를 생성한다.
  • 블랜드 트리의 이름은 ‘MoveBlend’로 변경한다.

MoveBlend를 더블 클릭하여 블랜드 트리 내부 레이어 화면으로 전환

 

  • 블랜드 트리 하단의 [+]버튼 – [Add Motion Field]를 선택하여 리스트를 두 개 추가한다.
  • 추가한 모션 리스트에 대기 애니메이션 클립과 이동 애니메이션 클립을 추가한다.

  • 블랜드 트리를 처음 만들었을 때 애니메이터 뷰에 ‘Blend’라는 이름으로 Float 타입의 파라미터 하나가 자동으로 생성된다.
  • 호출할 때 편리하게 구분할 수 있도록 인스펙터 뷰에서 Parameter 항목의 이름을 ‘MoveMotion’으로 변경

Base Layer로 돌아와 블랜드 트리 위에 마우스 커서를 올려놓고 마우스 오른쪽 버튼을 클릭하고 [Set as Layer Default State]를 선택한다.

 

기존의 demo_combat_idle 스테이트를 삭제한다.

 

public class PlayerMove : MonoBehaviour
{
. . . (생략) . . .
// 애니메이터 변수
Animator anim;
void Start()
{
// 캐릭터 콘트롤러 컴포넌트 받아오기
cc = GetComponent<CharacterController>();
// 애니메이터 받아오기
anim = GetComponentInChildren<Animator>();
}
. . . (생략) . . .
}
  • 애니메이터 컴포넌트를 제어할 수 있도록 애니메이터 변수를 선언한다.
  • Start() 함수에서 컴포넌트를 받는다.
. . . (생략) . . .
void Update()
{
. . . (생략) . . .
// 1. 사용자의 입력을 받는다.
float h = Input.GetAxis("Horizontal");
float v = Input.GetAxis("Vertical");
// 2. 이동 방향을 설정한다.
Vector3 dir = new Vector3(h, 0, v);
dir = dir.normalized;
// 이동 블랜딩 트리를 호출하고 벡터의 크기 값을 넘겨준다.
anim.SetFloat("MoveMotion", dir.magnitude);
. . . (생략) . . .
}

벡터 크기에 따라 애니메이션이 전환되도록 이동 방향 벡터의 크기(magnitude)를 파라미터로 전달

 

 

➡ 플레이어 이동 시 카메라 위치 조정

 

  • 이동 할 때에 캐릭터의 머리 형태가 간헐적으로 나타나는 문제 발생 
  • CamPosition 오브젝트 헬멧의 앞쪽으로 이동
  • 메인 카메라의 Near를 0.01로 조정한다.

  • 이동 할 때에 캐릭터의 머리 형태가 간헐적으로 나타나는 문제 발생 
  • CamPosition 오브젝트 헬멧의 앞쪽으로 이동
  • 메인 카메라의 Near를 0.01로 조정

 

 애니메이션 트랜지션 호출(이동 ↔공격)

 

  • demo_combat_shoot.FBX 파일을 애니메이터 뷰로 드래그해서 넣어 스테이트로 생성한다.
  • 이동 블랜드 트리와 공격 스테이트를 서로 트랜지션으로 연결한다.

이동 → 공격 트랜지션과 공격 → 이동 트랜지션의 정보를 위 그림과 같이 설정한다.

public class PlayerFire : MonoBehaviour
{
. . . (생략) . . .
// 애니메이터 변수
Animator anim;
void Start()
{
// 피격 이펙트 오브젝트에서 파티클 시스템 컴포넌트 가져오기
ps = bulletEffect.GetComponent<ParticleSystem>();
// 애니메이터 컴포넌트 가져오기
anim = GetComponentInChildren<Animator>();
}
}
  • 애니메이터 컴포넌트를 제어할 수 있도록 애니메이터 변수를 선언한다.
  • Start() 함수에서 컴포넌트를 받는다.
void Update()
{
. . . (생략) . . .
// 마우스 왼쪽 버튼을 누르면 시선이 바라보는 방향으로 총을 발사하고 싶다.
// 1. 마우스 입력을 받는다.
if (Input.GetMouseButtonDown(0))
{
// 만일 이동 블랜드 트리 파라미터의 값이 0이라면, 공격 애니메이션을 실시한다.
if (anim.GetFloat("MoveMotion") == 0)
{
anim.SetTrigger("Attack");
} 
. . . (생략) . . .
}
. . . (생략) . . .
}

블랜드 트리 파라미터의 값을 읽어 그 값이 0일 때(대기 상태)에만 공격 트랜지션을 호출한다.

 

플레이어의 공격 애니메이션 확인한다.

public class GameManager : MonoBehaviour
{
. . . (생략) . . .
void Update()
{
// 만일, 플레이어의 hp가 0 이하라면...
if(player.hp <= 0)
{
// 플레이어의 애니메이션을 멈춘다.
player.GetComponentInChildren<Animator>().SetFloat("MoveMotion", 0f);
. . . (생략) . . .
}
}
. . . (생략) . . .
}
  • 이동 중에 게임 오버된 경우에 이동 애니메이션 상태가 계속되는 문제 확인 
  • 게임 매니저 스크립트에서 게임 오버 시 플레이어의 이동 속도 값을 0으로 변경

 

길찾기 (네비 메시)

 

✔ 길 찾기 알고리즘 개요

 

현재 구현된 단순 좌표 이동만으로는 레벨 디자인이 적용된 맵에서 제대로 이동이 불가능 • 이러한 문제를 해결하기 위해 다양한 길 찾기(path finding) 알고리즘이 존재한다.

 

 

길 찾기 알고리즘의 종류

1) 좌선법, 우선법
 벽에 손을 짚고 한 방향(좌 or 우)으로만 이동하여 경로를 탐색

2) 깊이 우선 탐색(DFS), 넓이 우선 탐색(BFS)
 막다른 길이 나올 때까지 경로 탐색 후 다른 경로를 탐색하거나(DFS), 모든 갈림길을 가까운 곳부터 탐색하여 점점 먼 방향으로 경로 탐색 범위를 넓힘(BFS)

3) 다익스트라 알고리즘(Dijkstar algorithm)
 경우의 수만큼 분신을 만들어서 경로를 탐색 후 가장 최단 거리의 분신이 이동한 경로를 최종 경로로 찾아내는 알고리즘

4) A* 알고리즘(A* algorithm)
 다익스트라 알고리즘의 개량형. 현재 위치에서 목표에 가장 가까운 이웃 노드를 검색하여 최단 경로를 찾아내는 알고리즘
  • 유니티에서는 내비게이션 메시를 이용해 3D 지형 공간을 2D 형식처럼 만들고 그 영역에서 A* 알고리즘을 이용해 목적지(Destination)까지의 최단 경로(Path)를 발견 
  • 내비게이션 메시 및 A* 알고리즘을 개발자가 빠르고 손쉽게 사용할 수 있도록 여러 가지 컴포넌트와 API를 제공

목표 : 에너미를 이동시킬 때 지형을 분석해 장애물을 피해 목적지까지 최단 거리로 이동하게 하고 싶다.

순서:
1. 내비게이션 메시 기능으로 이동 가능한 공간 영역 설정하기
2. 에너미를 내비게이션 영역을 이동하기 위한 에이전트로 설정하기
3. 에너미 상태 머신 구조에 맞춰 내비게이션 기능 제어하기

  • 빈 게임 오브젝트를 하나 생성한 후 고정된 배경에 해당하는 게임 오브젝트들을 모두 자식 오브젝트로 등록
  • 빈 게임 오브젝트의 이름은 ‘Environment’로 변경

  •  Environment 오브젝트에서 Static 글자 우측의 [▼] 버튼을 클릭하고 [Navigation Static]을 선택한다.
  • 자식 오브젝트에도 같이 적용할 것인지를 묻는 팝업 창이 나타나면 [Yes, change children] 버튼을 선택

  • 유니티 에디터 상단의 [Window – AI – Navigation]을 선택하면 유니티 에디터 우측에 내비게이션 뷰가 생성
  • 내비게이션 뷰 상단의 [Bake] 탭을 클릭 
  • 내비게이션 뷰의 Agent Radius(둘레)를 ‘0.3’, Agent Height(높이)는 ‘1.8’로 설정 
  • 하단의 [Bake] 버튼을 클릭

씬 뷰에 내비게이션으로 이동할 수 있는 영역이 파란색으로 표시한다.

 

  • 내비게이션 베이크가 완료되면 현재 씬 파일이 있던 곳에 씬 이름과 동일한 이름의 폴더가 생성되면서 NavMesh.asset이라는 파일이 생성됨
  • NavMesh.asset 파일 안에 현재 베이크된 메시 정보가 저장된다.

 

내비 메시 에이전트 설정

 

에너미 오브젝트에서 [Add Component – Navigation – Nav Mesh Agent]를 선택해 Nav Mesh Agent 컴포넌트를 추가

 

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.AI;
public class EnemyFSM : MonoBehaviour
{ 
. . . (생략) . . .
// 내비게이션 에이전트 변수
NavMeshAgent smith;
void Start()
{
. . . (생략) . . .
// 내비게이션 에이전트 컴포넌트 받아오기
smith = GetComponent<NavMeshAgent>();
}
. . . (생략) . . .
}
  • 내비게이션 메시 기능을 사용하기 위해 네임 스페이스 추가 
  • 에이전트 컴포넌트를 제어하기 위해 내비게이션 에이전트 클래스 변수 선언 
  • Start() 함수에서 내비게이션 에이전트 컴포넌트를 캐싱
void Move()
{
. . . (생략) . . .
// 만일, 플레이어와의 거리가 공격 범위 밖이라면 플레이어를 향해 이동한다.
else if (Vector3.Distance(transform.position, player.position) > attackDistance)
{
// 이동 방향 설정
//Vector3 dir = (player.position - transform.position).normalized;
// 캐릭터 콘트롤러를 이용해 이동하기
//cc.Move(dir * moveSpeed * Time.deltaTime);
// 플레이어를 향해 방향을 전환한다.
//transform.forward = dir;
// 내비게이션으로 접근하는 최소 거리를 공격 가능 거리로 설정한다.
smith.stoppingDistance = attackDistance;
// 내비게이션의 목적지를 플레이어의 위치로 설정한다.
smith.destination = player.position;
}
. . . (생략) . . .
}
  • Move( ) 함수에서 기존에 캐릭터 콘트롤러로 이동했던 부분을 주석 처리한다.
  • 내비게이션 에이전트의 목적지까지의 접근 거리 (stopping distance)를 기존에 선언한 공격 가능 거리 변수(attack distance)만큼으로 지정한다.
void Move()
{
. . . (생략) . . .
// 만일, 플레이어와의 거리가 공격 범위 밖이라면 플레이어를 향해 이동한다.
else if (Vector3.Distance(transform.position, player.position) > attackDistance)
{
. . . (생략) . . .
// 내비게이션 에이전트의 이동을 멈추고 경로를 초기화한다.
smith.isStopped = true;
smith.ResetPath();
// 내비게이션으로 접근하는 최소 거리를 공격 가능 거리로 설정한다.
smith.stoppingDistance = attackDistance;
// 내비게이션의 목적지를 플레이어의 위치로 설정한다.
smith.destination = player.position;
}
. . . (생략) . . .
}
  • 공격 모션 중에 플레이어가 다른 곳으로 이동한다면 에너미가 공격 동작을 하면서도 미끄러지듯이 이동해버리는 문제 발생
  • 문제 해결을 위해 이동 상태에서 공격 상태로 전환될 때 에이전트의 이동을 멈추고 목적지 경로(Path)를 초기화
void Return()
{
// 만일, 초기 위치에서의 거리가 0.1f 이상이라면 초기 위치 쪽으로 이동한다.
if (Vector3.Distance(transform.position, originPos) > 0.1f)
{
//Vector3 dir = (originPos - transform.position).normalized;
//cc.Move(dir * moveSpeed * Time.deltaTime);
// 복귀 지점으로 방향을 전환한다.
//transform.forward = dir;
// 내비게이션의 목적지를 초기 저장된 위치로 설정한다.
smith.destination = originPos;
// 내비게이션으로 접근하는 최소 거리를 0으로 설정한다.
smith.stoppingDistance = 0;
}
. . . (생략) . . .
}

복귀 이동 시에도 기존 이동 기능을 주석 처리하고 내비게이션으로 이동하게끔 코드를 수정

 

void Return()
{
. . . (생략) . . .
// 그렇지 않다면, 자신의 위치를 초기 위치로 조정하고 현재 상태를 대기 상태로 전환한다.
else
{
// 내비게이션 에이전트의 이동을 멈추고 경로를 초기화한다.
smith.isStopped = true;
smith.ResetPath();
// 위치 값과 회전 값을 초기 상태로 변환한다.
transform.position = originPos;
transform.rotation = originRot;
. . . (생략) . . .
}
}

초기 위치까지 복귀를 완료했으면 에이전트의 이동을 멈추고 경로를 초기화

 

이동 중에 피격을 당하면 피격 애니메이션을 하면서도 미끄러지듯이 계속 이동하는 문제 발생한다.

 

. . . (생략) . . .
// 데미지 실행 함수
public void HitEnemy(int hitPower)
{
// 만일, 이미 피격 상태이거나 사망 상태 또는 복귀 상태라면 아무런 처리도 하지 않고 함수를 종료한다.
if (m_State == EnemyState.Damaged || m_State == EnemyState.Die || m_State == EnemyState.Return)
{
return;
}
// 플레이어의 공격력만큼 에너미의 체력을 감소시킨다.
hp -= hitPower;
// 내비게이션 에이전트의 이동을 멈추고 경로를 초기화한다.
smith.isStopped = true;
smith.ResetPath();
. . . (생략) . . .
}
. . . (생략) . . .

 

내비 메시 에이전트의 이동 속도나 회전 속도를 그림과 같이 조정한다.

컴포넌트 항목 기능 설명
Speed 에이전트의 최대 이동 속도
Angular Speed 에이전트의 회전 속도
Acceleration 에이전트의 가속도
Stopping Distance 목적지에 이 거리만큼 가까워지면 정지합니다.
Auto Braking 목적지에 다다르면 속도를 감소시킵니다. 패트롤처럼 복수의 목적지를 등속도로 이동하려면 이 항목을 꺼야 합니다