Как создать платформер пиксельный

Обновлено: 05.07.2024

Здравствуйте! В этом уроке мы создадим нашего персонажа и научим его двигаться.

Для этого нам нужен спрайт персонажа. Для себя я выбрал вот такой пак со спрайтами:

Дальше переносим спрайт на сцену, который мы хотим:

Если у Вас не отображается спрайт, увеличьте Order in Layer Если у Вас не отображается спрайт, увеличьте Order in Layer

Теперь нам надо добавить физику нашему персонажу, для этого в окне Инспектора нажимаем Add Component и выбираем RigidBody2D :

Также необходимо добавить само тело . Добавляем Box Collider 2D и нажимаем на значок:

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

Теперь же необходимо добавить тело земле , чтобы персонаж не проваливался сквозь неё: добавляем TileMap Collider 2D . Можно заметить, что у земли добавились зелёные линии, а если еще лучше присмотреться, то можно заметить, что эти линии находятся между тайлами, а это нам не нужно. Поэтому мы добавляем ещё Composite Collider 2D , а в TileMap Collider 2D ставим галочку у параметра Used By Composite . И о чудо, как теперь всё прекрасно выглядит!

Теперь можно запустить Игровой режим . Но что мы видим! Наша земля падает. Это случилось из-за автодобавления Rigid Body 2D , просто вместо Dynamic ставим Static . Вуа-ля, земля не падает!

Метод Start() исполняется, когда мы запускаем игру, а Update() выполняется каждый кадр .

Давайте проинициализируем переменные fox типа Rigidbody2D и speed типа float :

Теперь нам надо добавить движение нашей Лисе. Для этого в методе Update() (а лучше в FixedUpdate() , т.к. мы взаимодействуем с физическим объектом в скрипте; за уточнение спасибо Василию Курбатову , комментарий которого закреплен под этой статьёй; также уточню, что все взамидойсвтия с физ. объектами, которые будут в следующих уроках следует также добавлять именно в FixedUpdate() ) добавим одну сточку:

Не пугайтесь, сейчас расскажу, что она делает:

  1. fox.velocity - это скорость Лисы, и, как известно, у скорости есть направление, т.е. вектор ( new Vector2() );
  2. Input.GetAxisRaw() - это первый параметр вектора ( X ). Он принимает соответствующие нажатия для передвижения вправо/влево;
  3. *speed - это коэффициент скорости, который можем сами задать в интерфейсе Unity;
  4. fox.velocity.y - это координата Y Лисы.

Таким образом, эта строка создаёт вектор, который начинается на координатах Лисы и который направлен влево/вправо.

Теперь необходимо добавить прыжок . Для этого надо отслеживать нажатие клавиши (например Space ). Чтобы это делать, надо в методе Update() добавить проверку нажатия:

Коэффициент jump аналогичен speed, только для вертикали Коэффициент jump аналогичен speed, только для вертикали

В условии мы проверяем на нажатие Space . Если произошло нажатие, то мы Лисе добавляем вектор, направленный вверх.

Но нам не хватает одной детали: проверки на "опору":

В условии мы добавили grounded , т.е. если мы нажали на Space и Лиса на земле , то только тогда будет прыжок. Сам же Grounded только тогда принимает значение true , когда в радиусе находится слой ground .

Чтобы добавить этот слой в Unity выбираем группу тайлов земли и нажимаем в Инспекторе Layer, Add layer (Добавить Слой):

Дальше в свободной ячейке добавьте слой, а затем выберите его для этих тайлов Дальше в свободной ячейке добавьте слой, а затем выберите его для этих тайлов

Дальше создаём дочерний от нашего персонажа пустой игровой объект и переносим его в ноги. Затем возвращаемся к нашему персонажу.

В Ground Check я добавил пустой объект , а в Ground добавил тот слой, который только что создал.

Не забудьте поставить в Rigidbody 2D поставить галочку у Freeze Rotation по Z Не забудьте поставить в Rigidbody 2D поставить галочку у Freeze Rotation по Z

В этом уроке мы научились управлять персонажем и поправили баги в его управлении.

В следующем уроке мы добавим анимацию нашему персонажу при движении.

Продолжаем делать наш Unity for dummies платформер.

Полные версии кода будут находиться в конце поста.

Если вы не в курсе, как работают хуки жизненного цикла в Unity, вы можете ознакомиться с данной статьёй.

Затем выбираем наш объект Player слева, в меню иерархии, и закрепляем инспектор (меню справа), нажав на замок в верхнем правом углу.

