Разработка демо-игры для PowerVR MBX Lite: от архитектуры объектов до столкновений

Это вторая часть нашего исследования первого легендарного мобильного графического процессора PowerVR MBX Lite. Мы продолжаем практическое создание демонстрационной игры про автомобили «Жигули» с чистого листа.

❯ Практическая часть: Реализация игрового процесса

Важное замечание: данная демо-игра создавалась как учебный проект, заточенный исключительно под возможности PowerVR MBX и устройства Dell Axim X51v. Поэтому в ней используются фиксированные значения скорости и длительности, а не динамические расчеты на основе времени между кадрами (delta time).


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

Основа архитектуры — система игровых объектов. Для нашей задачи не требуется сложная Entity Component System (ECS) или граф сцены. Достаточно классического линейного списка объектов, подобного тому, что использовался в Half-Life. Мир игры (World) в каждом кадре проходит по этому списку, обновляя и отрисовывая каждую сущность.

Базовый абстрактный класс для всех игровых объектов может выглядеть так:

public abstract class Entity
{
public Transform transform;

public abstract void Update();
public abstract void Draw();
}

Менеджер мира отвечает за добавление, удаление и итерацию по всем объектам:

public void Spawn(Entity ent)
{
if (ent != null)
Entities.Add(ent);
}

public void Remove(Entity ent)
{
EntityRemovalList.Add(ent);
}

public void Update()
{
// Обновление систем (UI, рендерера, спаунера)
ui.Update();
renderer.Update();
spawner.Update();

// Обновление всех сущностей
foreach (Entity ent in Entities)
ent.Update();

// Удаление помеченных объектов
foreach (Entity ent in EntityRemovalList)
Entities.Remove(ent);

EntityRemovalList.Clear();
}

public void Draw()
{
ui.Draw();
renderer.Draw();

foreach (Entity ent in Entities)
ent.Draw();
}

❯ Машина игрока и управление

Первый и главный объект — автомобиль, которым управляет игрок. Для демо были взяты низкополигональные модели ВАЗ 21099 и VW Golf Mk2 с платформы SketchFab (спасибо их авторам!).

Класс машины игрока наследуется от Entity. В методе Update считывается состояние аппаратных кнопок (влево/вправо), на основе которого вычисляется горизонтальная скорость и угол поворота руля. Для плавности поворота используется интерполяция (Lerp) с эффектами EaseIn/EaseOut.

float hVel = Engine.Current.Input.GetKeyState(GamepadKey.Left) ? -1 : (Engine.Current.Input.GetKeyState(GamepadKey.Right) ? 1 : 0);

Transform.Position.X += hVel * SteerSpeed;
Transform.Rotation.Y = MathUtils.Lerp(Transform.Rotation.Y, 180 + (hVel * 35), 0.1f);

❯ Создание бесконечной дороги

Чтобы машина куда-то «ехала», нужна дорога. В Blender моделируется простой сегмент дороги с окружающим ландшафтом.

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

public class SectorRenderer
{
private Model road, terrain;
private Material roadMaterial, terrainMaterial;
private Vector3 sector1Pos, sector2Pos;
private float sectorSize = 100.0f;
private float scrollSpeed = 2.0f;

public SectorRenderer()
{
road = Model.FromFile("road.mdl");
roadMaterial.Diffuse = Texture2D.FromFile("road.tex");

terrain = Model.FromFile("terrain.mdl");
terrainMaterial.Diffuse = Texture2D.FromFile("grass.tex");

sector1Pos.Y = -4;
sector2Pos.Y = -4;
sector2Pos.Z = sectorSize;
}

public void Update()
{
// Двигаем оба сегмента
sector1Pos.Z -= scrollSpeed;
sector2Pos.Z -= scrollSpeed;

// Телепортируем сегмент, если он ушел за пределы
if (sector1Pos.Z + sectorSize < 0)
sector1Pos.Z = sectorSize;

if (sector2Pos.Z + sectorSize < 0)
sector2Pos.Z = sectorSize;
}

public void Draw()
{
Engine.Current.Graphics.DrawModel(road, sector1Pos, roadMaterial);
Engine.Current.Graphics.DrawModel(terrain, sector1Pos, terrainMaterial);
Engine.Current.Graphics.DrawModel(road, sector2Pos, roadMaterial);
Engine.Current.Graphics.DrawModel(terrain, sector2Pos, terrainMaterial);
}
}

В результате получается плавно движущийся фон. Примечание: артефакты на видео (если они есть) могут быть вызваны особенностью MBX Lite при работе с близкой плоскостью отсечения (near clip plane) в 0.1f. Изменение этого значения на 1.0f обычно решает проблему. Если поднять камеру выше и наклонить, игра начинает напоминать классический Traffic Racer!

❯ Машины трафика и их логика

Модели машин трафика загружаются при старте игры для оптимизации:

private static void LoadTrafficModel(int idx, string name)
{
PreloadedCars[idx] = Model.FromFile(name + ".mdl");
PreloadedMaterials[idx].Diffuse = Texture2D.FromFile(name + ".tex");
}

public static void Preload()
{
PreloadedCars = new Model[1];
PreloadedMaterials = new Material[1];

LoadTrafficModel(0, "traffic1");
}

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

Random rand = new Random();
Transform.Position.X = Game.Current.World.PickLane(rand.Next(0, 4));
Transform.Position.Y = Game.Current.World.Player.Transform.Position.Y;
Transform.Position.Z = rand.Next(ZOffset, ZOffsetMax);

selectedBias = rand.Next(0, SpeedBias.Length - 1);

int carModel = rand.Next(0, PreloadedCars.Length - 1);
Model = PreloadedCars[carModel];
Material = PreloadedMaterials[carModel];

В каждом кадре машина трафика «приближается» к игроку (на самом деле уменьшается её Z-координата):

Transform.Position.Z -= BaseSpeed * SpeedBias[selectedBias];

❯ Система столкновений

Для обработки столкновений используется классический алгоритм AABB (Axis-Aligned Bounding Box) — проверка пересечения прямоугольных областей, выровненных по осям.

public bool Intersects(BoundingBox box)
{
return (X < box.X + box.Width) && (X + Width > box.X) &&
(Y < box.Y + box.Height) && (Y + Height > box.Y);
}

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

bounds = model.Bounds;
Bounds.X += Transform.Position.X;
Bounds.Y += Transform.Position.Y;
Bounds.Z += Transform.Position.Z;

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

foreach (Entity ent in Game.Current.World.Entities)
{
if (ent is TrafficCar)
{
if (Player.Bounds.Intersects(((TrafficCar)ent).Bounds))
{
// Логика повреждения
Player.IsDestroyed = true;
}
}
}

❯ Интерфейс и завершение игры

Добавим простой интерфейс для отображения счета и сообщения о перезапуске после аварии.

public void Draw()
{
string scoreFmt = string.Format("Score: {0} x{1}", Game.Current.World.Statistics.Score, 1);
Engine.Current.Graphics.DrawString(scoreFmt, 15, 15, StatsColor);

if (Game.Current.World.Player.IsDestroyed)
{
int measure = Engine.Current.Graphics.MeasureString(RestartString);
Engine.Current.Graphics.DrawString("Press Return to restart",
Engine.Current.Graphics.ViewWidth / 2 - (measure / 2),
Engine.Current.Graphics.ViewHeight / 2, StatsColor);
}
}

В итоге получается работоспособная демо-игра. Шутка про ночной МКАД остается на совести автора :)

❯ Заключение


PowerVR MBX — это исторически важный GPU, который с появлением iPhone заложил фундамент для красивых мобильных игр, графически приближающихся к консолям. К сожалению, золотой век самодостаточных мобильных игр, не требующих постоянных платежей, закончился примерно с эпохи iPhone 5.

Надеемся, этот материал был интересен и познавателен, даже для тех, кто никогда не программировал игры. Был ли у вас Dell Axim X51v? Делитесь воспоминаниями в комментариях!

Исходный код и бинарники этой демо-игры доступны на GitHub автора.

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

Также автор собирает средства на самостоятельный проект: создание физической раковины с бортовым компьютером «по минимально возможной цене». На данный момент собрано 50 000 рублей из целевых 70 000. Спасибо всем, кто поддерживает!

[my]Опрос Программирование Гаджеты Смартфон 3D-графика Разработка игр 3D-видеокарта OpenglGlesDirectx Видео Без звука Вертикальное видео Длинная запись 3

Больше интересных статей здесь: Гаджеты.

Источник статьи: Первый легендарный мобильный GPU: каким был PowerVR MBX Lite? Пишем игру-демку про «жигули» с нуля, ч. 2.