2d games — Проблема с коллайдером 2D при наложении друг на друга


Содержание

2D игра на Unity. Подробное руководство. Часть 2

Создание игрока в Unity

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

  1. Скорируйте картинку в папку «Textures»
  2. Создайте новый спрайт и назовите его «Player»
  3. Настройте спрайт так, чтобы он отображался в свойстве «Sprite» компонента «Sprite Renderer»

Если у вас возникли проблемы, обратитесь к предыдущему уроку. Мы проделали точно такие же действия для фона и «реквизита».

  1. Поместите игрока в слой «2 — Foreground»
  2. Измените масштаб на (0.2, 0.2, 1)

Теперь несколько слов о компонентах. Мы только что говорили о компоненте «Sprite Renderer». Если вы еще не заметил, объект игры состоит из нескольких компонентов, видимых в панели «Инспектор».

По умолчанию пустой объект игры выглядит так:

Этот объект имеет только один компонент: Transform . Этот компонент является обязательным и не может быть отключен или удален. Вы можете добавить к объекту столько компонентов, сколько захотите. Например, скрипты добавляются в качестве компонента. Большинство компонентов может быть включено или отключено пока существует объект.

(Вы можете нажать на галочку чекбокса, чтобы отключить его. Вы можете щелкнуть правой кнопкой мыши на компоненте, чтобы вернуть прежнее свойство, удалить его и т.д.

Компоненты могут взаимодействовать с другими компонентами. Если объект имеет сомпонент, который требуется другому компоненту объекта для работы, вы можете просто перетащить весь объект внутрь этого компонента, и тогда компонент сам найдет все, что ему нужно.

Sprite Renderer является компонентом, который способен отображать спрайт-текстуру. Теперь, когда мы узнали о концепции компонента, давайте добавим один к игроку!

Добавляем бокс-коллайдер (Box Collider)

Нажмите на кнопку «Добавить компонент» объекта игрока. Выберите «Box Collider 2D». Вы можете увидеть коллайдер в редакторе «Сцена» зрения и настройки его размера в «Инспекторе» в поле «Размер» (Size).

Существует еще один способ редактирования бокс-коллайдера. Выберите игровой объект с помощью бокс-коллайдера и зажмите клавишу shift на клавиатуре. Вы увидите, что на бокс-коллайдере (зеленый прямоугольник ) появились четыре маленьких рычажка. Перетащите один из них, чтобы изменить форму бокс-коллайдера. Будьте осторожны, синий прямоугольник представляет собой компонент Transform вашего игрового объекта, а не коллайдер.

Мы будем устанавливать размер коллайдера равным (10, 10) .

Это слишком много для настоящего шмапа, но все же меньше, чем спрайт:

В настоящее время, этого вполне достаточно.

Совет: Если вы планируете создать шмап, вам придется уделить много времени настройке хитбоксов – они должны точно соответствовать маленькому элементу внутри игрового спрайта. Вы также можете изменить такой параметр коллайдера, как shape – например, с помощью «Circle Collider 2D». Благодаря Unity, его поведение при этом не меняется, но это позволяет немного улучить геймплей.

Сохраним объект игрок как префаб. Теперь у вас есть базовую сущность игрока!

2D полигональный коллайдер

Если вы хотите супер точный и произвольный формы хитбокс, воспользуйтесь компонентом Unity «Полигоннальный коллайдер 2D» (Polygon Collider 2D). Эффект от этого будет незначительный, но зато вы получите такую форму, какую вы хотите.

«Polygon Collider 2D» похож на остальные коллайдеры: вы можете изменять форму с помощью мышки в режиме «Scene». Для удаления точки зажмите cmd или ctrl , а чтобы отрегулировать положение точки или добавить ее в форму коллайдера, используйте shift

Магия Rigidbody

Последний компонент, необходимый для добавления на нашего игрока: «Rigidbody 2D». Это поможет физическому движку правильно задействовать объект в игровом пространстве. Более того, это позволит вам использовать столкновения в скрипте.

  1. Выберите объект Player в «Hierarchy».
  2. Добавьте компонент «Rigidbody 2D».

Теперь, нажмите кнопку «играть» и смотрите, что у нас вышло:

Корабль падает! И как падает! Передвайте привет нашей любимой силе тяжести. По мере того, как сменяются кадры с заранее заданной гравитацией и rigidbodies прибавляет объекту массы, корабль притягивается к нижней части экрана.

По-умолчанию, ускорние свободного падения в Unity равно 9.81 , т.е. мы имеем дело с земной гравитацией.

Гравитация может быть использована в любой игре, но нам она не нужна. К счастью, гравитацию на Rigidbody можн легко отключить. Просто установите «гравитационный масштаб» равным нулю. Вот и все, корабль снова летит. Не забудьте поставить галочку в окошке «Fixed Angles», чтобы предотвратить вращение корабля, обусловленное такой физикой.

Перемещение игрока

Настало время написать скриптик (вы ведь не думали, что все будет двигаться само)? Создайте в Unity C#-скрипт в папке «Scripts» и назовите это «PlayerScript». Откройте ваш любимый редактор или используйте подменю «Sync» (нажмите на «Assets» в строке меню, затем на «Sync MonoDevelop Project») для правки созданного Unity скрипта.

«Sync MonoDevelop Project»: Это подменю немного странное.Во-первых, невозможно изменить имя, даже если сменить редактора.
Мы также рекомендуем использовать это меню при создании первого скрипта, так как Unity создаст решения и привяжет их к библиотекам Unity (для Visual Studio, Xamarin Studio или MonoDevelop).
Если вместо этого вы просто откроете скрипт, компилятор вашего IDE, скорее всего, зарегистрирует определенные ошибки, не Unity. Это не имеет значения, потому что вам не придется использовать его напрямую, но функция автоматического завершения объектов Unity не помешает.

По умолчанию в скрипте уже прописаны методы Start и Update . Вот краткий список наиболее часто используемых функций:

  • Awake() вызывается один раз, когда объект создается. По сути аналог обычной функции-конструктора.
  • Start() выполняется после Awake() . Отличается тем, что метод Start() не вызывается, если скрипт не включен (remember the checkbox on a component in the «Inspector»).
  • Update() выполняется для каждого кадра in the main game loop.
  • FixedUpdate() вызывается каждый раз через определеннок число кадров. Вы можете вызывать этот метод вместо Update() когда имеете дело с физикой («RigidBody» и др.).
  • Destroy() вызывается, когда объект уничтожается. Это ваш последний шанс, чтобы очистить или выполнить код.


У вас также есть некоторые функции для обработки столкновений:

  • OnCollisionEnter2D(CollisionInfo2D info) выполняется, когда коллайдер объекта соприкасается с другим коллайдером.
  • OnCollisionExit2D(CollisionInfo2D info) выполняется, когда коллайдер объекта не соприкасается ни с одним другим коллайдером.
  • OnTriggerEnter2D(Collider2D otherCollider) выполняется, когда коллайдер объекта соприкасается с другим коллайдером с пометкой «Trigger».
  • OnTriggerExit2D(Collider2D otherCollider) выполняется, когда коллайдер объекта перестает соприкасаться с коллайдером, помеченным как «Trigger».

Итак, с теорией покончено, пора в бой. Или нет, погодите еще немного: обратите внимание, что почти все, о чем мы говорили с вами имеет, суффикс «2D». Box Collider 2D , a Rigidbody 2D , OnCollisionEnter2D , OnTriggerEnter2D и т.д. Эти новые компоненты или методы появились с Unity 4.3. Используя их, вы работаете с физическим движком, встроенным в Unity 4.3, для 2D-игр (на основе Box2D) вместо движка для 3D-игр (PhysX). Два движка имеют аналогичные концепции и объекты, но они не работают точно так же. Если вы начинаете работать с одним (например, Box2D для 2D-игр), придерживаqntcm его. Именно поэтому мы используем все объекты или методы с суффиксом «2D».

В скрипт для нашего игрока мы добавим несколько простых элементов управления, а именно: клавиши со стрелками, которые будут перемещать корабль.

Поясню цифры в комментариях к коду:

  1. Сначала определим публичную переменную, которая будет отображаться в окне «Инспектор». Это скорость, используемая для корабля.
  2. Сохраним движение для каждого кадра.
  3. Используем дефолтную ось, которую можно отредактировать в «Edit» -> «Project Settings» -> «Input». При этом мы получим целые значения между [-1, 1] , где 0 будет означать, что корабль неподвижен, 1 — движение вправо, -1 — влево.
  4. Умножим направление на скорость.
  5. Изменим скорость rigidbody. Это даст движку команду к перемещению объекта. Сделаем это в FixedUpdate() , предназначенном для всего, что связано с физикой.

Заметка о соглашениях C# : Посмотрите на видимость speed члена класса – он обозначен как публичный. В C# переменная члена класса должна быть приватной для соответствующего сохранения его внутренней репрезентации.
Но смена типа переменной на публичный позволяет редактировать ее в Unity через панель «Inspector», даже в процессе игры. Это одна из самых мощных возможностей Unity, позволяющая изменять геймплей без использования кода.
Помните, что в данном случае мы создаем скрипты, а это не то же самое, что классическое программирование на C#. Это предполагает некоторых правил и соглашений.

Теперь добавим скрипт к игровому объекту. Для этого перетащите скрипт из окна «Проект» (Project) на игровой объект в «Иерархии» (Hierarchy). Вы также можете нажать на «Add Component» и добвить его вручную.

Нажмите кнопку «Play» в верхней части окна редактора. Корабль движется! Congratulations, Вы только что сделали эквивалент «Hello, World!» для игры :)

