다형성
적 AI 및 Player 모두 포함하여 공유하는 기능을 가진, LivingEntity 클래스를 만들자.
느슨한 커플링을 사용해서, 어느 자식클래스인지 구체적으로 모르더라도, 부모가 가진 메서드면 호출하게끔 구현할 수 있다.
다형성 사용 패턴
- C#에서 부모클래스의 함수는 base로 접근할 수 있다.
이 정도만 추가로 숙지하면 될듯하다.
LivingEntity Class
- MonoBehaviour 및 IDamageable을 상속
- 체력
- 체력회복
- 공격당할수있음
- 살거나 죽는 상태가 존재함.
using System;
using UnityEngine;
// 생명체로서 동작할 게임 오브젝트들을 위한 뼈대를 제공
// 체력, 데미지 받아들이기, 사망 기능, 사망 이벤트를 제공
public class LivingEntity : MonoBehaviour, IDamageable {
public float startingHealth = 100f; // 시작 체력
public float health { get; protected set; } // 현재 체력
public bool dead { get; protected set; } // 사망 상태
public event Action onDeath; // 사망시 발동할 이벤트
// 생명체가 활성화될때 상태를 리셋
protected virtual void OnEnable() {
// 사망하지 않은 상태로 시작
dead = false;
// 체력을 시작 체력으로 초기화
health = startingHealth;
}
// 데미지를 입는 기능
public virtual void OnDamage(float damage, Vector3 hitPoint, Vector3 hitNormal) {
// 데미지만큼 체력 감소
health -= damage;
// 체력이 0 이하 && 아직 죽지 않았다면 사망 처리 실행
if (health <= 0 && !dead)
{
Die();
}
}
// 체력을 회복하는 기능
public virtual void RestoreHealth(float newHealth) {
if (dead)
{
// 이미 사망한 경우 체력을 회복할 수 없음
return;
}
// 체력 추가
health += newHealth;
}
// 사망 처리
public virtual void Die() {
// onDeath 이벤트에 등록된 메서드가 있다면 실행
if (onDeath != null)
{
onDeath();
}
// 사망 상태를 참으로 변경
dead = true;
}
}
두 군데 눈여겨볼만한 부분이 있다.
첫째, 아래에 주목하자. 이전에 공부한 Property이다.
public float health { get; protected set; } // 현재 체력
public bool dead { get; protected set; } // 사망 상태
public get, protected set으로 설정되어있다.
즉 클래스 외부에서 참조는 가능하지만 변경은 불가능하다는 뜻이다.
두 번째로, event에 주목하자.
public event Action onDeath; // 사망시 발동할 이벤트
event Action onDeath에서, onDeath는 사망시 실행할 메서드들이 등록된다.
Action이란?
Action 타입은 입력과 출력이 없는 메서드를 가리킬 수 있는 Delegate이다.
Delegate: 대리자 <- 메서드를 값으로 할당받을 수 있는 타입
즉, Action에는 void SomeFunction() 처럼 입력과 출력이 없는 메서드를 등록할 수 있고, 원하는 시점에 매번 실행이 가능하다.
Action 타입의 변수에는 += 로 메서드를 등록할 수 있다. 이 때 rhs의 메서드 끝에는 괄호없이 이름만 명시한다.
만약 괄호를 메서드 끝에 붙이게 된다면, 메서드를 '등록' 하는게 아니라 '실행' 후 그 리턴값을 Action타입의 변수에 할당하는 셈이 된다.
Event란?
event는 연쇄동작을 이끌어내는 사건이다.
event 자체는 일을 수행하지 않지만, 이벤트를 구독하는 처리들이 연쇄적으로 실행된다.
어떤 클래스에서 특정 사건이 일어났을 때, 다른 클래스에서 그것을 감지하고 관련된 처리를 할 수 있게 한다.
C#에서 이벤트는, Delegate를 클래스 외부로 공개함으로써 구현할 수 있다.
Delegate에 등록된 처리들을 "이벤트 리스너" 라고 한다.
이벤트는 자신의 명단에 등록된 메서드들의 내부구현을 몰라도 그들을 실행할 수 있다.
이벤트는 Tight Coupling 문제를 해소할 수 있다.
=> 특정 클래스가 다른 클래스에 강하게 결합되어있어, 코드를 유연하게 바꿀 수 없는 상태
예를 들어,
public class Player: MonoBehaviour{
public GameData gameData;
public void Die(){
gameData.save();
}
}
public class GameData:MonoBehaviour{
public void Save(){
Debug.Log("Save");
}
}
라면, Save()의 이름이 Save2()로 바뀐다고 했을 때, Player 클래스의 내용도 바꿔야 한다. 따라서 유연하지 못하다.
아래와 같이 구현하면 좀더 나은 방향의 구현이 가능하다.
public class Player: MonoBehaviour{
public Action onDeath;
public void Die(){
onDeath();
}
}
public class GameData:MonoBehaviour{
void Start(){
Player player = FindObjectOfType<Player>();
player.onDeath += Save;
}
public void Save(){
Debug.Log("Save");
}
}
이렇게 하면, Save()의 이름을 Save2() 로 바꾼다고 해도, Player의 코드는 변경할 필요가 없다.
Player 클래스는 단지 onDeath 이벤트를 구독하고만 있기 때문이다.
event
Delegate 타입 변수는 특별히 event를 붙여 선언할 수 있다.
어떤 Delegate 변수를 event로 선언하면, 해당 Delegate를 클래스 외부에서는 실행할 수 없게 된다.
이벤트를 소유하지 않은 측에서 멋대로 이벤트를 발동하는 것을 막을 수 있는 것이다.
LivingEntity.cs 코드분석
OnEnable()
-> virtual로 선언된 가상 메서드이지만 abstract와 다르게, 자식클래스측에서 구현이 필수는 아니다.
• interface는 메서드의 시그니처만 정의하고, 실제 구현은 제공하지 않습니다. 따라서 인터페이스를 구현하는 모든 클래스는 해당 메서드를 반드시 구현해야 합니다.
• virtual 메서드는 기본 구현을 제공하며, 자식 클래스는 필요에 따라 이를 재정의할 수 있습니다. 자식 클래스가 재정의하지 않으면 부모 클래스의 기본 구현이 사용됩니다.
virtual 을 사용해야, Loose Coupling을 활용할 수 있다.
-> 좀더 자세히: https://hongjinhyeon.tistory.com/93
-> 추가: https://zprooo915.tistory.com/11
당연히 protected를 사용했다. private를 사용하면 자식클래스가 접근을 못해서 확장을 못하니까.
• virtual 메서드는 protected나 public으로 선언할 수 있습니다. private로 선언할 수는 없습니다. virtual 메서드는 자식 클래스에서 접근하고 재정의할 수 있어야 하므로, private 접근 제한자는 사용할 수 없습니다.
'Development > Unity Engine' 카테고리의 다른 글
[Retro유니티] Shooter 구현 - IK를 사용한 손/발 위치와 총 위치 <-> Animation 대응 (1) | 2024.12.19 |
---|---|
[Retro유니티] Gun 스크립트 - Coroutine을 사용한 Shot, Reload 구현 (0) | 2024.12.19 |
[Retro유니티] 인터페이스를 통한 IDamageable 구현 및 Guns 객체생성 (0) | 2024.12.17 |
[Retro유니티] Cinemachine 을 통한 연출구현 (0) | 2024.12.15 |
[Retro유니티] 캐릭터 이동 구현 (1) | 2024.12.15 |