전투 Scene 위주로 구현하다가, 이제 타이틀 Scene에서 무기, 케릭터등을 선택하면,
선택한 데이터(오브젝트)들이 전투 Scene에서 사용될 수 있도록 간단하게 테스트용으로 코드롤 짰다.
데이터는 잘 넘어왔고 전투 Scene도 문제없이 재생되고 있었다.
전투 Scene 재생 중에, 만약에 Player가 죽으면 현재 Scene을 다시 Load하도록 코드를 짰는데, 갑자기 아래와 같은 처음보는 오류가 발생하였다.
MissingReferenceException: The object of type 'PlayerController' has been destroyed but you are still trying to access it.
Your script should either check if it is null or you should not destroy the object.
UnityEngine.Component.GetComponent[T] () (at <685c48cf8f0b48abb797275c046dda6a>:0)
StateMachine.GetState[T] () (at Assets/Scripts/Unit/Both/State/StateMachine.cs:24)
StateMachine.ChangeState[T] () (at Assets/Scripts/Unit/Both/State/StateMachine.cs:31)
IdleState.OnUseWeaponState (UnityEngine.InputSystem.InputAction+CallbackContext obj) (at Assets/Scripts/Unit/Player/State/IdleState.cs:37)
UnityEngine.InputSystem.Utilities.DelegateHelpers.InvokeCallbacksSafe[TValue] (UnityEngine.InputSystem.Utilities.CallbackArray`1[System.Action`1[TValue]]& callbacks, TValue argument, System.String callbackName, System.Object context) (at Library/PackageCache/com.unity.inputsystem@1.4.4/InputSystem/Utilities/DelegateHelpers.cs:46)
UnityEngine.InputSystem.LowLevel.<>c__DisplayClass7_0:<set_onUpdate>b__0(NativeInputUpdateType, NativeInputEventBuffer*)
UnityEngineInternal.Input.NativeInputSystem:NotifyUpdate(NativeInputUpdateType, IntPtr)
public override void Enter()
{
AddListeners();
base.weapon.StartShooting();
this.ctrl.moveSpeedMultiplier = Mathf.Min(1f, base.statContainer[StatType.WalkSpeed].Modify(0.35f));
this.ctrl.faceMouse = true;
this.ctrl.currentStateName = "UseWeaponState";
}
public override void Exit()
{
RemoveListeners();
this.ctrl.moveSpeedMultiplier = 1.0f;
base.weapon.StopShooting();
}
private IEnumerator WaitToChangeState<T>() where T : PlayerState
{
RemoveListeners();
while (!base.weapon.shotReady)
{
yield return null;
}
this.ctrl.ChangeState<T>();
this._changeStateCoroutine = null;
yield break;
}
private void CanNothingToggleChange(object sender, bool isDisabled)
{
if (isDisabled)
{
this.ctrl.ChangeState<CanNothingState>();
}
}
void OnDestroy()
{
this.RemoveListeners();
}
void AddListeners()
{
base.playerInput.actions["Weapon"].Enable();
base.playerInput.actions["Reload"].Enable();
base.playerInput.actions["Weapon"].canceled += this.OnCancelFire;
base.playerInput.actions["Reload"].started += this.OnReloadAction;
base.ammo.OnAmmoChanged.AddListener(new UnityAction<int>(this.OnAmmoChanged));
this.ctrl.canNothing.ToggleEvent += this.CanNothingToggleChange;
}
void RemoveListeners()
{
base.playerInput.actions["Weapon"].canceled -= this.OnCancelFire;
base.playerInput.actions["Reload"].started -= this.OnReloadAction;
base.playerInput.actions["Weapon"].Disable();
base.playerInput.actions["Reload"].Disable();
base.ammo.OnAmmoChanged.RemoveListener(new UnityAction<int>(this.OnAmmoChanged));
this.ctrl.canNothing.ToggleEvent -= this.CanNothingToggleChange;
}
따라서 델리게이트나 UnityEvent를 해제하는 코드를 작성을하고, 이미 기존에는 InputAction에 등록된 Method를 없애는 코드는 존재해서, 버그를 고쳤겠니 싶었는데 여전히 버그가 발생했다.
이에따라 발생한 오류 에다가 유니티 포럼에서 InputAction을 키워드로 추가로 검색을해서 알아보니,
InputAction은 등록한 Method를 지울뿐만아니라, action자체도 비활성화 시켜야지 missingreferenceexception 가 발생하지 않는다는 것 까지 알게됐다.
이에따라 위 코드 처럼 모든 등록된 delegate들을 전부 비활성화 시키는 method를 만들고, 파괴될 때 호출되도록 OnDestroy Method를 만들어 이를 담당하게 했다.
그래서 요즘 Delegate나 Observer Pattern등 스크립트간 "느슨한 결합"을 참 좋아했는데, 뭣 모르고 쓰다보니 결국 크게 데였다. 확실히 책임없는 쾌락을 저지른 대가를 치른것 같다. 거의 한 2시간은 쩔쩔매고, 수정하고 하는데 1시간은 넘게 썼는데, 결국 고치고 여러 정보도 얻었으니 결론적으로는 값진 경험이였다.
p.s
후일담으로 ChatGpt한테 Code를 보내면서 같이 디버깅을 하려고 했는데, 이 친구는 "델리게이트를 해제하지 않으면, 메모리 누수가 발생할 수 있고, 각종 오류가 발생할 수 있다" 라는 사실은 알고있는데, 코드에서 이러한 문제가 발생할 수 있다는 것은 아직 찾기 힘들어 하는 것 같다. 또 가끔식 전혀 다른 답을 하고 있기는 한데, 없는 것 보단 나으니 요긴하게 쓰고는 있다.
하지만 이런 디테일한 정보는 포럼과 공식 문서를 뒤져가는게 아직은 더 나은 것 같다.
'Unity Engine' 카테고리의 다른 글
Player Dash, Skill CoolTime(Cooldown) (0) | 2023.04.10 |
---|---|
Flast White, Shader (0) | 2023.04.02 |
구글 스프레드 시트 - 유니티 연동 Asset (0) | 2023.03.23 |
Input System (0) | 2023.03.19 |
Singleton Class & Manager Class (0) | 2023.03.18 |