Главная

Unity 2D платформер — Туториал Часть 2: прыжки, коробки и не только

Добро пожаловать во вторую часть нашего туториала по Unity 2D платформеру. Если в первой части ты учился управлять персонажем, то теперь — создашь полноценный игровой уровень, по которому приятно пройтись.

Вместо сложных врагов и сцен загрузки, мы сосредоточимся на игровом окружении: добавим монеты, платформы, ямы, стену, к которой прилипает крыса, и даже слегка ворчливого гриба. А ещё — кое-что неожиданное, что будет сопровождать игрока до самого конца.

Ты научишься размещать объекты, использовать коллайдеры и Rigidbody, а также настроишь UI-счётчик и простую логику для врагов и перезапуска уровня. Когда доберёшься до выхода — поймёшь, что главное было совсем не в нём.

Открывай Unity, запускай сцену — и начни не потому, что надо, а потому что где-то внутри уровня уже что-то ждёт, чтобы ты это создал.

Шаг 1. Установка стены и баг «липкая стена»

Начнём с добавления вертикальной стены в сцену. Используй тот же спрайт largeGround, что и для пола — просто поверни его и поставь вертикально. Unity автоматически создаст новый объект. Переименуй его в Wall (стена).

В Inspector для новой стены:

Теперь нажми Play и попробуй: беги к стене, прыгни на неё и удерживай клавишу движения. Крыса прилипнет к стене как крошечный супергерой. Это не магия — это трение.

Такое поведение происходит потому, что у стандартных коллайдеров Unity есть встроенное трение. Чтобы исправить это, мы создадим Physics Material 2D с нулевым трением.

В папке Assets:

Теперь выбери объект Rat. В его компоненте Capsule Collider 2D установи Material на noFriction.

Снова запусти сцену — крыса больше не должна застревать на стенах и платформах.

Добавлена стена и крысe назначен материал noFriction
Вертикальная стена добавлена, материал noFriction назначен крысе.

Шаг 2. Сбор монет и проигрывание звука

Давайте дадим нашей крысе что-то, что можно собирать — монетки!

Импортируй изображение coin в проект: Скачать изображение (мы используем монетку из этого спрайтшита). Затем перетащи её в сцену — Unity создаст новый объект.

Выдели монетку в Hierarchy, затем в Inspector:

Создай новый тег с именем Coin и назначь его объекту монеты.

Переименуй монетку в CoinPrefab, затем перетащи её в папку Assets, чтобы создать префаб.

Настройка объекта монеты в Unity (коллайдер и тег Coin)
Объект монеты настроен: добавлен Circle Collider 2D, включён Is Trigger и задан тег Coin.

Теперь создадим скрипт для сбора монет. В папке Assets:

Вставь следующий код в скрипт:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class CoinController : MonoBehaviour {

    // Звук при сборе монеты
    public AudioClip CoinSound;

    // Счётчик монет
    public int coin;

    void OnTriggerEnter2D (Collider2D other) {
        // Если объект имеет тег Coin
        if (other.tag == "Coin") {
            // Увеличить счётчик
            coin = coin + 1;

            // Проиграть звук сбора
            AudioSource.PlayClipAtPoint(CoinSound, transform.position);

            // Удалить монету
            Destroy(other.gameObject);
        }
    }
}

Прикрепи скрипт CoinController к объекту Rat. Скачать звук монеты можно здесь: Скачать CoinSound.wav. Импортируй CoinSound в проект и назначь его в поле Coin Sound скрипта в Inspector.

💡 Подсказка: мы используем PlayClipAtPoint, поэтому звук воспроизводится в мировом пространстве. Если он звучит слишком тихо, скорее всего, Audio Listener остаётся на камере. Чтобы это исправить, можно либо приблизить камеру к персонажу, либо переместить Audio Listener с камеры на объект Rat.

Сцена с монетой и скриптом CoinController, прикреплённым к крысе
Монетка добавлена на сцену. Скрипт CoinController прикреплён к крысе, звук подключён.

Шаг 3. Отображение счётчика монет с помощью TextMeshPro

Покажем, сколько монет собрано — с помощью UI-текста.

В Hierarchy выбери:

Выбери объект Canvas. В Inspector:

Настройки Canvas в Unity для отображения UI-текста
Настройки Canvas: выбран режим Scale With Screen Size и Expand для адаптивного UI.

Дважды кликни по Canvas в Hierarchy, чтобы отцентрировать UI. Появится белый прямоугольник — это область экрана.

Выбери элемент Text (TMP) и внеси следующие изменения:

Настройки TextMeshPro для CoinCounterText в Unity
Настройки CoinCounterText: размер, выравнивание и автошрифт.

Теперь обновим скрипт CoinController, чтобы связать его с текстом.

using UnityEngine;
using TMPro;

public class CoinController : MonoBehaviour
{
    // Звук при сборе монеты
    public AudioClip CoinSound;

    // Счётчик монет
    public int coin;

    // Ссылка на текст TextMeshPro
    public TMP_Text CoinCounterText;

    void OnTriggerEnter2D(Collider2D other)
    {
        // Проверка на тег Coin
        if (other.tag == "Coin")
        {
            coin = coin + 1;

            // Воспроизведение звука
            AudioSource.PlayClipAtPoint(CoinSound, transform.position);

            // Обновление текста
            CoinCounterText.text = coin.ToString();

            // Удаление монеты
            Destroy(other.gameObject);
        }
    }
}

Выдели Rat в Hierarchy. В компоненте CoinController перетащи объект CoinCounterText в поле Coin Counter Text.

Нажми Play — при сборе монет счётчик будет обновляться в реальном времени.

UI-элемент TextMeshPro, отображающий количество монет
TextMeshPro-счётчик монет настроен и подключён к скрипту крысы.

Шаг 4. Создание тупика и перезапуск уровня

Добавим простое испытание: яму, в которую может упасть игрок.

Перемести одну из платформ, чтобы создать разрыв в полу — это будет зона падения.

Теперь щёлкни правой кнопкой в Hierarchy и выбери: Create Empty. Переименуй новый объект в DeadEnd.

В Inspector сделай следующее:

Теперь создай новый MonoBehaviour скрипт и назови его DeadEndController. Вставь в него следующий код:

using UnityEngine;
using UnityEngine.SceneManagement; // подключаем сцену для перезапуска

// Скрипт вешается на объект-яму, чтобы перезапустить уровень при падении
public class DeadEndController : MonoBehaviour
{
    // Метод вызывается, когда кто-то входит в триггер-область
    void OnTriggerEnter2D(Collider2D other)
    {
        // Проверяем, тег ли у объекта "Player"
        if (other.tag == "Player")
        {
            // Перезапускаем текущую сцену
            SceneManager.LoadScene(SceneManager.GetActiveScene().name);
        }
    }
}

Выдели объект DeadEnd и прикрепи к нему скрипт DeadEndController. Затем создай префаб: перетащи DeadEnd из Hierarchy в папку Assets.

Когда крыса попадает в область триггера, сцена перезагружается — это простой способ сбросить уровень без дополнительной логики.

Сцена с триггером DeadEnd и скриптом управления
Триггер DeadEnd расположен под ямой. Попадая в него, игрок перезапускает сцену.

Шаг 5. Добавление простого врага (гриб)

Пора добавить подвижное препятствие: врага-гриба, который ходит туда-сюда. Если крыса прыгнет ему на голову — он исчезает. Если коснётся сбоку — уровень перезапускается.

Помести префаб Ground, а под ним — DeadEnd. Затем перетащи спрайт гриба из ассетов в сцену, на последнюю платформу. Переименуй новый объект в Enemy.

В Inspector для Enemy:

Теперь создай новый скрипт и назови его MushroomController. Вставь в него следующий код:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;

public class MushroomController : MonoBehaviour
{
    // Максимальное расстояние, на которое гриб может отходить от стартовой позиции (в Unity-единицах)
    public float distanceToRun = 2f;

    // Скорость движения гриба
    public float speed = 2f;

    // Направлен ли гриб сейчас влево?
    public bool isLookingLeft = true;

    // Начальная позиция гриба
    Vector3 startPos;

    // Ссылка на Rigidbody2D компонента гриба
    Rigidbody2D rb;

    void Start()
    {
        rb = GetComponent<Rigidbody2D>();
        startPos = transform.position;

        // Упрощаем вычисление дистанции: сравниваем квадрат расстояния
        distanceToRun = distanceToRun * distanceToRun;
    }

    void FixedUpdate()
    {
        // Текущее смещение от старта
        Vector2 dist = transform.position - startPos;

        if (isLookingLeft)
        {
            // Если гриб ушёл слишком влево — разворачиваем
            if (transform.position.x < startPos.x && dist.sqrMagnitude > distanceToRun)
            {
                TurnTheMushroom(); // меняем направление
                rb.linearVelocity = new Vector2(speed, rb.linearVelocity.y);
            }
            else
            {
                rb.linearVelocity = new Vector2(-speed, rb.linearVelocity.y);
            }
        }
        else
        {
            // Если гриб ушёл слишком вправо — разворачиваем
            if (transform.position.x > startPos.x && dist.sqrMagnitude > distanceToRun)
            {
                TurnTheMushroom(); // меняем направление
                rb.linearVelocity = new Vector2(-speed, rb.linearVelocity.y);
            }
            else
            {
                rb.linearVelocity = new Vector2(speed, rb.linearVelocity.y);
            }
        }
    }

    // Разворачивает гриб и зеркалит его спрайт по оси X
    void TurnTheMushroom()
    {
        isLookingLeft = !isLookingLeft;
        transform.localScale = new Vector3(
            transform.localScale.x * -1,
            transform.localScale.y,
            transform.localScale.z
        );
    }

    // Если игрок врезается в гриб сбоку — перезапускаем уровень
    void OnCollisionEnter2D(Collision2D other)
    {
        if (other.collider.gameObject.tag == "Player")
        {
            SceneManager.LoadScene(SceneManager.GetActiveScene().name);
        }
    }

    // Если игрок прыгает на голову грибу (через триггер) — уничтожаем гриб
    void OnTriggerEnter2D(Collider2D other)
    {
        if (other.tag == "Player")
        {
            Destroy(gameObject);
        }
    }
}

Прикрепи скрипт MushroomController к объекту Enemy. Отрегулируй параметры Speed и Distance To Run в Inspector (например, оба по 2).

Наконец, перетащи Enemy в папку Assets, чтобы создать повторно используемый префаб.

Гриб-враг с коллайдерами, скриптом и префабом
Гриб-враг с двумя коллайдерами, Rigidbody2D и скриптом для патрулирования.

Шаг 6. Создание движущейся платформы

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

Перетащи спрайт largeGround в сцену и переименуй новый объект в Platform.

В Inspector для объекта Platform:

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

Создай новый MonoBehaviour скрипт с именем PlatformMoving и вставь в него этот код:


using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class PlatformMoving : MonoBehaviour
{
    // Скорость движения платформы
    public float speed;

    // Верхняя точка, к которой движется платформа
    public Transform endPoint;

    // Стартовая позиция
    Vector3 startPoint;

    // Ссылка на Rigidbody2D
    Rigidbody2D rb;

    // Текущая скорость
    float currentSpeed;

    void Start()
    {
        // Сохраняем начальную позицию
        startPoint = transform.position;

        // Получаем Rigidbody2D
        rb = GetComponent<Rigidbody2D>();

        // Задаём начальное направление движения
        currentSpeed = speed;
    }

    void FixedUpdate()
    {
        // Если прошли endPoint — меняем направление
        if (transform.position.y > endPoint.position.y)
            currentSpeed = -speed;

        // Если вернулись к началу — снова меняем направление
        if (transform.position.y < startPoint.y)
            currentSpeed = speed;

        // Применяем скорость к Rigidbody
        rb.linearVelocity = new Vector3(0, currentSpeed, 0);
    }
}

В Hierarchy создай пустой GameObject и назови его EndPoint. (Можно включить иконку, чтобы он был виден.) Расположи его над платформой.

Снова выбери Platform. Прикрепи скрипт PlatformMoving. Установи Speed в 1 и перетащи объект EndPoint в соответствующее поле.

Запусти сцену и посмотри, как платформа движется вверх и вниз между двумя точками.

Добавь стену справа от платформы и назначь ей Box Collider 2D. Затем помести префаб DeadEnd под платформу и подгони его размеры.

Настройка движущейся платформы с Rigidbody, endPoint и скриптом
Движущаяся платформа с компонентами Rigidbody2D, Box Collider и скриптом PlatformMoving.

Шаг 7. Добавление физической коробки

Добавим коробку, которая будет взаимодействовать с крысой и другими объектами с помощью физики.

Перетащи спрайт imageGround в сцену и переименуй новый объект в Box.

В Inspector для объекта Box:

Теперь ты можешь толкать коробку в сцене — попробуй прокатиться на ней на платформе или использовать как ступеньку!

Коробка из imageGround с Rigidbody и Collider
Простая физическая коробка, сделанная из спрайта imageGround и размещённая на платформе.

Шаг 8. Создание выхода

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

Сначала добавь ещё один префаб Ground там, где движущаяся платформа будет останавливаться — в верхней точке. Затем поставь стену в конце уровня, используя спрайт largeGround, и добавь к ней Box Collider 2D.

Перетащи спрайт Exit из папки Assets в сцену — он уже добавлен в проект. Размести его рядом с верхней стеной, на такой высоте, чтобы крыса не могла допрыгнуть до него сама. Чтобы добраться до выхода, игроку понадобится Box в качестве подставки.