Попробуйте настроить скорость: нажмите на игрока, измените значения скорости в «Инспекторе», и посмотрите что из этого получится.

Будьте осторожны: изменения параметров, сделанные во время, игры теряются, когда вы ее остановите! Инспекторе — это отличный инструмент для настройки геймплея, но запомните или запишите, что вы делали, если хотите сохранить изменения. Этот же трюк подходит, если вы хотете проверить что-то новое, но не хотите вносить изменения в реальный проект.

Первый враг

Теперь добавим неприятелей, стремящихся уничтожить наш корабль. Пусть им будет зловещий спрут, названный «Poulpi»:

Создадим новый спрайт. Для этого:

  1. Скопируйте картинку в папку «Textures».
  2. Создайте новый спрайт, используя это изображение.
  3. Измените свойство «Масштаб» (Scale) в разделе Трансформирование (Transform) на (0.4, 0.4, 1) .
  4. Добавьте «Box Collider 2D» размером (4, 4) .
  5. Add a «Rigidbody 2D» with a «Gravity Scale» of 0 and «Fixed Angles» ticked.

Сохраните префаб и. вуаля!

Скрипт

Теперь напишем простенький скрипт, отвечающий за движение осьминога в определенном направлении. Для этого создайте новый скрипт, назвав его «MoveScript».

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

