오늘은 팀프로젝트 완료 기념으로 트러블 슈팅을 해보자
우선 몇개의 스크립트를 살펴보자
차례대로
인터페이스 IInteractable
public interface IInteractable
{
string GetPrompt();
void Interact();
}
를 상속받은 AnimatedObject
public class AnimatedObject : MonoBehaviour, IInteractable
{
protected Animator Animat;
public AnimatedData Data;
private void Start()
{
if(!TryGetComponent<Animator>(out Animator animator))
{
Animat = this.gameObject.AddComponent<Animator>();
}
else
{
Animat = this.gameObject.GetComponent<Animator>();
}
}
public string GetPrompt()
{
return $"<b>{Data.ObjectName}</b>\n{Data.Description}";
}
public virtual void Interact()
{
Data.Interact(Animat);
}
public virtual void Temp()
{
if (Data.GetType() == typeof(Door) || Data.GetType() == typeof(AuditoryDoor))
{
Door data = (Door)Data;
data.Temp();
}
}
}
의 SO파일 데이터인 AnimatedData
public class AnimatedData : ScriptableObject
{
public RuntimeAnimatorController _controller;
public string ObjectName;
public string Description;
public virtual void Interact(Animator animator)
{
animator.runtimeAnimatorController ??= _controller;
}
}
를 상속받은 Door
[CreateAssetMenu(fileName = "Door", menuName = "New AnimatedObject/Door", order = 0)]
public class Door : AnimatedData
{
private Animator _animator;
private Key _key;
[SerializeField] private int DoorTag;
public bool IsLock = false;
public bool IsOpen = false;
public override void Interact(Animator animator)
{
base.Interact(animator);
_animator = animator;
if(IsOpen == true)
{
CloseDoor();
}
else
{
OpenDoor();
}
}
public virtual void Temp()
{
UnlockDoor();
}
private void OpenDoor()
{
if (IsLock == true)
{
if (GameManager.Instance.Player.HandItemData?.GetType() == typeof(Key))
{
_key = (Key)GameManager.Instance.Player.HandItemData;
if (_key.Tag == DoorTag)
{
IsLock = false;
AudioManager.Instance.PlaySfx("LockedDoorOpen");
_animator.SetBool("isLock", IsLock);
GameManager.Instance.Player.QuickSlot.UnEquipShow(GameManager.Instance.Player.HandItemData);
GameManager.Instance.Player.QuickSlot.CurEquipShow(GameManager.Instance.Player.QuickSlot.Slots[0].Data);
GameManager.Instance.Player.QuickSlot.RemoveItem();
if (QuestManager.Instance.CurQuestIndex == 0)
{
Debug.Log("퀘스트 0 완료");
QuestManager.Instance.QuestClearCheck(0);
}
GameManager.Instance.Player.HandItemData = null;
GameManager.Instance.Player.Equipment.EquipNew(GameManager.Instance.Player.QuickSlot.Slots[0].Data);
return;
}
}
AudioManager.Instance.PlaySfx("DoorLocked");
return;
}
else
{
//애니메이션 실행
AudioManager.Instance.PlaySfx("OpenDoor");
IsOpen = true;
_animator.SetBool("isOpen", IsOpen);
return;
}
}
private void CloseDoor()
{
if (IsOpen == true)
{
//애니메이션 실행
AudioManager.Instance.PlaySfx("CloseDoor");
IsOpen = false;
_animator.SetBool("isOpen", IsOpen);
return;
}
}
private void UnlockDoor()
{
if (IsLock == true)
{
IsLock = false;
}
}
}
정리하자면
IInteractable > AnimatedObject > AnimatedData > Door 순으로 연결이 되어있다.
정확한 상속 표기를 하자면
IInteractable | |||||
상속 | |||||
Item | InteratableObject | AnimatedObject | |||
의 데이터 SO | 의 데이터 SO | 의 데이터 SO | |||
ItemData | InteractableData | AnimatedData | |||
상속 | 상속 | 상속 | |||
Consumable | Equipable | Keypad | Note | Door | Drawer |
이렇게 될 것이다.
그렇다면 오브젝트가 Item인지 다른 것인지 구분하는 방법이 있을까?
여기서 나는 TryGetComponent<T>(out T)를 사용했는데
해당 오브젝트에서 GetComponent를 실행하고 성공한다면 True 아니라면 False를 반환 후
out 매개변수를 통해 성공한 결과를 반환한다.
예를 들어 레이를 통해 충돌하고 반환하는 오브젝트가 Interactable 오브젝트인지 그냥 아이템인지 구분하기 위해
private Camera _camera;
void Start()
{
_camera = Camera.main;
}
void Update()
{
Ray ray = _camera.ScreenPointToRay(new Vector3(Screen.width / 2, Screen.height / 2));
if (Physics.Raycast(ray, out RaycastHit hit, _range, TargetLayer))
{
if(hit.collider.gameObject.TryGetComponent<Item>(out Item item))
{
//Ray에 검출된 오브젝트는 아이템일 것
//왜냐하면 아이템만이 Item 스크립트 컴포넌트를 가지고 있을테니까
}
}
}
라는 조건문을 생성하게 되면 충돌한 오브젝트에서 Item스크립트가 발견된다면
해당 오브젝트는 아이템인 것으로 판별할 수 있게 된다.
이 방식을 사용하다가 어느날 Item의 SO데이터인 ItemData를 상속받은 Consumable 내부의 Use메소드가 필요해졌다.
처음엔 별 대수롭지 않게
ItemData를 상속받은 Consumable이니까 ItemData로 받아와서 TryGetComponent로 처리하면 되겠군 싶어서
행동으로 옮겼고
Argument Exception 에러가 발생하는 것을 확인했다.
원인은 SO파일은 컴포넌트가 아닌 필드 데이터이기 때문에 TryGetComponent로 가져올 수 없었고
여기서 두 가지의 방법이 생각났다.
하나는 새로운 상속 방식을 사용하는 것
IInteractable | |||||
상속 | |||||
Item | InteratableObject | AnimatedObject | |||
상속 | 상속 | 상속 | |||
Consumable | Equipable | Keypad | Note | Door | Drawer |
의 데이터 SO | 의 데이터 SO | 의 데이터 SO | |||
ItemData | InteractableData | AnimatedData |
Item을 상속하는 다른 데이터 타입을 만들어서 virtual - override된 Use메소드를 불러오는 방법인데
이 방법의 장점은 Item을 상속받은 데이터는 MonoBehaviour를 상속받기 때문에
TryGetComponent 등의 MonoBehaviour 메서드를 사용해서 편안한 작업 환경이 조성된다는 점
단점은 여태 만들어온 걸 다 날리고 새로 작업해야 한다는 점
애초에 저렇게 만들껄...
두 번째 방법은
public class InteractableObject: MonoBehaviour, IInteractable
{
public InteractableData Data;
}
위의 예시와 같은 방식으로 구성되어 있으니 InteractableObject A가 있다면
A.Data로 SO파일에 접근해서 ->
해당 형식을 내가 원하는 형식이 맞는 지 체크 ->
맞다면 해당 형식으로 데이터를 가져오기;
를 하는 방식이 있다.
단점은 진짜 모든 타입별로 조건문을 만들어서 해당 타입이 맞는지 확인해야 한다는 것이고
장점은 기존 작업환경을 부수지 않아도 된다는 것이다.
여러분은 SO파일을 상속하는 누를 범하지 마십시오
기존 환경을 부수기에는 시간이 너무 촉박해서 두 번째 방법으로 결정했고
아이템을 사용하려는데 해당 아이템이 Consumable 타입인지 Equipment 타입인지 확인 하는 방법은 다음과 같다.
if (item.ItemData.GetType() == typeof(Consumable))
{
Consumable _data = (Consumable)CurEquip.ItemData;
}
GetType()과 typeof()를 통해 내가 원하는 타입이 맞는지 비교해준 후
명시적 형변환을 통해 데이터를 담아서 사용했다.
TTE
1118 TIL - 플랫포머 2D 캐릭터 이동하기 (0) | 2024.11.18 |
---|---|
1112 TIL - 개인 프로젝트 진행 중 (0) | 2024.11.12 |
1030 TIL - C# 제네릭 싱글톤 (0) | 2024.10.30 |
1025 TIL - 오브젝트 풀을 활용한 효과음 구현 (1) | 2024.10.25 |
1017 TIL - 씬 전환 시 정보 이동 (0) | 2024.10.17 |