Туториал 5: 2Д игра платформер (урок для начинающих). Часть 2.

Введение:

Это вторая часть туториала о 2Д игре платформере. Вам понадобятся некоторые игровые рессурсы для этого туториала. Скачайте .zip архив здесь. Также вы можете скачать первую часть туториала здесь. Запустите юнити проект с первой частью туториала и загрузите сцену scenePlatformer.
load scenePlatformer

Как сделать стену:

Начнем с легкого. Импортируйте картинку wall из скаченного архива.
import wall image
Поместите wall в сцену. Будет создан новый объект. Измените его Z-позицию на 0 (если отличается от 0). Добавьте к нему Box Collider 2D.
place wall in the scene
Теперь о вопросе, о котором обычно спрашивают на форумах. Щапустите сцену, бегите на стену и прыгните, продолжая удерживать бег. Крыса повиснет на стене. Это происходит из-за стандартного значения трения. Тут много различных путей исправить это. Мы просто добавим новый физический материал с 0 трением к стене (или крысе). Создайте новый Physics Material 2D, назовите его noFriction.
create new physics material 2D
Выберите созданный материал noFriction и измените Friction на 0.
change materials friction to 0
Выберите теперь Rat в hierarchy, посмотрите на Inspector и измените Material в Capsule Collider 2D на noFriction.
change colliders materials
Крыса теперь не должна залипать на стенах.

Как собирать и подсчитывать монеты:

Добавим что нибудь для собирания игроком. Пусть это будет монета. Импортируйте картинку монеты в проект. Поместите монету в сцену. Будет созлан новый игровой объект. Выберите coin в иерархии и посмотрите на его Inspector. Измените Z-позицию на 0. Измените Order in Layer на -10 (чтобы монета рисовалась позади крысы). Добавьте Circle Collider 2D и включите IsTrigger (нам понадобится триггер для проверки соприкосновений).
create coin
Добавьте новый тэг Coin и измените тэг монеты на Coin.
change coins tag to Coin
Выделите coin в иерархии на смените название на CoinPrefab. Создайте теперь префаб монеты. (просто перетащите CoinPrefab в Assets).
create prefab of coin
Нам нужен скрипт для сбора монет. Создайте новый C# скрипт (правый клик в Assets и там найдите этот пункт). Назовите его CoinController.
create C# script
Мы будем использовать функцию OnTriggerEnter2D для проверки соприкосновения крысы и монеты. Скрипт получится такой:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class CoinController : MonoBehaviour {

//звук сбора монеты
public AudioClip CoinSound;
//переменная количества собранных монет
public int coin;

//запустится если collider2D попал в триггер
void OnTriggerEnter2D (Collider2D other) {
//проверка, имеет ли попавший объект тэг Coin
if (other.tag == "Coin") {
//увеличить счетчик монет
coin = coin + 1;
//проиграть звук поднятия монеты на позиции крысы
AudioSource.PlayClipAtPoint (CoinSound, transform.position);
//удалить монету из сцены
Destroy (other.gameObject);
}
}
}
Добавьте этот скрипт к крысе. Импортируйте звук CoinSound в проект. Выберите крысу в иерархии и поместите CoinSound в поле Coin Sound (чейчас мы используем 3Д позиционирование звука и он будет относительно тихим, так как восприятие звука находится на камере. Мы потом переделаем Audio Listener для более правильного звука).
add script to the rat
Теперь сделаем простой интерфейс для отображения количества монет. Добавьте новый UI - Text. Будет созданно три новых объекта: Canvas, Text, EventSystem.
add new UI-Text
Выделите Canvas в иерархии. Измените UI Scale Mode на Scale With Screen Size и Screen Match Mode на Expand. С этими параметрами будет неплохое скалирование размера интерфейса на разных экранах.
change settings of the Canvas
Сцена и интерфейс теперь находятся в одном экране. Сделайте двойной клик на Canvas в иерархии. Камера сфокусируется на белом прямоугольнике. Этот прямоугольние представляет как будет выглядеть экран в игре.
select the Canvas
Выберите Text в иерархии. Сделайте его больше и перетащите на то место, где он должен отображаться. Измените привязку на право-вверх. Измените Text на 0. Alignment на середину. Включите Best Fit (заполнение пространства). Измените Max Size на что-то около 100. Настройте Color по желанию.
set up the text gameobject
Нам нужен скрипт для управления текстом. Откройте снова скрипт CoinController.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
//эта библиотека нужна для работы с интерфейсом
using UnityEngine.UI;

