Unity 5로 '절대강좌 유니티 5' Space Shooter 예제 따라하기 계속. 6장. 적 캐릭터 제작.
역시나 게임은 용량이 큰 탓에 스냅샷만 올림.

- 몬스터 추가, Navigation 기능 활용하여 주인공 추적 자동화하기, 몬스터 상태에 따른 행동 처리(널널/추적/공격/죽음), 주인공 총알에 맞을 경우 피 튀는 효과, 핏자국 효과, 양손목에 구형 충돌체 추가하여 그것에 주인공이 닿으면 주인공 체력 닳게 처리, 몬스터 죽을 경우 처리, 주인공 죽음시 처리.

* 특히 Navigation 기능에 감탄. 지정된 타겟까지의 최단 이동 거리를 계산, 타겟을 추적하는 인공지능 기능을 간단한 설정 몇 개로 처리가 가능하다.

16/1/13 수

* 몬스터 본 설정: Monster.fbx 선택, Inspector-Rig탭에서 Animation Type: Humanoid로 설정, Apply. Configure 버튼 클릭. Avatar 설정화면 Muscles 탭에서 Muscle Group Preview의 슬라이더로 움직임 체크. Pre-Muscle Settings에서는 각 관절의 회전 최소/최대 각도 범위 설정 가능. Done을 눌러 나오고 Animation 탭에서 idle, walk, attack, fall 4개의 애니메이션 클립에 Loop Time 체크, Apply.
Monster.fbx 드래그하여 Hierarchy에 배치.

* 몬스터 Animator 기본 설정:
Monster.fbx 있는 폴더에서 마우스 우클릭, Create-Animator Controller 선택하여 MonsterAnim.controller 생성.
MonsterAnim.controller를 Hierarchy-Monster의 Animator 컴포넌트 Controller에 드래그하여 적용.
MonsterAnim을 더블클릭하면 Animator 창이 뜬다. 창에는 하늘색 Any State(어느 스테이트건 조건 만족시 분기해야할 스테이트), 녹색 Entry(최초 진입점), 빨간색 Exit(모든 스테이트의 종료되는 마지막 스테이트) 스테이트가 떠 있다.
Monster.fbx 내 idle 애니메이션 클립을 Animator 창으로 드래그. idle이란 이름의 주홍색 스테이트가 생성되고 Entry에서 선이 이어져 있다. 해당 모델에서 처음 실행되는 기본 애니메이션 클립이란 의미. 다른 애니메이션 클립을 기본 스테이트로 변경하려면 변경할 스테이트에 마우스 우클릭하여 뜨는 메뉴 중 Set as Layer Default State를 선택하면 됨.
walk 애니메이션 클립도 드래그하여 넣고 idle 스테이트에 우클릭, Make Transition 선택한 뒤 나온 선을 walk 스테이트에 연결.
Parameters탭으로 바꾸고 + 클릭한 뒤 Bool 선택, 이름 'isTrace'로 지정.
idle->walk 이어지는 선 선택, Conditions + 누르고 isTrace true로 선택. Has Exit Time은 체크 해제. 체크되어 있을시 원래의 애니메이션이 모두 종료된 뒤에 해당 애니메이션으로 변화됨.
walk->idle로 트랜지션 만들고 Conditions +, isTrace false로 선택, Has Exit Time 체크 해제.

* 네비게이션 설정, 베이크하기
Hierarchy-Walls-Floor 선택, Inspecter 우상단 Static 체크박스 체크하면 모든 기능에대해 Static 처리가 되므로 옆 아래 화살표 클릭해 Navigation Static만 체크.
Project-pfBarrel 선택, 역시 Navigation Static만 체크하면 Change Static Flags 팝업창이 뜨는데 Yes, change children 클릭. Mesh Renderer가 하위에 있기 때문.
메뉴 Windows-Navigation 클릭하면 뜨는 Navigation 뷰의 우하단 Bake 클릭.
추적할 대상인 Hierarchy-Monster에 Component-Navigation-NavMeshAgent 컴포넌트 추가. Stopping Distance: 2로 설정.
Hierarchy-Player에 Player Tag 설정.

csMonsterCtrl.cs 스크립트 생성
public class csMonsterCtrl : MonoBehaviour {
//속도 향상을 위해 각종 컴포넌트를 변수에 할당
private Transform monsterTr;
private Transform playerTr;
private NavMeshAgent nvAgent;

	void Start () {
		//몬스터의 Transform 할당
		monsterTr = this.gameObject.GetComponent<Transform> ();
		//추적대상인 Player의 Transform 할당
		playerTr = GameObject.FindWithTag
		("Player").GetComponent<Transform> ();
		//NavMeshAgent 컴포넌트 할당
		nvAgent = this.gameObject.GetComponent<NavMeshAgent>();
		//추적 대상 위치를 설정하면 바로 추적 시작
		nvAgent.destination = playerTr.position;
	}
}