Скопируем некоторые части кода, который мы написали в «PlayerScript» для движения персонажа. We will add another designer (a public member you can alter in the «Inspector») variable for the direction:

Прикрепите скрипт к осьминогу. Нажмите «Play» и убедитесь, что спрут движется так, как показано на рисунке ниже:

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

Вы узнали, как добавить игрока, движущегося с помощью клавиатуры. Также, мы создали врага с зачаточным AI. Теперь мы хотим иметь возможность уничтожить его! А для этого, нам нужны боеприпасы, которые мы создадим в следующем уроке 2D игра на Unity. Подробное руководство. Часть 3.

столкновение — C ++ DirectX11 2d Game: Остановить вражеские спрайты, движущиеся друг над другом?

я использую IntersectsWith(this->boundingBox)) метод обнаружения столкновений между спрайтами и игроком. Я хочу каким-то образом использовать этот метод при обнаружении моих вражеских спрайтов, которые сталкиваются друг с другом, и когда они это делают, чтобы убедиться, что они не перемещаются друг на друга.

Все вражеские спрайты следуют за игроком.

Перебирает каждого врага в векторе и выполняет цикл обновления:

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

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

Кто-нибудь знает правильный код, чтобы остановить их перемещение друг на друга? Благодарю.

Решение

Когда вы обнаружите пересечение между двумя объектами столкновения, вам нужно принять решение о том, как вы собираетесь противодействовать перекрытию (как я уверен, вы выяснили). Однако то, как это сделать, немного сложнее, чем просто «толкнуть» их в сторону (как вы сделали в своем коде). То, что вы, вероятно, хотите сделать, — это иметь, так сказать, противодействие приложенной силе. По сути, вы хотите рассчитать минимальный перевод или направление, в котором потребуется наименьшее количество движения, чтобы получить хотя бы одну из коробок для перемещения OUT другой.

Это немного сложнее, чем просто «поставить меня на правую (или левую, в зависимости от того, как вы устанавливаете свои координаты, я полагаю) сторону другого парня», что сейчас более или менее соответствует тому, что делает ваш код.

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


Пример:
[Править] Изучив мой предыдущий ответ на этот вопрос, я понял, что вам нужно будет рассчитать перекрытие блоков, что значительно облегчит выполнение всего этого примерно так:

Чтобы дополнительно уточнить (помимо комментариев), то, что мы в основном делаем здесь, это проверка расстояния между перекрывающимися линиями. Например:

Обратите внимание, что минимум из двух ограничивающих рамок, который используется для перекрытия, является максимумом между двумя минимумами (xmin0 и xmin1), а максимум, который используется для перекрытия, является минимумом между двумя максимумами (xmax0 и xmax1).

Расчет по оси Y работает точно так же. Получив обе оси, мы просто проверяем, какая из них имеет более низкое абсолютное значение (какое расстояние короче), и перемещаемся вдоль этого расстояния, чтобы противодействовать пересечению.

Скрипт движения 2D персонажа

Еще один скрипт управления персонажем, заточенный под 2D физику. В отличии от прочих, здесь имеются некоторые настройки. Возможен выбор осей. Например, если выбрать только ось Х, в этом случаи включается режим как для платформера, персонаж может двигаться по горизонтали и использовать прыжок. Если выбрать оси ХY, тогда скрипт переходит в режим как для скроллера, проще говоря, подходит для управления неким самолетом или вроде того, объект может двигаться не только по горизонтали, но и по вертикали, а та-же самая клавиша, что и в первом варианте, на этот раз выполняет роль ускорителя, то есть добавляет скорости. Кроме того, есть опция отслеживания позиции курсора, чтобы персонаж смотрел на него.

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

speed — скорость движения, как не странно.

addForce — если выбран режим Оnly X, будет использовано для прыжка, при нажатии соответствующий клавиши. Во втором режиме, значение addForce будет прибавлено к speed, тем самым придавая ускорение.

lookAtCursor — отслеживание позиции курсора, персонаж будет вращаться по оси Z. Важно помнить, что лицом считается ось Х.

isFacingRight — если на старте сцены персонаж смотрит вправо, то надо ставить true.

При движении только по горизонтали, разворот персонажа осуществляется через функцию Flip. Так же, чтобы прыжок был возможен, все объекты по которым возможно передвижение, как бы земля/поверхность, у всех них должен быть тег Ground.

Параллакс для 2D игры без нервов и костылей

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

У меня получилось. Хочу поделиться с вами опытом.

Справедливо замечу, что параллакс — это не достояние веба. Еще в дремучие времена существования 8-битных игр параллакс успешно применялся для создания иллюзии объема в двухмерной игре. Коротко говоря, параллакс — это наслоение изображений, каждый слой движется со своей скоростью. Ближайший к игроку имеет самую высокую скорость, соответственно дальний (последний) — самую низкую.

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

Что вам потребуется:

  • базовые знания Unity3D 5 (на уровне создания скриптов, добавления компонентов);
  • понимание С#;
  • 3 или больше картинок в формате .png;
  • внимательность и желание.

Ладно, последнее не очень обязательно =)

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

Подготовка

Запускаем Unity3D, создаем новый проект, называем его, например, Parallax2D.

Закидываем в папку Assets наши бэкграунды. Рекомендую сложить их в отдельную папку. В моем случае они лежат в Assets – StarSky.

Цукерберг рекомендует:  Вакансии King Bird