public class CoinController : MonoBehaviour {

//ссылка на объект с текстом
public GameObject TextObject;
//ссылка на текстовый компонент
Text textComponent;
//звук поднятия монеты
public AudioClip CoinSound;
//переменная подсчета монет
public int coin;

//запустится один раз при запуске скрипта
void Start () {
//делаем линк на текстовый компонент, который находится на текстовом объекте
textComponent = TextObject.GetComponent <Text> ();
}

//запустится если collider2D попал в триггер
void OnTriggerEnter2D (Collider2D other) {
//проверка, имеет-ли объект тэг Coin
if (other.tag == "Coin") {
//увеличить переменную подсчета монет
coin = coin + 1;
//записать в текст результат счета монет, преобразованный в текстовую переменную
textComponent.text = coin.ToString();
//проиграть звук поднятия монеты на позиции крысы
AudioSource.PlayClipAtPoint (CoinSound, transform.position);
//удалить монету из сцены
Destroy (other.gameObject);
}
}
}
Сфокусируйте камеру назад в сцену (сделайте двойной клик по крысе в иерархии). Выберите Rat в иерархии. У скрипта CoinController пояаилось новое поле Text Object. Поместите объект Text в это поле. Интерфейс теперь должен отображать значение переменной сбора монет. Запустите игру и проверьте это.
place Text gameobject in the script

Как сделать яму:

Передвиньте платформу так, что бы крыса могла упасть между ними.
move the platform
Создайте новый empty gameobject.
create empty gameobject
Выберите созданный GameObject и назовите его DeadEnd. Включите иконку для лучшей видимости. Включите кнопку перемещения и передвиньте DeadEnd на низ ямы. Смените Z-позицию на 0. Добавьте Box Collider 2D. Включите Edit Collider и измените размер на длину ямы (тяните за зеленые точки). Включите Is Trigger.
create dead end
Нам нужен скрипт для управления DeadEnd. Мы будем проверять, попала-ли крыса в триггер и просто перезагрузим сцену. Создайте новый C# скрипт и назовите его DeadEndController.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
//это библиотека для работы со сценами
using UnityEngine.SceneManagement;

public class DeadEndController : MonoBehaviour {

//запустится если 2D collider попал в триггер
void OnTriggerEnter2D (Collider2D other) {
//проверка, имеет ли объект тэг Player
if (other.tag == "Player") {
//ппосмотреть название текущей сцены и загрузить её (reload)
SceneManager.LoadScene (SceneManager.GetActiveScene().name);
}
}
}
Выберите DeadEnd в иерархии и добавьте этот скрипт. Создайте префаб DeadEnd (перетащите DeadEnd из иерархии в Assets.)
add script to the DeadEnd

Как сделать простого врага:

Добавьте Ground и DeadEnd префабы в сцену.
add next ground and dead end
Импортируйте картинку гриба из скачанного архива в Assets. Перетащите спрайт гриба в сцену на последнюю платформу. Новый игровой объект mushroom будет создан. Назовите его Enemy. Выберите Enemy и посмотрите на его Inspector. Измените Z-позицию на 0. Добавьте Capsule Collider 2D (это будет тело гриба). Нажмите на кнопку Edit Collider и настройте размер коллайдера (смотрите скриншот). Добавьте Box Collider 2D (будет использоваться как триггер для проверки прыгнула-ли крыса на шляпу гриба). Включите Is Trigger. Нажмите на кнопку Edit Collider и настройте размеры коллайдера (смотрите скриншот). Добавьте Rigidbody 2D. Измените Interpolate на Interpolate (для более плавного движения). Включите Freeze Rotation Z (что бы избежать вращения).
add components to the enemy
Нам нужен скрипт для управления грибом. Логика гриба будет простая. Гриб будет двигаться между двумя точками и исчезнет, если крыса прыгнет ему на шляпу. Создайте новый C# скрипт и назовите его MushroomController.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
//библиотека для управления сценами
using UnityEngine.SceneManagement;

public class MushroomController : MonoBehaviour {

//как далеко гриб может отойти от начальной точки
public float distanceToRun;
//скорость движения
public float speed;
//направление взгляда гриба
public bool isLookingLeft = true;
//стартовая позиция
Vector3 startPos;
//линк на компонент Rigidbody2D
Rigidbody2D rb;

//выполнится 1 раз при старте игры
void Start () {
//делаем линк на Rigidbody2D компонент
rb = GetComponent <Rigidbody2D> ();
//запоминаем стартовую точку гриба
startPos = transform.position;
//будем сравнивать квадрат вектора расстояния с квадратом расстояния
//так как извлечение корня медленный процесс
distanceToRun = distanceToRun * distanceToRun;
}

//выполняется на каждом фиксированном интервале
void FixedUpdate () {
//проверить, смотрит-ли гриб влево
if (isLookingLeft) {
//посчитать вектор направления от стартовой точки к грибу
Vector2 dist = transform.position - startPos;
//проверить, что гриб слева от стартовой точки
//и расстояние между грибом и стартовой точкой больше разрешенного удаления
if (transform.position.x < startPos.x && dist.sqrMagnitude > distanceToRun) {
//вызвать функцию поворота гриба
TurnTheMushroom ();
//начать движение направо
rb.velocity = new Vector2 (speed, rb.velocity.y);
//если проверка была негативной
} else {
//продолжить движение влево
rb.velocity = new Vector2 (-speed, rb.velocity.y);
}
}
//проверить, что гриб смотрит вправо
if (!isLookingLeft) {
//посчитать вектор направления от стартовой точки к грибу
Vector2 dist = transform.position - startPos;
//проверить, что гриб справа от стартовой точки
///и расстояние между грибом и стартовой точкой больше разрешенного удаления
if (transform.position.x > startPos.x && dist.sqrMagnitude > distanceToRun) {
//вызвать функцию поворота гриба
TurnTheMushroom ();
//начать движение налево
rb.velocity = new Vector2 (-speed, rb.velocity.y);
//если проверка была негативной
} else {
//продолжить движение направо
rb.velocity = new Vector2 (speed, rb.velocity.y);
}
}
}
//функция поворота гриба
void TurnTheMushroom ()
{
//инвертировать (изменить на противоположный) указатель направления взгляда
isLookingLeft = !isLookingLeft;
//повернуть гриб
transform.localScale = new Vector3 (transform.localScale.x * -1, transform.localScale.y, transform.localScale.z);
}

//запустится, если коллайдер коснулся другого коллайдера
void OnCollisionEnter2D (Collision2D other) {
//проверить, что у коллайдера тэг Player
if (other.collider.gameObject.tag == "Player") {
//посмотреть имя загруженной сцены и загрузить такую сцену (reload)
SceneManager.LoadScene (SceneManager.GetActiveScene().name);
}
}

//запустится, если другой коллайдер попал в триггер
void OnTriggerEnter2D (Collider2D other) {
//проверить, что у коллайдера тэг Player
if (other.tag == "Player") {
//удалить гриб
Destroy (gameObject);
}
}
}
Теперь добавим эффект взрыва, если гриб был уничтожен. Используем систему частиц для этого. Добавьте Particle System. Переименуте её в Explosion. Измените Z-позицию на 0. Измените все Rotation на 0, Duration на 1, отключите Looping, Start Lifetime на 1.
create particle system
Раскройте закладку Emission. Измените Rate over Time на 0. Добавьте начальный Burst (нажмите на кнопку +). Разверните закладку Shape и измените Shape на Circle.
change emission and shape of particle system
Разверните закладку Renderer и измените Order in Layer на 100.
change order in layer of explosion
Надо будет удалить Explosion из сцены, после того, как взрыв был показан. Понадобится маленький скрипт. Создайте новый C# скрипт и назовите его ExplosionsDestroyer.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class ExplosionsDestroyer : MonoBehaviour {

//как долго взрыв будет в сцене
public float timeToDestroy;

//выполнится один раз при старте скрипта
void Start () {
//удалить взрыв из сцены
Destroy (gameObject, timeToDestroy);
}
}
Выберите Explosion в иерархии. Добавьте этот скрипт к Explosion. Измените Time To Destroy на 1. Создайте префаб Explosion. После создания префаба удалите взрыв из сцены (правый клик на Explosion в иерархии и нажать Delete).
create prefab of the explosion
Теперь добавим взрыв и его звук к скрипту гриба. Откройте скрипт MushroomController.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
//библиотека для управления сценами
using UnityEngine.SceneManagement;

