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

Это продолжение нашей статьи о первом легендарном мобильном графическом процессоре: что такое PowerVR MBX Lite? Создание демо-игры с нуля об автомобилях Лада (Часть 1

❯ Практическая часть: Геймплей

Отказ от ответственности: эта игра была создана как простая, но образовательная демоверсия исключительно для PowerVR MBX и Axim X51v. Таким образом, нет регулярных таймеров обновления или расчета разницы во времени, а игра привязана к фиксированным значениям продолжительности и скорости!


Так как же такая игра работает «под капотом»? Модели фоновых пейзажей и дороги просто прокручиваются или телепортируются одна за другой, когда части сцены уходят за пределы экрана, создавая эффект бесконечной дороги. И этот прием используется во многих играх. Что касается машин, которых нам следует избегать, то не мы ведем их, они везут нас. В результате получается эффект, что мы с вами куда-то едем и избегаем машин, хотя на самом деле это не так!

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

общедоступная сущность абстрактного класса
{
публичное преобразование конверсии;

publicabstractvoid Обновление();
publicabstractvoid Draw();
}

общественная пустота Spawn (Entity ent)
{
если (ent! = ноль)
Сущности.Добавить(ент);
}

publicvoid Удалить (entity ent)
{
Список удаления объекта.Добавить(ent);
}

публичное недействительное обновление()
{
Пустой.Обновление();
рендерер.Обновление();
спаунер.Обновление();

foreach (сущность внутри сущности ent)
ент.Обновление();

foreach (объект, введенный в объектеEntityRemovalList)
Сущности.Удалить(ент);

Список удаления объектов. Очистить();
}

publicvoidDraw()
{
Пустой.Рисовать();

рендерер.Рисовать();

foreach (сущность внутри сущности ent)
ент.Рисовать();
}

Наш первый объект — управляемая машина игрока!
я получил низкополигональную модель от SketchFab. Вот ссылки на ВАЗ 21099 и VW Golf Mk2. Спасибо авторам моделей за их труд!

Наследуйте Entity и реализуйте абстрактные методы с объектной логикой. Здесь мы получаем состояние левой и правой аппаратных кнопок. Он соответствующим образом вычисляет направление вращения машины и фактически вращает машину, складывая вычисленное направление и координату X и умножая их на «скорость». Вращение машины. Используйте эффекты 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 примитивную дорогу с элементами ландшафта:

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

Рендерер государственного сектора()
{
дорога = Модель.ИзФайла("дорога.mdl");
roadmaterial.diffuse = Texture2D.FromFile("road.tex");

Terrain = Model.FromFile("Terrain.mdl");
terrainMaterial.Diffuse = Текстура2D.FromFile("grass.tex");

Сектор 1.Позиция.Y = -4;
Сектор 2.Позиция.Y = -4;
Сектор2.позиция.Z = размер сектора;
}

публичное недействительное обновление()
{
Сектор1.позиция.Z -= скорость прокрутки;
Сектор2.позиция.Z -= скорость прокрутки;

если (сектор1.Позиция.Z + Размер сектора <0)
Сектор1.позиция.Z = размер сектора;

если (сектор2.Позиция.Z + Размер сектора <0)
Сектор2.позиция.Z = размер сектора;
}

publicvoidDraw()
{
Engine.Current.Graphics.DrawModel(дорога, сектор 1, дорожный материал);
Engine.Current.Graphics.DrawModel(Ландшафт, Сектор 1, Материал ландшафта);
Engine.Current.Graphics.DrawModel(дорога, сектор 2, дорожный материал);
Engine.Current.Graphics.DrawModel(Ландшафт, Сектор 2, Материал ландшафта);
}

здесь Terrain.mdl — это окружающий ландшафт, а road.mdl — это собственно сетка дорог. Вы сможете добиться следующих эффектов:

артефакт на видео является результатом проблемы точности с плавающей запятой в MBX Lite во время обрезки геометрии с близкой плоскостью отсечения 0,1f.

Обратите внимание: По каким параметрам выбрать и как лучше эксплуатировать мобильный телефон..

Измените его на 1.0f и все снова будет работать нормально :)
Если немного изменить проекцию, переместив камеру выше и наклонив ее на 45 градусов, игра уже напоминает Traffic Racer!

