Створення та управління різними типами ворогів може бути складним завданням. Використовуючи Scriptable Objects в Unity, розробники можуть створити гнучку та модульну систему для визначення характеристик ворогів. У цій статті я розгляну, як використовувати Scriptable Objects для покращення управління об'єктами ворогів у грі.
scriptable objects spawning
Створення класу ScriptableObject
Спочатку створюю новий клас ScriptableObject
у папці Enemy Scripts, щоб визначити EnemyType
з їхніми префабами та налаштуваннями. Його завдання — зберігати дані для кожного типу ворога в грі. Щоб зробити клас ScriptableObject
, достатньо додати це слово після двокрапки, замінивши ключове слово Monobehaviour
.
Це визначення успадкування дозволяє класу EnemyType
наслідувати клас ScriptableObject Unity. Таке успадкування дуже важливе, оскільки дозволяє створювати контейнери даних, які не залежать від конкретних ігрових об'єктів і можуть бути повторно використані в різних сценах чи навіть проектах. У цьому випадку кілька ворогів будуть використовувати одні й ті ж контейнери даних.
public class EnemyData : ScriptableObject
Unity потрібно вказати, щоб створити пункт меню в вікні проекту для створення екземплярів цього скриптового об'єкта. Це дозволяє легко створювати та управляти активами даних ворогів. У класі потрібно оголосити атрибут:
[CreateAssetMenu(fileName = “New Enemy Data”, menuName = “Enemy Data”)]
Кожен тип ворога потребує змінної для збереження посилання на префаб, який є візуальним та функціональним представленням ворога. Префаб — це тип даних GameObject
, і оскільки це для префаба ворога, називатимемо його enemyPrefab
.
public GameObject enemyPrefab;
Згодом, наприклад, якщо у мене є префаб "Basic Enemy", я перетягну цей префаб у це поле в Інспекторі, коли створюватиму скриптовий об'єкт "BasicEnemyType".
Кожен тип ворога також потребує значення для визначення його швидкості, яке буде використовуватися скриптом руху ворога для визначення швидкості його переміщення в грі. Для зберігання цієї змінної використовується тип даних float
, і змінна матиме ім’я speed
.
public float speed;
Кожен ворог може зіткнутися з лазером, і коли це відбувається, його здоров'я зменшується, а коли воно досягає нуля, ворог знищується. Для збереження кількості зіткнень, які він може витримати до знищення, використовуємо тип даних int
і змінну health
.
public int health;
До цього скриптового об'єкта можна додати ще багато змінних, щоб зберігати додаткові властивості ворогів, такі як сила атаки, показники захисту, спеціальні здібності або будь-які інші дані, важливі для гри.
Кожен тип ворога також потребує змінної для збереження посилання на префаб, який є візуальним та функціональним представленням лазера.
Прив'язка префабу до змінної типу GameObject
дає можливість зберігати посилання на префаб. Оскільки це префаб для лазера ворога, називаємо його enemyLaserPrefab
.
public GameObject enemyLaserPrefab;
Повертаючись до редактора Unity:
- Клацніть правою кнопкою миші в Project View > Create > ScriptableObject, і з'явиться опція створити актив з назвою “Enemy Type”, що буде екземпляром класу
EnemyType
.
Після створення класу я перейменовую клас з NewEnemyType на EnemyType.
Створення активів ScriptableObject
Наступним кроком я створюю окремі Scriptable Object для кожного EnemyType і переіменовую їх так, щоб назва відповідала імені скрипта Enemy.
- У вікні проекту Unity клацніть правою кнопкою і перейдіть до Create > ScriptableObjects > EnemyType.
- Створіть окремий актив
EnemyType
для кожного ворога (ChasingEnemy
,SideToSideEnemy
,CirclingLeft
,CirclingEnemy
,SmartEnemy
,HorizontalEnemy
,Enemy
). - Призначте відповідні префаби, лазери та стандартні властивості (як-от швидкість і здоров'я) для кожного активу.
налаштування префабів та змінних у Scriptable об'єкті
[
Створення модульної архітектури гри в Unity за допомогою ScriptableObjects
Цей посібник надає поради та хитрощі від професіоналів для впровадження ScriptableObjects у виробництво.
unity.com
](https://unity.com/resources/create-modular-game-architecture-with-scriptable-objects-ebook?source=post_page-----d87cf7491e1e--------------------------------)
Зміни в SpawnManager
Наступним кроком я змінюю SpawnManager
, щоб використовувати активи ScriptableObject, які були створені у вікні проекту замість прямого посилання на префаби. На даний момент я закоментую всі окремі префаби ворогів і заміню їх на масив: private EnemyType[] enemyType;
закоментування окремих префабів і заміна їх на масив
На даний момент у циклі while
я закоментую умовний оператор, який перевіряє, чи зупинилися вороги, і оператор switch
, який використовувався для створення кожного типу ворога.
Після того як я визначив нову поведінку за допомогою ScriptableObjects, я видалю старе визначення, яке було закоментовано.
закоментування старого циклу while, який використовував оператор switch
Корутина IEnumerator SpawnEnemyRoutine()
замінить старий оператор switch
і кілька посилань на префаби масивом для більш ефективної системи ScriptableObject
.
використання масиву для обробки інстанціації типів ворогів ScriptableObjects
while (!_stopSpawning && enemiesSpawned < enemyCount)
створює ворогів, поки умова_stopSpawning
не спрацює і поки не буде досягнуто вказаної кількості ворогів.EnemyType selectedEnemyType = enemyTypes[Random.Range(0, enemyTypes.Length)]
випадковим чином вибирає ворога, використовуючи масивScriptableObject
, замінюючи операторswitch
, який використовувався для випадкового вибору ворога за допомогою цілого числа (int enemyType = Random.Range(0, 7)
).GameObject spawnedEnemy = Instantiate(selectedEnemyType.enemyPrefab, positionToSpawn, Quaternion.identity);
використовує одну команду для інстанціації префабу ворога з вибраногоScriptableObject
в обраній точці появи без обертання, тоді як операторswitch
раніше обробляв інстанціацію для кожного випадку.Enemy enemyScript = spawnedEnemy.GetComponent();
отримує компонентEnemy
з інстанційованого об'єкта.if (enemyScript != null) { enemyScript.Initialize(selectedEnemyType.speed, selectedEnemyType.enemyLaserPrefab); }
перевіряє, чи знайдений компонент enemyScript, після чого викликає методInitialize()
, щоб задати швидкість ворога та префаб лазера зScriptableObject
.spawnedEnemy.transform.parent = _enemyContainer.transform;
продовжує організовувати інстанційованого ворога під батьківським об'єктом контейнера ворогів для кращого управління ієрархією сцени.enemiesSpawned++;
збільшує лічильник для кожного створеного ворога.yield return new WaitForSeconds(3.0f);
додає затримку між появами ворогів, щоб встановити темп, який буде справедливим для гравця.
Модифікація кожного Scriptable Enemy Object
Тепер, коли кожен ворог став Scriptable Object завдяки визначенню скрипта EnemyType
як Scriptable Object (тобто шаблон для об'єктів гри ворогів), і SpawnManager
допомагає створювати екземпляри Scriptable Objects (selectedEnemyType
), я можу почати модифікацію кожного конкретного скрипта ворога, щоб ініціалізувати змінні в шаблоні Scriptable Object (тобто EnemyType
). Ось один приклад, але всі типи ворогів будуть мати це визначення або подібне. Метод Initialize()
дозволяє мені налаштувати ворога з конкретною швидкістю руху та типом лазера.
Це схоже на надання ворогу його "налаштувань" перед тим, як він почне діяти в грі.
ініціалізація швидкості та префабу лазера ворога
public void Initialize(float speed, GameObject laserPrefab)
отримує значенняspeed
(десяткове число) і префаб лазера, який буде перетягнуто та відпущено в інспектор кожного Scriptable Object._speed = speed;
приймає надану швидкість і зберігає її в приватній змінній з ім'ям_speed
._enemyLaserPrefab = laserPrefab;
зберігає в приватній змінній_enemyLaserPrefab
і дозволяє скрипту запам'ятовувати, який префаб лазера використовувати при стрільбі лазерами.
Виключення: Горизонтальні та Розумні вороги
Є два EnemyType
(и), які не з'являються в тому ж місці і не використовують той самий префаб лазера; це Горизонтальний Ворог (case 5
) і Розумний Ворог (case 6
).
випадок 5 і 6 мають конкретну точку появи і напрямок руху
Як керувати цими виключеннями? Я маю розширити EnemyType
ScriptableObject, щоб включити особливу поведінку спауна та прапорці. Пізніше їх можна буде знайти в інспекторі ScriptableObjects і налаштувати відповідно до спеціальних ворогів, які потребують особливих налаштувань спауна та руху.
нові прапорці для керування поведінкою спауна для конкретних ScriptableObjects
public bool requiresUniqueSpawnPosition = false;
оголошується та за замовчуванням встановлюється вfalse
, що вказує на те, чи потрібно ворогу спеціальне місце для спауна, чи він може використовувати стандартне.public Vector3 uniqueSpawnPosition;
зберігає точне місце, де ворог має з'явитися, коли зміннаrequiresUniqueSpawnPosition
встановлена вtrue
в інспекторі ScriptableObject.public bool requiresRotation = false;
встановлено вfalse
і визначає, чи потрібно ворогу повертатися з його стандартної орієнтації при спаунінгу.public Quaternion enemyRotation;
визначає бажану орієнтацію ворога, якщо зміннаrequiresRotation
встановлена вtrue
в інспекторі ScriptableObject.public bool isHorizontalEnemy = false;
за замовчуванням встановлено вfalse
і вказує, чи повинен ворог рухатися горизонтально, замість звичайного вертикального руху.
Повернувшись до редактора Unity, я можу застосувати змінні, оголошені в класі EnemyType : ScriptableObject
:
- вибрати ScriptableObject (Горизонтальний Ворог або Розумний Ворог)
- встановити прапорці для змінних, які стосуються мого Горизонтального Ворога і Розумного Ворога відповідно
налаштування ScriptableObject для Горизонтального Ворога щодо позиції спауна та відстеження до спауна другого
[
Поворот і орієнтація в Unity
Unity використовує систему координат лівої руки. У Unity ви можете використовувати як кути Ейлера, так і кватерніони для представлення…
docs.unity3d.com
](https://docs.unity3d.com/Manual/QuaternionAndEulerRotationsInUnity.html?source=post_page-----d87cf7491e1e--------------------------------)
Для Розумного Ворога є трохи інша конфігурація, оскільки він з'являється внизу сцени і повинен рухатися вгору. Для цього використовується тип даних Quaternion
, щоб представити поворот в Unity, що відображається в спадному меню інспектора. При використанні всередині Quaternion
уникатиме таких проблем, як блокування карданного валу, і вони відображаються як чотири компоненти: X, Y, Z, W
. В Unity компонент W кватерніона не відповідає безпосередньо осі, як X, Y, Z.
Натомість він представляє скалярну частину кватерніона і працює разом з іншими компонентами, щоб визначити величину та напрямок обертання в 3D просторі. Щоб повернути SmartEnemy
на 180 градусів навколо осі Z, встановіть значення Quaternion в інспекторі на: X: 0, Y: 0, Z: 1, W: 0
налаштування ScriptableObject для Розумного Ворога щодо повороту і позиції спауна
Модифікація SpawnManager для виняткових ігрових об'єктів
Наступним кроком я маю модифікувати SpawnManager, щоб врахувати два спеціальні сценарії спауна та контролювати поведінку спауна ворогів та їх розташування. Ці рядки координують, коли, де і як з'являються вороги, при цьому уникнувши надмірного спауна горизонтальних ворогів та забезпечуючи правильний спаун кожного ворога.
умови, які контролюють позицію спауна горизонтальних ворогів і відстежують їх, реалізовані
if (selectedEnemyType.isHorizontalEnemy && _horizontalEnemyActive)
перевіряє, чи є вибраний ворог горизонтальним, і запобігає спауну іншого, якщо один вже активний, забезпечуючи, щоб одночасно був лише один горизонтальний ворог.yield return null;
іcontinue;
працюють разом, щоб пропустити поточний кадр, коли горизонтальний ворог вже активний, гарантуючи, що подальші дії в цій ітерації циклу не виконуються, без зависання гри.
тернарні оператори
Vector3 positionToSpawn = selectedEnemyType.requiresUniqueSpawnPosition ? selectedEnemyType.uniqueSpawnPosition : new Vector3(Random.Range(-8f, 8f), 7, 0);
визначає позицію спауна для ворога за допомогою тернарного умовного оператора. Якщо ворог вимагає унікальної позиції, використовується задане значення; в іншому випадку, він спауниться випадково вздовж осі x в межах і трохи вище екрану.if (selectedEnemyType.isHorizontalEnemy) { _horizontalEnemyActive = true; }
позначає прапорець_horizontalEnemyActive
якtrue
, щоб запобігти появі кількох горизонтальних ворогів.
Модифікація класів Горизонтальний Ворог і Розумний Ворог
Я додаю метод Initialize()
як до Горизонтального Ворога, так і до Розумного Ворога, але оскільки вони використовують різні префаби лазерів, їм необхідні різні змінні для посилань на ці специфічні GameObject
-и. У випадку з горизонтальним ворогом, префаб лазера — це _homingMisslePrefab
, а для розумного ворога — _smartLaserPrefab
.
ініціалізація швидкості і префабу лазера для Горизонтального Ворога
ініціалізація швидкості і префабу лазера для Розумного Ворога
У класі Горизонтальний Ворог я додам два рядки коду для керування інстанцією:
скидання позиції Горизонтального Ворога по осі Y
float randomY = Random.Range(5f, 7f);
ініціалізує його на випадковій вертикальній позиції між 5 і 7 одиницями, і …transform.position = new Vector3(-14.85f, randomY, 0);
телепортує ворога до лівого краю екрану з новою випадковою вертикальною позицією, створюючи ефект зациклення.
[
Object.FindObjectOfType
Suggest a change Thank you for helping us improve the quality of Unity Documentation.
Хоча ми не можемо прийняти всі…
docs.unity3d.com
](https://docs.unity3d.com/6000.0/Documentation/ScriptReference/Object.FindObjectOfType.html?source=post_page-----d87cf7491e1e--------------------------------)
Я оголошую метод OnDestroy()
, щоб повідомити SpawnManager, що тепер він може інстанціювати Горизонтального Ворога, оскільки попередній був знищений.
пошук SpawnManager для повідомлення, що об'єкт гри був знищений
private void OnDestroy()
автоматично викликається, коли об'єкт горизонтального ворога знищується, очищаючи і повідомляючиSpawnManager
, коли ворог видаляється зі сцени.FindObjectOfType().OnHorizontalEnemyDestroyed();
шукає сцену на наявністьSpawnManager
і викликає методOnHorizontalEnemyDestroyed()
, щоб повідомитиSpawnManager
, що ворог був знищений, щоб він міг коригувати кількість ворогів або ініціювати новий спаун.
Призначення ScriptableObject в Unity Inspector
- відкрийте компонент SpawnManager в інспекторі.
- лівою кнопкою миші клікніть на Enemy Types > введіть кількість типів ворогів; у моєму випадку їх є 7
- перетягніть і вставте всі створені
EnemyType
ScriptableObjects в масивenemyTypes
уSpawnManager
.
налаштування типів ворогів у масиві, визначеному в скрипті SpawnManager
Невірне призначення функцій ворогам
Під час тестування виникла проблема з Горизонтальним Ворогом і Розумним Ворогом, яким SpawnManager
призначав EnemyShield
. Щоб усунути цю помилку, потрібно знайти спосіб виключити HorizontalEnemy
і SmartEnemy
з логіки ApplyShield
в оновленому SpawnManager
. Знову ж таки, я повернувся до EnemyType : ScriptableObject
і оголосив нову змінну.
змінна для керування (включення/виключення), чи можна призначити EnemyShield для ScriptableObject
У SpawnManager
метод ApplyShield
потребує умовного виразу, що перевіряє, чи не є тип ворога таким, у якого в інспекторі встановлено прапорець, що вказує, що він може отримати щит, інакше return
, щоб повторити процес перевірки умови на наступному спауненому об'єкті гри. Параметр типу даних EnemyType enemyType
додається, щоб використовувати для застосування щита.
умовний вираз для перевірки, чи є у об'єкта гри активною ця функція в його інспекторі ScriptableObject
Горизонтальний Ворог не матиме цієї опції увімкненою, але всі інші типи ворогів матимуть її включеною.
Горизонтальний Ворог не має щита, але Переслідуючий Ворог та інші типи ворогів матимуть його увімкненим
У методі SpawnEnemyRoutine()
в SpawnManager змінна selectedEnemyType
додається як параметр до методу ApplyShield()
і передається логіка.
передача логіки в метод, додаючи змінну як параметр
І, зрештою, момент істини
Повертаючись до редактора Unity, запустіть додаток, і хвиля ворогів з'являється, використовуючи масив для спауна ScriptableObjects з SpawnManager
.
спаун ScriptableObjects
Використання Scriptable Objects дає гнучку основу для ефективного управління кількома типами ворогів шляхом роз'єднання множини об'єктів гри подібного типу від SpawnManager.
Цей метод не лише спрощує управління об'єктами гри, але й покращує динаміку ігрового процесу.
Готові підняти свої навички розробки ігор на новий рівень? Підписуйтеся на більше порад щодо ефективного використання Unity!
[
Вступ до Scriptable Objects - Unity Learn
Scriptable Objects — це чудові контейнери даних. Їх не потрібно прикріплювати до GameObject у сцені. Вони можуть бути…
learn.unity.com
](https://learn.unity.com/tutorial/introduction-to-scriptable-objects?source=post_page-----d87cf7491e1e--------------------------------)
[
ScriptableObject
Перехід до програмування ScriptableObject — це контейнер даних, який можна використовувати для збереження великих обсягів даних, незалежно від…
docs.unity3d.com
](https://docs.unity3d.com/Manual/class-ScriptableObject.html?source=post_page-----d87cf7491e1e--------------------------------)
[
Архітектура вашого коду для ефективних змін та налагодження за допомогою ScriptableObjects | Unity
Залишайте свій код гнучким, зручним для керування та легким для налагодження, використовуючи архітектуру з ScriptableObjects.
unity.com
](https://unity.com/how-to/architect-game-code-scriptable-objects?source=post_page-----d87cf7491e1e--------------------------------)
Перекладено з: Scriptable Objects in Unity: Managing Game Objects Efficiently