-NavMeshAgent.destination이 추적할 목표를 지정하는 속성. 이것 외에 NavMeshAgent.SetDestination(Vector3 pos)메서드로도 지정이 가능.
-몬스터에 적용된 NavMeshAgent 때문에 몬스터가 바닥에서 좀 뜬 채 이동한다.
Base Offset: -0.1로 설정하면 실린더 모양 NavMeshAgent가 y축으로 올라가 몬스터가 바닥에서 뜨지 않게 된다.

* 몬스터 상태에 따른 행동 처리용 스크립트 추가. csMonsterCtrl.cs
주인공과의 거리를 계산하여 추적 범위 내에 있으면 추적, 공격 범위내에 있으면 공격, 먼 거리에 있으면 빈둥대기. 추적 재개와 중단은 NavAgent.Resume(), NavAgent.Stop()를 이용.

csMonsterCtrl.cs 스크립트에 애니메이션 처리를 위해 IsTrace 변수 변경 추가.
//Animator의 IsTrace 변수를 true로 설정
animator.SetBool("IsTrace", true);
SetBool() 매서드로 추적 중일 때는 Animator의 IsTrace 변수를 true로, idle 상태일 때는 false로 변경하여 애니메이션을 전환.

Animator에 attack 애니메이션 클립 추가, walk 스테이트와 상호 트랜지션 만들고 IsAttack이란 Bool형 새 패러미터 생성, IsAttack이 true: walk -> attack, false: attack -> walk로 설정. 스크립트의 추적 중 상태에 IsAttack 변수를 false로, 공격 중 상태에 true로 변경하게 처리.

* 몬스터 피격 리액션:
Hierarchy-Monster에 Component-Physic-Capsule Collider 추가. Center y: 1, Radius: 0.5, Height: 2 적용.
Animator에 gothit 애니메이션 클립 추가, IsHit이란 Trigger형 새 패러미터 생성 ,
AnyState > gothit(IsHit),
gothit > idle(ExitTime: 0.9, IsTrace:false),
gothit > walk(ExitTime: 0.9, IsTrace:true),
gothit > attack(ExitTime: 0.9, IsAttack:true) 트랜지션 만들고 조건 설정.

csMonsterCtrl.cs 스크립트에 void OnCollisionEnter(Collision col){} 함수 추가. 총알 충돌시 SetTrigger() 메서드로 피격 애니메이션으로 변경.
//IsHit Trigger를 발생시키면 Any State에서 gothit상태로 전이
animator.SetTrigger("IsHit");

* 피 튀기는 효과: BloodEffect.unitypackage 임포트. 총격 받은 장소에 피 튀기는 효과 생성하고 2초뒤 제거하게 스크립트로 처리.
* 혈흔 데칼 효과: 바닥에 핏자국 생성.
Quad 생성, 'pfBloodDecal'로 명명, x축 90도 회전. 새 Material(Shader: Unlit/Transparent) 생성, BloodDecal.psd를 Texture로 연결. 만든 Material을 Hierarchy-pfBloodDecal에 적용. pfBloodDecal을 Project로 드래그해 프리팹화.
csMonsterCtrl.cs 스크립트에서 총알과 충돌시 피튀는 효과, 혈흔 데칼 생성하고 각각 2초, 5초뒤에 제거되게 처리. 3:45

* 몬스터 공격능력 부여: 몬스터의 왼쪽, 오른쪽 손목에 리지드바디, 구 충돌체 추가하기.
Hierarchy-Monster-hip-belly-L_arm-L_elbow-L_wrist, R_wrist에 Component-Physics-Rigidbody(Use Gravity: 체크 해제, Is kinematic: 체크), Sphere Collider(Is Trigger: 체크) 추가. 새 태그 'PUNCH' 생성해서 각각 적용.

- 몬스터의 양손목에 추가한 충돌체는 자신의 몸에도 닿아 지속적인 충돌감지가 되어 무거워지므로 레이어를 나눠 불필요한 부하를 줄여줘야 한다.
Inspector 우상단 Layer-Add Layer 클릭한 뒤
User Layer 8: PLAYER, User Layer 9: EBODY, User Layer 10: EPUNCH로 레이어 3개 새로 생성.
Hierarchy-Player의 레이어를 PLAYER로 변경하고 팝업창에서 Yes,change children 클릭.
Hierarchy-Monster의 레이어를 EBODY로 변경하고 팝업창에서 No,this object only 클릭.
Hierarchy-Monster-...-L_wrist, R_wrist 레이어를 EPUNCH로 변경하고 팝업창에서 No,this object only 클릭.
메뉴 Edit-Project Settings-Physics 선택한 뒤 EBODY, EPUNCH 부분의 체크 3개를 해제해준다.

Hierarchy-Player에 Component-Physics-Rigidbody(Freeze Rotation: x, z축 체크), Capsule Collider(Center y : 1, Height: 2) 추가.
csPlayerCtrl.cs 스크립트에 hp 변수와 OnTriggerEnter() 함수 추가하여 적 공격에 맞으면 생명이 줄어들고 다 줄어들면 죽게 처리.

