Из опыта изучения различных стартер китов могу предположить, что на данный момент разработчики, использующие Unity, вообще очень редко используют какие-либо общепринятые шаблоны программирования. Для себя я вижу причину этому в небольшом времени жизни игры как продукта, плюс специфика самого Unity. Раз, два - и в производство, так сказать.
Если говорить о способе организации пользовательского интерфейса, для себя я решил, что оптимальная модель работы будет нечто аналогичное MVVM, плюс шаблон Команда.
В сцене создаем пользовательский интерфейс, затем объект-менеджер, который будет отвечать за интеграцию интерфейса с логикой. Назовем его, например, GUIImpl
. Класс должен быть наследником MonoBehaviour
для того, чтобы мы могли работать с ним в редакторе. В приведенном ниже примере я использую NGUI для самого интерфейса.
Следующим шагом необходимо создать класс-описание графического элемента, например, панели. Я делаю этот класс внутренним по отношению к GUIImpl
, но это на любителя.
[System.Serializable]
public class MenuPanel : GUIMenuBase {
public GameObject SettingsButton;
public GameObject CancelButton;
public GameObject ExitButton;
public GameObject ResetButton;
public override void Init() {
associateButton(NewGameButton, new NewGameCommand(this));
associateButton(SettingsButton, new SettingsCommand(this));
associateButton(CancelButton, new CancelCommand(this));
associateButton(ExitButton, new ExitCommand(this));
}
public override void Show() {
base.Show();
}
public override void Hide() {
base.Hide();
}
}
Как вы можете видеть, класс панели является наследником GUIMenuBase
. Этот класс специфичен для вашего пользовательского интерфейса. Приведу мою базовую функциональность.
[System.Serializable]
public abstract class GUIMenuBase : IGUIMenu {
public GameObject Instance;
protected Dictionary<GameObject, ICommand> associations = new Dictionary<GameObject, ICommand>();
protected List<GameObject> activeButtons;
public GUIMenuBase() {
}
public abstract void Init();
public GameObject GetGOInstance() {
return Instance;
}
public virtual void Show() {
GUILogic.ShowMenu(this);
}
public virtual void Hide() {
GUILogic.HideMenu(this);
}
public virtual bool CanShow(GameObject guiItem) {
return true;
}
public virtual bool IsShown(GameObject guiItem) {
return true;
}
public virtual bool IsAvalibale(GameObject guiItem) {
return true;
}
public virtual bool CanButtonClick(GameObject button) {
if (!associations.ContainsKey(button))
return false;
return associations[button].CanExecute(button);
}
public virtual void ButtonClick(GameObject button) {
if (!associations.ContainsKey(button))
return;
if (associations[button].CanExecute(button))
associations[button].Execute(button);
}
public virtual void OnSourceChanged() {
foreach(var item in associations) {
UIButton buttonScript = item.Key.GetComponent<UIButton>();
if (buttonScript != null) {
buttonScript.isEnabled = item.Value.CanExecute(item.Key);
buttonScript.enabled = true;
}
}
}
protected virtual void adjustPanelButtons() {
}
protected virtual void associateButton(GameObject guiItem, ICommand command) {
associate(guiItem, command);
UIEventListener.Get(guiItem).onClick += ButtonClick;
}
protected virtual void associate(GameObject guiItem, ICommand command) {
associations.Add(guiItem, command);
}
protected virtual void removeAssociation(GameObject guiItem) {
if (associations.ContainsKey(guiItem))
associations.Remove(guiItem);
}
}
IGUIMenu
public interface IGUIMenu {
void Show();
void Hide();
bool CanShow(GameObject guiItem);
bool IsShown(GameObject guiItem);
bool IsAvalibale(GameObject guiItem);
bool CanButtonClick(GameObject button);
void ButtonClick(GameObject button);
void OnSourceChanged();
}
IGUIMenu
содержит методы для работы с различными элементами. Я привел интерфейс работы только с кнопками. Далее реализуем действие, по клику на кнопки.
public class NewGameCommand : CommandBase {
public NewGameCommand(GUIMenuBase aPanel)
: base(aPanel) {}
public override void Execute(GameObject button) {
if (!CanExecute (button))
return;
DatabaseManagerFactory.GetDefaultDatabaseManager().Reset();
Application.LoadLevel(Application.loadedLevel);
}
}
public abstract class CommandBase : ICommand {
protected GUIMenuBase panel;
public CommandBase(GUIMenuBase aPanel) {
panel = aPanel;
}
public virtual bool CanExecute(GameObject button) {
return true;
}
public virtual void Execute(GameObject button) {
}
}
public interface ICommand {
void Execute (GameObject context);
bool CanExecute (GameObject context);
}
В принципе это все. Остается лишь реализовать класс GUILogic
, которой содержит логику работы панелей. Затем открываем редактор, находим наш объект для управления пользовательским интерфейсом и назначаем элементы различных панелей соответствующим полям. Таким образом, для каждой панели должен быть свой класс, который содержит все необходимые поля. Пожалуйста, обратите внимание на поле public GameObject Instance;
класса GUIMenuBase
. Instance
- это объект самой панели. Для большинства операций с панелью нам понадобиться ее корневой элемент.