Пока закрываем все ненужные компоненты (Box Collider 2D, etc), нажимая на стрелочку у каждого из них.

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

Если зайти в Edit => Project Settings => Input Manager, то мы можём увидеть настройки управления. Например, прыжок (Jump) назначен на пробел, в то время, как движение по горизонтали назначено на a, d, ←,→.

Запомним это для того, чтобы затем использовать в коде (названия инпутов Jump, Horizontal).

Пора покодить. Два раза нажимаем на наш скрипт PlayerBehaviour, и Unity отдаст его в руки Visual Studio.

Честно, данную IDE я не люблю. Потому я открываю его в VScode (а вы хоть в VIMе пишите - это ваш выбор).

using System.Collections; using System.Collections.Generic; using UnityEngine; public class PlayerBehaviour: MonoBehaviour < /** ** Ускорение игрока **/ [Header("Player velocity")] // Ось Ox public int xVelocity = 5; // Ось Oy public int yVelocity = 8; // Физическое поведение (тело) объекта private Rigidbody2D rigidBody; private void Start() < // Получаем доступ к Rigidbody2D объекта Player rigidBody = gameObject.GetComponent<Rigidbody2D>(); >private void Update() < updatePlayerPosition(); >// Обновляем местоположение игрока private void updatePlayerPosition() < // Получаем значение ввода горизонтального перемещение float moveInput = Input.GetAxis("Horizontal"); // Получаем значение ввода прыжка float jumpInput = Input.GetAxis("Jump"); // Значения xVelocity, yVelocity можно задать через инспектор if (moveInput < 0) < // Движ влево rigidBody.velocity = new Vector2(-xVelocity, rigidBody.velocity.y); >else if (moveInput > 0) < // Движ вправо rigidBody.velocity = new Vector2(xVelocity, rigidBody.velocity.y); >if (jumpInput > 0) < //Тип прыгает rigidBody.velocity = new Vector2(rigidBody.velocity.x, yVelocity); >> >

Я не буду часто использовать try/catch в этом коде, но вам очень советую - сильно экономит время.

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

Мне не нравится, что персонажа переворачивает. Исправим.

Заходим в инспектор, открываем Rigidbody2D и ставим галочку на Freeze Rotation Z.

Жить можно, но есть проблема - камера не двигается за персонажем.

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

1. Подвинуть камеру внутри сцены, чтобы она была на персонаже, а затем в привязать камеру к объекту персонажа через меню иерархии.

2. Задать движение камеры за персонажем програмно.

Создаём скрипт CameraBehaviour, и привязываем его к объекту Main Camera (добавляем через инспектор).

Открываем наш новый скрипт, и начинаем кодить (да, мне на работе не хватает, продолжаю и после).

using System; using System.Collections; using System.Collections.Generic; using UnityEngine; public class CameraBehaviour : MonoBehaviour < // Добавляем объект, за которым будет двигаться камера [Header("GameObject")] // Пишу с нижним подчеркиванием, так gameObject - ключевое слово Unity. // а придумывать новое название переменной мне влом (сорян) public Transform _gameObject; void Update() < UpdateCameraPosition(); >// Изменяем позицию камеры на экране void UpdateCameraPosition() < try < transform.position = new Vector3( // Положение игрового объекта, за которым мы двигаемся _gameObject.position.x, _gameObject.position.y, // Положение камеры z должно оставать неизменным transform.position.z // (если камеры куда-то проваливается, заменить на, например, -10) ); >catch (Exception error) < // Ловим ошибку, если по каким то причинам код не может быть выполнен (например, забыли подставить объект в _gameObject) Debug.LogError(error); >> >

Фиксируем объект камеры на замок в инспекторе, и перетаскиваем наш объект Player в GameObject.

Теперь камера двигается за игроком.

Однако камера постоянно вылезает за пределы уровня, показывая синий фон.

Я не уверен, что моё решение правильное. Буду рад, если кто-нибудь поделится своими соображениями на данный счёт.

Если у камеры пропала рамка (у меня, внезапно, случилась такая оказия - потратил аж 2 минуты на решение проблемы), надо зайти в Gizmos и отметить чекбокс на камере.

Добавляем в наш скрипт CameraBehaviour следующий код:

[Header("Camera position restrictions")] public float minY; public float maxY; public float minX; public float maxX;

Двигаем камеру, и снимаем наши измерения ее положений.

На скрине ниже камера стоит в левом нижнем углу сцены.

Исходя из поля Position, я выбираю минимум по Х = -12, по Y = -0.5 (числа округлены их -12.09 и -0.84) соответсвенно.

Проставляем снятые позиции для камеры (мои и ваши позиции будут отличаться).

Теперь обновим метод UpdateCameraPosition в скрипте CameraBehaviour

// Изменяем позицию камеры на экране void UpdateCameraPosition() < try < transform.position = new Vector3( // Положение игрового объекта, за которым мы двигаемся Mathf.Clamp(_gameObject.position.x, minX, maxX), Mathf.Clamp(_gameObject.position.y, minY, maxY), // Положение камеры z должно оставать неизменным transform.position.z // (если камеры куда-то проваливается, заменить на, например, -10) ); >catch (Exception error) < // Ловим ошибку, если по каким то причинам код не может быть выполнен (например, забыли подставить объект в _gameObject) Debug.LogError(error); >>

Mathf.Clamp принимает первым аргументом позицию объекта, за которым следует игрок, вторым и третим аргументами минимум и максимум - этими значениями метод будет ограничивать число, которые по итогу получит камера в качестве своей координаты.

Отлично, камера перестала "гулять". Работаем дальше.

Всё бы хорошо, но наш персонаж может летать - надо всего лишь жать кнопку прыжка. Добавим проверку на нахождение на земле.

Выбираем наш Foreground, и создаём новый слой 'Ground' в инспекторе.

Снова тыкаем на Foreground, и выбираем в поле Layer наш только что созданный слой.

Переходим к коду.

Добавим в PlayerBehaviour такую строчку.

Теперь в объекте Player мы можем выбрать наш свежесозданный слой.

Обновляем наш PlayerBehaviour. Добавим коллайдер (rigidBody мы создавали ранее).

Теперь мы можем обновить обработку перемещения персонажа в методе updatePlayerPosition

// Обновляем местоположение игрока private void updatePlayerPosition() < // Получаем значение ввода горизонтального перемещение float moveInput = Input.GetAxis("Horizontal"); // Получаем значение ввода прыжка float jumpInput = Input.GetAxis("Jump"); // Значения xVelocity, yVelocity можно задать через инспектор if (moveInput < 0) < // Движ влево rigidBody.velocity = new Vector2(-xVelocity, rigidBody.velocity.y); >else if (moveInput > 0) < // Движ вправо rigidBody.velocity = new Vector2(xVelocity, rigidBody.velocity.y); >else if (coll.IsTouchingLayers(ground)) < rigidBody.velocity = Vector2.zero; // Лично меня дико бесит инерция вбок при приземлении персонажа, отключаем >if (jumpInput > 0 && coll.IsTouchingLayers(ground)) < //Тип прыгает, если стоит на земле rigidBody.velocity = new Vector2(rigidBody.velocity.x, yVelocity); >>

Тут я вспомнил, что забыл глянуть, как "наделся" Box Collider на персонажа. Кажется, все ок - линии идут ровно по персонажу (жёлтая рамка). Если что, всегда можно ткнуть на Edit Collider и подтянуть его.

Летать мы разучились, а по платформе больше не скользим. Победа!

  • Из-за слоя Ground персонаж может прыгать от стенок. Не знаю, как это можно решить.
  • Является ли ограничение движения камеры, которое я написал, отимальным.
  • Иногда персонаж застревает на месте. Такое ощущение, что коллайдер видит какой то микропиксель, и персонаж застревает на нём.
using System.Collections; using System.Collections.Generic; using UnityEngine; public class PlayerBehaviour : MonoBehaviour < /** ** Ускорение игрока **/ [Header("Player velocity")] // Ось Ox public int xVelocity = 5; // Ось Oy public int yVelocity = 8; [SerializeField] private LayerMask ground; // Физическое поведение (тело) объекта private Rigidbody2D rigidBody; // Коллайдер, проверка на столкновения private Collider2D coll; private void Start() < // Получаем доступ к Rigidbody2D объекта Player rigidBody = gameObject.GetComponent<Rigidbody2D>(); coll = gameObject.GetComponent<Collider2D>(); >private void Update() < updatePlayerPosition(); >// Обновляем местоположение игрока private void updatePlayerPosition() < // Получаем значение ввода горизонтального перемещение float moveInput = Input.GetAxis("Horizontal"); // Получаем значение ввода прыжка float jumpInput = Input.GetAxis("Jump"); // Значения xVelocity, yVelocity можно задать через инспектор if (moveInput < 0) < // Движ влево rigidBody.velocity = new Vector2(-xVelocity, rigidBody.velocity.y); >else if (moveInput > 0) < // Движ вправо rigidBody.velocity = new Vector2(xVelocity, rigidBody.velocity.y); >else if (coll.IsTouchingLayers(ground)) < rigidBody.velocity = Vector2.zero; // Лично меня дико бесит инерция вбок при приземлении персонажа, отключаем >if (jumpInput > 0 && coll.IsTouchingLayers(ground)) < //Тип прыгает, если стоит на земле rigidBody.velocity = new Vector2(rigidBody.velocity.x, yVelocity); >> > using System; using System.Collections; using System.Collections.Generic; using UnityEngine; public class CameraBehaviour : MonoBehaviour < // Добавляем объект, за которым будет двигаться камера [Header("GameObject")] // Пишу с нижним подчеркиванием, так gameObject - ключевое слово Unity. // а придумывать новое название переменной мне влом (сорян) public Transform _gameObject; [Header("Camera position restrictions")] public float minY; public float maxY; public float minX; public float maxX; void Update() < UpdateCameraPosition(); >// Изменяем позицию камеры на экране void UpdateCameraPosition() < try < transform.position = new Vector3( // Положение игрового объекта, за которым мы двигаемся Mathf.Clamp(_gameObject.position.x, minX, maxX), Mathf.Clamp(_gameObject.position.y, minY, maxY), // Положение камеры z должно оставать неизменным transform.position.z // (если камеры куда-то проваливается, заменить на, например, -10) ); >catch (Exception error) < // Ловим ошибку, если по каким то причинам код не может быть выполнен (например, забыли подставить объект в _gameObject) Debug.LogError(error); >> >

Я не уверен, что моё решение правильное. Буду рад, если кто-нибудь поделится своими соображениями на данный счёт

Привет.
Я, конечно, дохуя вовремя, когда у тебя уже пять статей и рефакторинг, но вот щас иду за тобой пока вот на этом месте.
Короче, в ассет-сторе есть проект 2D Game Kit от разрабов движка. Там движение камеры сделано буквально следующим образом, если помню (давно открывал). Уровень завёрнут в коллайдер, ещё один коллайдер на камере. Соответственно, когда она о него стукается, то расслабляется и не двигается. И ещё там угарный камера-лаг стоит, чтоб камера не жёстко следовала за персонажем, а как бы за верёвочку.

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

Курсы и туториалы по созданию двумерных игр — вторая статья из цикла «Разработка».

Автор: Дмитрий Старокожев. Начал программировать на пятом курсе университета, влюбился в Objective-C и разработку под iOS, после чего попал в Pixonic. Работает ведущим разработчиком на проекте War Robots, а в свободное время преподаёт.

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

Учить программированию в одной статье нет смысла. К тому же, руководств в интернете множество. Многие наверняка будут делать свои первые прототипы в 2D — на этой теме и сконцентрируемся сегодня.

Если вы решили делать сразу в 3D, не спешите закрывать страницу — знания всё равно пригодятся. Приступим.

2D. Звучит олдскульно, правда? В наши дни разработка двумерных игр приобретает какой-то особый шарм. Чувствуешь себя ценителем, которому открылась недоступная другим истина. Но одного чувства прекрасного недостаточно — у разработки в 2D есть множество нюансов.

Очевидно, что главная особенность двухмерных игр — отсутствие третьего измерения. Как определить, кто ближе к зрителю: машина, куст или огромный боевой робот? Чтобы решить эту проблему, во всех 2D-движках предусмотрен механизм сортировки спрайтов — то есть двумерных графических объектов — по оси Z. Он может называться Z-order или Sorting Layers — в любом случае, с его помощью можно перемещать объекты со слоя на слой.

Другими словами, проблема уже решена за нас. Можно не тратить силы на изобретение велосипеда, а обратить свое внимание на особенности конкретных движков и жанров.

Для разработки прототипа звуки и анимация в большинстве случаев не так важны. Но нужно понимать, что работа с ними в 2D значительно отличается от 3D. В трёхмерном мире анимация скелетная: у каждой модели есть «скелет» (rig). Двигая его участки, разработчик анимирует модель.

А в 2D анимация создаётся покадрово: нужно создать атлас изображений, чтобы они стали последовательностью кадров анимации (sprite sheet). Уолт Дисней всё делал кистью и роллером, и это были шедевры (с). Этим же способом можно создавать очень красивых рисованных персонажей. Как в Cuphead, которая разрабатывалась на Unity.

А вот со звуком в 2D всё проще. Если при отрисовке мира отсутствие третьего измерения накладывает ограничения, то работа со звуком, наоборот, упрощается. Не нужно учитывать расстояние от слушателя (персонажа в игре) до источника звука — слушателем всегда будет выступать сам игрок.

Но нужна ли вообще прототипу музыка? Не думаю, что можно дать однозначный ответ (как и с анимацией). Давайте посмотрим на Hidden Folks. Это не просто 2D-игра, а настоящее произведение искусства.

Весь звук в Hidden Folks состоит из странных похрюкиваний и притопываний её разработчика, что само по себе USP — уникальное торговое предложение игры. Каждое прикосновение к экрану смартфона заставляет улыбнуться. Ладно, если запись похрюкиваний вам не подходит, в Asset Store Unity можно найти огромное количество платных и бесплатных ассетов для прототипа, а иногда и для релизной версии игры.

Рекомендации по выбору жанра можно прочитать в статье прошлого цикла. Здесь же мы будем говорить о некоторых путях, по которым можно пойти начинающему разработчику. Кому-то кажется, что вариантов не так много и обязательно надо закончить профильный университет с хорошей профессурой. Но если копнуть глубже, то обнаруживается бесконечный океан знаний, которые лежат прямо перед носом — остается только ухватиться за предоставленную интернетом возможность. Из доступного на ум сразу приходят:

Туториалы (tutorial) отличаются от курсов тем, что дают чёткий алгоритм действий для воспроизведения результата. Они не выходят за рамки поставленной задачи и отлично подходят, когда нужно сделать что-то конкретное, пока не пропал энтузиазм.

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

Есть YouTube-канал Brackeys. На нём — отличное вводное видео о том, с чего начать разработку платформера с плиточной графикой (tile based). Если сложно воспринимать на слух, можно включить английские субтитры.

Автор ролика приводит много примеров существующих проектов и объясняет, чем отличаются два различных подхода к созданию двумерных игр: sprite и tile based. В плейлистах канала можно найти староватый, но не потерявший актуальность туториал по созданию полноценного 2D-платформера с нуля — пошагово и со всем кодом, который можно повторить у себя и получить такой же результат.

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

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

Наконец, есть официальные туториалы от Unity. Видео сопровождаются текстовыми описаниями и даже листингами программного кода. В процессе обучения создаётся roguelike RPG в tile based мире.

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

Принимаясь за изучение нового туториала, не стоит волноваться о том, какую версию движка использует автор.

Unity везде ведёт себя похожим образом, а функции движка пятой версии доступны и в самой последней. Конечно, инструменты совершенствуются, но авторы обучающих материалов редко копают настолько глубоко, так что об этом можно не беспокоиться.

Если нужно подтянуть навыки программирования, то на арену выходит старый и проверенный игрок — Code School. Всё происходит прямо в интерфейсе сайта, который выглядит свежо и удобно. И не чувствуешь на затылке укоризненный взгляд Лобачевского со старого портрета над доской с графиком дежурств. Точно стоит обратить внимание хотя бы на бесплатные курсы, чтобы понять, насколько удобен такой формат обучения.

Есть официальные курсы от Unity, не надо далеко ходить. Кажется, что они и дальше готовы инвестировать в это направление (а после курсов можно еще получить сертификат).

У GeekBrains есть два отличных курса, разбитых по уровням сложности. Раз уж мы говорим о 2D-играх, нас интересует первый. Оба курса требуют определенной алгоритмической подготовки, программированию там не учат, только разработке на Unity.

Вы научитесь делать игры в 2D буквально с нуля: на первом уроке установите движок, а на восьмом уже запустите проект на Android. Вы даже можете заметить меня в списке преподавателей и случайно попасть на мой поток.

Наступает время самостоятельной работы. Пора писать код своей первой игры!

  • Определите главную механику. Например, у Tower Defence это строительство башен и волны врагов.
  • Найдите туториал по главной механике или по жанру в целом.
  • Реализуйте главную механику на практике, используя примитивную графику.

О нюансах 3D-игр и туториалах для них поговорим отдельно — в следующей статье.

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



Кадр из игры Mega Man X. Видны границы ячеек и hitbox (зона поражения) игрока.
Примеры игр: Super Mario World, Sonic the Hedgehog, Mega Man, Super Metroid, Contra, Metal Slug, и практически все остальные платформеры 16-битной эпохи.

Как это работает.

Если предположить, что на карте нет наклонных и односторонних платформ, алгоритм несложен :

1. Последовательно разбить на шаги движение по осям X и Y. Если затем вы планируете ввести наклоны, начните разбивку с оси Х, в противном случае порядок не имеет большого значения. Затем, для каждой оси:

3. Определите, с какими линиями сетки пересекается ограничивающий прямоугольник – так вы получите минимальное и максимальное значение на противоположной оси (т.е. при движении по горизонтали мы найдем номера вертикальных ячеек с которыми пересекаемся). К примеру, если вы двигаетесь налево, игрок пересекает ряды номер 32, 33 и 34 (то есть ячейки с координатами Y = 32 * TS, Y = 33 * TS, и Y = 34 * TS, где TS = размер ячейки).

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

5. Минимальное из двух значений (расстояние до ближайшего препятствия и расстояние на которое вы собирались продвинуться изначально) и будет значением расстояния на которое мы передвигаем персонажа.

6. Передвиньте игрока на новую позицию. С новой позиции, повторите шаги, и так далее.

Наклоны



Система, которую я сейчас опишу, позволяет вводить произвольные наклоны, хотя, по чисто визуальным причинам, эти два вида наклонов встречаются чаще всего и включают в себя всего 12 ячеек (6, обозначенных выше и их зеркальные отражения). Алгоритм столкновений для горизонтального движения изменяется следующим образом:

Для вертикального движения:

Односторонние платформы


■ По оси Х ячейка не является «твёрдой» никогда;
■ По оси Y ячейка является препятствием только в том случае, если до начала движения игрок целиком находился над ней, т. е. нижняя координата игрока располагалась как минимум на один пиксель выше верхней координаты односторонней платформы. Чтобы обеспечить это условие, желательно сохранить начальную позицию игрока перед началом движения .

Возможно, вам покажется хорошим подходом определять что произошло столкновение когда вертикальная скорость игрока имеет положительное значение (например, при падении). Однако, этот подход неверен: игрок может прыгнуть так, что он по факту пересечёт платформу, но до её верха не допрыгнет и, следственно, должен упасть вниз. В этой ситуации игроку всё равно следует проходить сквозь платформу, хотя вертикальная скорость будет положительной.

Лестницы (вертикальные)


Переход в «лестничное» состояние возможен следующими путями:

■ Хитбокс игрока пересекает лестницу на уровне пола или в полёте, игрок нажимает клавишу «вверх» (в некоторых играх переход возможен также по нажатию клавиши «вниз»);
■ Персонаж стоит на верхней «лестничной» ячейке (которая чаще всего по сути является односторонней платформой, чтобы по ней можно было пройтись сверху), игрок нажимает «вниз».

Таким образом, возникает эффект моментального совмещения x-координаты игрока с лестницей. Для движения вниз с верхушки лестницы нужно переместить y-координату персонажа так, чтобы игрок оказался внутри лестницы. В некоторых играх для таких ситуаций вводится альтернативный хитбокс, отслеживающий позицию игрока в пределах лестницы. К примеру, в Mega Man, он представляет собой единичную ячейку, совпадающую с верхней ячейкой спрайта персонажа.

Покинуть лестницу можно следующими способами:

■ Игрок достигает верха лестницы. Здесь обычно включается особая анимация, при которой игрок перемещается на несколько пикселей вверх по оси Y и оказывается над лестницей в стоячем положении;

■ Игрок достигает низа висячей лестницы. В этом случае игрок попросту падает, хотя в некоторых играх это не допускается;

■ Игрок движется влево или вправо. Если нет препятствий, игрок сходит с лестницы в соответствующем направлении;

■ Игрок прыгает. В некоторых играх позволяется отпустить лестницу таким образом.

Будучи на лестнице, действия игрока ограничены движением вверх-вниз, а также иногда возможно атаковать.

Лестницы (наклонные)


Данный тип лестниц встречается редко. В основном, наклонные лестницы присущи играм серии Castlevania. Их реализация во многом совпадает с реализацией обычных лестниц, за некоторыми исключениями:

■ Шаг игрока составляет либо целую ячейку, либо половину ячейки (как в Dracula X);
■ Во время каждого «шага» по лестнице, игрок смещается одновременно по обеим осям на заранее заданное значение;

Движущиеся платформы


Для решения этой проблемы существует несколько путей. Рассмотрим такой алгоритм:

■ Перед тем как что либо сдвинуть на карте, задайте условие, при котором игрок считается находящимся на движущейся платформе. Пускай программа следит за тем, чтобы, например, нижний средний пиксель персонажа располагался ровно на один пиксель выше поверхности платформы. Если это условие соблюдается, сохраните указатель на платформу и её позицию в персонаж
■ Сдвиньте все движущиеся платформы на один шаг. Обязательно сделайте это перед тем, как будете двигать персонажей и другие объекты;
■ Для всех персонажей, стоящих на движущейся платформе, определите дельта-положение платформы, т. е. полное перемещение, совершённое ей по каждой оси. Затем, переместите персонажа в соответствии с этим значением;
■Передвигайте персонажей дальше как обычно.