В Inspector объекта Exit:

Теперь, когда крыса касается выхода — сцена перезапускается. Но только если тебе удалось туда забраться.

Объект Exit с коллайдером и скриптом
Выход расположен чуть вне досягаемости — понадобится коробка, чтобы туда добраться.

Шаг 9 (необязательный). Добавление движущегося фона с параллаксом

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

Скачать изображение фона можно здесь: Скачать BackGround.png

Импортируй изображение в проект и перетащи его в сцену. В Inspector:

Создай новый скрипт на MonoBehaviour с именем Parallax и вставь в него следующий код:


using UnityEngine;

public class Parallax : MonoBehaviour
{
    // Игрок, за которым мы будем следить
    private GameObject player;

    // Позиция игрока в предыдущем кадре (чтобы знать, куда он двинулся)
    private Vector3 lastPosition;

    // Насколько быстро фон будет двигаться (меньше — медленнее, создавая эффект глубины)
    public float speed = 60f;

    void Start()
    {
        // Находим игрока по тегу "Player"
        player = GameObject.FindWithTag("Player");

        // Запоминаем стартовую позицию игрока
        if (player != null)
        {
            lastPosition = player.transform.position;
        }
    }

    void Update()
    {
        // Если игрок не найден — ничего не делаем
        if (player == null) return;

        // Разница между новой и старой позицией игрока
        Vector3 offset = player.transform.position - lastPosition;

        // Сдвигаем фон пропорционально этому смещению, но медленнее — для эффекта параллакса
        transform.Translate(offset * speed * Time.deltaTime);

        // Обновляем сохранённую позицию на текущую
        lastPosition = player.transform.position;
    }
}

Прикрепи скрипт к объекту фона. Установи значение Speed60 подойдёт для начала, но можно поэкспериментировать.

Объект фона с параллакс-скриптом и настройками
Параллакс-фон мягко движется вслед за игроком, создавая ощущение глубины.

Шаг 10. Облако, что следует за тобой (Самый важный шаг)

Это тот шаг, к которому всё тихо вело. Не монетки, не гриб, даже не выход. Именно он — настоящая причина.

В ассетах есть улыбающееся облако. Оно не атакует, не даёт очков. Но оно — единственное, что остаётся с тобой. Всегда.

Перетащи спрайт облака в сцену и назови его Cloud. Установи Z Position в 0 и Order in Layer в -50, чтобы оно было позади крысы.

Размести его где-нибудь выше и сзади крысы — например, (x - 2, y + 3). Не нужно точности. Главное — чтобы оно было.

Создай новый MonoBehaviour скрипт с именем CloudFollower и прикрепи его к объекту Cloud. Вставь этот код:


using UnityEngine;

public class CloudFollower : MonoBehaviour
{
    // Смещение облака относительно игрока (влево и вверх)
    public Vector3 offset = new Vector3(-2f, 3f, 0f);

    // Скорость, с которой облако догоняет игрока
    public float followSpeed = 1.5f;

    // Ссылка на объект, за которым следует облако (игрок)
    private Transform target;

    void Start()
    {
        // Ищем объект с тегом "Player"
        GameObject playerObj = GameObject.FindWithTag("Player");
        if (playerObj != null)
        {
            target = playerObj.transform; // Сохраняем ссылку на трансформ игрока
        }
    }

    void Update()
    {
        // Если цель не найдена — ничего не делаем
        if (target == null) return;

        // Целевая позиция: позиция игрока + смещение
        Vector3 desiredPos = target.position + offset;

        // Плавно перемещаем облако к целевой позиции
        transform.position = Vector3.Lerp(
            transform.position,    // текущая позиция
            desiredPos,           // желаемая позиция
            followSpeed * Time.deltaTime  // интерполяция по времени
        );
    }
}

Теперь облако будет следовать за крысой — не идеально, не точно, а с мягким отставанием. Оно не помогает. Оно не говорит. Оно просто остаётся.

Если ты замедлишься или упадёшь — оно подождёт. Если перезапустишь уровень — оно вернётся. Ты не один в этом уровне. По-настоящему — нет.

Облако парит позади и над крысой
Облако. Спутник. Причина.

Шаг 11. Монетки, грибы и всё, что приходит после

Теперь можно добавить монетки и грибы. Скинуть ящик под платформу. И — возможно — остаться в этом уровне чуть дольше, чем планировалось.

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

А потом — уже сами создадите свою игру. Ту, которая будет по-настоящему вашей.