지난주에 object별 Read 동작을 구현하기 위해 Net을 설계하면서, MonoBehaviour를 상속한 Net 추상 클래스를 만들고 Read() 메서드를 오버라이드 하도록 구현했다.
이 방식… MonoBehaviour를 상속해서 구현한다는 점에서 좀 아쉽다… 상속하고나면 나중에 사용할 땐 MonoBehaviour라는 이름이 가려져서 뭔가… 찝찝하다…
컴포넌트로 만들어 멤버로 갖는 형태로는 구현이 안되는지 다시 고민했고, 델리게이트를 사용해 구현 가능한 것을 확인했다!!
델리게이트로 바꿔보려니 문제 발생. 아래의 형태로 사용하고 싶은데, 그러려면 NetAction이 Component여야 한다. MonoBehaviour를 상속해야 하는데, 다른 Transform과 같이 GameObject로 clone 되지 않고 스크립트의 멤버로 할당 가능한 형태로 사용하고 싶은데 방법을 찾아야 한다.
// gameObject의 Read 호출
NetAction netAction = gameObject.GetComponent<NetAction>();
netAction.Read(inStream);
ScriptableObject를 상속해 구현하면 가능하다는 것을 보았다.
시도해봤다. 사용 방법이 다르다. 이건 GetComponent()를 할 수 없다.
아!!! 멍청이였따!!!
Shuttlecock 스크립트의 멤버로 NetAction을 갖는 게 아니라, Shuttlecock의 컴포넌트로 NetAction을 갖게 하면 되는 것이었다. 이걸 Shuttlecock 스크립트의 멤버에서 참조하면 해결되는 것이었다. 그러면 당연히 GameObject로 만들지 않기 때문에 Scene에도 없다!
이를 통해~ NetAction을 멤버로 가지면 커스텀 Read를 구현할 수 있음을 확인했다.
아래와 같이 일반화된 Read 호출 코드를 구현하기 위한 고군분투였다!!
// ReplicationManager.cs
// ...
// gameObject의 Read 호출
NetAction netAction = gameObject.GetComponent<NetAction>();
netAction.Read(inStream);
델리게이트를 사용하면 런타임에 실행할 함수를 찾기 때문에 abstract class 보다 4배는 더 늦을 수 있다고 한다. 더욱이 Read() 호출은 동기화 필요한 오브젝트가 많을수록 빈번히 호출될테니 성능에 영향을 줄 수 있는 코드이다. 다만 일반화가 잘 되었고, has-a 관계로 read 함수 동작을 구현했기 때문에 Shuttlecock 클래스와의 결합도도 낮다. 설계로는 아주 맘에 든다!!
개선하긴 했으나, Shuttlecock에서 Read() 메서드를 반드시 만들도록 강제하지 못하고 있어서 강제하고 싶어서 고민을 더 해보았는데, 방법이 있다! 우선 적어두고 다음에 개선해보자.
Shuttlecock의 멤버인 NetAction은 Read 함수 포인터를 갖고 있다. Shuttlecock 객체를 생성할 때 NetAction의 Read 함수 포인터도 반드시 초기화 하도록 NetAction 생성자를 강제하면 된다.