Новая система GUI

Целью для написания новой структуры GUI стояла возможность отделить логику от визуального отображения, а также обезопасить себя от возможных ошибок дизайнера. Структура GUI приведена на рис.

В данной системе логика и отображение связаны двумя посредниками: синглтоном Displayer и хранилищем всех вьюверов GuiController. Задача дизайнера сводится к помещению префаба любого вьювера в дочерний объект GuiController, который при старте сцены получит все дочерние объекты, реализующие интерфейс IGuiTargetViewer. После чего подпишется на события синглтона Displayer на отображение или сокрытие окон. Также существуют вьюверы без generic компонента. Обычно они используются, если не нужно отображать конкретную модель, допустим простое окно. Рассмотрим код.

public class GuiController : MonoBehaviour
{
private readonly Dictionary<Type, IGuiTargetViewer> targetViewers = new Dictionary<Type, IGuiTargetViewer>();
private readonly Dictionary<Type, IGuiViewer> simlpeViewers = new Dictionary<Type, IGuiViewer>();
private void Awake()
{
foreach (var v in gameObject.GetGenericComponentsInChildren<IGuiTargetViewer>())
{
if (!targetViewers.ContainsKey(v.TargetType))
targetViewers.Add(v.TargetType, v);
if (v.HideOnAwake)
v.Hide();
}
foreach (var v in gameObject.GetGenericComponentsInChildren<IGuiViewer>())
{
if (!simlpeViewers.ContainsKey(v.GetType()) && !targetViewers.ContainsKey(v.GetType()))
simlpeViewers.Add(v.GetType(), v);
if (v.HideOnAwake)
v.Hide();
}
Displayer.Instance.DisplayTargetViewer += OnDisplayTargetViewer;
Displayer.Instance.Hided += OnSomethingNeedToHide;
Displayer.Instance.DisplayViewer += OnDisplaySimpleViewer;
Displayer.Instance.TargetHided += OnTargetHided;
}
private void OnDestroy()
{
Displayer.Instance.DisplayTargetViewer -= OnDisplayTargetViewer;
Displayer.Instance.Hided -= OnSomethingNeedToHide;
Displayer.Instance.DisplayViewer -= OnDisplaySimpleViewer;
Displayer.Instance.TargetHided -= OnTargetHided;
}
private void OnDisplayTargetViewer(object obj, IViewerTarget viewerTarget)
{
IGuiTargetViewer v = null;
Type type = null;
object target = obj;
if (viewerTarget != null)
{
type = viewerTarget.TargetType;
target = viewerTarget.Target;
}
else if (obj != null)
type = obj.GetType();
if (type != null && targetViewers.TryGetValue(type, out v))
{
v.Show(target);
}
}
private void OnDisplaySimpleViewer(Type type)
{
IGuiViewer v = null;
if (simlpeViewers.TryGetValue(type, out v))
v.Show();
}
private void OnSomethingNeedToHide(Type t)
{
IGuiTargetViewer v = null;
if (targetViewers.TryGetValue(t, out v))
{
v.Hide();
}
else
{
IGuiViewer v1 = null;
if (simlpeViewers.TryGetValue(t, out v1))
v1.Hide();
}
}
private void OnTargetHided(object obj)
{
IGuiTargetViewer v = null;
Type type = null;
if (obj != null)
type = obj.GetType();
if (type != null && targetViewers.TryGetValue(type, out v))
{
v.Hide();
}
}
}

GuiController имеет 4 обработчика событий, показать/спрятать вьювер с целью или тоже самое для простого вьювера. Отображение произойдет только в том случае, если в словаре найдет нужный вьювер. Таким образом мы избегаем вероятности NullReferenceException по неосторожности, ничего перетаскивать не нужно. Структура синглона Displayer представляет собой просто перенаправление событий, завуалированно от любого контроллера.