public class MushroomController : MonoBehaviour {

//префаб взрыва
public GameObject Explosion;
//звук взрыва
public AudioClip Boom;
//как далеко гриб может отойти от начальной точки
public float distanceToRun;
//скорость движения гриба
public float speed;
//направление взгляда гриба
public bool isLookingLeft = true;
//стартовая позиция гриба
Vector3 startPos;
//линк на компонент Rigidbody2D
Rigidbody2D rb;

//выполнится один раз при старте гриба
void Start () {
//делаем линк на компонент Rigidbody2D
rb = GetComponent <Rigidbody2D> ();
//запоминаем стартовую позицию гриба
startPos = transform.position;
//будем сравнивать квадрат вектора расстояния с квадратом расстояния
//так как извлечение корня медленный процесс
distanceToRun = distanceToRun * distanceToRun;
}

//выполняется на каждом фиксированном интервале
void FixedUpdate () {
//проверить, что гриб смотрит налево
if (isLookingLeft) {
//посчитать вектор направления от стартовой точки к грибу
Vector2 dist = transform.position - startPos;
//проверить, что гриб слева от стартовой точки
//и расстояние между грибом и стартовой точкой больше разрешенного удаления
if (transform.position.x < startPos.x && dist.sqrMagnitude > distanceToRun) {
//вызвать функцию поворота
TurnTheMushroom ();
//начать движение направо
rb.velocity = new Vector2 (speed, rb.velocity.y);
//если проверка была отрицательной
} else {
//продолжить движение налево
rb.velocity = new Vector2 (-speed, rb.velocity.y);
}
}
//проверить, что гриб смотрит направо
if (!isLookingLeft) {
//посчитать вектор направления от стартовой точки к грибу
Vector2 dist = transform.position - startPos;
//проверить, что гриб справа от стартовой точки
//и расстояние между грибом и стартовой точкой больше разрешенного удаления
if (transform.position.x > startPos.x && dist.sqrMagnitude > distanceToRun) {
//вызвать функцию поворота
TurnTheMushroom ();
//начать движение налево
rb.velocity = new Vector2 (-speed, rb.velocity.y);
//если проверка была отрицательной
} else {
//продолжить движение направо
rb.velocity = new Vector2 (speed, rb.velocity.y);
}
}
}
//функция поворота гриба
void TurnTheMushroom ()
{
//изменить указатель направления взгляда на противоположный
isLookingLeft = !isLookingLeft;
//повернуть гриб
transform.localScale = new Vector3 (transform.localScale.x * -1, transform.localScale.y, transform.localScale.z);
}

///запустится, если коллайдер коснулся другого коллайдера
void OnCollisionEnter2D (Collision2D other) {
//проверить, что у коллайдера тэг Player
if (other.collider.gameObject.tag == "Player") {
//посмотреть имя загруженной сцены и загрузить такую сцену (reload)
SceneManager.LoadScene (SceneManager.GetActiveScene().name);
}
}

//запустится, если другой коллайдер попал в триггер
void OnTriggerEnter2D (Collider2D other) {
//проверить, что у коллайдера тэг Player
if (other.tag == "Player") {
//спавн взрыва
Instantiate (Explosion, transform.position, Quaternion.identity);
//проиграть звук взрыва
AudioSource.PlayClipAtPoint (Boom, transform.position);
//удалить гриб
Destroy (gameObject);
}
}
}
Импортируйте звуковой файл Boom в Assets. Выберите Enemy в иерархии. У Mushroom Controller появилось два новых поля в скрипте. Поместите префаб Explosion в Explosion поле. Поместите аудиофайл Boom в поле Boom. Сделайт префаб Enemy. проверьте сцену.
add explosions prefab and sound to the script

Восприятие звука:

По стандарту, Audio Listener (компонент отвественный за восприятие звука) прикреплен к Main Camera. Audio Listener можно представить как виртуальные уши. Смотрите Unity Documentation для подробностей. Надо передвинуть этот компонент к крысе. Выберите Main Camera (дочерний объект крысы) ит отключите Audio Listener в Inspector (нам нужен только один такой компонент в сцене).
disable audio listener
Выьерите Rat в иерархии и добавьте Audio Listener к ней.
add audio listener to the rat
Проверьте сцену.

Как сделать движущуюся платформу:

Добавим движущуюся платформу. Поместите спрайт largePlatform в сцену. Будет создан новый игровой объект. Назовите его Platform. Выберите Platform в иерархии. Измените его Z-позицию на 0. Измените Tag и Layer на Ground. Добавьте Rigidbody 2D к нему. Измените Body Type на Kinematic. Поставьте Interpolate на Interpolate. Включите Freeze Position X и Freeze Rotation Z. Добавьте Box Collider 2D.
create platform
Нам нужен скрипт движения для платформы. САоздайте новый C# скрпт и назовите его 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 () {
//проверка, находится-ли платформа выше конечной точки
if (transform.position.y > endPoint.position.y)
//скорость для вектора движения вниз
currentSpeed = -speed;
//проверка, находится-ли платформа ниже начальной точки
if (transform.position.y < startPoint.y)
//скорость для вектора движения вверх
currentSpeed = speed;
//направить платформу по вектору движения
rb.velocity = new Vector3 (0, currentSpeed, 0);
}
}
Создайте новый пустой объект (empty).
Назовите его endPoint. Добавьте к нему иконку для лучшего вида. Поместите endPoint над Platform (испольуйте инструмент движения).
create endPoint
Выберите Platform в иерархии. Добавьте скрипт PlatformMoving. Измените Speed на 1. Поместите endPoint в поле End Point.
add PlatformMoving script
Запустите игру и проверьте (можете переключить на сцену, пока игра запущена).
check the scene
Поместите wall в сцену справа от платформы. Выьерите эту стену в иерархии и добавьте Box Collider 2D.
add next wall
Поместите следующий Dead End префаб в сцену под двигающейся платформой. Нажмите на Edit Collider и настройте размер.
add next dead end

Как добавить физические объекты:

Импортируйте картинку Box из скачанного архива. Поместите спрайт Box в сцену (на движущуюся платформу). Выьерите Box в иерархии. Измените Tag и Layer на Ground. Смените Z-позицию на 0. Добавьте Rigidbody 2D. Измените Interpolate на Interpolate. Добавьте Box Collider 2D.
create box gameobject
Теперь сможете взаимодействовать с этим объектом.

Конец уровня:

Добавьте Ground префаб в сцену, где платформа перестаёт двигаться наверх (смотрите скриншот). Добавьте последнбб стену (поместите спрайт wall в сцену и добавьте Box Collider 2D ).
add last ground and wall
Импортируйте картинку Exit в Assets. Добавьте спрайт Exit в сцену возле последней стены. Крыса не должна допрыгивать до Exit, если она просто прыгает с пола. Boxпонадобится как подставка. Выберите Exit в иерархии. Добавьте Box Collider 2D. Включите Is Trigger. Добавьте Dead End Controller. У нас сейчас нет следуюшего уровня, поэтому просто перезагрузим сцену.
add exit

Как сделать движущийся фон с эффектом параллакс:

С эффектом параллакс мы можем имитировать глубину сцены при движении. Дальние объекты будут двигаться медленнее (или быстрее, если хотите). Сделаем маленький скрипт. Создайте новый C# скрипт и назовите его Parallax.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Parallax : MonoBehaviour {

//ссылочная переменная на игрока
GameObject player;
//позиция игрока в предыдущем кадре
//и разница в позиции между кадрами
Vector3 lastPos, offset;
//скорость движения фона
public float speed;

//выполнится один раз при запуске скрипта
void Start () {
//делаем линк на игрока
player = GameObject.FindWithTag ("Player");
//запоминаем его позицию
lastPos = player.transform.position;
}

//выполняется каждый кадр
void Update () {
//посчитать разницу в позициях между кадрами
offset = player.transform.position - lastPos;
//пересчитать разницу в зависимости от скорости и передвинуть фон по полученному вектору
//Time.deltaTime корректировка для разного фпс
transform.Translate (offset * speed * Time.deltaTime);
//перезаписать позицию в текущем кадре
lastPos = player.transform.position;
}
}
Импортируйте BackGround в Assets. Поместите BackGround в сцену. Измените X и Y Scale на 4. Передвиньте на позицию крысы (смотрите скриншот). Измените Order in Layer на -100. Добавьте Parallaxскрипт. Измените Speed на 60 (потестите разные настройки).
create backgrond with parallax effect

Как добавить фоновую музыку:

Теперь простое. Импортируйте звуковой файл Music. Выберите Rat в иерархии. Перетащите Music на Rat ы иерархии (или добавьте Audio Source и поместите Music в поле AudioClip field). Включите Loop.
add background music
Проверьте сцену.

Это конец туториала. Знаю, тут очень много вещей, которые можно улучшить (например: например анимация прыжка на углах. Можете пофиксить это используя: Physics2D.OverlapBox) но каждая игра отлична от другой и доработка мелочей индивидуальна.

download UnityPackage этот туториал как юнити пакет