🏠 Главная

🔗 Как код становится игрой в Unity

Освоив основы языка C#, многие новички сталкиваются с одной и той же проблемой: они понимают синтаксис, но не понимают, как применить его при создании игры.

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

В этом уроке мы разберёмся, как между ними появляется связь, и построим тот самый мост между кодом и объектами на сцене.

В Unity нет магии. Есть связи. И из этих связей рождается действие.

Игровые объекты — актёры сцены

Все объекты на сцене в Unity являются игровыми объектами (GameObject). При этом не важно, видите вы их или нет. Игрок, NPC, декорации, элементы интерфейса, камера — всё это игровые объекты.

Они могут отличаться по назначению, но всех их объединяет одно — они участники сцены.

Именно поэтому у каждого игрового объекта есть компонент Transform. Он отвечает за положение объекта в игровом мире: где он находится, куда повёрнут и какого размера.

Камера с открытым Inspector
У каждого GameObject есть компонент Transform

От чего зависит поведение объекта

Важно понять: игровой объект — это лишь оболочка. Сам по себе он ничего не делает. Его поведение полностью определяется тем, какие компоненты к нему добавлены.

Выше вы видели объект камеры. Помимо обязательного компонента Transform, у него были ещё два компонента: Camera и Audio Listener.

Компонент Camera позволяет объекту показывать нам сцену, а Audio Listener — слышать звуки, которые создают другие объекты.

Игровой объект — это контейнер. Его «умения» — это компоненты. Так что работает правило: "покажи свой компонент — скажу, кем ты работаешь" 🙂

Inspector — пульт управления реальностью

Добавим на сцену новый игровой объект. Например, обычный квадратный спрайт. Unity автоматически создаст GameObject с обязательным компонентом Transform и компонентом Sprite Renderer, который отвечает за отображение спрайта.

Выберите этот объект в Hierarchy и посмотрите на его Inspector. Перед вами — все компоненты, из которых сейчас «собран» этот объект.

Если GameObject — это актёр, то Inspector — его гримёрка и панель управления одновременно. Здесь видно всё, что определяет его поведение.

И самое интересное — вы можете менять параметры прямо во время игры.

Вы не пишете код. Вы просто меняете значения. Но объект уже реагирует.

Небольшой секрет: когда вы изменяете параметры в Inspector, Unity делает под капотом то же самое, что программист делает через код. Вы уже управляете движком — просто без набора текста.

Square с открытым Inspector
Square с открытым Inspector

Скрипт как компонент

Скрипт, который вы пишете на C#, тоже может быть компонентом. Пока он просто лежит в папке Assets, он ничего не делает. Это всего лишь текст.

Но стоит добавить его к игровому объекту — и он становится частью этого объекта. Теперь Unity начнёт выполнять ту логику, которую вы в нём описали. Ну… по крайней мере ту, что вы действительно написали 🙂

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

using UnityEngine;

public class TheBestScript : MonoBehaviour
{
    // Start is called once before the first execution of Update after the MonoBehaviour is created
    void Start()
    {
        
    }

    // Update is called once per frame
    void Update()
    {
        
    }
}

Когда вы запускаете игру, Unity сначала выполнит код в Start(), а затем будет вызывать Update() каждый кадр.

Теперь добавьте этот скрипт к объекту Square. В данный момент он ничего не делает — но у объекта появился новый компонент: TheBestScript.

Square с TheBestScript в Inspector
Square с компонентом TheBestScript

Связь логики и действия

Теперь подходим к самому важному моменту. Раньше мы меняли параметры компонентов через Inspector и сразу видели результат.

А изучая C#, вы уже познакомились с переменными, типами данных и работой со значениями. Осталось соединить эти два мира: сделать так, чтобы переменные в скрипте изменяли параметры компонентов.

Для этого скрипту нужно получить доступ к компоненту, с которым он будет взаимодействовать. Делается это с помощью метода GetComponent<ИмяКомпонента>().

Повторим наши действия из Inspector, но теперь через код.


using UnityEngine;

public class TheBestScript : MonoBehaviour
{
    // Переменная для хранения ссылки на SpriteRenderer
    private SpriteRenderer sr;

    void Start()
    {
        // Получаем ссылку на компонент SpriteRenderer
        sr = GetComponent<SpriteRenderer>();

        // Меняем цвет на красный
        sr.color = Color.red;
    }

    void Update()
    {
        // Вращение по оси Z
        transform.Rotate(0f, 0f, 90f * Time.deltaTime);
    }
}
  

Теперь объект сам меняет цвет при запуске и вращается каждый кадр. То, что раньше вы делали вручную, теперь делает скрипт.

Обратите внимание: компонент Transform есть у каждого объекта по умолчанию, поэтому к нему можно обращаться напрямую через transform, без GetComponent.