Другие особенности


Есть игры, в которых применены гораздо более сложные и уникальные техники, и особенно в этом плане выделяется серия Sonic the Hedgehog. Эти приёмы выходят за рамки данной статьи, но могут послужить материалом для будущих.

Похож на предыдущий, только для определения столкновения используются не крупные тайлы, а пиксели. Такая техника позволяет улучшить детализацию, но при этом увеличивается сложность исполнения и использование памяти. Редактор уровней больше напоминает какой-нибудь paint. Часто для создания графики тайлы не используются, поэтому для каждого уровня могут требоваться большие, замысловатые, индивидуальные изображения. В связи со всем этим, данная техника не является распространённой, но с её использованием можно добиться более качественных результатов, по сравнению с типами, основанными на ячеистом принципе. Она также подходит для создания динамичного окружения, такого как разрушаемая местность в Worms – в битовой маске можно «рисовать», и тем самым изменять модель уровня.


Примеры: Worms, Talbot’s Odyssey

Как это работает

Склоны


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

Приведу в общих чертах алгоритм, изпользуемый в Talbot’s Odyssey:

■ Найдите растояние, на сколько нужно сместиться по каждой оси;
■ Шаги нужно проводить по каждой оси отдельно, начиная с той, на которой наибольшее смещение;
■ Для движения по горизонтали, ограничивающий прямоугольник игрока (AABB) нужно сместить на 3 пикселя вверх(чтобы он смог забираться на склоны);
■ Выполните проверку столкновений, сверяясь со всеми препятствиями и самой битовой маской, и определите, на сколько пикселей персонаж может продвинутся, прежде чем столкнуться с чем-либо. Передвиньте его на эту новую позицию;
■ Если движение шло по горизонтали, передвиньтесь вверх на столько пикселей сколько необходимо (обычно, не больше 3-х) чтобы компенсировать склон;
■ Если в конце движения хотя бы один пиксель игрока накладывается на препятствие, отмените движение по этой оси;
■ Независимо от исполнения предыдущего условия, примените алгоритм для второй оси.

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

Также трудность представляет точный расчёт количества пикселей, на которое может продвинуться персонаж, прежде чем коснуться чего-либо. Могут быть и другие усложняющие факторы, такие как односторонние платформы (см. «плитки со сглаживанием») и скольжение вниз по крутым склонам (довольно сложно и за рамками данной статьи). В целом, эта техника требует точной настройки, отладки, и по своей природе является менее стабильной, чем подходы с использованием тайлов. Я могу порекомендовать её лишь в том случае, если вам в вашей игре действительно не обойтись без детализированной местности.

В этом способе используется векторный подход (линии и многоугольники) для определения границ области коллизии. Несмотря на сложность реализации, он набирает все большую популярность благодаря повсеместному использованию физических движков, таких как Box2D. Векторный способ обладает теми же преимуществами, что и битовая маска, но без большого расхода памяти и с использованием иного способа редактирования уровней.



Braid (редактор уровней) с видимыми слоями (наверху) и …полигонами (внизу)

Как это работает
Есть два основных способа этого достигнуть:
1. Движение и столкновения реализуйте способом похожим на битовую маску, но используя при этом многоугольники, чтобы вычислять отклонения и получать правильный наклон.
2. Используйте физический движок (например, Box2D)
Очевидно, второй способ более распространенный (хотя я подозреваю, что Braid первый), потому что он проще и позволяет делать различные вещи с динамикой в игре.
К сожалению, приходится быть очень внимательным, когда идешь этим путем, чтобы не превратить игру в обычный физический платформер.

Составные объекты

Этот подход имеет свои проблемы. Иногда бывает трудно определить, стоит игрок на полу (из-за ошибок округления), ударяется о стену или скользит вниз по крутому склону. При использовании физического движка трение может стать проблемой, если захотите усилить его у ног и ослабить по бокам.

Существуют различные способы решения этой проблемы, но наиболее популярным решением является разделение персонажа на несколько разных многоугольников: так, помимо туловища, у вас будет узкий прямоугольник для ступней и два узких прямоугольника для боков, еще один для головы или другая подобная комбинация. Иногда им придают коническую форму, чтобы избежать препятствий. У них разные физические свойства, и функции определения пересечений движка в данном случае используются для определения состояния персонажа. Для получения дополнительной информации могут быть использованы датчики (несталкивающиеся предметы, используемые только для проверки наличия перекрытий). К ним можно прибегнуть, чтобы определить достаточно ли близко к полу мы находимся для прыжка или персонаж упирается в стену и т.д.

