Физический движок. Часть 3

В наших прошлых статьях: «Физический движок: взгляд изнутри. Часть 1» и «Физический движок: взгляд изнутри. Часть 2» — мы рассмотрели теоретическую часть физики и вскользь упомянули основные элементы, которые могут быть использованы для имитации снарядов и выстрелов в играх, похожих на Angry Birds. В этой статье мы закрепим пройденный материал и применим на практике то, о чем мы говорили ранее: детально посмотрим на код простой игры и разберемся что к чему.

Для тех, кому интересно, скажем, что игра написана с использование Sprite Kit API для платформы iOS. Упомянутый API использует возможности портированного на Objective-C физического движка Box2D. Но принцип остается неизменным и то, о чем пойдет речь ниже, можно реализовать на любом другом движке.

Построение игрового мира

Вот демонстрационное видео, показывающее результат нашей сегодняшней статьи:

Общая концепция игры такова:

  1. На каждом уровне есть некоторая башня, которая состоит из различных физических тел.
  2. Внутри каждой башни находится один или несколько объектов-целей.
  3. Рядом с башней находится спусковой механизм — катапульта. Он создает снаряд и придает ему мгновенный импульс. Когда происходит столкновение снаряда с каким-либо объектом, физический движок начинает моделировать физический мир со всеми вытекающими обстоятельствами законами.
  4. В случае, если выпущенный нами снаряд или какой-либо игровой объект касается цели, игрок побеждает. Разумеется, сбить нужно все существующие на экране цели. Обнаружение столкновения происходить опять-таки при помощи физического движка.

Первым делом нам придется создать границу вокруг экрана. Мы же не хотим, чтобы наши объекты провалились в бездну под действием силы тяжести? Следующий код добавляется в инициализацию сцены или в функцию -(void)loadLevel:

Добавление объектов

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

Отлично! Давайте немного рассмотрим приведенный код. С квадратом и прямоугольником все ясно: для инициализации физического объекта (16 и 27 строки соответственно) достаточно указать размеры самого спрайта. Но если мы также поступим и в случае с треугольником, то визуально мы получим треугольник, а на деле к нему будет прикреплено физическое тело-прямоугольник (или квадрат, если треугольник равнобедренный). Чтобы избежать такого расклада, мы создаем массив точек, который и будет “обрамлять” треугольник.

Цель игры — звезду — мы создадим аналогичным образом, однако тело у нее будет круглое:

Готовьсь. Цельсь. Пли!

Сам пусковой механизм — катапульта — не нуждается в каком-либо физическом теле. Мы никак не собираемся обрабатывать столкновения с ним. Он используется только в качестве отправной точки для снаряда.

А вот, собственно, и сам метод создания снаряда:

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

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

Теперь у нас есть башня, состоящая из различных блоков, и цель (звезда). Но как узнать, что мы действительно попали в звезду?

Обработка столкновений

Для начала, парочка определений:

  • Контакт (contact) используется, когда два тела коснулись друг друга.
  • Коллизия (collision) используется, когда два тела пересеклись.

Слушатель контактов

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

Добавим следующий код в начало нашего файла:

… и скажем игре, что слушателем является текущий класс:

Это позволяет нам использовать наш самописный код, когда происходит какой-либо контакт:

Но прежде чем мы начнем писать тело функции — пару слов о категориях.

Категории

Для того, чтобы сортировать по группам физические тела, используют категории. Sprite Kit, в частности, использует битовые категории. Это означает, что мы ограничены 32 категориями в момент контакта. Мы предпочитаем объявлять категории таким образом:

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

Зададим категории, используя следующие свойства:

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

Вывод

Итак, мы узнали об основах работы 2D движка, а теперь еще и применили наши знания на практике.

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

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

Перевод статьи «Projectile Physics Engines: Building a Game World»