Hyper Casual игра на Unity с нуля. #1 Начало

Всем привет. Хочу написать цикл обучающих статей на тему разработки Hyper Casual игры на Unity под мобильные платформы. С нуля и до полной готовности.

Чтобы немного упростить, не будем придумывать новую игру, а сделаем клон популярной игры Roller Splat от Voodoo (Google Play, App Store).

Примерный список того, что будем делать:

  • архитектура проекта;
  • геймплей;
  • UI;
  • редактор уровней;
  • локализации;
  • тесты;
  • реклама, аналитика, инапы.

Переходим к делу

Разработку будем вести на последней версии Unity (на текущий момент 2019.2.4f1). Качаем Unity Hub, устанавливаем, создаем новый проект.

Подготовим базовую структуру папок (будет меняться в процессе разработки).

Hyper Casual игра на Unity с нуля. #1 Начало

В папке Assets создадим две папки "_" и "AwesomeCompany". Вы можете назвать их по-другому. В первой мы будем хранить все файлы текущего проекта. Во вторую вынесем общие скрипты, которые можно будет использовать в других проектах.

Resources - важно назвать именно так, из этой папки потом мы сможем загружать данные во время игры. Ознакомиться с зарезервированными именами папок Unity можно здесь.

Resources/DynamicData - здесь будем хранить различные динамические данные (уровни, локализации и т.д.).

Resources/Gameplay - здесь объекты, которые используются непосредственно в геймплее (префабы шаров, стен, пола).

Resources/UI/Windows - очевидно, интерфейс.

Scenes - сцены. Скорее всего она будет одна.

Scripts - скрипты. Внутри будет деление по папкам.

Sprites - изображения, которые используются в интерфейсе.

Пишем скрипты

AwesomeCompany/Common/Singleton.cs - Базовый класс для всех синглтонов в проекте. Подробнее можно почитать, например, здесь.

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

using UnityEngine; namespace AwesomeCompany.Common { public class Singleton<T> : MonoBehaviour where T : Component { private static T _instance; private static bool _isApplicationQuitting; public static T instance { get { if (_isApplicationQuitting) return null; if (_instance != null) return _instance; _instance = FindObjectOfType<T>(); if (_instance != null) return _instance; var obj = new GameObject { name = typeof(T).Name }; _instance = obj.AddComponent<T>(); return _instance; } } public virtual void Touch() { } protected virtual void Awake() { if (_instance == null) { _instance = this as T; DontDestroyOnLoad(gameObject); } else Destroy(gameObject); } protected virtual void OnApplicationQuit() { _isApplicationQuitting = true; } } }

AwesomeCompany/Common/UI/WindowManager.cs - менеджер, к которому мы будем обращаться чтобы открывать или закрывать окна интерфейса. Загружает окна из папки Resources и добавляет на сцену, каждое следующее окно будет поверх предыдущего.

using System.Collections.Generic; using System.Linq; using UnityEngine; namespace AwesomeCompany.Common.UI { public class WindowManager : MonoBehaviour { public List<WindowBase> windows { get; } private int _sortingOrder; public WindowManager() { windows = new List<WindowBase>(); } public T Show<T>() where T : WindowBase { _sortingOrder++; var existWindow = windows.FirstOrDefault(w => w is T); if (existWindow != null) { existWindow.canvas.sortingOrder = _sortingOrder; return existWindow as T; } var type = typeof(T); var windowPrefab = Resources.Load<T>($"UI/Windows/{type.Name}"); var window = Instantiate(windowPrefab, transform, false); window.Bind(this).canvas.sortingOrder = _sortingOrder; windows.Add(window); return window; } public T Get<T>() where T : WindowBase { var existWindow = windows.FirstOrDefault(w => w is T); if (existWindow == null) return default; return existWindow as T; } public void Close(WindowBase window) { var lastWindow = windows.LastOrDefault(); if (lastWindow == window) _sortingOrder--; DestroyWindow(window); } public void Close<T>() where T : WindowBase { var lastWindow = windows.LastOrDefault(); if (lastWindow is T) { _sortingOrder--; DestroyWindow(lastWindow); return; } DestroyWindow(windows.LastOrDefault(w => w is T)); } public void CloseAll() { foreach (var window in windows) Destroy(window.gameObject); windows.Clear(); _sortingOrder = 0; } private void DestroyWindow(WindowBase window) { if (window == null) return; windows.Remove(window); Destroy(window.gameObject); if (windows.Count <= 0) _sortingOrder = 0; } } }

