Chapter 7-4. AI : 동물의 이중 상속
카테고리: Unity Lesson 3
태그: Unity Game Engine
인프런에 있는 케이디님의 [유니티 3D] 실전! 생존게임 만들기 - Advanced 강의를 듣고 정리한 필기입니다. 😀
🌜 강의 들으러 가기 Click
Chapter 7. 동물들의 공격형 AI, 도망형 AI 구현
🚖 이중 상속
📜Pig 는 📜WeakAnimal 을 상속 한다. 📜WeakAnimal 은 📜Animal 를 상속 하므로, 📜Pig 는 📜WeakAnimal, 📜WeakAnimal 두 가지 내용을 동시에 가진다.
- 상속 구조
- 📜Animal
- 📜WeakAnimal
- 📜Pig
- 📜WeakAnimal
- 📜Animal
- 📜Animal
- 모든 동물들이 가지는 공통적인 부분
- 이름, 체력 등등
- 애니메이션
- 걷기, 데미지, 죽음
- 모든 동물들이 가지는 공통적인 부분
- 📜WeakAnimal
- 약한 동물들이 가지는 공통적인 부분
- 데미지 입으면 👉 도망감
- 플레이어를 절대 공격하지 않음
- 📜Pig, 사슴, 토끼 등등..
- 약한 동물들이 가지는 공통적인 부분
- 📜StrongAnimal
- 강한 동물들이 가지는 공통적인 부분
- 데미지 입으면 👉 공격함
- 플레이어를 먼저 공격함
- 악어, 호랑이 등등..
- 강한 동물들이 가지는 공통적인 부분
- 완전히 동일하게 공통적인 부분은 그냥 상속.
- 동일하게 가지는 부분이나, 내용이 좀 다르다면
virtual
,override
로 재정의 - 완전 다른, 그 동물만의 새로운 특성이라면 새롭게 정의해주면 됨.
📜Animal.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Animal : MonoBehaviour
{
[SerializeField] protected string animalName; // 동물의 이름
[SerializeField] protected int hp; // 동물의 체력
[SerializeField] protected float walkSpeed; // 걷기 속력
[SerializeField] protected float runSpeed; // 달리기 속력
[SerializeField] protected float turningSpeed; // 회전 속력
protected float applySpeed;
protected Vector3 direction; // 방향
// 상태 변수
protected bool isAction; // 행동 중인지 아닌지 판별
protected bool isWalking; // 걷는지, 안 걷는지 판별
protected bool isRunning; // 달리는지 판별
protected bool isDead; // 죽었는지 판별
[SerializeField] protected float walkTime; // 걷기 시간
[SerializeField] protected float waitTime; // 대기 시간
[SerializeField] protected float runTime; // 뛰기 시간
protected float currentTime;
// 필요한 컴포넌트
[SerializeField] protected Animator anim;
[SerializeField] protected Rigidbody rigid;
[SerializeField] protected BoxCollider boxCol;
protected AudioSource theAudio;
[SerializeField] protected AudioClip[] sound_Normal;
[SerializeField] protected AudioClip sound_Hurt;
[SerializeField] protected AudioClip sound_Dead;
protected void Start()
{
currentTime = waitTime; // 대기 시작
isAction = true; // 대기도 행동
theAudio = GetComponent<AudioSource>();
}
protected void Update()
{
if (!isDead)
{
Move();
Rotation();
ElapseTime();
}
}
protected void Move()
{
if (isWalking || isRunning)
rigid.MovePosition(transform.position + transform.forward * applySpeed * Time.deltaTime);
}
protected void Rotation()
{
if (isWalking || isRunning)
{
Vector3 _rotation = Vector3.Lerp(transform.eulerAngles, new Vector3(0f, direction.y, 0f), turningSpeed); // turningSpeed 만큼 보간 됨
rigid.MoveRotation(Quaternion.Euler(_rotation));
}
}
protected void ElapseTime()
{
if (isAction)
{
currentTime -= Time.deltaTime;
if (currentTime <= 0) // 랜덤하게 다음 행동을 개시
ReSet();
}
}
protected virtual void ReSet() // 다음 행동 준비
{
isAction = true;
isWalking = false;
anim.SetBool("Walking", isWalking);
isRunning = false;
anim.SetBool("Running", isRunning);
applySpeed = walkSpeed;
direction.Set(0f, Random.Range(0f, 360f), 0f);
// RandomAction();
}
protected void TryWalk() // 걷기
{
currentTime = walkTime;
isWalking = true;
anim.SetBool("Walking", isWalking);
applySpeed = walkSpeed;
Debug.Log("걷기");
}
public virtual void Damage(int _dmg, Vector3 _targetPos)
{
if (!isDead)
{
hp -= _dmg;
if (hp <= 0)
{
Dead();
return;
}
PlaySE(sound_Hurt);
anim.SetTrigger("Hurt");
// Run(_targetPos);
}
}
protected void Dead()
{
PlaySE(sound_Dead);
isWalking = false;
isRunning = false;
isDead = true;
anim.SetTrigger("Dead");
}
protected void RandomSound()
{
int _random = Random.Range(0, 3); // 돼지의 일상 사운드는 3 개
PlaySE(sound_Normal[_random]);
}
protected void PlaySE(AudioClip _clip)
{
theAudio.clip = _clip;
theAudio.Play();
}
}
자식 클래스가 사용할 수 있도록 public을 제외한 모든 private 을 protected 로 보호 수준 올리기
- 저 많은 변수 들은 모든 동물들이 공통적으로 가지는 멤버 변수이다.
- 📜WeakAnimal, 📜Pig 가 그대로 가지게 됨
- 모든 동물들이 내용 그대로를 공통적으로 가지는 기능
- Start
- Update
- 이동 Move
- 회전 Rotation
- 행동 타이밍 + 다음 행동 개시 ElapseTime
- 걷기 TryWalk
- 사망 Dead
- 랜덤 사운드 재생 RandomSound
- 사운드 재생 PlaySE
- 모든 동물들이 공통적으로 가지고 있기는 하나 내용은 조금 다르게 가질 수 있는 기능 👉 자식 클래스에서 재정의 해야할 가상 함수
virtual
- 다음 행동 준비를 위한 초기화 Reset()
- 데미지 입었을 때 처리 Damage
📜WeakAnimal.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class WeakAnimal : Animal
{
public void Run(Vector3 _targetPos)
{
direction = Quaternion.LookRotation(transform.position - _targetPos).eulerAngles;
currentTime = runTime;
isWalking = false;
isRunning = true;
applySpeed = runSpeed;
anim.SetBool("Running", isRunning);
}
public override void Damage(int _dmg, Vector3 _targetPos)
{
base.Damage(_dmg, _targetPos);
if (!isDead)
Run(_targetPos);
}
}
자식 클래스가 사용할 수 있도록 protected 이상으로 보호 수준 올리기
- 모든 약한 동물들만이 내용 그대로를 공통적으로 가지는 기능
- 도망가기 Run
- 약한 동물들만 도망가기 때문
- 도망가기 Run
- 모든 동물들이 가지나 약한 동물일 땐 내용이 조금 다른 기능 👉 재정의
override
- 데미지 입었을 때 처리 Damage
- 약한 동물일 경우, 데미지를 입을 때 모든 동물들이 데미지를 입을 때 하는 행동에다가
- 추가로 도망가는 기능을 덧붙여 한다.
base.Damage(_dmg, _targetPos); if (!isDead) Run(_targetPos);
- 추가로 도망가는 기능을 덧붙여 한다.
- 약한 동물일 경우, 데미지를 입을 때 모든 동물들이 데미지를 입을 때 하는 행동에다가
- 데미지 입었을 때 처리 Damage
📜Pig.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Pig : WeakAnimal
{
protected override void ReSet()
{
base.ReSet();
RandomAction();
}
private void RandomAction()
{
RandomSound();
int _random = Random.Range(0, 4); // 대기, 풀뜯기, 두리번, 걷기
if (_random == 0)
Wait();
else if (_random == 1)
Eat();
else if (_random == 2)
Peek();
else if (_random == 3)
TryWalk();
}
private void Wait() // 대기
{
currentTime = waitTime;
Debug.Log("대기");
}
private void Eat() // 풀 뜯기
{
currentTime = waitTime;
anim.SetTrigger("Eat");
Debug.Log("풀 뜯기");
}
private void Peek() // 두리번
{
currentTime = waitTime;
anim.SetTrigger("Peek");
Debug.Log("두리번");
}
}
- 모든 동물들이 가지나 돼지일 땐 내용이 조금 다른 기능
- 다음 행동 준비를 위한 초기화 Reset()
- 돼지 만의 다음 행동 결정 함수인 RandomAction() 을 추가로 실행한다.
- 다음 행동 준비를 위한 초기화 Reset()
- 오로지 돼지들만이 가지는 기능
- 돼지만의 랜덤 행동들 결정 RandomAction
- 걷기는 모든 동물들이 가지는 기능이지만, 대기, 풀 뜯기, 두리번 거리는 것은 돼지만의 기능이다. Wait(), Eat(), Peek()
이름이 바뀐 멤버들은 컴파일 되면 할당 되었던게 사라지므로 다시 인스펙터에서 할당해 주어야 한다.
🚖 다형성
📜PickaxeController.cs
protected override IEnumerator HitCoroutine()
{
while (isSwing)
{
if (CheckObject())
{
if (hitInfo.transform.tag == "Rock")
hitInfo.transform.GetComponent<Rock>().Mining();
else if (hitInfo.transform.tag == "Twig")
hitInfo.transform.GetComponent<Twig>().Damage(this.transform);
else if (hitInfo.transform.tag == "Weak_Animal")
{
SoundManager.instance.PlaySE("Animal_Hit");
hitInfo.transform.GetComponent<WeakAnimal>().Damage(currentCloseWeapon.damage, transform.position);
}
isSwing = false;
Debug.Log(hitInfo.transform.name);
}
yield return null;
}
}
else if (hitInfo.transform.tag == "Weak_Animal")
{
SoundManager.instance.PlaySE("Animal_Hit");
hitInfo.transform.GetComponent<WeakAnimal>().Damage(currentCloseWeapon.damage, transform.position);
}
다형성에 의하여 GetComponent<Pig
»()… GetComponent<Rabbit
>()… 이렇게 나열하지 않아도 모든 약한 동물들을 GetComponent<WeakAnimal
>() 하나로 다 받을 수 있다. 다형성에 의하여 !
“Weak_Animal” 태그를 새로 만들고 돼지의 태그를 이걸로 바꾸어 주었다.
🌜 개인 공부 기록용 블로그입니다. 오류나 틀린 부분이 있을 경우
언제든지 댓글 혹은 메일로 지적해주시면 감사하겠습니다! 😄
댓글 남기기