Вступление
Разработка игры часто включает в себя имитацию реальных сценариев реальной жизни: будь то работает и прыгает, прокат скал на склоне или стрельба снаряжения с рогатки, разработчик должен найти способ показать его на 2D или 3D-сцене таким образом, чтобы это было достаточно правдоподобно привлечь игрока. В ходе разработки новой игры мне потребность в конкретном движении персонажа, который вовлечен на воздушные скольжения и езда на воздушных токах. Основная проблема заключалась в том, чтобы подражать движению планера, такого как объект таким образом, чтобы выглядеть достаточно реалистично. Поскольку для рассмотрения много возможных подходов, мне бы хотелось бы удобную среду, которая позволяет нам быстро к лесам и тестировать различные решения.
Основы
Настройка Phaser
Поскольку предполагаемая платформа является веб, я решил использовать популярные Фазер 3 Игровой двигатель, который работает с HTML5 и JavaScript. Для удобства я начал с котельной Phaser/Starter Kit. Однако обратите внимание, что это совершенно нормально, чтобы начать с нуля, если предпочтительнее. Для справки я использовал Шаблон стартера Phaser 3 ES6 Для этого проекта.
Для простоты проект установлен с основной сценой и погрузчиком (Splash Screen). Это следует за общую практику загрузки активов на сцене погрузчика, а затем переключаться на основную сцену.
Игра сцена
Игровая сцена (Game-Scene.js) – очень простая установка наземных/небе с несколькими объектами на земле для ориентации. Мы используем функцию Create () для создания и заполнения всех объектов и установить камеру и размеры мира. В этом случае мир в два раза ширина экрана, поэтому мы можем прокрутить любую его часть.
class GameScene extends Phaser.Scene { constructor() { super({key: 'Game'}); } /** * Called when a scene is initialized. Method responsible for setting up * the game objects of the scene. */ create(/* data */) { let gameW= this.sys.game.config.width; let gameH= this.sys.game.config.height; let worldSizeW = gameW*2; // Sky Background let skyBg = this.add.tileSprite(0, 0, worldSizeW, gameH, 'skyTile'); skyBg.setOrigin(0, 0); let platforms = this.physics.add.staticGroup(); // Ground let ground= this.add.tileSprite(0,0, worldSizeW, 64, 'grass'); ground.setOrigin(0,0); ground.setPosition(0, gameH-60); platforms.add(ground); this.add.image(gameW-200, gameH-100, 'redFlag'); this.add.image(worldSizeW*0.8, gameH-100, 'greenFlag'); this.glider = new Glider(this); // Set a collision check for the ground this.physics.add.collider(this.glider, platforms); // Camera scroll and follow this.cameras.main.setBounds(0, 0, worldSizeW, gameH); this.cameras.main.startFollow(this.glider, true, 0.5, 0.5 ); } }
Последние две строки устанавливают встроенную камеру для прокрутки на мировых границах и следуют на объекте планера, чтобы оно остается в виду, как он движется.
Планер
Для достижения хорошей и чистой реализации я решил инкапсулировать большую часть функциональности в одном компонент – объект планера. Это прямой вверх по фазерному спрайту: объект дисплея, который удерживает изображение, с добавлением физического тела. Расширение спрайта и добавление пользовательского кода – это путь.
Я использовал изображение бумажной плоскости (справа) и загрузил его как «планер». В качестве любого объекта в Phaser мы можем подключить к методам жизненного цикла объекта: а именно создание и обновление. Во время создания вызывается после инициализации, обновление вызывается на каждом кадре. В спрайт мы также можем использовать конструктор для создания.
Ниже приведен начальный класс планера, который инициализируется с помощью изображения планера, который был загружен ранее. Точка поворота SPRITE установлена в центре, тело физики включено, а спрайт добавляет себя к родительской сцене.
class Glider extends Phaser.GameObjects.Sprite { /** * Glider class * * @extends Phaser.GameObjects.Sprite */ constructor(scene) { super(scene, 0, 0, 'glider'); this.setOrigin(0.5); // Physics scene.physics.world.enable(this); scene.add.existing(this); this.body.setGravityY(0); this.body.setBounceY(0.2); } update(t, td) { // Place frame uptade code here } }
Deep Div: Plider Physics
Модель физики
Планер не имеет никаких двигателей, чтобы продвинуть его, и он зависит от внешних сил, чтобы держать его в воздухе. Фактическая модель физики немного сложна, а не в объеме этой статьи, но в основном она сводится к этим силам:
Подводя итоги: гравитация толкает планера вниз, в то время как подтягивает толкает перпендикулярно плоскости планера. Кроме того, есть (намного меньше) сила сопротивления или трение воздуха, толкая напротив направления движения.
Основная цель – добиться циклического движения, где циклы скользящего объекта между спуском и похожим на это:
В реальной жизни есть мера контроля над планером, наклоняя самолет, но в нашем случае мы больше заинтересованы в репликации движения с помощью простой активации. По этой причине я решил имитировать бумажный самолет вместо того, поскольку он использует ту же модель.
Подделать его с упрощенной моделью
Реализация такой модели, как это может быть трудно и трудоемким, а не в объеме этой статьи. Вместо этого мы хотим упростить его, чтобы достичь достаточно близких результатов с гораздо менее усилием. Для этого мы хотели бы воспользоваться собственной системой физики Phaser Arcade для обработки моделирования физики и избежать выполнения ручных расчетов.
Ограничения аркадной физики
Аркадная физика само по себе – это упрощенная физическая система, которая предназначена для выполнения общих задач с минимальной настройкой. Важно понимать, что он ограничен простым API, который опускает некоторые из более сложных факторов и не обрабатывает применение сил непосредственно или правильное трение. Однако он позволяет устанавливать ускорение, которое пропорционально заставить. Если мы пренебрегаем массой объекта, ускорение будет эквивалентно силе. Обратите внимание, что мы в основном говорим о постоянном ускорении, в отличие от силы со временем. Трение в аркаде работает только тогда, когда тело имеет нулевое ускорение (никакие силы не применяются), поэтому он не очень полезен. Еще одним ограничением, на которые стоит упомянуть, связано с столкновениями: аркада может обрабатывать только прямоугольные тела с AABB (ограничиваемая AABL (выровненная осью), что означает, что коробка столкновения не может вращаться с телом. К счастью, это не представляет никаких проблем в нашем случае.
Как обрабатывать силы в аркаде
Поскольку мы должны иметь дело с применением сил, есть два случая:
- Постоянная сила – достигается, устанавливая ускорение и оставляя его как
- Импульс (мгновенно) силы – то же самое, но в течение фиксированного времени (например, 1 секунду)
Для импульса мы используем хитрость сброса ускорения после задержки. Таким образом, система физики просто будет использовать импульс объекта, пока он не начнет падать, что эквивалентно обнулению сил.
Мы добавляем следующую функцию в класс планера:
// Apply a momentary force to simulate impluse applyImpulseForce(forceVec, duration=1) { this.body.setAcceleration(forceVec.x, forceVec.y); this.scene.time.delayedCall(duration*1000, () => this.body.setAcceleration(0,0)); }
Руки на: надеть код вместе
Начать симуляцию
Начнем с очень простого запуска бумажной плоскости в воздух. Физика похожа на съемку снаряда из пушки: на добавлении функции запуска наносят один раз, что при добавлении функции запуска, которая принимает сил, как это:
launch(force, dir) { this.curState= GlideStates.LAUNCH; this.body.setGravityY(200); let forceVec = new Phaser.Math.Vector2(dir); // Clone it forceVec.scale(force); // Multiply direction with force this.applyImpulseForce(forceVec, 1.1); }
Таким образом, мы можем пройти любую силу и направление, чтобы использовать в запуске, с вариантом для игрока установить его со сцены.
Обновление со временем: обращение с вращением самолета
Для функции обновления нам необходимо использовать параметр Delta Time – DT, который содержит прошедшее время в миллисекундах с момента последнего кадра.
update(t, dt) { this.updateRotation(dt); }
Итак, теперь у нас есть основное движение, но что-то отсутствует: самолет не меняет шаг (вращается), как и ожидается от реальной жизни. Чтобы сделать самолетную точку в местном направлении «вперед», нам нужно что-то вроде этой функции, что вычисляет угол из вектора скорости:
updateRotation(dt) { const dir= this.body.velocity; // Update the rotation if (dir.x > 0.05) { this.rotation = dir.angle(); } }
Это в основном работает, но имеет пару вопросов. Во-первых, угол высоты тона иногда колеблется в ужасно. Причина того, что угол идет от около нуля до более 2pi, поэтому нам нужно нормализовать его. Класс Phaser Math имеет функцию только для этого: math.gant.wrap ().
// Update the rotation if (dir.x > 0.05) { this.rotation = Phaser.Math.Angle.Wrap( dir.angle()); }
Отделочные штрихи: более плавное вращение
Это исправляет это, но вращение выглядит очень дрожит и не чувствует себя естественным. Мы справимся с этим, добавив шаг вращения с линейной интерполяцией, используя функцию Math.Linear.
Линейная интерполяция или lerp для короткого, представляет собой общий метод для изменения значения со временем, который создает хорошее плавное вращение путем постепенного увеличения интерполированного значения на каждом раме. Шаг параметр контролирует скорость вращения или фракцию на раму. Умножая это по (DT/1000), мы нормализуем время использования секунд. Обратите внимание, что мы можем сделать вращение напряженным или свободным, изменив размер шага.
Хорошая метрика представляет собой DT/1000 в течение 1 секунды, 2 * DT/1000 на полтора секунды и так далее.
updateRotation(dt) { const dir= this.body.velocity; const step = dt * 0.001 * 2; // convert to sec const targetRot = Phaser.Math.Angle.Wrap( dir.angle()); // Update the rotation smoothly. if (dir.x > 0.05) { this.rotation = Phaser.Math.Linear(this.rotation, targetRot, step); } }
Окончательный результат можно увидеть здесь:
За пределы основного движения
Текущий код показывает только начальный запуск, но можно расширить дальше. Например, мы могли бы использовать функцию OnsaurePhulseforce и вызывать ее с различной силой и направлением.
Несколько возможных идей для расширения приходят на ум, в том числе:
- Добавить цель на запуск, поэтому игрок может установить направление и силу
- Дополнительные циклы тяги на основе ввода игрока
- Обнаружить, когда самолет достигает пика и собирается погрузиться вниз
- Измерьте максимальное расстояние. Самолет летел до удара на землю
Это, однако, слишком долго, чтобы покрыть здесь, и фактические детали для них могут быть покрыты в отдельном посте.
Обертывание
Полный код проекта доступен на GitHub: github.com/amosl/bapherplane.
Результатом является простая демо, которая стала много функций с очень несколькими строками кода. Это идет, чтобы показать, что, используя правильные инструменты и методы, многое может быть достигнуто за короткое время. С недавно выпущенной версией 3, Phaser – это очень мощный игровой двигатель, который предлагает множество приятных функций, но требует некоторого звона, чтобы выяснить, как его эффективно использовать. Простой механик может быть дополнительно расширен и образует базу для полной игры.