Unity — це рушій гри. C# — мова якою ти керуєш цим рушієм. Кожен скрипт у Unity — це клас що успадковує MonoBehaviour, спеціальний Unity-клас з купою вбудованих можливостей.
🎮
Передумова: Цей курс передбачає що ти вже знаєш базовий C# (класи, методи, типи). Якщо ні — спочатку пройди курс "C# з нуля".
Структура Unity скрипта
MyScript.cs
// Unity автоматично додає ці using при створенні скриптаusing UnityEngine; // основний namespace Unityusing System.Collections; // для Coroutinesusing System.Collections.Generic; // для List, Dictionary// Клас ОБОВ'ЯЗКОВО має мати ту ж саму назву що й файл!// Файл: MyScript.cs → клас: MyScriptpublic classMyScript : MonoBehaviour
{
// [SerializeField] — поле видне в інспекторі Unity,// але private (не доступне іншим скриптам)
[SerializeField] privatefloat speed = 5f;
// public — теж видне в інспекторіpublicint health = 100;
// [HideInInspector] — public але не в інспекторі
[HideInInspector] publicbool isReady;
voidStart()
{
// Виконується ОДИН РАЗ при появі об'єкта на сцені
Debug.Log("Скрипт запущено!");
}
voidUpdate()
{
// Виконується КОЖЕН КАДР (зазвичай 60 разів/сек)// Тут основна ігрова логіка
}
}
Чому назва файлу = назва класу?
Unity знаходить скрипти за назвою файлу і зіставляє їх з класом. Якщо назви не збігаються — скрипт не працюватиме. Це найпоширеніша помилка новачків!
⚠️
Правило Unity: Файл PlayerController.cs → клас public class PlayerController. Завжди однакові назви. Навіть регістр важливий!
Debug.Log — твій найкращий друг
Debugging.cs
// Виводить текст у Console вікно Unity
Debug.Log("Звичайне повідомлення");
Debug.LogWarning("Попередження!"); // жовтий трикутник
Debug.LogError("Помилка!"); // червоний хрестик// З форматуванням:float hp = 75.5f;
Debug.Log($"HP: {hp}"); // "HP: 75.5"// Показати об'єкт:
Debug.Log($"Позиція: {transform.position}");
// ВАЖЛИВО: Debug.Log() у Update() = 60 повідомлень/сек!// Використовуй лише для налагодження, видаляй з готового коду.
// КВІЗ
Який атрибут робить приватне поле видимим в Unity Інспекторі?
// УРОК 02 — LIFECYCLE
Lifecycle методи Unity
Unity автоматично викликає певні методи твого MonoBehaviour у визначені моменти. Знати їх порядок — критично важливо для написання правильного коду.
AwakeПерший з усіх. Ще до Start. Ідеально для ініціалізації посилань між компонентами.× 1
OnEnableЩоразу коли об'єкт вмикається. Підписка на події.× N
StartОдин раз перед першим Update. Ініціалізація яка потребує інших компонентів.× 1
UpdateКожен кадр. Основна ігрова логіка. Рух, ввід, таймери.× frame
FixedUpdateФіксований інтервал (50/сек). Фізичні операції з Rigidbody.× fixed
LateUpdateПісля всіх Update. Ідеально для камери що слідкує за гравцем.× frame
public classLifecycleDemo : MonoBehaviour
{
privateRigidbody2D _rb;
privatefloat _timer;
voidAwake()
{
// GetComponent тут — бо Awake іде до Start інших скриптів
_rb = GetComponent<Rigidbody2D>();
}
voidStart()
{
// Тут всі скрипти вже ініціалізовані, можна посилатись на них
_timer = 0f;
Debug.Log("Гравець готовий!");
}
voidUpdate()
{
// Time.deltaTime — час між кадрами (~0.016 сек при 60fps)// ЗАВЖДИ множ на deltaTime якщо хочеш fps-незалежний рух!
_timer += Time.deltaTime;
}
voidFixedUpdate()
{
// Фізика завжди тут, не в Update!
_rb.AddForce(Vector2.up * 5f);
}
voidLateUpdate()
{
// Камера слідує за гравцем — після того як гравець вже рухнувся
}
}
Time.deltaTime — ключ до fps-незалежності
DeltaTime.cs
// БЕЗ deltaTime — рух залежить від fps:
transform.Translate(Vector3.right * 0.1f);
// На 60fps: 0.1 × 60 = 6 units/сек// На 30fps: 0.1 × 30 = 3 units/сек ← вдвічі повільніше!// З deltaTime — однаково на будь-якому fps:
transform.Translate(Vector3.right * speed * Time.deltaTime);
// На 60fps: speed × 0.0166 × 60 = speed units/сек// На 30fps: speed × 0.0333 × 30 = speed units/сек ← однаково!// Таймер:privatefloat _cooldown = 0f;
voidUpdate()
{
_cooldown -= Time.deltaTime;
if (_cooldown <= 0f && Input.GetKeyDown(KeyCode.Space))
{
Shoot();
_cooldown = 0.5f; // 0.5 секунди між пострілами
}
}
// КВІЗ
Де правильно застосовувати фізичні операції з Rigidbody?
// УРОК 03 — TRANSFORM
Transform та Vector3
Transform — це компонент є в КОЖНОМУ GameObject у Unity. Він зберігає позицію, поворот та масштаб. Без нього неможливо нічого розмістити або зрушити у сцені.
Vector3 та Vector2
Vectors.cs
// Vector3 — struct з трьох float: x, y, z
Vector3 pos = new Vector3(1f, 2f, 0f);
Vector3 dir = new Vector3(0f, 1f, 0f); // вгору// Зручні константи:
Vector3.zero // (0, 0, 0)
Vector3.one // (1, 1, 1)
Vector3.up // (0, 1, 0)
Vector3.down // (0, -1, 0)
Vector3.left // (-1, 0, 0)
Vector3.right // (1, 0, 0)
Vector3.forward // (0, 0, 1) — тільки для 3D// Арифметика:
Vector3 a = new Vector3(1, 0, 0);
Vector3 b = new Vector3(0, 1, 0);
Vector3 c = a + b; // (1, 1, 0)
Vector3 d = a * 5f; // (5, 0, 0)// Довжина вектора (відстань від origin):float len = a.magnitude; // 1.0// Нормалізований — той самий напрям, довжина = 1:
Vector3 norm = a.normalized;
// Відстань між двома точками:float dist = Vector3.Distance(a, b); // ~1.41
Маніпуляції з Transform
TransformOps.cs
// Отримати позицію (повертає КОПІЮ Vector3, бо struct!)
Vector3 pos = transform.position;
// Встановити позицію (завжди assign цілий Vector3):
transform.position = new Vector3(5f, 0f, 0f);
// ❌ transform.position.x = 5f; → НЕ ПРАЦЮЄ (struct = копія)// ✓ var p = transform.position; p.x = 5f; transform.position = p;// Переміщення відносно поточної позиції:
transform.Translate(Vector3.right * speed * Time.deltaTime);
// Поворот:
transform.rotation = Quaternion.Euler(0f, 90f, 0f); // 90° по Y
transform.Rotate(0f, 45f * Time.deltaTime, 0f); // обертатись
transform.LookAt(target.transform); // дивитись на об'єкт// Масштаб:
transform.localScale = new Vector3(2f, 2f, 2f); // вдвічі більший// Плавне переміщення (Lerp):
transform.position = Vector3.Lerp(
transform.position,
targetPosition,
5f * Time.deltaTime // швидкість наближення
);
⚠️
Vector3 — це struct! Пам'ятаєш урок про struct? transform.position повертає КОПІЮ. Тому transform.position.x = 5f; змінює копію, не оригінал. Завжди присвоюй цілий Vector3.
// КВІЗ
Чому transform.position.x = 5f; не працює в Unity?
// УРОК 04 — INPUT
Input — клавіатура і миша
Unity має два способи отримати ввід від гравця: старий Input (простий) та новий Input System (гнучкий). Починаємо зі старого — він є в кожному проекті за замовчуванням.
InputBasics.cs
// ══ КЛАВІАТУРА ══// GetKey — утримується кнопка (кожен кадр поки натиснута)if (Input.GetKey(KeyCode.W))
MoveForward();
// GetKeyDown — тільки в момент натискання (один кадр)if (Input.GetKeyDown(KeyCode.Space))
Jump();
// GetKeyUp — тільки в момент відпусканняif (Input.GetKeyUp(KeyCode.LeftShift))
StopSprint();
// ══ ОСІ (плавне значення -1 до 1) ══float h = Input.GetAxis("Horizontal"); // A/D або ←/→float v = Input.GetAxis("Vertical"); // W/S або ↑/↓// Значення: -1 (ліво/низ), 0 (нічого), 1 (право/верх)// Плавно наростає/спадає (є інерція)// GetAxisRaw — без інерції, тільки -1, 0, 1:float rawH = Input.GetAxisRaw("Horizontal");
// ══ МИША ══// Кнопки: 0=ліва, 1=права, 2=середняif (Input.GetMouseButtonDown(0))
Shoot();
// Позиція миші на екрані (пікселі):
Vector3 mousePos = Input.mousePosition;
// Позиція миші у світі (2D):
Vector3 worldPos = Camera.main.ScreenToWorldPoint(Input.mousePosition);
Платформер — повний контролер руху
PlatformerMove.cs
public classPlatformerMove : MonoBehaviour
{
[SerializeField] privatefloat moveSpeed = 5f;
[SerializeField] privatefloat jumpForce = 10f;
[SerializeField] private LayerMask groundLayer;
privateRigidbody2D _rb;
privatebool _isGrounded;
voidAwake() => _rb = GetComponent<Rigidbody2D>();
voidUpdate()
{
// Горизонтальний рухfloat input = Input.GetAxisRaw("Horizontal");
_rb.velocity = new Vector2(input * moveSpeed, _rb.velocity.y);
// Стрибок — тільки якщо на земліif (Input.GetKeyDown(KeyCode.Space) && _isGrounded)
{
_rb.velocity = new Vector2(_rb.velocity.x, jumpForce);
_isGrounded = false;
}
// Flip спрайту при поворотіif (input != 0)
transform.localScale = new Vector3(
input, 1, 1// від'ємний x = дзеркало
);
}
voidOnCollisionEnter2D(Collision2D col)
{
if (col.gameObject.layer == LayerMask.NameToLayer("Ground"))
_isGrounded = true;
}
}
// КВІЗ
Яка функція повертає true лише в ОДИН кадр — у момент натискання кнопки?
// УРОК 05 — PHYSICS
Rigidbody та фізика
Rigidbody дозволяє Unity фізичному рушієві (PhysX/Box2D) керувати об'єктом: гравітація, зіткнення, сили. Замість вручну рахувати траєкторію — додаєш Rigidbody і Unity робить це сам.
PhysicsBasics.cs
public classPhysicsBasics : MonoBehaviour
{
privateRigidbody2D _rb;
voidAwake() => _rb = GetComponent<Rigidbody2D>();
voidFixedUpdate()
{
// ══ ШВИДКІСТЬ (velocity) ══// Прямо задати швидкість:
_rb.velocity = new Vector2(3f, _rb.velocity.y);
// Зберігаємо .y — щоб не скасувати гравітацію!// ══ СИЛА (force) ══// Поступово прискорює (реалістично):
_rb.AddForce(Vector2.right * 10f);
// Миттєвий поштовх (стрибок, вибух):
_rb.AddForce(Vector2.up * 500f, ForceMode2D.Impulse);
// ══ TORQUE — обертальна сила ══
_rb.AddTorque(5f);
// ══ ЗАМОРОЗИТИ ОСЬ ══// Через інспектор або:
_rb.constraints = RigidbodyConstraints2D.FreezeRotation;
}
voidUpdate()
{
// Читати velocity можна в Update:
Debug.Log($"Швидкість: {_rb.velocity.magnitude}");
// Обмежити максимальну швидкість:if (_rb.velocity.magnitude > 10f)
_rb.velocity = _rb.velocity.normalized * 10f;
}
}
💡
Rigidbody2D vs Rigidbody: Для 2D ігор — Rigidbody2D та Vector2. Для 3D — Rigidbody та Vector3. Не мішай їх!
Raycast — промінь запиту
Raycast.cs
// Raycast = невидимий промінь, перевіряє що на шляху// 2D: чи є земля під гравцем?
RaycastHit2D hit = Physics2D.Raycast(
transform.position, // звідки
Vector2.down, // напрям0.1f, // довжина
groundLayer // тільки шар "Ground"
);
bool isGrounded = hit.collider != null;
// Відлагодження — побачити промінь у Scene view:
Debug.DrawRay(transform.position, Vector2.down * 0.1f, Color.red);
// УРОК 06 — COLLISIONS
Колізії та тригери
Collider — фізична форма об'єкта (прямокутник, коло, тощо). Тригер — Collider без фізичного блокування, лише визначення перекриття.
Метод
Коли викликається
Тип
OnCollisionEnter2D
Фізичне зіткнення почалось
Collision
OnCollisionStay2D
Зіткнення триває
Collision
OnCollisionExit2D
Зіткнення завершилось
Collision
OnTriggerEnter2D
Об'єкт увійшов у тригер
Trigger
OnTriggerStay2D
Об'єкт всередині тригера
Trigger
OnTriggerExit2D
Об'єкт вийшов з тригера
Trigger
CollisionDemo.cs
public classCollisionDemo : MonoBehaviour
{
// ══ COLLISION — фізичне зіткнення ══voidOnCollisionEnter2D(Collision2D col)
{
// col.gameObject — об'єкт з яким зіткнулись
Debug.Log($"Зіткнення з: {col.gameObject.name}");
// Перевірка тегу — у Unity кожен об'єкт має тег:if (col.gameObject.CompareTag("Enemy"))
{
TakeDamage(10);
}
// Перевірка шару:if (col.gameObject.layer == LayerMask.NameToLayer("Ground"))
{
_isGrounded = true;
}
// Точка та нормаль зіткнення:
ContactPoint2D contact = col.contacts[0];
Debug.Log($"Точка: {contact.point}");
}
// ══ TRIGGER — зона без фізики ══voidOnTriggerEnter2D(Collider2D other)
{
// Підбір предмета:if (other.CompareTag("Coin"))
{
coins++;
Destroy(other.gameObject); // знищити монету
}
// Портал — телепортація:if (other.CompareTag("Portal"))
{
transform.position = spawnPoint.position;
}
}
private bool _isGrounded;
private int coins;
[SerializeField] private Transform spawnPoint;
private voidTakeDamage(int dmg) { }
}
💡
CompareTag vs ==: Завжди використовуй CompareTag("Enemy") замість tag == "Enemy". CompareTag ефективніший і не генерує garbage collection.
// КВІЗ
Яку різницю між Collision і Trigger подіями?
// УРОК 07 — GETCOMPONENT
GetComponent та посилання між об'єктами
GetComponent — один з найважливіших методів Unity. Він дозволяє одному скрипту знайти та звернутись до іншого компонента або скрипта на тому ж або іншому GameObject.
GetComponentDemo.cs
public classPlayerAttack : MonoBehaviour
{
// ══ НА ТОМУ Ж GAMEOBJECT ══privateRigidbody2D _rb;
privateAnimator _anim;
privatePlayerHealth _health; // наш власний скрипт!voidAwake()
{
// Кешуємо в Awake — GetComponent дорогий виклик!
_rb = GetComponent<Rigidbody2D>();
_anim = GetComponent<Animator>();
_health = GetComponent<PlayerHealth>();
}
// ══ НА ДОЧІРНЬОМУ ОБ'ЄКТІ ══
[SerializeField] private Transform weaponHolder;
privateSpriteRenderer _weaponSprite;
voidStart()
{
_weaponSprite = weaponHolder.GetComponent<SpriteRenderer>();
}
// ══ НА БАТЬКІВСЬКОМУ ОБ'ЄКТІ ══voidFindParentScript()
{
var parent = GetComponentInParent<CharacterStats>();
}
// ══ ЗНАЙТИ ПО СЦЕНІ ══voidFindOnScene()
{
// Знайти ОДИН об'єкт (повільно, не в Update!):GameManager gm = FindObjectOfType<GameManager>();
// Знайти за тегом:
GameObject player = GameObject.FindWithTag("Player");
// Знайти за ім'ям:
GameObject obj = GameObject.Find("SpawnPoint");
}
voidAttack()
{
// Використовуємо закешований компонент:
_anim.SetTrigger("Attack");
Debug.Log($"HP перед атакою: {_health.CurrentHP}");
}
}
⚠️
Кешуй GetComponent! Виклик GetComponent() в Update() кожен кадр — це повільно. Завжди кешуй у Awake() або Start() в приватну змінну.
// УРОК 08 — COROUTINES
Coroutines — асинхронні задачі
Coroutine — метод що може "призупинитись" і продовжитись пізніше. Ідеально для анімацій, таймерів, spawn хвиль ворогів — всього що потребує затримки без блокування гри.
Coroutines.cs
public classCoroutineDemo : MonoBehaviour
{
voidStart()
{
// Запустити coroutine:StartCoroutine(SpawnWave());
StartCoroutine(FlashRed());
// Зупинити всі:// StopAllCoroutines();// Зупинити конкретну:Coroutine c = StartCoroutine(SpawnWave());
StopCoroutine(c);
}
// ══ Coroutine ЗАВЖДИ повертає IEnumerator ══
IEnumerator SpawnWave()
{
Debug.Log("Хвиля 1...");
SpawnEnemy();
yield return new WaitForSeconds(2f); // ← чекаємо 2 секунди
Debug.Log("Хвиля 2...");
SpawnEnemy();
SpawnEnemy();
yield return new WaitForSeconds(3f);
Debug.Log("Боса хвиля!");
SpawnBoss();
}
// Мигання при пораненні:
IEnumerator FlashRed()
{
SpriteRenderer sr = GetComponent<SpriteRenderer>();
for (int i = 0; i < 3; i++)
{
sr.color = Color.red;
yield return new WaitForSeconds(0.1f);
sr.color = Color.white;
yield return new WaitForSeconds(0.1f);
}
}
// Типи yield:
IEnumerator YieldTypes()
{
yield return new WaitForSeconds(1f); // чекати секундиyield return new WaitForFixedUpdate(); // чекати FixedUpdateyield return new WaitForEndOfFrame(); // кінець кадруyield return null; // наступний кадрyield return new WaitUntil(() => _hp <= 0); // до умови
}
private int _hp = 100;
private voidSpawnEnemy() {}
private voidSpawnBoss() {}
}
// КВІЗ
Який тип повертає метод-coroutine?
// УРОК 09 — SCRIPTABLEOBJECTS
ScriptableObjects
ScriptableObject — спеціальний клас Unity для зберігання даних незалежно від сцени. Замість жорстко прописаних значень у коді — редаговані asset файли в Unity Editor.
WeaponData.cs
using UnityEngine;
// CreateAssetMenu дозволяє створити цей SO через контекстне меню Unity
[CreateAssetMenu(fileName = "NewWeapon", menuName = "Game/Weapon")]
public classWeaponData : ScriptableObject
{
publicstring weaponName = "Меч";
publicint damage = 25;
publicfloat attackSpeed = 1.0f;
publicfloat range = 1.5f;
public Sprite icon;
public AudioClip attackSound;
}
// ══════════════════════════════════════════// Використання у скрипті гравця:public classPlayerWeapon : MonoBehaviour
{
// Перетягни SO asset прямо в інспекторі!
[SerializeField] privateWeaponData currentWeapon;
voidAttack()
{
Debug.Log($"Атака {currentWeapon.weaponName}: {currentWeapon.damage} шкоди");
// Тепер зміна damage відбувається в Unity Editor,// а не в коді — дизайнеру не треба вміти програмувати!
}
voidSwapWeapon(WeaponData newWeapon)
{
currentWeapon = newWeapon;
Debug.Log($"Змінили на: {newWeapon.weaponName}");
}
}
🎮
Коли використовувати SO? Статичні дані що не змінюються під час гри: характеристики зброї, предметів, персонажів, рівнів. Це відокремлює дані від логіки — золоте правило хорошої архітектури!
// УРОК 10 — EVENTS
Events та делегати
Events (події) — механізм сповіщення. Один об'єкт "видає" подію, інші "слухають" і реагують. Це основа слабкого зв'язку між компонентами — кожен знає тільки що потрібно знати.
Events.cs
using System;
using UnityEngine;
using UnityEngine.Events;
// ══ C# EVENTS ══public classPlayerHealth : MonoBehaviour
{
// Action — делегат без параметрів або з параметрамиpublic static event Action OnPlayerDied;
public static event Action<int> OnHealthChanged; // передає intpublic static event Action<int, int> OnDamaged; // передає 2 intprivateint _hp = 100;
public voidTakeDamage(int dmg)
{
_hp -= dmg;
OnHealthChanged?.Invoke(_hp); // ?. = null-безпечний виклик
OnDamaged?.Invoke(dmg, _hp);
if (_hp <= 0)
{
_hp = 0;
OnPlayerDied?.Invoke();
}
}
}
// ══ ПІДПИСКА НА ПОДІЇ ══public classUIHealthBar : MonoBehaviour
{
voidOnEnable()
{
// += для підписки
PlayerHealth.OnHealthChanged += UpdateBar;
PlayerHealth.OnPlayerDied += ShowGameOver;
}
voidOnDisable()
{
// -= для відписки (ОБОВ'ЯЗКОВО щоб уникнути memory leak!)
PlayerHealth.OnHealthChanged -= UpdateBar;
PlayerHealth.OnPlayerDied -= ShowGameOver;
}
voidUpdateBar(int newHp)
{
Debug.Log($"UI оновлено: {newHp} HP");
// оновити полоску здоров'я на екрані
}
voidShowGameOver()
{
Debug.Log("Game Over!");
}
}
// ══ UNITYEVENT — для Inspector ══public classButton3D : MonoBehaviour
{
// UnityEvent видно і налаштовується прямо в Інспекторі!public UnityEvent onPressed;
voidOnTriggerEnter2D(Collider2D other)
{
if (other.CompareTag("Player"))
onPressed?.Invoke(); // викличе все що прив'язано в Інспекторі
}
}
// КВІЗ
Чому важливо відписатись від події (-=) в OnDisable?
// УРОК 11 — FINAL PROJECT
🎮 Фінал: архітектура реальної гри
Зведемо все разом. Ось повна архітектура простого 2D платформера що використовує всі концепції курсу: lifecycle, фізику, колізії, coroutines та events.
GameManager.cs — серце гри
using UnityEngine;
using UnityEngine.SceneManagement;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
// Singleton — один GameManager на всю груpublic classGameManager : MonoBehaviour
{
public staticGameManager Instance { get; private set; }
[Header("Game Settings")]
[SerializeField] privateint maxLives = 3;
[SerializeField] privatefloat respawnDelay = 2f;
[SerializeField] private GameObject enemyPrefab;
[SerializeField] private Transform[] spawnPoints;
privateint _score;
privateint _lives;
private List<GameObject> _activeEnemies = new();
// Events для UI та інших системpublic static event Action<int> OnScoreChanged;
public static event Action<int> OnLivesChanged;
public static event Action OnGameOver;
voidAwake()
{
// Singleton patternif (Instance != null) { Destroy(gameObject); return; }
Instance = this;
DontDestroyOnLoad(gameObject); // зберегти між сценами
}
voidStart()
{
_lives = maxLives;
PlayerHealth.OnPlayerDied += HandlePlayerDeath;
StartCoroutine(SpawnLoop());
}
voidOnDestroy()
{
PlayerHealth.OnPlayerDied -= HandlePlayerDeath;
}
public voidAddScore(int points)
{
_score += points;
OnScoreChanged?.Invoke(_score);
// LINQ: видалити мертвих ворогів зі списку
_activeEnemies = _activeEnemies
.Where(e => e != null)
.ToList();
}
private voidHandlePlayerDeath()
{
_lives--;
OnLivesChanged?.Invoke(_lives);
if (_lives <= 0)
{
OnGameOver?.Invoke();
StartCoroutine(LoadGameOver());
}
else
{
StartCoroutine(RespawnPlayer());
}
}
IEnumerator RespawnPlayer()
{
yield return new WaitForSeconds(respawnDelay);
SceneManager.LoadScene(SceneManager.GetActiveScene().name);
}
IEnumerator LoadGameOver()
{
yield return new WaitForSeconds(2f);
SceneManager.LoadScene("GameOver");
}
IEnumerator SpawnLoop()
{
while (true)
{
yield return new WaitForSeconds(5f);
if (_activeEnemies.Count < 5)
{
// Рандомна точка спавну:
Transform spawn = spawnPoints[Random.Range(0, spawnPoints.Length)];
GameObject enemy = Instantiate(enemyPrefab, spawn.position, Quaternion.identity);
_activeEnemies.Add(enemy);
}
}
}
}
Структура проекту
📁
Scripts/Player/
PlayerController.cs, PlayerHealth.cs, PlayerAttack.cs — все що стосується гравця
📁
Scripts/Enemies/
EnemyBase.cs (abstract), Goblin.cs, Orc.cs — наслідування в дії
📁
Scripts/Managers/
GameManager.cs, UIManager.cs, AudioManager.cs — сервіси гри
📁
ScriptableObjects/
WeaponData.cs, EnemyStats.cs, LevelConfig.cs — дані без логіки
📁
Prefabs/
Player.prefab, Goblin.prefab, Coin.prefab — готові ігрові об'єкти
🏆
Вітаємо з закінченням курсу Unity! Ти вивчив: MonoBehaviour, lifecycle, Transform/Vector3, Input, Rigidbody, колізії, GetComponent, Coroutines, ScriptableObjects та Events. Це вже достатньо щоб зробити свою першу справжню гру!
Наступні кроки
1
Зроби свою першу гру
Починай з малого: Pong, Snake, або простий платформер. Завершена маленька гра цінніша за незакінчений шедевр.
2
Вивчи Animator Controller
Анімації — важлива частина Unity. State Machine для анімацій повністю аналогічна до звичайної state machine в коді.
3
UI з Canvas та TextMeshPro
Кожна гра потребує меню, HUD, інвентар. Canvas + TMP — стандарт Unity.
4
Патерни: Singleton, Observer, State
Наступний курс в Academy — архітектура ігор та патерни проєктування.