Страницы

четверг, 29 августа 2013 г.

Менеджеры в Unity3d, новый подход

Как я уже говорил в предыдущем посте, разработчикам использующим Unity3d, свойственно использовать игровые объекты в их сценах, содержащие компоненты, которые действуют, как синглтоны, не так ли?
Обычно нам нужно, чтобы этот игровой объект был на сцене изначально, чтобы мы успели провести инициализацию(например найти все нужные инстансы на сцене) и после этого он будет готов принимать запросы. Кроме того, хотелось бы, чтобы этот объект существовал все время, для этого мы можем вызвать DontDestoyOnLoad(this); в нем.
Этот решение будет приемлимо, если у вас всего одна сцена и игра работает в одном ее контексте, но если у вас в проекте несколько сцен, например, с множеством уровней, все усложняется. Предположим, что у вас есть компонент, цель которого найти самые используемые объекты на уровне (такие как объект игрока) и закешировать их, предоставляя другим компонентам доступ к ним, через статические переменные. Проблемы со множеством сцен возникает в тот момент, когда вы переходите на другую сцену и не обновляете ссылки на все кешированные игровые объекты.
Я использую несколько другой подход, когда система представляет из себя Конечный Автомат. Позвольте мне объяснить:
Во первых, суть моей идеи в создании статического класса, который содержит в себе ссылки на все менеджеры в игре, например:
[RequireComponent(typeof(GameManager))]
[RequireComponent(typeof(ScreenManager))]
[RequireComponent(typeof(AudioManager))]
[RequireComponent(typeof(MissionManager))]
public class Managers : MonoBehaviour
{
    private static GameManager gameManager;
    public static GameManager Game
    {
        get { return gameManager; }
    }
 
    private static ScreenManager screenManager;
    public static ScreenManager Screen
    {
        get { return screenManager; }
    }
 
    private static AudioManager audioManager;
    public static AudioManager Audio
    {
        get { return audioManager; }
    }
 
    private static MissionManager missionManager;
    public static MissionManager Mission
    {
        get { return missionManager; }
    }
 
    // Use this for initialization
    void Awake ()
    {
        //Находим ссылки
        gameManager = GetComponent<GameManager>();
        screenManager = GetComponent<ScreenManager>();
        audioManager = GetComponent<AudioManager>();
        missionManager = GetComponent<MissionManager>();
 
        //Делаем этот игровой объект постоянным(persistant)
        DontDestroyOnLoad(gameObject);
    }
}
Как видите, у нас есть несколько ссылок, которые дают нам доступ ко всем необходимым менеджерам:
Managers.Audio.Play(transform.position, clip);
Очевидно, эти компоненты(такие как AudioManager) должны быть присоединены к этому игровому объекту, чтобы скрипт работал. Суть этой схемы в том, что все менеджеры должны быть "всегда готовы", как системные службы, и что у них не будет никаких зависимостей ни от какой конкретной сцены. Например, AudioManager может воспроизводить звуки в любой сцене, в любой момент времени.
Могут быть и другие менеджеры, для которых необходимы конкретные условия(сцены или состояния) для их работы. Так же возникает проблема с глобальным состоянием игры. Представляю вам невероятный класс GameManager:
public class GameManager : MonoBehaviour
{
    private GameState currentState;
    public GameState State
    {
        get { return currentState; }
    }
 
    //Changes the current game state
    public void SetState(System.Type newStateType)
    {
        if (currentState != null)
        {
            currentState.OnDeactivate();
        }
 
        currentState = GetComponentInChildren(newStateType) as GameState;
        if (currentState != null)
        {
            currentState.OnActivate();
        }
    }
 
    void Update()
    {
        if (currentState != null)
        {
            currentState.OnUpdate();
        }
    }
 
    void Start()
    {
        SetState(typeof(GameplayState));
    }
}
И его верный компаньон, абстрактный класс GameState:
public abstract class GameState : MonoBehaviour
{
    public abstract void OnActivate();
    public abstract void OnDeactivate();
    public abstract void OnUpdate();
}
Эти классы образуют простой Конечный Автомат, для различных состояний в вашей игре(пауза, игры, экран меню, и т.д.). Вы должны наследовать GameState класс, для того чтобы использовать эти классы. Состояние - это также и компонент на игровом объекте, который ответственен за активацию/деактивацию каждого игрового менеджера, который зависит от конкретного состояния. Для упорядочивания, я предлагаю присоеденять ваши состояния к игровым объектом с именем этого состояния, которые являются чайлдами игровых объектов менеджеров, вот так:
Manager object distribution
Расположение объектов менеджеров
Таким образом, у нас есть под-менеджеры, присоединенные к этим чайлдам и все прекрасно структурированно.
Надеюсь эта статья будет вам полезна. Позже, я напишу больше по этой теме.

Комментариев нет :

Отправить комментарий