public sealed class Displayer : Singletone<Displayer>
{
public event Action<object, IViewerTarget> DisplayTargetViewer = delegate { };
public event Action<Type> DisplayViewer = delegate { };
public event Action<Type> Hided = delegate { };
public event Action<object> TargetHided = delegate { };
public void Display(object target)
{
EventSender.SendEvent(DisplayTargetViewer, target, null);
}
public void DisplayViewerWithTarget(IViewerTarget target)
{
EventSender.SendEvent(DisplayTargetViewer, null, target);
}
public void Display(Type viewerType)
{
EventSender.SendEvent(DisplayViewer, viewerType);
}
public void Hide(Type viewerType)
{
EventSender.SendEvent(Hided, viewerType);
}
public void Hide(object target)
{
EventSender.SendEvent(TargetHided, target);
}
}

Единственное, о чем следует позаботится, чтобы Awake у GuiController сработал раньше, чем у любого другого скрипта, который хочет что-либо отобразить. Иначе нужный вьювер просто не успеет попасть в словарь. Ошибки не будет, но и работать тоже не будет.

Далее предлагаю ознакомится с интерфейсом IGuiTargetViewer, базовым классом классом BaseViewer и абстрактным generic классом BaseGuiTargetViewer.

public interface IGuiTargetViewer
{
Type TargetType { get; }
bool HideOnAwake { get; }
void Show(object target);
void Hide();
}

Следует отметить возможность легко скрывать ненужные при старте сцены окна, дизайнер сам выставляет булевое поле HideOnAwake. У GuiControllerа проверяется это поле, как было показано выше.

public abstract class BaseViewer : MonoBehaviour
{
[SerializeField] protected GameObject objectToHide = null;
[SerializeField] protected bool hideOnAwake = true;
protected virtual void Awake()
{
CheckObjectToHide();
}
protected virtual void OnDestroy()
{
}
protected void CheckObjectToHide()
{
if (objectToHide == null)
objectToHide = gameObject;
}
}

Очень важно, что в структуре префаба появляется такой объект, как objectToHide, который выступает просто в виде хранилища, если вьювер нужно скрыть и скрывается именно objectToHide, чтобы к основной скрипт мог выполнять какие-либо функции, его можно было гетнуть и т.д.

public abstract class BaseGuiTargetViewer<TTarget> : BaseViewer, IGuiTargetViewer where TTarget : class
{
protected TTarget Target { get; set; }
Type IGuiTargetViewer.TargetType
{
get
{
return typeof (TTarget);
}
}
bool IGuiTargetViewer.HideOnAwake
{
get { return hideOnAwake; }
}
void IGuiTargetViewer.Show(object target)
{
Show(target as TTarget);
}
public virtual void Show(TTarget target)
{
Target = target;
CheckObjectToHide();
GameObjectHelper.SetActive(objectToHide, true);
}
public virtual void Hide()
{
Target = null;
CheckObjectToHide();
GameObjectHelper.SetActive(objectToHide, false);
}
}

Базовый класс длю любого вьювера, дает возможность переопределить методы Show и Hide. Следует отметить, что Target присваевается на вызове Show и обнуляется при вывозе Hide. Это очень важно для построения алгоритма отображения. Бывают ситуации, когда нужно начала отработать со старым Targetом, а потом переназначить его. Рассмотрим создание своего вьювера.

public sealed class CrystalsViewer : BaseGuiTargetViewer<CrystalsViewerData>
{
public override void Show(CrystalsViewerData target)
{
if (Target != null)
Target.Pouch.ResourceCountChanged -= OnResourceCountChanged;
base.Show(target);
if (Target == null) return;
Target.Pouch.ResourceCountChanged += OnResourceCountChanged;
ShowCrystals();
}
public override void Hide()
{
GameObjectHelper.SetActive(goldCrystalIcon, false);
foreach (var silverGoal in silverCrystalsIcons)
GameObjectHelper.SetActive(silverGoal, false);
if (Target == null) return;
Target.Pouch.ResourceCountChanged -= OnResourceCountChanged;
base.Hide();
}
}

Основная идея, чтобы вызвать Show только тогда, когда нужно назначить Target, обычно при первом обращении. Вот как это выглядит в сцене.

Из минусов у этой системы следует выделить не возможность использовать разные по типу Targets для одного вьювера. Сейчас ведется работа по устранению этого недочета.

0 ответы

Ответить

Want to join the discussion?
Feel free to contribute!

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *