Это вторая часть нашего исследования первого легендарного мобильного графического процессора 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.