2024. 1. 18. 03:56ㆍ카테고리 없음
부숴버린 오브젝트 복구하기! 그리고 부수기!
부숴버리겠다.
뭘?
당신을.. 지금 이것을 보고있는 당신!!!! 이 아니라 오브젝트를 부숴보자.
일단은 생각한 것이 있다.
어떻게 부수는가?
일단 그부분은 어제 생각해 보았다.
오브젝트의 역재생이 전부 끝마쳤을때에 부모오브젝트의 스크립트에 옵저버 패턴으로 불값이 전달된다는 것은 54일차 til에서 설명했다.**그렇다면 역재생이 끝났을 때 조각난 오브젝트들을 비활성화 시키고 완전체 오브젝트를 활성화 시키면** 마치 시간이 되돌아가 부숴진 조각들이 모여 완전한 형태를 이룬 모습처럼 보이게 될 것이다.**그런데 나는 조각들이 모여서 복구가 되는 모습도 구현하고 싶고 완전체 오브젝트를 파괴시키고도 싶었다.그러기 위해선 이 오브젝트들을 어떻게 하고싶은지 타입을 지정해야 했다.만약 이 오브젝트를 부수고 싶다면 DESTROY 복구하고 싶다면 RESTRORE을 타입으로 지정한다.** 물론 둘다 아니라면 NONE으로 지정하여도 된다. NONE은 그 물체를 일반 오브젝트로 지정하겠다는 뜻으로 타입이 NONE이 된다면 더이상 부숴지거나 복구되진 않는다. (생각해보면 NONE타입을 넣지 않고 상속하지 않은 ReplayRecorder스크립트를 넣어놓는게 더 좋을수도 있다는 생각이 들기도 했다. 그러면 녹화된 내용을 삭제하는 기능이 있으니..)
ParentObject
클래스의 핵심 메서드와 이벤트
상태변경이벤트
public Dictionary<DestroyedObject, (bool isDone, bool isClick)> childStates = new Dictionary<DestroyedObject, (bool, bool)>();
// 자식 오브젝트들의 상태를 저장하는 딕셔너리
private int doneCount = 0;
// 완료된 자식 오브젝트의 수를 세는 변수
// 이벤트 구독
private void OnEnable()
{
foreach (var child in GetComponentsInChildren<DestroyedObject>())
{
child.OnStateChange += HandleStateChange;
//타입이 NONE 이라면 비활성화
if (child.Type == ObjectPropertyType.NONE)
{
child.gameObject.SetActive(false);
}
}
}
// 이벤트 취소
private void OnDisable()
{
foreach (var child in GetComponentsInChildren<DestroyedObject>())
{
child.OnStateChange -= HandleStateChange;
}
}
HandleStateChange
메서드
HandleStateChange
메서드는 자식 오브젝트(DestroyedObject
)의 상태가 변경될 때 호출된다.
이 메서드는 자식 오브젝트의 상태를 파악하여 떄에 맞게 변경한다.
private void HandleStateChange(DestroyedObject child, bool isDone, bool isClick)
{
// 자식 오브젝트의 상태를 딕셔너리에서 조회
if (childStates.TryGetValue(child, out var state))
{
// 상태 변경에 따른 doneCount 조정
if (isDone && !state.isDone)
{
doneCount++;
}
else if (!isDone && state.isDone)
{
doneCount--;
}
}
// 자식 오브젝트의 상태 업데이트
childStates[child] = (isDone, isClick);
// 변경할 자식 오브젝트 리스트 생성
List<DestroyedObject> childrenToChange = new List<DestroyedObject>();
// 모든 자식 오브젝트를 순회하면서 상태 변경 대상 찾기
foreach (var childObject in childStates.Keys)
{
if (isClick && !childObject.isClick) // isClick 상태가 아닌 자식 찾기
{
childrenToChange.Add(childObject);
}
else if (!isClick && childObject.isClick) // !isClick 상태인 자식 찾기
{
childrenToChange.Add(childObject);
}
}
// 찾은 자식 오브젝트들의 상태 변경
foreach (var childObject in childrenToChange)
{
childObject.Recording(childStates[childObject].isDone, isClick);
}
// 파괴 또는 복구 코루틴 시작
if (child.Type == ObjectPropertyType.DESTROY && child.isDone)
{
StartCoroutine(DstroyCorutine());
}
else if (child.Type == ObjectPropertyType.RESTRORE && doneCount + 2 == childStates.Count)
{
StartCoroutine(RestroreCorutine());
}
}
RestroreCorutine
및 DstroyCorutine
메서드
이 두 코루틴은 오브젝트의 상태에 따라 특정 동작을 수행한다.
RestroreCorutine
은 모든 자식 오브젝트를 비활성화하고 첫 번째 자식 오브젝트를 활성화한다. 반면, DstroyCorutine
은 모든 자식 오브젝트를 비활성화하고 나머지 자식 오브젝트들을 활성화한다.
즉 오브젝트를 부수는 것이 DstroyCorutine
, 복구하는 것이 RestroreCorutine
이다.
private IEnumerator RestroreCorutine()
{
yield return new WaitForSeconds(1f);
// 모든 자식 오브젝트를 비활성화하고 첫 번째 오브젝트만 활성화
foreach (var childObject in childStates.Keys)
{
childObject.GetComponent<ReplayRecorder>().StopPlaybackAndClearFrames();
childObject.gameObject.SetActive(false);
}
transform.GetChild(0).gameObject.SetActive(true);
}
private IEnumerator DstroyCorutine()
{
yield return new WaitForSeconds(1f);
// 모든 자식 오브젝트를 비활성화하고 나머지 자식 오브젝트들을 활성화
foreach (var childObject in childStates.Keys)
{
childObject.GetComponent<ReplayRecorder>().StopPlaybackAndClearFrames();
childObject.gameObject.SetActive(false);
}
for (int i = 1; i < transform.childCount; i++)
{
transform.GetChild(i).gameObject.SetActive(true);
}
}
트러블슈팅
문제
오브젝트를 부수고 나서 나머지 잔해 오브젝트들이 전체적을 클릭이 먹히지 않음
즉..
한번 상태가 변화해서 비활성화했던 오브젝트(부서진 잔해)를 활성화 시키면
활성화된 오브젝트는 전체적으로 클릭이 먹히지 않고 개별적으로 작동하던 문제
시도
첫번째
if (isClick) // 마우스 클릭이 발생한 경우, 모든 자식 오브젝트의 상태를 업데이트합니다.
{
foreach (var childObject in childStates.Keys)
{
childObject.Recording(childStates[childObject].isDone, isClick);
}
}
한번 클릭하면 전체로 클릭을 알림으로 전달한다.
스택오버플로 발생
HandleStateChange와 Recording 메소드 간에 무한 재귀 호출이 발생
기각!
두번째
// childObject.gameObject.SetActive(false);
혹시 비활성화 시킨것이 문제일까 싶어 비활성화 문구를 주석처리 하고 시작
그럼에도 새로 활성화된 것들에게서 문제가 발생
하지만 비활성화라는게 뭔가 낌새가 이상함.
Hierarchy에서 보니 오브젝트가 비활성화 되어있음.
잠시만..
- Start 메서드: 이 메서드는 장면이 시작될 때 모든 활성 GameObject에서 호출됩니다. GameObject가 비활성화된 경우(비활성화) 'Start'가 호출되지 않습니다.
- 게임 오브젝트 활성화: 게임 후반부(장면이 시작된 후)에서 게임 오브젝트를 활성화하면 활성화 순간 Start가 호출됩니다. 이는 GameObject가 처음 활성화될 때만 발생합니다. 비활성화했다가 다시 활성화하면 Start가 다시 호출되지 않습니다.
- Awake 및 OnEnable 메서드: 대안으로 Awake 및 OnEnable 메서드를 사용할 수 있습니다. 'Awake'는 GameObject의 활성 여부에 관계없이 스크립트 인스턴스가 로드될 때 호출됩니다. 'OnEnable'은 스크립트가 활성화되면 호출되며, 이는 GameObject가 활성화될 때 발생합니다. GameObject를 비활성화했다가 다시 활성화하면 활성화될 때마다 OnEnable이 호출됩니다.
일단 이 부분을 보고 다시 해보았다.
그냥 원문 코드대로하면
DestroyedObject.cs
transform.parent.GetComponent<ParentObject>().childStates.Add(this, (false, false));
Debug.Log( transform.parent.GetComponent<ParentObject>().childStates.Count); //1,2,3,4,5,6,7,8,9 차례로 나옴
DestroyedObject.cs
private IEnumerator DstroyCorutine()
{
yield return new WaitForSeconds(1f);
// 모든 자식 오브젝트를 비활성화하고 나머지 자식 오브젝트들을 활성화
foreach (var childObject in childStates.Keys)
{
childObject.GetComponent<ReplayRecorder>().StopPlaybackAndClearFrames();
childObject.gameObject.SetActive(false);
}
for (int i = 1; i < transform.childCount; i++)
{
transform.GetChild(i).gameObject.SetActive(true);
}
}
childStates.Count // 1
이런식으로 나온다. 즉 부모오브젝트는 중간에 실행된 녀석은 알아채지 못한다. 왜지?
public delegate void StateChangeHandler(DestroyedObject sender, bool isDone, bool isClick);
public delegate void StateUpdateHandler(DestroyedObject sender, (bool isDone, bool isClick) state);
public event StateChangeHandler OnStateChange;
public event StateUpdateHandler OnStateUpdate;
void Start()
{
base.Start(); // 부모 클래스의 Start 메서드 호출
OnStateUpdate?.Invoke(this, (false, false));
//transform.parent.GetComponent<ParentObject>().childStates.Add(this, (false, false));
Debug.Log( transform.parent.GetComponent<ParentObject>().childStates.Count);
}
혹시나 싶어 이벤트로도 해보았다. 이것도 실패
왜인지는 모른다. 코드 어렵다 정말 내일 물어봐야지
실패
세번쨰
그래서 이번엔 비활성화를 모두 없애고 활성화 상태에서 시작한다.
public class ParentObject : MonoBehaviour
{
public Dictionary<DestroyedObject, (bool isDone, bool isClick)> childStates = new Dictionary<DestroyedObject, (bool, bool)>();
private int doneCount = 0;
private void OnEnable()
{
foreach (var child in GetComponentsInChildren<DestroyedObject>())
{
child.OnStateChange += HandleStateChange;
if (child.Type == ObjectPropertyType.NONE)
{
child.gameObject.SetActive(false);
}
}
}
....
그리고 코드로 만약 타입이 NONE이라면 (추후엔 타입말고 겟컴포넌트해서 없냐고 해도 될듯)
비활성화 시킨다.
이렇게 되면 처음에 넣은것만 인정해주는 동네바보형 스크립트가 잘 인식한다
뭐.. 힘들었다..