Давайте поговорим о Unity, хорошо? В частности, давайте поговорим о создании простого в использовании диспетчера ввода в Unity, который мы можем изменять из инспектора и иметь экземпляры в наших игровых сценах!

Разделение ввода при разработке кода значительно упрощает процесс разработки игр, поэтому я покажу вам, как создать собственный диспетчер ввода, позволяющий создавать схемы управления с помощью клавиатуры или геймпада с использованием устаревшей системы ввода Unity. Я использовал этот пользовательский диспетчер ввода в нескольких моих собственных 2D-проектах, и теперь вы можете использовать его в своих!

Отказ от ответственности. Да, я знаю, что сейчас [текущий год] и в Unity есть новая система ввода, но многие люди до сих пор ее не используют — и, вероятно, не будут, пока не будут вынуждены, поэтому я решил написать статью, которая поможет вам создать собственный менеджер ввода.

В итоге у вас должно получиться что-то вроде этого…

Самое приятное то, что вы можете экспортировать это в пакет, который вы можете использовать снова и снова в своих проектах! Итак, давайте начнем.

Прежде всего, создайте в сцене пустой GameObject и назовите его InputManager. Затем создайте новый сценарий C#, назовите его InputManager.cs и прикрепите к пустому игровому объекту.

Затем вы хотите открыть свой скрипт InputManager.cs и начать программировать!

Во-первых, вы хотите добавить эту строку в начало сценария InputManager.cs.

   public static InputManager instance { get; set; }

Эта строка гарантирует, что ваш InputManager.cs является статическим классом, к которому можно получить доступ из любого места, а также является геттером и сеттером, что означает, что вы можете возвращать значения и устанавливать их из других классов. Чтобы узнать больше о том, что делают геттеры и сеттеры НАЖМИТЕ ЗДЕСЬ

После этого вы создадите перечисление, содержащее все ваши схемы управления, которые можно изменить в инспекторе (а также в игре). Это значительно ускорит тестирование вашего ввода с несколькими схемами управления, а также позволит вам дать вашему игроку некоторую гибкость в том, как он играет. Я обычно по умолчанию использую для этого параметра клавиатуру, но вы можете установить по умолчанию контроллер, если хотите.

 public enum CONTROLS { Controller, Keyboard }
 public CONTROLS controlSetting = CONTROLS.Keyboard;

Далее мы хотим создать несколько делегатов событий. Делегаты по сути являются контейнерами для функций, и они могут быть очень полезны в подобных случаях. Если вам интересно узнать больше о делегатах, я написал еще одну статью об их использовании для перепрограммирования системы в старой игре, которую вы можете посмотреть ЗДЕСЬ.

В этом случае нам понадобятся два делегата. Один для обработки ввода из направлений (принимая в качестве параметра Vector2, так как это 2D-игра), а другой для обработки ввода из действий (принимая строку в качестве параметра).

public delegate void InputEventHandler(Vector2 direction);
public static event InputEventHandler onInputEvent;

public delegate void InputActionHandler(string action);
public static event InputActionHandler onActionInput;

Это позволит нам «подписываться» на входные события других скриптов и запускать код на основе этого статуса подписки.

Вы также должны убедиться, что ваш InputManager GameObject не уничтожается при загрузке сцены в Awake (если только вы не хотите добавлять объект в каждую сцену, но я не уверен, почему вы это сделали).

  private void Awake()
    {
        DontDestroyOnLoad(this);
        instance = this;
    }

Сладкий! На данный момент мы создали InputManagerGameObject, присвоили ему компонент InputManager.cs, создали класс доступны из всех скриптов и добавлены делегаты событий, на которые могут подписаться другие скрипты!

Затем мы хотим создать несколько переменных KeyCode, чтобы мы могли редактировать их из инспектора, если мы хотим изменить схему управления играми. В данном случае мы просто создадим настройки управления клавиатурой. В игре, для которой я создал этот, есть только клавиши перемещения и кнопка тире, так что этот пример будет содержать именно это. Вы можете добавить столько кодов клавиш, сколько вам нужно для вашего собственного проекта. В этом случае мы просто настроим основные элементы управления WASD и несколько действий.

  [Header("Keyboard Controls")]
    public KeyCode left = KeyCode.A;
    public KeyCode right = KeyCode.D;
  

    public KeyCode shield = KeyCode.LeftShift;
    public KeyCode pause = KeyCode.Escape;
    public KeyCode select = KeyCode.S;

Каждая из этих клавиш теперь может быть переназначена на разные вводы с клавиатуры с помощью инспектора, когда мы захотим! Круто, правда? Теперь давайте двигаться дальше…

Следующее, что мы хотим сделать, это убедиться, что наш менеджер проверяет ввод для каждого кадра. Мы делаем это, создавая функцию с именем KeyboardControls().

   private void KeyboardControls()
    {
        //movement input
        float x = 0f;
        if (Input.GetKey(left)) x = -1f;
        if (Input.GetKey(right)) x = 1f;

        Vector2 direction = new Vector2(x, 0);
        InputEvent(direction);

        //Action Inputs
        if (Input.GetKeyDown(shield)) onActionInput("Shield");
        if (Input.GetKeyDown(pause)) onActionInput("Pause");
        if (Input.GetKeyDown(select)) onActionInput("Select");
    }

По сути, мы определяем, что здесь делает каждый из кодов клавиш. Поскольку в этой игре режим персонажа игрока будет располагаться только по оси X, я определил только эту ось. Во-первых, я создал число с плавающей запятой, просто назвав его «x», и инициализировал его значение равным 0f. Затем, если я ввожу ключ, привязанный к команде «влево», он меняет значение на -1f, а если «вправо», он меняет значение на 1f.

После этого я создал новый Vector2 (тип, который нужно передать в обработчик события ввода) и присвоил значение x первому аргументу (x) в новом Vector2.

Если ввод действия нажат, вызывается делегат onActionInput, который передает строковое значение введенного ввода. Эта функция будет постоянно выполняться в функции Update(), если выбрано управление с клавиатуры:

 private void Update()
    {
        if (controlSetting == CONTROLS.Keyboard) KeyboardControls();
    }

Нам нужны еще две функции, которые вызывают каждого делегата:

  public static void InputEvent(Vector2 direction)
    {
        if (onInputEvent != null) onInputEvent(direction);
    }

    public static void InputActionEvent(string action)
    {
        if (onActionInput != null) onActionInput(action);
    }

По сути, эти функции всегда проверяют ввод направления и действия. Большой! Но как нам это использовать? Помните, я говорил, что мы используем делегаты событий, чтобы другие классы могли подписаться на диспетчер ввода? Что ж, этим мы и займемся!

В PlayerScript.cs мы подпишемся на обоих делегатов OnEnable() и отменим подписку на OnDisable(). (Примечание: насколько я знаю, отписываться на самом деле не нужно, но, насколько я знаю, это лучшая практика).

    private void OnEnable()
    {
        InputManager.onInputEvent += InputEvent;
        InputManager.onActionInput += ActionInputEvent;
    }

    private void OnDisable()
    {
        InputManager.onInputEvent -= InputEvent;
        InputManager.onActionInput -= ActionInputEvent;
    }

Это работает следующим образом: делегат события InputManager ВЫЗЫВАЕТ подписанную на него функцию (справа). Думайте об этом как о функции, которая вызывает функцию. Основное предостережение здесь заключается в том, что функция должна быть объявлена ​​в классе, в котором она подписана, и с ТАКИМ ИМЯ и ПАРАМЕТРАМИ, как определено в делегате события.

Итак, например, функция InputEvent в этом классе должна быть объявлена ​​следующим образом:

private void InputEvent(Vector2 direction)
 {
// SOME CODE....
}

Это сделает так, что всякий раз, когда вызывается InputEvent, код в этой функции будет выполняться. В моем случае я хочу, чтобы этот код контролировал движение игрока. У меня есть Vector2, который я создал в верхней части класса, которому будет присвоено переданное значение направления, а затем он будет использоваться для управления движением игрока.

public Vector2 inputDirection;

private void InputEvent(Vector2 direction)
{
inputDirection = direction;
 direction = new Vector2(direction.x, 0);
 transform.Translate(direction * movementSpeed * Time.deltaTime);
            if (inputDirection.x > 0)
            {
                playerSprite.flipX = false;
            }
            if (inputDirection.x < 0)
            {
                playerSprite.flipX = true;
            }
}

Здесь есть несколько других правил, специфичных для моего скрипта плеера, например, в каком направлении должен быть спрайт, когда нажата клавиша определенного направления. На самом деле это не основное внимание в этой статье, поэтому я не буду вдаваться в подробности здесь.

Та же концепция применяется к делегату события действия, который будет иметь правила для передачи предопределенных строк действия:

 private void ActionInputEvent(string action)
    {
    
        if (action == "Shield" && !isShieldUp)
         {
     isShieldUp = !isShieldUp;
                playerAnimator.SetBool("ShieldIsUp", true);

                if (shield != null)
                {
                    shield.transform.position = shieldUpPosition.position;
                }
            
}

И это почти все. Я могу расширить класс InputManager, чтобы включить любое количество входных данных, которое я хочу, и сохранить логику ввода четко отделенной от другой логики в игре!

Вы можете проверить игру, из которой взят этот пример кода, чтобы проверить InputManager в действии по ссылке ниже. Спасибо за прочтение!

https://tkgstudios.itch.io/skate-and-protect