Каждое изображение называем удобно и понятно. Я назвала их по порядку размещения (Background – задний фон, MiddleBackground – средний, TopBackground – верхний слой).

Для того, чтобы картинка перемещалась гладко, нам необходимо настроить ее в Inspector. Обратите внимание, этот этап очень важен, иначе все размажет, как звезды за иллюминатором Энтерпрайза на 3-й космической скорости.

В поле Texture Type выбираем тип Texture, во Wrap Mode отмечаем Repeat. И радостно тыкаем Apply. Без этого действия изменения не сохранятся, а потом можно долго недоумевать, почему же оно не работает. Совершаем эти телодвижения и для 2-х остальных текстур.

Подготовив картинки, переходим к этапу размещения их на сцене. Часто в этих ваших интерентах можно встретить совет — размещать их с помощью GameObject – Plane. Вы, конечно, можете потрудиться и заставить 3D объект нормально функционировать в 2D игре. Но, на самом деле, это все будет уныло, как последний эпизод «Звездных войн», а работать это чудовище будет чуть более быстро, чем аж никак. Поэтому я рекомендую долго не мучиться и использовать элемент UI – Canvas.

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

В Hierarchy выбираем UI – Canvas. Собственно, если работать с Юнькой для вас не впервой, то вы явно знаете еще много других способов, как добавить в Hierarchy объект. Так что делайте это любым удобным способом.

Создав Canvas, добавляем чайлдами («внутрь» канвы) три Panel для 3-х наших фонов .

После добавления наша Hierarchy выглядит так:

Переименовываем Canvas и Panel, чтобы у всех были свои пароли и явки.


А теперь засучиваем рукава и беремся препарировать — подготавливать каждый компонент в Inspector.

Начнем с ParallaxBackground.

Изменяем Layer на Ignore Raycast, чтобы наш фон не реагировал на касания пальцами. Unity обязательно спросит, применить ли эти изменения ко всем «детям» — соглашаемся.

Далее переходим к компоненту Canvas.

Находим Render Mode, выбираем Screen SpaceCamera. В Render Camera добавляем нашу текущую дефолтную камеру (Main Camera(Camera)).

Plane Distance ставим пока 110, но этот показатель проще отстроить во время теста. По факту — это расстояние от камеры до нашей канвы. То есть, изменяя его, мы будем получать разную глубину изображения.

Остальное не трогаем и переходим к Back.

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

Удаляем компонент Image (Script), вместо него добавляем Raw Image (Script) (напомню, Add Component – UI – Raw Image (Scriot)). В Texture добавляем картинку нашего самого последнего слоя.

Те же операции проделываем и для остальных слоев. Можно сделать немного проще, наколдовать изменения в первой Panel , дублировать ( Ctrl + D ), поставить в каждую свою текстуру, переименовать. Тут уже зависит от того, как вам будет удобнее.

Запускаем сцену — любуемся. Три картинки прекрасно легли друг на друга.

Немного черной магии

Теперь весь смак. Мы с вами напишем скрипт, который заставит наши картинки двигаться. Создаем новый скрипт, напомню, пишем на C#, назовем его BackgroundHelper. Он у нас будет один, поэтому нет смысла делать отдельную папку, кидаем его прямо в основную Assets. Открываем созданный скрипт и понеслась тяжкая работа на 5 строчек:

using UnityEngine.UI; // обязательно добавляем библиотеку пользовательского интерфейса, без нее кин о не будет

public class BackgroundHelper : MonoBehaviour <

public float speed = 0; //эта публичная переменная отобразится в инспекторе, там же мы ее можем и менять. Это очень удобно, чтобы настроить скорость разным слоям картинки

float pos = 0; //переменная для позиции картинки

private RawImage image; //создаем объект нашей картинки

image = GetComponent RawImage >();//в старте получаем ссылку на картинку

//в апдейте прописываем как, с какой скоростью и куда мы будем двигать нашу картинку

image.uvRect = new Rect (pos, 0, 1, 1);

Сохраняем скрипт и добавляем его, как компонент к каждому слою.

Скорости у меня такие:

Back 0.0001

Middle 0.00015

Top 0.00024

Наслаждаемся успешной работой

У вас остались вопросы или возникли трудности? Пишите в комментариях.

