Главная

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

Это вторая часть нашего туториала по Unity. В первой ты учился двигаться. Во второй — строишь мир, по которому действительно хочется пройтись.

Мы не будем добавлять врагов с поведением на деревьях. Не будем делать меню или переходы между сценами. Вместо этого мы создадим пространство — с монетками, стенами, ямами, слегка недовольным грибом… и кое-чем, что будет рядом с тобой до самого конца.

Ты расставишь объекты. Починишь стену, к которой крыса липнет как ниндзя. Прокатишься на платформе. А когда дойдёшь до выхода — поймёшь, что он не главное, ради чего всё это строилось.

Так что открывай 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, чтобы создать префаб.

Теперь создадим скрипт для сбора монет. В папке 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 находится на камере. Мы это потом поправим.

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

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

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

В Hierarchy выбери:

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

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

Выбери 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 сделай следующее:

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

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

public class DeadEndController : MonoBehaviour {
    void OnTriggerEnter2D(Collider2D other) {
        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
{
    // Дальность движения гриба от стартовой позиции (в юнитах)
    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);
            }
        }
    }

    // Разворот гриба — визуально и логически
    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:

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

Создай новый скрипт с именем 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 там, где движущаяся платформа будет останавливаться — в верхней точке. Затем поставь стену в конце уровня, используя спрайт wall, и добавь к ней Box Collider 2D.

Импортируй спрайт Exit (или используй красную табличку из текущего набора ассетов) и перетащи его в сцену рядом с верхней стеной. Он должен быть достаточно высоко, чтобы крыса не могла допрыгнуть до него сама — понадобится Box.

В Inspector объекта Exit:

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

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

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

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

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

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

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


using UnityEngine;

public class Parallax : MonoBehaviour
{
    // Ссылка на игрока
    private GameObject player;

    // Позиция в предыдущем кадре
    private Vector3 lastPosition;

    // Скорость параллакса
    public float speed = 60f;

    void Start()
    {
        // Ищем игрока по тегу
        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). Не нужно точности. Главное — чтобы оно было.

Создай новый скрипт C# с именем 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()
    {
        // Находим игрока по тегу
        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);
    }
}

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

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

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