Заметьте также, что названия полей компонентов часто совпадают с тем, как вы обращаетесь к ним в коде. В начале придётся иногда заглядывать в документацию - или спрашивать помощника ИИ 🙂 - но со временем это станет привычным.

Самая частая ошибка

Ошибки в логике и NullReferenceException — самые частые ошибки, которые вы увидите при создании своих игр.

Пока проект маленький, вы ещё можете быть уверены, что нужный компонент точно есть на объекте. Но по мере роста проекта лучше добавлять проверку, чтобы избежать ошибки во время выполнения.

Более аккуратный вариант кода будет выглядеть так:


using UnityEngine;

public class TheBestScript : MonoBehaviour
{
    // переменная для хранения ссылки на SpriteRenderer
    private SpriteRenderer sr;

    void Start()
    {
        // получаем ссылку на компонент SpriteRenderer
        sr = GetComponent<SpriteRenderer>();

        // проверяем, был ли найден компонент
        if (sr != null)
        {
            // если найден — меняем цвет на красный
            sr.color = Color.red;
        }
        else
        {
            // если компонента нет — выводим сообщение в консоль
            Debug.LogWarning("SpriteRenderer не найден на объекте!");
        }
    }

    void Update()
    {
        // вращение по оси Z со скоростью 90 градусов в секунду
        transform.Rotate(0f, 0f, 90f * Time.deltaTime);
    }
}

Мы просто проверяем, была ли получена ссылка на компонент. Если компонента нет — код не попытается к нему обратиться.

NullReferenceException — это не «сломанная игра», а отсутствие связи. Скрипт пытается обратиться к объекту, которого нет.

Если ошибка всё же появилась, не пугайтесь. Прочитайте сообщение в консоли — Unity обычно прямо указывает, в какой строке возникла проблема. Двойной клик по ошибке перенесёт вас к нужному месту в коде.

Компоненты другого объекта

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

Для этого нужно сначала получить ссылку на нужный объект, а затем уже использовать знакомый GetComponent<>() или обратиться к его Transform.

Основные способы получить ссылку на объект:

Для примера сделаем управление объектом Square из другого объекта.

Добавьте тег Player для Square. Затем создайте пустой объект и добавьте к нему следующий скрипт:


using UnityEngine;
using UnityEngine.InputSystem;

public class SquareMoving : MonoBehaviour
{
    // публичная ссылка на другой игровой объект
    public GameObject otherGameObject;

    void Start()
    {
        // если ссылка не указана в Inspector
        if (otherGameObject == null)
        {
            // ищем объект по тегу
            otherGameObject = GameObject.FindWithTag("Player");
        }

        // если объект найден — попробуем получить его SpriteRenderer
        if (otherGameObject != null)
        {
            SpriteRenderer sr = otherGameObject.GetComponent<SpriteRenderer>();

            // если у объекта есть SpriteRenderer — меняем цвет на синий
            if (sr != null)
            {
              sr.color = Color.blue;
            }
        }
    }

    void Update()
    {
        // если объект так и не найден — прекращаем выполнение
        if (otherGameObject == null) return;

        float x = 0f;

        // проверяем наличие клавиатуры
        if (Keyboard.current != null)
        {
            if (Keyboard.current.aKey.isPressed)
                x -= 1f;

            if (Keyboard.current.dKey.isPressed)
                x += 1f;
        }

        // формируем направление движения
        Vector3 direction = new Vector3(x, 0f, 0f);

        // перемещаем другой объект
        otherGameObject.transform.Translate(direction * 5f * Time.deltaTime);
    }
}
  

Теперь вы можете либо вручную перетащить Square в поле otherGameObject в Inspector, либо оставить поле пустым — тогда скрипт сам найдёт объект по тегу.

Запустите игру — и вы увидите, что Square двигается, хотя сам скрипт движения находится на другом объекте.

При запуске игры квадрат может стать синим. Однако теперь у нас уже два скрипта изменяют одно и то же свойство — цвет объекта. Будет ли он красным или синим, зависит от порядка выполнения скриптов.

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

Скрипт не обязан управлять только «своим» объектом. Главное — получить ссылку. Теперь вы понимаете, как GetComponent<>() позволяет работать с компонентами другого объекта.

GameObject с компонентом SquareMoving в Inspector
GameObject с добавленным скриптом SquareMoving

Что мы только что сделали

Мы начали с изменения параметров вручную через Inspector. Затем повторили то же самое через скрипт. А теперь научились управлять даже другим объектом.

Цвет, движение, поворот — всё это всего лишь свойства компонентов, к которым можно получить доступ через ссылку.

В Unity всё строится на связях: получил ссылку → получил компонент → изменил состояние.