Коллайдеры (Coll >

Collider components define the shape of an object for the purposes of physical collisions. A collider, which is invisible, need not be the exact same shape as the object’s mesh and in fact, a rough approximation is often more efficient and indistinguishable in gameplay.

The simplest (and least processor-intensive) colliders are the so-called primitive collider types. In 3D, these are the Box Collider, Sphere Collider and Capsule Collider. In 2D, you can use the Box Collider 2D and Circle Collider 2D. Any number of these can be added to a single object to create compound colliders.

With careful positioning and sizing, compound colliders can often approximate the shape of an object quite well while keeping a low processor overhead. Further flexibility can be gained by having additional colliders on child objects (eg, boxes can be rotated relative to the local axes of the parent object). When creating a compound collider like this, there should only be one Rigidbody component, placed on the root object in the hierarchy.

Note, that primitive colliders will not work correctly with shear transforms — that means that if you use a combination of rotations and non-uniform scales in the tranform hierarchy so that the resulting shape would no longer match a primitive shape, the primitive collider will not be able to represent it correctly.

There are some cases, however, where even compound colliders are not accurate enough. In 3D, you can use Mesh Colliders to match the shape of the object’s mesh exactly. In 2D, the Polygon Collider 2D will generally not match the shape of the sprite graphic perfectly but you can refine the shape to any level of detail you like. These colliders are much more processor-intensive than primitive types, however, so use them sparingly to maintain good performance. Also, a mesh collider will normally be unable to collide with another mesh collider (ie, nothing will happen when they make contact). You can get around this in some cases by marking the mesh collider as Convex in the inspector. This will generate the collider shape as a “convex hull” which is like the original mesh but with any undercuts filled in. The benefit of this is that a convex mesh collider can collide with other mesh colliders so you may be able to use this feature when you have a moving character with a suitable shape. However, a good general rule is to use mesh colliders for scene geometry and approximate the shape of moving objects using compound primitive colliders.

Colliders can be added to an object without a Rigidbody component to create floors, walls and other motionless elements of a scene. These are referred to as static colliders. In general, you should not reposition static colliders by changing the Transform position since this will impact heavily on the performance of the physics engine. Colliders on an object that does have a Rigidbody are known as dynamic colliders. Static colliders can interact with dynamic colliders but since they don’t have a Rigidbody, they will not move in response to collisions.

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

Физические материалы (Physics Materials)

When colliders interact, their surfaces need to simulate the properties of the material they are supposed to represent. For example, a sheet of ice will be slippery while a rubber ball will offer a lot of friction and be very bouncy. Although the shape of colliders is not deformed during collisions, their friction and bounce can be configured using Physics Materials. Getting the parameters just right can involve a bit of trial and error but an ice material, for example will have zero (or very low) friction and a rubber material with have high friction and near-perfect bounciness. See the reference pages for Physic Material and Physics Material 2D for further details on the available parameters. Note that for historical reasons, the 3D asset is actually called Physic Material (without the S) but the 2D equivalent is called Physics Material 2D (with the S).


Триггеры (Triggers)

The scripting system can detect when collisions occur and initiate actions using the OnCollisionEnter function. However, you can also use the physics engine simply to detect when one collider enters the space of another without creating a collision. A collider configured as a Trigger (using the Is Trigger property) does not behave as a solid object and will simply allow other colliders to pass through. When a collider enters its space, a trigger will call the OnTriggerEnter function on the trigger object’s scripts.

Функции обратного вызова при коллизии

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

On the first physics update where the collision is detected, the OnCollisionEnter function is called. During updates where contact is maintained, OnCollisionStay is called and finally, OnCollisionExit indicates that contact has been broken. Trigger colliders call the analogous OnTriggerEnter , OnTriggerStay and OnTriggerExit functions. Note that for 2D physics, there are equivalent functions with 2D appended to the name, eg, OnCollisionEnter2D . Full details of these functions and code samples can be found on the Script Reference page for the MonoBehaviour class.

У обычных не триггерных коллизий есть ещё дополнительная деталь: как минимум один из вовлечённых в коллизию объектов должен обладать не кинематическим Rigidbody (т.е. IsKinematic должен быть выключен). Если оба объекта являются кинематическими, то тогда не будут вызываться функции, вроде OnCollisionEnter и т.д. С триггерными столкновениями это условие не применяется, так что и кинематические и не кинематические Rigidbody будут незамедлительно вызывать OnTriggerEnter при пересечении триггерного коллайдера.

Взаимодействия коллайдеров

Коллайдеры взаимодействуют друг с другом по разному, в зависимости от того, как настроены их компоненты Rigidbody. Тремя важными конфигурациями являются статичный коллайдер (Static Collider) (т.е. компонент Rigidbody отсутствует вообще), Rigidbody коллайдер (Rigidbody Collider), и кинематический Rigidbody коллайдер (Kinematic Rigidbody Collider).

Статичный коллайдер (Static Collider)

Это игровой объект, у которого есть коллайдер, но нету Rigidbody. Статичные коллайдеры используются для геометрии уровней, которая всегда стоит на месте и совсем не двигается. Встречные Rigidbody объекты будут врезаться в статичный коллайдер, но его не сдвинут.

В физический движок заложено предположение, что статичные коллайдеры никогда не двигаются или меняются, и, на основе этого предположения, движок делает полезные оптимизации. Следовательно, статичные коллайдеры нельзя включать/выключать, двигать или масштабировать во время игрового процесса. Если вы измените статичный коллайдер, то в результате физическим движком будет вызван дополнительный внутренний перерасчёт, который будет сопровождаться большим падением производительности. Хуже того, изменения иногда могут оставить коллайдер в неопределённом состоянии, в результате чего будут производиться ошибочные физические расчёты. Например, рейкаст к изменённому статичному коллайдеру может не обнаружить коллайдера или обнаружить его в случайном месте в пространстве. Кроме того Rigidbody объекты, в которых врежется статичный коллайдер, не обязательно будут “разбужены”, и статичный коллайдер не применит никакого трения. По этим причинам, следует изменять только коллайдеры с Rigidbody. Если вы хотите, чтобы на коллайдер объекта не влияли встречные Rigidbody, но чтобы его можно было двигать при помощи скрипта, то вам следует прикрепить кинематический Rigidbody компонент к нему, нежели вообще не добавлять Rigidbody.

Rigidbody коллайдер (Rigidbody Collider)

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

Кинематические Rigidbody коллайдеры (Kinematic Rigidbody Collider)

Это игровой объект, к которому прикреплён коллайдер и кинематический Rigidbody (т.е. свойство IsKinematic компонента Rigidbody включено). Изменяя компонент Transform, вы можете перемещать объект с кинематическим Rigidbody, но он не будет реагировать на коллизии и приложенные силы так же, как и не кинематические Rigidbody. Кинематические Rigidbody должны использоваться для коллайдеров, которые могут двигаться или периодически выключаться/включаться, иначе они будут вести себя как статичные коллайдеры. Примером этого является скользящая дверь, которая обычно является недвижимым физическим препятствием, но по надобности может открываться. В отличие от статичного коллайдера, движущийся кинематический Rigidbody будет применять трение к другим объектам и, в случае контакта, будет “будить” другие Rigidbody.

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

A Rigidbody component can be switched between normal and kinematic behavior at any time using the IsKinematic property.

A common example of this is the “ragdoll” effect where a character normally moves under animation but is thrown physically by an explosion or a heavy collision. The character’s limbs can each be given their own Rigidbody component with IsKinematic enabled by default. The limbs will move normallly by animation until IsKinematic is switched off for all of them and they immediately behave as physics objects. At this point, a collision or explosion force will send the character flying with its limbs thrown in a convincing way.

Матрица действий коллизии

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

Как исправить ошибку, где мой Rig >

Коробки стоят рядом друг с другом, но каким-то образом игроку удается столкнуться с краем призрака. Я использую AddForce для управления проигрывателем с помощью Box Collider, хотя я также пытался использовать Mesh Collider, и я попытался установить столкновение игрока с непрерывен. Это также происходит в 2D.

Я видел сообщения других людей об этой ошибке, но я не мог понять, как это исправить. Как это исправить?

2 ответа

Я думаю, что у меня была аналогичная проблема в 2D. Попробуйте заменить ваш Box Collider сферой или капсулой с разумным размером.

В 2D я закончил использование Circle Collider с меньшим Box Collider. Круглый коллайдер сделал это так, чтобы мой игрок не повесился на пол и стены, которые состояли из дискретных ящиков (с соответствующими Box Xolliders). Я добавил меньший Box Collider к игроку, потому что, как 2D-платформер, я не хотел, чтобы мой плеер соскользнул с края препятствий.

Попробуйте изменить материал ваших коллайдеров. Кроме того, я нашел подобный поток на форумах Unity. Это о 2D, но проблема, похоже, одна и та же.

Основы физики 2D платформера, часть 5: Обнаружение столкновения объекта с объектом

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

Демонстрация

Демонстрация показывает конечный результат этого урока. Используйте WASD для перемещения персонажа. Средняя кнопка мыши порождает одностороннюю платформу, правая кнопка мыши порождает сплошной тайл, а пробел порождает клон персонажа. Слайдеры изменяют размер персонажа игрока. Объекты, которые обнаруживают столкновения сделаны полупрозрачными.

Это демо было опубликовано под Unity 5.4.0f3, и исходный код также совместим с этой версией Unity.

Обнаружение столкновения

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

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

Пространственное разбиение!

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

Цукерберг рекомендует:  Taskbar - Показать текст в Win10 на taskbar

Метод


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

Здесь есть один нюанс: объект может находиться более, чем в одном подпространстве одновременно. Это абсолютно нормально — просто это значит, что мы должны обнаруживать объекты, принадлежащие каждой области, ч которой пересекается наш прежний объект.

Данные для разделения

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

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

Для наших объектов нам понадобится список областей, с которыми объект в данный момент пересекается, а также его индекс в каждой секции. Давайте добавим все это в класс MovingObject .

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

Инициализация секций

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

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

Теперь давайте инициализируем секции.

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

Назначаем секции объекта

Теперь пришло время создать функцию, которая будет обновлять области, с которыми пересекается конкретный объект.

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

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

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

Теперь, возможно объект расположен полностью внутри одной секции, возможно — в двух, или он может занимать пространство как раз там, где встречаются четыре секции. Это все делается в предположении, что нет объектов размером больше, чем размер секции, когда объект, будучи достаточно большим, может занимать всю карту и все секции сразу. Я работал, исходя из этого допущения, поэтому именно так мы будем поступать с этим в уроке. Модификации, позволяющие более большие объекты, достаточно тривиальны, но, тем не менее, я также объясню, в чем они состоят.

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

Если же это не этот случай, и координаты одинаковы по оси x, значит объект пересекает две разных секции по вертикали.

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

Та же логика применима, если одинаковы только вертикальные координаты.

Наконец, если все координаты разные, нам нужно добавить все четыре области.

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

Как видите, процедура очень простая — мы добавляем индекс области в список областей, пересекаемых объектом, мы добавляем соответствующий индекс в список id объектов, и наконец мы добавляем объект в секцию.

Теперь давайте создадим функцию удаления.

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

Чтобы удалить объект, мы будем менять местами его с последним объектом в списке. Это потребует от нас также убедиться, что индекс объекта для этой конкретной области будет обновлен на тот, который был у нашего удаленного объекта. Если бы мы не меняли объекты местами, нам нужно было бы обновлять индексы всех объектов, которые идут после того, который нам нужно удалить. Вместо этого, нам нужно обновить только тот, с которым мы меняемся местами.

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

Теперь нам нужно найти интересующую нас область в списке областей обмениваемого объекта, и изменить индекс в списке id на индекс удаленного объекта.

Наконец, мы можем удалить последний объект из секции, который теперь является ссылкой на объект, который нам нужно удалить.

Полный текст функции должен выглядеть вот так:

Давайте вернемся к функции UpdateAreas.

Мы знаем, какие области перекрывает объект в этом кадре, но в предыдущем кадре объект мог быть уже назначен в те же или другие области. Сначала, давайте обойдем в цикле старые области, и если объект больше с ними не пересекается, давайте удалим его оттуда.

Теперь давайте обойдем в цикле новые области, и если объект не был назначен им ранее, давайте добавим их сейчас.

Напоследок очистим список пересекающих областей, чтобы он был готов к обработке следующего объекта.

Вот и все! В финальном виде функция должна выглядеть вот так:

Обнаружение столкновений между объектами


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

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

Это будет очень удобно, потому что мы сможем сохранить данные, как есть, в момент столкновения, а если мы сохраним только ссылку на объект, с которым мы столкнулись, у нас не только будет слишком мало данных, с которыми мы сможем работать, но также могут измениться позиция и другие переменные для этого объекта к моменту, когда мы на самом деле начнем обрабатывать столкновение в цикле обновления объекта.

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

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

Теперь давайте вернемся обратно к классу Map и создадим функцию CheckCollisions . Это будет наша сверхмощная функция, в которой мы будем обнаруживать столкновения между всеми игровыми объектами.

Чтобы обнаружить столкновения, мы будем перебирать все секции.

Для каждой секции, мы будем перебирать в цикле каждый объект внутри нее.

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

Теперь мы можем проверить, пересекаются ли AABB объектов друг с другом.

Вот что происходит в функции AABB OverlapsSigned .

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

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

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

Чтобы упростить вещи для нас, мы примем, что единицы (speed1, pos1, oldPos1) в структуре данных столкновений всегда ссылаются на владельца данных столкновения, а двойки — это данные, относящиеся к другому объекту.

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

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

Чтобы убрать такую возможность, мы просто проверяем, определяли ли мы уже столкновение между двумя объектами. Если это так, то мы пропускаем эту итерацию.

Функция HasCollisionDataFor реализуется следующим образом.

Она просто перебирает в цикле все структуры данных столкновения и ищет есть ли среди такие, которые принадлежат объекту, который мы собираемся проверить на столкновение.

Это нормально, в основном пользоваться предположением, что мы не ожидаем столкновения объекта со множеством других объектов, поэтому просмотр списка будет проходить быстро. Однако при другом сценарии возможно будет лучше заменить список CollisionData словарем, тогда вместо перебора в цикле мы сможем сразу сказать, находится в нем уже элемент, или нет.

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

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

Теперь давайте вызовем функцию, которую мы только что закончили, в главном цикле игры.

Вот и все! Теперь все наши объекты имеют данные о столновениях.

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

Как видите, похоже, что обнаружение прекрасно работает!

Заключение

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

Если у вас есть вопрос, подсказка, как сделать что-либо лучше, или просто у вас есть свое мнение об уроке, не стесняйтесь пользоваться секцией комментариев, чтобы донести это до меня!

Скрипт движения 2D персонажа

Еще один скрипт управления персонажем, заточенный под 2D физику. В отличии от прочих, здесь имеются некоторые настройки. Возможен выбор осей. Например, если выбрать только ось Х, в этом случаи включается режим как для платформера, персонаж может двигаться по горизонтали и использовать прыжок. Если выбрать оси ХY, тогда скрипт переходит в режим как для скроллера, проще говоря, подходит для управления неким самолетом или вроде того, объект может двигаться не только по горизонтали, но и по вертикали, а та-же самая клавиша, что и в первом варианте, на этот раз выполняет роль ускорителя, то есть добавляет скорости. Кроме того, есть опция отслеживания позиции курсора, чтобы персонаж смотрел на него.

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

speed — скорость движения, как не странно.

addForce — если выбран режим Оnly X, будет использовано для прыжка, при нажатии соответствующий клавиши. Во втором режиме, значение addForce будет прибавлено к speed, тем самым придавая ускорение.

lookAtCursor — отслеживание позиции курсора, персонаж будет вращаться по оси Z. Важно помнить, что лицом считается ось Х.

isFacingRight — если на старте сцены персонаж смотрит вправо, то надо ставить true.

При движении только по горизонтали, разворот персонажа осуществляется через функцию Flip. Так же, чтобы прыжок был возможен, все объекты по которым возможно передвижение, как бы земля/поверхность, у всех них должен быть тег Ground.


Основы физики 2D платформера, часть 5: Обнаружение столкновения объекта с объектом

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

Демонстрация

Демонстрация показывает конечный результат этого урока. Используйте WASD для перемещения персонажа. Средняя кнопка мыши порождает одностороннюю платформу, правая кнопка мыши порождает сплошной тайл, а пробел порождает клон персонажа. Слайдеры изменяют размер персонажа игрока. Объекты, которые обнаруживают столкновения сделаны полупрозрачными.

Это демо было опубликовано под Unity 5.4.0f3, и исходный код также совместим с этой версией Unity.

Обнаружение столкновения

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

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

Пространственное разбиение!

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

Метод

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

Здесь есть один нюанс: объект может находиться более, чем в одном подпространстве одновременно. Это абсолютно нормально — просто это значит, что мы должны обнаруживать объекты, принадлежащие каждой области, ч которой пересекается наш прежний объект.

Данные для разделения

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

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

Для наших объектов нам понадобится список областей, с которыми объект в данный момент пересекается, а также его индекс в каждой секции. Давайте добавим все это в класс MovingObject .

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

Инициализация секций

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

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

Теперь давайте инициализируем секции.

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

Назначаем секции объекта

Теперь пришло время создать функцию, которая будет обновлять области, с которыми пересекается конкретный объект.

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

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

Цукерберг рекомендует:  Css - Нужна помощь

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

Теперь, возможно объект расположен полностью внутри одной секции, возможно — в двух, или он может занимать пространство как раз там, где встречаются четыре секции. Это все делается в предположении, что нет объектов размером больше, чем размер секции, когда объект, будучи достаточно большим, может занимать всю карту и все секции сразу. Я работал, исходя из этого допущения, поэтому именно так мы будем поступать с этим в уроке. Модификации, позволяющие более большие объекты, достаточно тривиальны, но, тем не менее, я также объясню, в чем они состоят.

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

Если же это не этот случай, и координаты одинаковы по оси x, значит объект пересекает две разных секции по вертикали.

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

Та же логика применима, если одинаковы только вертикальные координаты.

Наконец, если все координаты разные, нам нужно добавить все четыре области.

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

Как видите, процедура очень простая — мы добавляем индекс области в список областей, пересекаемых объектом, мы добавляем соответствующий индекс в список id объектов, и наконец мы добавляем объект в секцию.

Теперь давайте создадим функцию удаления.

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


Чтобы удалить объект, мы будем менять местами его с последним объектом в списке. Это потребует от нас также убедиться, что индекс объекта для этой конкретной области будет обновлен на тот, который был у нашего удаленного объекта. Если бы мы не меняли объекты местами, нам нужно было бы обновлять индексы всех объектов, которые идут после того, который нам нужно удалить. Вместо этого, нам нужно обновить только тот, с которым мы меняемся местами.

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

Теперь нам нужно найти интересующую нас область в списке областей обмениваемого объекта, и изменить индекс в списке id на индекс удаленного объекта.

Наконец, мы можем удалить последний объект из секции, который теперь является ссылкой на объект, который нам нужно удалить.

Полный текст функции должен выглядеть вот так:

Давайте вернемся к функции UpdateAreas.

Мы знаем, какие области перекрывает объект в этом кадре, но в предыдущем кадре объект мог быть уже назначен в те же или другие области. Сначала, давайте обойдем в цикле старые области, и если объект больше с ними не пересекается, давайте удалим его оттуда.

Теперь давайте обойдем в цикле новые области, и если объект не был назначен им ранее, давайте добавим их сейчас.

Напоследок очистим список пересекающих областей, чтобы он был готов к обработке следующего объекта.

Вот и все! В финальном виде функция должна выглядеть вот так:

Обнаружение столкновений между объектами

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

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

Это будет очень удобно, потому что мы сможем сохранить данные, как есть, в момент столкновения, а если мы сохраним только ссылку на объект, с которым мы столкнулись, у нас не только будет слишком мало данных, с которыми мы сможем работать, но также могут измениться позиция и другие переменные для этого объекта к моменту, когда мы на самом деле начнем обрабатывать столкновение в цикле обновления объекта.

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

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

Теперь давайте вернемся обратно к классу Map и создадим функцию CheckCollisions . Это будет наша сверхмощная функция, в которой мы будем обнаруживать столкновения между всеми игровыми объектами.

Чтобы обнаружить столкновения, мы будем перебирать все секции.

Для каждой секции, мы будем перебирать в цикле каждый объект внутри нее.

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

Теперь мы можем проверить, пересекаются ли AABB объектов друг с другом.

Вот что происходит в функции AABB OverlapsSigned .

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

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

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

Чтобы упростить вещи для нас, мы примем, что единицы (speed1, pos1, oldPos1) в структуре данных столкновений всегда ссылаются на владельца данных столкновения, а двойки — это данные, относящиеся к другому объекту.

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

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

Чтобы убрать такую возможность, мы просто проверяем, определяли ли мы уже столкновение между двумя объектами. Если это так, то мы пропускаем эту итерацию.

Функция HasCollisionDataFor реализуется следующим образом.

Она просто перебирает в цикле все структуры данных столкновения и ищет есть ли среди такие, которые принадлежат объекту, который мы собираемся проверить на столкновение.

Это нормально, в основном пользоваться предположением, что мы не ожидаем столкновения объекта со множеством других объектов, поэтому просмотр списка будет проходить быстро. Однако при другом сценарии возможно будет лучше заменить список CollisionData словарем, тогда вместо перебора в цикле мы сможем сразу сказать, находится в нем уже элемент, или нет.

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

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

Теперь давайте вызовем функцию, которую мы только что закончили, в главном цикле игры.

Вот и все! Теперь все наши объекты имеют данные о столновениях.

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

Как видите, похоже, что обнаружение прекрасно работает!


Заключение

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

Если у вас есть вопрос, подсказка, как сделать что-либо лучше, или просто у вас есть свое мнение об уроке, не стесняйтесь пользоваться секцией комментариев, чтобы донести это до меня!

Учимся делать 2D-игры с нуля

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

Автор: Дмитрий Старокожев. Начал программировать на пятом курсе университета, влюбился в 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.

Еще один канал от энтузиаста — N3K EN. Можно найти ролики как по отдельным механикам или основам C # и Unity, так и плейлисты по разработке проектов с нуля. Например, создание прототипа Fruit Ninja за четыре часа в режиме реального времени со всем кодом.

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

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

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

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

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

Курсы

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

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

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

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

Хотите узнать, насколько глубока кроличья нора? Я падаю, стул тоже падает, здесь всё в 2D, и мне это очень нравится. Присоединяйтесь.

Домашнее задание

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

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

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

Понравилась статья? Поделиться с друзьями:
Все языки программирования для начинающих