基底クラスを消費するSingletonがあまり好みでないので、以下のようなServiceLocator的に参照を得るスタイルを試している。
ComponentLocator.cs
public class ComponentLocator {
static ComponentLocator instance;
public static ComponentLocator Instance {
get {
if (instance == null) {
instance = new ComponentLocator();
}
return instance;
}
}
Dictionary<Type, WeakReference> cache = new Dictionary<Type, WeakReference>();
public TComponent Get<TComponent>() where TComponent : Component {
Type compType = typeof(TComponent);
WeakReference compRef;
if (cache.TryGetValue(compType, out compRef)) {
var target = compRef.Target as TComponent;
if (target) {
return target;
} else {
Uncache(compType);
}
}
var comp = GameObject.FindObjectOfType<TComponent>();
if (comp) {
Cache(comp);
return comp;
}
GameObject go = new GameObject(compType.Name, compType);
comp = go.GetComponent<TComponent>();
if (comp) {
Cache(comp);
return comp;
}
return null;
}
// 初回のFindObjectOfTypeも回避したければ、Script Execution Orderを引き上げた後、Awake()でCache()を呼んでおく。
public void Cache(Component comp) {
cache[comp.GetType()] = new WeakReference(comp);
}
public void Uncache(Type compType) {
cache.Remove(compType);
}
public void ClearCache() {
cache.Clear();
}
}
こう使う。
void Awake() {
foo = ComponentLocator.Instance.Get<Foo>();
}
void Update() {
foo.bar();
// まぁ、Getは長命なコンポーネントならコスト小さいので以下でもよい。
ComponentLocator.Instance.Get<Foo>().bar();
}
対象のコンポーネントはシーンに置いといてもいいし、なければ呼び出し時に作られる。
シーンをまたいで存在しうるかはComponentLocatorでは関知しないので、必要ならコンポーネント実装側でDontDestroyOnLoadすること。
このスタイルが気に入っているのは、Singletonっぽいなと思いつつも考えるの後回しにしてて、とりあえずインスタンスはひとつしかないからAwake内でFindObjectOfTypeして使う。という怠惰な姿勢と合うこと。かな。