AwesomeCompany/Common/UI/WindowBase.cs - базовый скрипт окна, от него будут наследоваться все остальные окна.

using UnityEngine; namespace AwesomeCompany.Common.UI { [RequireComponent(typeof(Canvas))] public abstract class WindowBase : MonoBehaviour { public Canvas canvas { get; private set; } private WindowManager _windowManager; public WindowBase Bind(WindowManager windowManager) { _windowManager = windowManager; return this; } public virtual void Close() { _windowManager.Close(this); } protected virtual void Awake() { canvas = GetComponent<Canvas>(); } } }

_/Scripts/App.cs - Основной скрипт нашего приложения, в нем будут все сервисы, менеджеры и различные данные. Наследуется от синглтона - значит будет существовать в единственном экземпляре и к нему будет доступ из любого скрипта.

using AwesomeCompany.Common; using AwesomeCompany.Common.UI; using AwesomeCompany.RollerSplat.UI.Windows; using UnityEngine; namespace AwesomeCompany.RollerSplat { public class App : Singleton<App> { public WindowManager windowManager { get; private set; } protected override void Awake() { base.Awake(); windowManager = new GameObject("WindowManager").AddComponent<WindowManager>(); windowManager.transform.SetParent(transform); } private void Start() { // test window manager windowManager.Show<MainMenuWindow>(); } } }

_/Scripts/UI/Windows/MainMenuWindow.cs - скрипт окна главного меню. Пока что ничего не делает.

using AwesomeCompany.Common.UI; using UnityEngine; namespace AwesomeCompany.RollerSplat.UI.Windows { public class MainMenuWindow : WindowBase { private void Start() { Debug.Log("MainMenuWindow Start"); } } }

Настроим сцену

Создадим пустой объект, правой кнопкой в окне Hierarchy -> Create Empty, назовем его App и добавим на него скрипт App.cs. Этот объект будет существовать на протяжении всего времени пока запущена игра, даже если загрузить другую сцену.

Затем, создадим UI -> Canvas. Переименуем объект в MainMenuWindow и повесим на него скрипт MainMenuWindow. Выставим параметры скейла в компоненте Canvas Scaler в Full HD (1080x1920) портретная ориентация. Создадим префаб данного окна, перетащив его в _/Resources/UI/Windows и удалим его со сцены.

В методе Start скрипта App мы добавили тестовый показ окна MainMenuWindow. А в методе Start окна мы выводим в консоль сообщение для проверки работоспособности.

Запускаем игру, видим что в окне иерархии внутри объекта WindowManager создался объект MainMenuWindow и в консоли сообщение "MainMenuWindow Start".

На сегодня, пожалуй, это все.

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

141141
59 комментариев

большинство проектов и уроков дальше "#1 Начало" не заходят

23

Планирую все же закончить начатое :)

6

Чисто мои ощущения от поисков уроков по Visual Studio и SQL =\

Бро, это урок для какого уровня? Если для начинающих, то фейл. Ты выкладываешь сотню строк кода без малейших комментариев. Нуб не знает, чем отличается public WindowBase Bind(WindowManager windowManager) от windowManager = new GameObject("WindowManager").AddComponent<WindowManager>();
и что это значит. Если пишешь для про, то переходи сразу к объяснению реализации геймплея, а не создания менеджера окон, так как про это и так знает.

16

Длч ньюбов в геймдеве. Например, меня.
В загашнике большой опыт с c# и поверхостные знания Unity(). Так что для меня эти уроки самое то.

3

В данной части решил common скрипты не комментировать пока, использовать "AS IS" :)

Астрологи объявили урок по Юнити.
Количество игр выпускаемых в будущем может резко возрасти.
Качество упадёт.

3