//충돌한 Collider의 IsTrigger옵션이 체크됐을 때 발생
void OnTriggerEnter(Collider col){
	//충돌한 Collider가 몬스터의 PUNCH이면 Player의 hp 차감
	if (col.gameObject.tag == "PUNCH") {
		hp -= 10;
		//Player 생명이 0이하면 사망 처리
		if(hp <= 0){
			PlayerDie();
		}
	}
}

* 몬스터의 양손목 본만 노출시키기: Monster.fbx 선택뒤 Inspector-Rig 탭에서 Optimize Game Objcet 체크, Extra Transforms to Expose + 클릭, hip-belly-L_arm-L_elbow-L_wrist, R_wrist로 따라내려가 L_wrist, R_wrist를 추가시키고 Apply하면 Hierarchy 뷰에서 양손목 본만 노출된다.

* 몬스터 공격 중지 처리 1(Tag 방식): 'MONSTER'란 새 태그 추가하고 Hierarchy-Monster의 태그로 설정.
csPlayerCtrl.cs 스크립트에서 플레이어 사망시 SendMessage() 메서드로 모든 몬스터의 OnPlayerDie() 함수를 순차적으로 실행시키게 처리.

//MONSTER 태그를 가진 모든 게임오브젝트 찾기
GameObject[] monsters = GameObject.FindGameObjectsWithTag ("MONSTER");
//모든 몬스터의 OnPlayerDie 함수 순차적 호출
foreach (GameObject monster in monsters) {
monster.SendMessage("OnPlayerDie", SendMessageOptions.DontRequireReceiver);
}

-Animator에 fall 애니메이션 클립 추가, 새 파라미터 'IsPlayerDie'(Trigger) 추가. AnyState > fall(Has Exit Time: 체크 해제, IsPlayerDie)로 트랜지션 연결, 조건 설정.
csMonsterCtrl.cs에 OnPlayerDie()함수 추가하고 모든 코루틴 함수 정지시키고 추적을 멈춘 뒤 animator의 IsPlayerDie 트리거 패러미터를 켜서 적이 fall 애니메이션으로 전이하게 처리. (떨어지는 애니메이션인데 춤추는 듯 보이기도 한다)

//몬스터의 상태를 체크하는 코루틴 함수 모두 정지시키기
StopAllCoroutines ();

//추적 정지, 애니메이션 수행
nvAgent.Stop ();

animator.SetTrigger ("IsPlayerDie");

* 몬스터 공격 중지 처리 2(이벤트 구동방식-Delegate, Event):
-Delegate는 함수를 가리키는 변수. 선언부에서 델리게이트 선언, 델리게이트 변수에 함수 연결뒤 실행.
//델리게이트 및 이벤트 선언
//public delegate void 델리게이트명(인자);
public delegate void PlayerDieHandler();
//public static event 델리게이트명 이벤트명
public static event PlayerDieHandler OnPlayerDie;
void PlayerDie(){ OnPlayerDie (); }
//이벤트 발생시키기

-이벤트는 반드시 스크립트 활성화 시점에 연결하고 비활성화될 때 해제해야 한다. 하여 csMonsterCtrl.cs에 아래 함수 추가.
//이벤트 발생시 수행할 함수 연결
void OnEnable(){
csPlayerCtrl.OnPlayerDie += this.OnPlayerDie;

}
//이벤트 발생시 연결된 함수 해제
void OnDisable(){
csPlayerCtrl.OnPlayerDie -= this.OnPlayerDie;

}

* 몬스터 사망 처리:
-Animator에 die 애니메이션 클립 추가, 새 파라미터 'IsDie'(Trigger) 추가.
AnyState > die(Has Exit Time: 체크 해제, IsDie)로 트랜지션 연결, 조건 설정.
한데 몬스터가 공중에 떠있다. 해결을 위해 Monster.fbx 선택, Inspector-Animation 탭으로 이동, die Clip을 선택, Root Transform Position(Y)의 Bake Into Pose를 체크하거나 Base upon 값을 feet으로 설정하면 Pivot 좌표값이 몬스터의 발위치로 조정된다. Apply 클릭.

csMonsterCtrl.cs에 hp 변수, MonsterDie()함수 추가. 총알과 충돌시마다 체력을 깎고 체력이 0 이하가 되면 코루틴함수들 중지, 추적 중지, IsDie 트리거 온 시켜 죽는 애니 실행시키기.

주인공이 적캐릭터의 공격에 맞을 때도 피 튀는 효과 추가.
한데 주인공 캐릭터가 죽으면 죽었던 적 캐릭터까지 모두 되살아나 춤추는 애니를 실행하는 버그가 있다. IsDie 패러미터를 Trigger에서 Bool로 바꾸고 Any State > fall의 조건을 IsPlayerDie 뿐 아니라 IsDie-false인 것을 추가하면 해결될 줄 알았는데 해결되지 않고 몬스터가 죽을 경우 쓰러지지 않고 벌벌 떨면서 천천히 회전하게 되어 실패.