Это вторая часть нашего туториала по Unity. В первой ты учился двигаться. Во второй — строишь мир, по которому действительно хочется пройтись.
Мы не будем добавлять врагов с поведением на деревьях. Не будем делать меню или переходы между сценами. Вместо этого мы создадим пространство — с монетками, стенами, ямами, слегка недовольным грибом… и кое-чем, что будет рядом с тобой до самого конца.
Ты расставишь объекты. Починишь стену, к которой крыса липнет как ниндзя. Прокатишься на платформе. А когда дойдёшь до выхода — поймёшь, что он не главное, ради чего всё это строилось.
Так что открывай Unity. Сделай вдох. И начни — не потому что надо, а потому что **здесь что-то тихо ждёт**, чтобы ты это разместил.
Начнём с добавления вертикальной стены в сцену. Используй тот же
спрайт largeGround
, что и для пола — просто поверни его и
поставь вертикально. Unity автоматически создаст новый объект.
Переименуй его в Wall (стена).
В Inspector для новой стены:
0
.Теперь нажми Play и попробуй: беги к стене, прыгни на неё и удерживай клавишу движения. Крыса прилипнет к стене как крошечный супергерой. Это не магия — это трение.
Такое поведение происходит потому, что у стандартных коллайдеров Unity есть встроенное трение. Чтобы исправить это, мы создадим Physics Material 2D с нулевым трением.
В папке Assets:
noFriction
.0
.
Теперь выбери объект Rat. В его компоненте
Capsule Collider 2D установи
Material на noFriction
.
Снова запусти сцену — крыса больше не должна застревать на стенах и платформах.
noFriction
назначен крысе.
Давайте дадим нашей крысе что-то, что можно собирать — монетки!
Импортируй изображение coin
в проект:
Скачать изображение
(мы используем монетку из этого спрайтшита). Затем перетащи её в сцену
— Unity создаст новый объект.
Выдели монетку в Hierarchy, затем в Inspector:
0
.-10
(чтобы
она отображалась позади крысы).
Создай новый тег с именем Coin
и назначь его объекту
монеты.
Переименуй монетку в CoinPrefab, затем перетащи её в папку Assets, чтобы создать префаб.
Теперь создадим скрипт для сбора монет. В папке Assets:
CoinController
.
Вставь следующий код в скрипт:
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 находится на камере. Мы это потом поправим.
Покажем, сколько монет собрано — с помощью UI-текста.
В Hierarchy выбери:
Canvas
,
CoinCounterText
и EventSystem
.
Выбери объект Canvas. В Inspector:
Scale With Screen Size
.
Expand
.
Дважды кликни по Canvas в Hierarchy, чтобы отцентрировать UI. Появится белый прямоугольник — это область экрана.
Выбери CoinCounterText и сделай следующие изменения:
CoinCounterText
.0
.
Теперь обновим скрипт 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 — при сборе монет счётчик будет обновляться в реальном времени.
Добавим простое испытание: яму, в которую может упасть игрок.
Перемести одну из платформ, чтобы создать разрыв в полу — это будет зона падения.
Теперь щёлкни правой кнопкой в Hierarchy и выбери:
Create Empty. Переименуй новый объект в
DeadEnd
.
В Inspector сделай следующее:
0
.
Теперь создай новый 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.
Подсказка: когда крыса попадает в область триггера, сцена перезагружается — это простой способ сбросить уровень без дополнительной логики.
Пора добавить подвижное препятствие: врага-гриба, который ходит туда-сюда. Если крыса прыгнет ему на голову — он исчезает. Если коснётся сбоку — уровень перезапускается.
Помести префаб Ground
, а под ним — DeadEnd
.
Затем перетащи спрайт гриба из ассетов в сцену, на последнюю
платформу. Переименуй новый объект в Enemy.
В Inspector для Enemy:
0
.Interpolate
.
Теперь создай новый скрипт и назови его
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, чтобы создать повторно используемый префаб.
Добавим вертикальную движущуюся платформу, на которой крыса сможет ехать вверх и вниз.
Перетащи спрайт largeGround
в сцену и переименуй новый
объект в Platform.
В Inspector для объекта Platform:
0
.Ground
.
Kinematic
.
Interpolate
.
Теперь добавим скрипт, который будет двигать платформу вверх и вниз между двумя точками.
Создай новый скрипт с именем 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 под платформу и подгони его размеры.
Добавим коробку, которая будет взаимодействовать с крысой и другими объектами с помощью физики.
Перетащи спрайт imageGround
в сцену и переименуй новый
объект в Box.
В Inspector для объекта Box:
Ground
.
0
.Scale X = 2
, Y = 1.5
).
Interpolate
.
Теперь ты можешь толкать коробку в сцене — попробуй прокатиться на ней на платформе или использовать как ступеньку!
Добавим выход из уровня. Он пока не загружает новую сцену, но работает как финишный триггер — если ты сможешь до него добраться.
Сначала добавь ещё один префаб Ground там, где
движущаяся платформа будет останавливаться — в верхней точке. Затем
поставь стену в конце уровня, используя спрайт wall
, и
добавь к ней Box Collider 2D.
Импортируй спрайт Exit
(или используй красную табличку из
текущего набора ассетов) и перетащи его в сцену рядом с верхней
стеной. Он должен быть достаточно высоко, чтобы крыса
не могла допрыгнуть до него сама — понадобится
Box.
В Inspector объекта Exit:
0
.DeadEndController
(мы используем его для
перезапуска сцены).
Теперь, когда крыса касается выхода — сцена перезапускается. Но только если тебе удалось туда забраться.
Чтобы добавить ощущение глубины, сделаем так, чтобы фон немного двигался при движении игрока. Это называется эффект параллакса — он имитирует глубину, прокручивая дальние слои медленнее.
Скачать изображение фона можно здесь: Скачать BackGround.png
Импортируй изображение в проект и перетащи его в сцену. В Inspector:
X: 7
,
Y: 5
(при необходимости подгони).
-100
, чтобы
фон отрисовывался позади всего остального.
Создай новый скрипт на 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;
}
}
Прикрепи скрипт к объекту фона. Установи значение
Speed — 60
подойдёт для начала, но можно
поэкспериментировать.
Это тот шаг, к которому всё тихо вело. Не монетки, не гриб, даже не выход. Именно он — настоящая причина.
В ассетах есть улыбающееся облако. Оно не атакует, не даёт очков и даже не двигается само. Но оно — единственное, что остаётся с тобой. Всегда.
Перетащи спрайт облака в сцену и назови его 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);
}
}
Теперь облако будет следовать за крысой — не идеально, не точно, а с мягким отставанием. Оно не помогает. Оно не говорит. Оно просто остаётся.
Если ты замедлишься или упадёшь — оно подождёт. Если перезапустишь уровень — оно вернётся. Ты не один в этом уровне. По-настоящему — нет.