Ускорение


Super Mario World (низкое ускорение), Super Metroid (среднее ускорение), Mega Men 7 (высокое ускорение)

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

Ускорение – это скорость изменения скорости. Если оно низкое, персонаж долго разгоняется или долго тормозит, когда игрок отпускает кнопку. Это делает движение персонажа «скользящим», что создает сложности для играющего. Такое движение обычно ассоциируется с серией игр Super Mario. Когда ускорение высокое, персонаж разгоняется до максимума за считанные секунды (либо моментально) и так же быстро останавливается, что приводит к очень быстрому, «дерганому» управлению джойстиком, как, например, в Мега Мэн. Полагаю, что в Мега Мэн на самом деле используется неограниченное ускорение, т.е. вы либо бежите на полной скорости, либо стоите на месте.

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

Как это работает

Реализация ускорения на деле очень проста, но существует несколько подводных камней , о которых следует помнить.

1. Определите скорость xTargetSpeed. Она должна быть 0, если игрок не нажимает кнопок джойстика, -maxSpeed если нажата кнопка «Влево» или +maxSpeed если нажата кнопка «Вправо».
2. Определить значение yTargetSpeed. Оно должно быть 0, если игрок стоит на платформе. В противном случае +terminalSpeed.
3. Для каждой оси увеличить текущую скорость до заданной, используя либо среднее взвешенное, либо добавочное ускорение.

Два вида ускорения:
Среднее взвешенное : число (“a”) от 0 (без движения) до 1 (мгновенное ускорение).
Используйте это значение для вставки между заданной и текущей скоростью, а результат установите как заданную скорость.

vector2f curSpeed = a * targetSpeed + (1-a) * curSpeed;
if (fabs(curSpeed.x) < threshold) curSpeed.x = 0;
if (fabs(curSpeed.y) < threshold) curSpeed.y = 0;

Когда персонаж натыкается на препятствие, лучше всего снизить его скорость до нуля.

Контроль прыжков



Super Metroid, Самус выполняет «Космический прыжок» (с использованием Screw Attack)

Возмность прыжка в игре-платформере сводиться к определению стоит ли игрок на земле (или, то что он был на земле последние n кадров). В таком случае персонажу задается отрицательная начальная скорость y speed (соответствует физическому термину «импульс»), остальное – дело гравитации.

Существуют четыре основных способа управления прыжками:

Анимация и управление


Black Thorne, анимация персонажа замедляется перед выстрелом назад (кнопка Y)

Во многих играх анимация вашего персонажа будет проигрываться до реального выполнения требуемого действия, во всяком случае, в играх, основанных на резких движениях. Это разочарует игроков, поэтому НЕ ДЕЛАЙТЕ ЭТОГО! Вам следует ставить на первое место анимацию, когда речь идет о прыжках и беге, но если вам не все равно, ограничьтесь такой анимацией, чтобы само действие происходило независимо от анимации.

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

· Используйте числа с плавающей запятой (тип float) для всех расчетов и хранения данных положения, и подводите итог в целых числах всякий раз, когда вы визуализируете изображение или рассчитываете коллизии. Быстро и просто, но при удалении от (0,0) начинает теряться точность. Это, вероятно, не имеет значения, если у вас очень большое игровое поле, но об этом следует помнить. В противном случае используется тип double.

· Используйте числа с фиксированной запятой для всех расчетов и положения и снова подведите итог в целых числах , когда визуализируете изображение или рассчитываете коллизии. Менее точный, чем float и с более узким диапазоном формат, но в этом случае точность всегда одна и та же, а на некоторых устройствах работа с ним протекает быстрее (особенно медленно работа протекает с числами с плавающей запятой на мобильных устройствах).

· Храните данные о положении в виде целых чисел, но держите «остаток» в типе float. Рассчитайте дельта-движение как число с плавающей запятой, приплюсуйте к нему остаток, затем целую часть добавьте к положению, а дробную – к полю «остаток». В следующем кадре к остатку будет добавлено значение. Преимущество этого метода в том, что вы используете целые числа везде, кроме расчета движения, что гарантирует отсутствие сложностей с плавающей запятой и повышает производительность. Этот способ также подойдет, если в вашем фреймворке положение объекта должно быть выражено целым числом, или если float, но это же положение используется для отрисовки на экран. В этом случае вы можете хранить только целочисленные значения, чтобы отрисовка всегда выравнивалась по пикселям.

Читайте также: