Настройка управления игрой для мобильных шутеров от первого лица в Unity

В этой статье мы создадим систему, аналогичную настройке элементов управления PUBG в Unity, где пользователи смогут заменять, изменять размер и непрозрачность кнопок, а затем сохранять ее для последующих сеансов.

Многие мобильные игры позволяют вам изменять расположение кнопок, которое будет предпочтительнее для вашего стиля игры. Эта система была значительно адаптирована такими популярными играми, как PUBG и Call of Duty, для расширения возможностей своих игроков играть в игру более чем двумя пальцами. В этой статье мы создадим систему, аналогичную системе настройки элементов управления PUBG в Unity, где пользователи смогут заменять, изменять размер и непрозрачность кнопок, а затем сохранять это для последующих сеансов.

После того, как вы пошагово выполните эту статью, ваш конечный результат должен выглядеть следующим образом:

Пример работы.

Начало работы с основами.

Для начала работы над проектом я создал базовый пользовательский интерфейс с кучей кнопок и изображений и разместил их на холсте пользовательского интерфейса. Я написал скрипт под названием GameButton.cs

public class GameButton: MonoBehaviour, IDragHandler, IPointerDownHandler { public string id; private Vector2 _buttonPosition; private Vector3 _movedPosition; private RectTransform _rectTransform; private UIManager _uiManager; private CanvasGroup _canvasGroup; public Vector2 DefaultSize; public Vector2 DefaultPosition; void Start() { id = gameObject.name; _canvasGroup = GetComponent<CanvasGroup> (); _rectTransform = GetComponent<RectTransform > (); _uiManager = FindObjectOfType<UIManager > (); LoadData(); } // Update is called once per frame void Update() { CalculatePosition(); } void CalculatePosition() { _buttonPosition = RectTransformUtility.WorldToScreenPoint(new Camera(), transform.position); _buttonPosition.x = Mathf.Clamp(_buttonPosition.x, 0, Screen.width); _buttonPosition.y = Mathf.Clamp(_buttonPosition.y, 0, Screen.height); RectTransformUtility.ScreenPointToWorldPointInRectangle(_rectTransform, _buttonPosition, new Camera(), out _movedPosition); transform.position = _movedPosition; } public void OnDrag(PointerEventData eventData) { transform.position = Input.mousePosition; } public void SetSizeAndAlpha(float size, float alpha) { _rectTransform.transform.localScale = DefaultSize * size; _canvasGroup.alpha = alpha; } public void OnPointerDown(PointerEventData eventData) { _uiManager.gameButton = this; _uiManager.SetButtonData(transform.localScale.x, _canvasGroup.alpha); }

Как вы можете видеть, приведенный выше скрипт реализует два интерфейса IDragHandler и IPointerDownHandler, мы используем эти два интерфейса, чтобы получить функции OnDrag и OnPointerDown. В OnDrag функции мы устанавливаем положение преобразования в положение нашего курсора мыши, то есть input.mousePosition это делается для перемещения перетаскиваемого объекта с помощью нашего курсора, как OnDrag вызывается системой событий каждый раз, когда указатель что-то перетаскивает.

Для получения дополнительной информации о OnDrag и IDragHandler ознакомьтесь с официальной документацией Unity.

После выполнения этого вам нужно назначить этот скрипт объекту, который вы хотите переместить, и тогда вы сможете перемещать перетаскиваемый объект по экрану. В функции обновления мы вызываем функцию calculate position для каждого кадра, чтобы зафиксировать движение объекта на концах экрана устройства. После этого мы создадим функцию с именемSetSizeAndAlpha, которая принимает два значения с плавающей точкой size и alpha, позже это будет использоваться с uiManager для настройки размера и прозрачности кнопки.

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

Обработка данных с помощью UIManager

Для изменения данных, таких как размер и прозрачность кнопок, мы будем использовать UIManager класс, который обрабатывает все ссылки, связанные с данными.

using System.Collections.Generic; using System.Linq; using UnityEngine; using UnityEngine.UI; public class UIManager: MonoBehaviour { public Slider SizeSlider; public Slider TransparencySlider; private List < GameButton > GameButtons; public GameButton gameButton { get; set; } private void Start() { GameButtons = FindObjectsOfType<GameButton> ().ToList(); SizeSlider.onValueChanged.AddListener(OnUpdate); TransparencySlider.onValueChanged.AddListener(OnUpdate); } void OnUpdate(float value) { gameButton.SetSizeAndAlpha(SizeSlider.value, TransparencySlider.value); } public void SetButtonData(float size, float alpha) { Debug.Log("Size set to " + size); TransparencySlider.value = alpha; SizeSlider.value = size; }

Теперь мы создадим пустой файл gameObject и назначим ему этот UIManager скрипт, затем назначим ссылки на наши ползунки через инспектор. Обязательно ограничьте значения ползунков по вашему желанию. В этом скрипте вы можете видеть, что у нас есть две ссылки на ползунок SizeSlider и TransparencySlider, эти ползунки используются для установки наших значений размера и прозрачности. Вы также можете увидеть созданный нами список типов gameButtons, в котором мы будем сохранять все наши экземпляры GameButton на протяжении всего времени выполнения игры. При начальной инициализации мы использовали FindObjectsOfType<GameButton>, который возвращает нам массив экземпляров игровых кнопок в нашей текущей среде выполнения, а затем мы преобразуем массив в список и назначаем его списку игровых кнопок. После этого мы добавили прослушиватель к нашим ползункам размера и прозрачности onUpdate (значение с плавающей запятой), который обновляется каждый раз, когда мы меняем значение нашего ползунка или используем его. В OnUpdate мы получаем доступ к нашему текущему элементу управления gameButton и устанавливаем его размер и альфа на соответствующее значение ползунка. Если вы помните, мы делали это в нашем gameButton скрипте каждый раз, когда указатель был направлен вниз.

//Reference of GameButton Script public void OnPointerDown(PointerEventData eventData) { _uiManager.gameButton = this; _uiManager.SetButtonData(transform.localScale.x, _canvasGroup.alpha); }

Итак, что мы делаем, так это сообщаем UIManager, что каждый раз, когда мы перетаскиваем GameButton и отпускаем ее, наша текущая отредактированная кнопка является текущей gameobject. Это помогает нам найти ссылку на перетаскиваемую кнопку и узнать ее отредактированное значение. Мы также вызвали _uiManager SetButtonData и отправили масштаб кнопки по оси X и альфа-группу canvas group, чтобы мы могли установить текущие значения кнопки для ползунка.

Сохранение и загрузка данных

После выполнения всех этих действий вы должны иметь возможность перемещать, изменять прозрачность и масштабировать кнопки по своему желанию, но после выхода из режима unity Play вы можете заметить, что он возвращается в положение по умолчанию, поэтому для этого нам придется создать систему сохранения и загрузки. Для этой системы сохранения и загрузки мы будем использовать PlayerPrefs и JsonUtility. Итак, для начала мы сначала создаем класс со всеми необходимыми типами данных, которые необходимо сохранить.

[Serializable] public class ButtonData { public string id=""; public float Size=1f; public float Alpha=1f; public float PosX=0f; public float PosY=0f; }

Здесь мы создали класс с именем ButtonData, который является сериализуемым и содержит необходимые данные, которые необходимо сохранить. id - это наш уникальный идентификатор кнопки, Size - это размер нашей кнопки, Alpha - прозрачность нашей кнопки и PosX и PosY - это наши значения rect преобразований anchoredPosition. Чтобы сохранить данные для каждой кнопки, мы затем создаем функцию, вызываемую SaveData в нашем GameButton скрипте.

public void SaveData() { ButtonData btnData = new ButtonData(); btnData.id = gameObject.name; btnData.Size = gameObject.transform.localScale.x; btnData.Alpha = _canvasGroup.alpha; btnData.PosX = _rectTransform.anchoredPosition.x; btnData.PosY = _rectTransform.anchoredPosition.y; PlayerPrefs.SetString(id + Constants.BTN_DATA,JsonUtility.ToJson(btnData)); }

В этой SaveData функции мы создаем новый объект нашего класса ButtonData, а затем присваиваем его переменным значения наших данных. Здесь мы присваиваем id имя нашего игрового объекта, размер - текущее localScaleX значение игрового объекта, альфа-значение альфа-группы GameObject Canvas Group, а PosX и PosY - recttransform привязанное положение кнопки. После присвоения значения мы используем playerprefs для сохранения класса в JSON с его уникальным идентификатором. Вы можете видеть, что мы упаковываем значения в JSON и сохраняем его в виде строки в prefs, так что нам не придется назначать все эти данные по отдельности, а также это позволяет управлять данными. Вы могли заметить, что мы добавили – id – в нашу постоянную строку, мы делаем это для того, чтобы сохранить данные нашей кнопки по отдельности и получить доступ к уникальным данным с помощью идентификатора нашей кнопки. После написания функции мы переходим в наш UIManager и добавляем функцию.

//Reference of UIManager Script public void Save() { foreach(var btn in GameButtons) { btn.SaveData(); } }

Позже вы можете назначить это событию кнопки, и это приведет к циклическому просмотру списка всех наших функций GameButtons и вызову их SaveData функцию.


Теперь, после завершения части сохранения, нам нужно найти способ загрузить данные, чтобы создать новую функцию в нашем GameButton.cs called LoadData().

public void LoadData() { ButtonData btnData = JsonUtility.FromJson <ButtonData> (PlayerPrefs.GetString(id + Constants.BTN_DATA, "")); if (btnData == null) { return; } SetSizeAndAlpha(btnData.Size, btnData.Alpha); _rectTransform.anchoredPosition = new Vector2(btnData.PosX, btnData.PosY); }

Здесь, в этой функции, мы делаем то же самое, что и SaveData, но наоборот, мы создаем локальную переменную области видимости с типом ButtonData, которую мы используем JsonUtility.FromJSON для распаковки данных, которые мы ранее сохранили в нашей playerprefs. Затем мы проверяем, имеет ли значение btnData значение null, и если это так, мы возвращаемся оттуда, но оно не равно null, мы устанавливаем размер кнопки и альфа-значение btnData.Размер и btnData.Alpha доступ к которым осуществляется через распакованный JSON для преобразования данных объекта, мы также устанавливаем привязанную позицию rectTransfrom к новым данным Vector2 с сохраненными PosX и PosY. Мы вызываем LoadData функцию в начале, чтобы данные автоматически загружались в первую очередь init.

Сброс данных.

public void ResetData() { SetSizeAndAlpha(1f,1f); _rectTransform.anchoredPosition = DefaultPosition; }

Это функция сброса данных в скрипте GameButton.cs

public void Reset() { foreach(var btn in GameButtons) { btn.ResetData(); } }

А это та же функция, но для UIManager.cs

Вышеупомянутая функция используется для возврата кнопки в состояние по умолчанию, вы можете видеть, что мы установили размер и альфа равны 1f, что является нашим размером и непрозрачностью по умолчанию, и мы установили RectTransform anchoredPosition на DefaultPosition который может быть назначен через инспектор. Чтобы вызывать эту функцию при каждом нажатии кнопки сброса, вы можете добавить приведенную ниже функцию в UIManager.cs и назначить ее кнопке сброса.

Спасибо за внимание.

Данная статья является переводом англоязычной статьи с сайта Yarsalabs от автора с ником Prerak Giri.

4K4K показов
147147 открытий
1 комментарий

Я понимаю что общаюсь с пастой, но за все

FindObjectsOfType ().ToList();
Debug.Log();

Готов руки оторвать оригинальному автору этой статьи. Да и вообще, не стоит стесняться и многие вещи в чистый .net выкидывать из юнити. Тот же UIManager можно статичным объектом запилить, и пусть все снизу стучатся к нему.

Ответить