Перейдем к реализации машины трафика. Модель автомобиля загружается в начале игры:

private static void LoadTrafficModel (int idx, имя строки)
{
PreloadedCars[idx] = Model.FromFile(name + ".mdl");
Предварительно загруженные материалы[idx].Diffuse = Texture2D.FromFile(name + ".tex");
}

предварительная загрузка publicstaticvoid()
{
PreloadedCars = новая модель[1];
Предварительно загруженные материалы = новый материал[1];

LoadTrafficModel(0, «трафик1");
}

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

рандом = новый случайный();
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);
Модель = PreloadedCars[carModel];
материал = предварительно загруженный материал [carModel];

И во время обновления машина едет все ниже и ниже. Даже без реструктуризации логика очень проста.

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

Перейдем к обработке столкновений. Помните, что на этапе преобразования модели мы рассчитывали ограничивающую рамку, выровненную по каждой оси модели? Мы используем классический алгоритм AABB (Rect vs. Rect:

пересечение public bool (поле BoundingBox)
{
return (X < box.X + box.X2 && Y < box.Y + box.Y2 && Z < box.Z + box.Z2 && box.X < X + X2 && box.Y < Y + Y2 && box. Z < Z + Z2);
}

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

граница = модель.граница;
Границы.X += Transform.Position.X;
Bounds.Y += Transform.Position.Y;
Границы.Z += Transform.Position.Z;

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

foreach (сущность в Game.Current.World.Entities)
{
if (ent — TrafficCar)
{
if (Player.Bounds.Intersects(((TrafficCar)ent).Bounds))
{
// TODO: логика повреждения
Player.IsDestroyed = правда;
}
}
}

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

публичная пустота Draw()
{
string coreFmt = string.Format("Оценка: {0} x{1}", Game.Current.world.Statistics.Score, 1);
Engine.Current.Graphics.DrawString(scoreFmt, 15, 15, StatsColor);

если (Game.Current.world.Player.IsDestroyed)
{
intmeter = Engine.Current.Graphics.MeasureString(RestartString);
Engine.Current.Graphics.DrawString("Нажмите Return, чтобы перезапустить", Engine.Current.Graphics.ViewWidth / 2 - (мера / 2), Engine.Current.Graphics.ViewHeight / 2, StatsColor);
}
}

Вот что я получил:

Правда ли, что что-то подобное происходит каждую ночь на МКАД?Я не из МСК :)

❯ Заключение


Это документ о PowerVR MBX. С выпуском iPhone этот графический процессор положил начало появлению красивых мобильных игр с уровнем графики, приближающимся к уровню полноценных консолей. Жаль, что золотой век интересных, самостоятельных мобильных игр, не требующих донатов, закончился. Это закончилось с iPhone 5 :(

В остальном, я надеюсь, этот материал был достаточно интересным и познавательным для всех читателей, даже для тех, кто никогда не программировал игры. У вас когда-нибудь был Dell Axim X51v, напишите об этом в комментариях!

Исходный код и двоичные файлы демо-версии можно найти на моем GitHub.

Этот документ был создан при поддержке TimeWeb Cloud. Подпишитесь на меня @Timeweb.Cloud и не пропустите новую статью каждую неделю. Еще у меня есть собственная корзина, где я время от времени публикую закулисные статьи и ссылки на новый материал!

Всего голосов:

Он также собирает деньги на самостоятельный проект по созданию настоящей физической раковины и ее электронной модификации с использованием бортового компьютера «по минимально возможной цене». Вы уже собрали 50 000 рублей из 70 000 рублей, которые планировали купить автомобиль. Из этой суммы 45 000 рублей составили мои личные сбережения, а 5 000 рублей — взносы читателей. Большое спасибо :)

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

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

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