Я создаю 3D локации из игр
Сейчас провожу опрос на стриме. Пожалуйста, примите участие ! Варианты: Doom2, Quake2, LBA, Neverhood.
Сейчас провожу опрос на стриме. Пожалуйста, примите участие ! Варианты: Doom2, Quake2, LBA, Neverhood.
Претенденты:
Валерия Агафонова, работа - Хашим, Disciples III
2. Анатолий Усенко, работа - Сильфида (Дева ветра) из Disciples III
3. Роберт Берков, работы - Демон Утер и ассассин, Disciples II
4. Дмитрий 'WiNion' Кошелев, постройки Снежные и ледяные пещеры из невышедшего дополнения для Disciples III.
Это продолжение поста Первый легендарный мобильный GPU: каким был PowerVR MBX Lite? Пишем игру-демку про «жигули» с нуля, ч.1
Дисклеймер: игра была написана как простенькая, но познавательная демка именно для PowerVR MBX и именно для Axim X51v. Именно поэтому здесь нет нормального Update-таймера, расчёта дельты времени, а игра прибита к константным временным отрезкам и величинам скорости!
Итак, как же игры подобного планы работают «под капотом»? По факту, обычно мы с вами никуда не едем: фоновые модели ландшафта и дороги просто скроллятся и телепортируются друг за другом, когда одна из частей уходят за экран, что создаёт эффект бесконечной дороги. И эта техника используется во многих играх! Что же касается машинок, от которых мы должны лавировать, то это не мы едем на них, это они едут на нас! По итогу создаётся эффект будто мы с вами куда-то едем и уворачиваемся от машинок, хотя на деле это не так!
Начинаем с реализации базовой вещи в архитектуре любой современной игры, а именно системы игровых объектов. В нашей игре нет необходимости в реализации сложного графа сцены с комплексной компонентной системой, или, например, 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()
{
sky.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()
{
sky.Draw();
renderer.Draw();
foreach (Entity ent in Entities)
ent.Draw();
}
Самым первым нашим объектом будет машинка игрока, которой можно будет управлять!
Модельки я взял лоуполи со скетчфаба, вот ссылка на ВАЗ 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);
Теперь нам нужно, чтобы машинка где-то «ездила». Для этого мы моделируем в блендере примитивный кусок дороги с элементами ландшафта:
А затем реализуем примитивный рендерер фона, который будет скроллить два одинаковых seamless-куска уровня и как я уже говорил ранее, просто телепортировать их друг за другом, создавая эффект бесконечности.
public SectorRenderer()
{
road = Model.FromFile("road.mdl");
roadMaterial.Diffuse = Texture2D.FromFile("road.tex");
terrain = Model.FromFile("terrain.mdl");
terrainMaterial.Diffuse = Texture2D.FromFile("grass.tex");
sector1.Position.Y = -4;
sector2.Position.Y = -4;
sector2.Position.Z = SectorSize;
}
public void Update()
{
sector1.Position.Z -= ScrollingSpeed;
sector2.Position.Z -= ScrollingSpeed;
if (sector1.Position.Z + SectorSize < 0)
sector1.Position.Z = SectorSize;
if (sector2.Position.Z + SectorSize < 0)
sector2.Position.Z = SectorSize;
}
public void Draw()
{
Engine.Current.Graphics.DrawModel(road, sector1, roadMaterial);
Engine.Current.Graphics.DrawModel(terrain, sector1, terrainMaterial);
Engine.Current.Graphics.DrawModel(road, sector2, roadMaterial);
Engine.Current.Graphics.DrawModel(terrain, sector2, terrainMaterial);
}
Где terrain.mdl — окружающий ландшафт, а road.mdl — собственно, сам меш дороги. Получаем вот такой эффект:
Артефакты на видео — следствие проблем с точностью float у MBX Lite в процессе клиппинга геометрии при ближней плоскости отсечения в 0.1f. Меняем на 1.0f и всё снова работает нормально :)
Чуть изменяем проекцию, переместив камеру выше и наклонив на 45 градусов и игра уже похожа на 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");
}
А сама их логика предельно проста. При спавне, машинка выбирает себе полосу, по которой будет ехать и рандомный множитель скорости, который вносит разнообразие в игру:
Rand 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];
А при обновлении, машинка просто продолжает ехать вниз! Логика простая до жути, даже без перестроений.
Transform.Position.Z -= BaseSpeed * SpeedBias[selectedBias];
Переходим к обработке столкновений. Помним, что мы на этапе конвертации моделей посчитали Axis Aligned Bounding Box для каждой модели? В качестве алгоритма мы будем использовать классический AABB — или Rect vs rect:
public bool Intersects(BoundingBox box)
{
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);
}
Теперь для проверки столкновения между ними, нам надо посчитать абсолютный 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))
{
// TODO: Damage logic
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! С выходом iPhone, этот GPU дал толчок для появления красивых мобильных игр с уровнем графики, близким к полноценным домашним консолям… жаль, что золотая эра интересных, самодостаточных и бездонатных мобильных игр и закончилась во времена iPhone 5 :(
В остальном же, надеюсь материал был достаточно интересен и познавателен для всех моих читателей, даже тех, кто никогда не программировал игры! Был у вас Dell Axim X51v? Пишите в комментариях!
Исходный код демки и бинарники можно найти на моём гитхабе.
Материал написан при поддержке TimeWeb Cloud. Подписывайтесь на меня и @Timeweb.Cloud , чтобы не пропускать новые статьи каждую неделю! А ещё у меня есть своя телега, куда я публикую бэкстейдж статей и вовремя публикую ссылки на новый материал!
А ещё я собираю деньги на проект с уже настоящим, физическим ТАЗом и его электронным дооснащением бортовым компьютером "по самому дешману" своими руками! Уже собрано 50.000 рублей из планируемых 70.000 на машину, из них 45.000 моих личных сбережений и 5.000 рублей - помощь читателей, за что вам большое спасибо :)
Мы постарались сделать каждый город, с которого начинается еженедельный заед в нашей новой игре, по-настоящему уникальным. Оценить можно на странице совместной игры Torero и Пикабу.
Реклама АО «Кордиант», ИНН 7601001509
Недавно загорелись идеей: а что если часть скалистой породы поместить в рамку, и на стену или, скажем, на рабочий стол?
Скалы под рукой не было... но, совершенно случайно оказались: пластилин, силикон, гипс и деревянная рамка подходящего размера)
Слепили форму, залили силиконом, и voila! молд готов, и пошла штамповка форм из гипса.
А дальше больше: покраска - процесс увлекательный, но, сосредоточенность и увлеченность процессом заставляет нас задуматься: а может попробовать не только скалу одного времени года, а, скажем, зимнюю или весеннюю (цветущую).
И началось:
- а как имитировать снег?
- а как посадить травку?
- а как сделать кустики?
и еще много увлекательных вопросов пришлось решить.
Но, надеюсь, результат того стоил.
Какая игра вам запала в душу, делитесь в комментариях.
Сейчас активно готовлю материал о том, как работали 3D-игры "под капотом" на слабеньких кнопочниках, каким образом разработчикам удавалось достичь приемлемый FPS и какие графические API для отрисовки трехмерной графики существовали на Java-телефонах. А дабы материал не был голословным, в практической части мы с нуля напишем 3D-шутер "а-ля 90е" с использованием Mascot Capsule и погоняем его на реальном телефоне Sony Ericsson! Материал выйдет в течении следующей недели (скорее всего среда-четверг).
Мне показалось, что будет интересно начать делать блог по моим шагам в 3D. Когда видишь, что люди замечают твои посты/модели, комментируют — это даёт толчок заниматься. Я сама не ожидала, что это может так мотивировать. Так что добро пожаловать в мой укромный уголок :) Одним вечером после просмотра видео по Backrooms во мне зажегся внутренний огонь, полный амбиций, сделать такие же видео. И я, наполненная этой мотивацией, решила сесть за что-то простое для начала. И тогда же мне попались туториалы по созданию комнаты в Blender — это и стало основой под мою модель.
TW: Я новичок в 3D (за моими плечами всего два видео), и из-за этого тут может быть большое количество ошибок/недочётов. Все мы учимся, но буду рада фидбеку.
Концепция
Ослепленной мыслью, что сейчас как сяду и сделаю лучшую в мире модель, я столкнулась с тем, что это всё намного сложнее, чем кажется на первый взгляд. Сначала надо было придумать концепцию, расставить обьекты красиво в кадре — и, главное, не испортить композицию. Как раз с самым последним было сложнее всего, ведь ты добавляешь что-то и думаешь: "Хмм, отдельно выглядит красиво, однако со всеми остальными обьектами кажется уже плохим". И ты сидишь пару часов — крутишь, вертишь, ищешь другие варианты. Для меня это было максимально необычно и непривычно, ведь я, в общем, никогда не создавала что-то своё (в плане концепции).
Так выглядят сами Backrooms 0 lvl:
Мне хотелось передать немного хоррор-атмосферу и добавить пару небольших деталей. Также я оставила отсылку на уровень Веселья (шарик), Улыбашку — не могла пройти мимо. Старалась не добавлять детали, которые дают уход от канона. Альфа-текстуры Создав примерный вид сцены и обьектов, появилась другая проблема с созданием отпечатков кровью. После этого где-то на протяжение часа пыталась из mesh сделать сам отпечаток :D Не спрашивайте как, но я хотела это сделать, мне в тот момент казалось, что это максимально правильный путь. И только потом до меня дошло, что надо использовать альфа-текстуры. Сразу пришла в голову мысль: «Сейчас как дело пойдёт, всего-то надо найти png картинки». Ага, ошиблась, в 3Д всё не так просто. Я пыталась на разных сайтах найти подходящие текстуры, везде они были ужасное качества, та и прозрачный фон был очень неровным. И после пару часов мучений (даже была попытка в Photoshop самой сделать png фон) мне скинули сайт Bridge. Quixel Bridge — сайт с большим ассортиментом текстур, кистей, ассетов. Очень советую его, он меня спас. Алгоритм использования: - Создать аккаунт в Epic Games. - Войти на сайте через этот аккаунт, тем самым все будет доступно бесплатно. Но желательно прочесть соглашение, там есть пару условий в пользование (для меня некритичных) . - Использовать версию Blender не выше 3.1, так как иначе будет ошибка в экспорте.
Доброе комьюнити
Главная проблема, с которой я сразу же после этого столкнулась — нажала ctr C, ctr V, в результате получила два одинаковых объекта. Но у одного из них прозрачность не работает, хотя я ничего не меняла, а у второго — всё идеально. Мучилась над этим лет 100, думала, что проблема в самих текстурах. Как оказалось, в настройках надо было поставить Alpha Clip у нового объекта. К большому удивление, мне с этим помог незнакомый человек, чьи видео я тогда смотрела на Youtube. Настолько отчаявшись, написала ему в тг — и мы даже созвонились, где мне всё обьяснили. Для меня это был максимально добрый поступок, ведь было 12 часов ночи, а мы даже не знакомы.
Огромный респект ему — https://youtube. com/@PlusSorok? si=24wr-Am3IwXxSz31
Персонаж
К сожалению, я признаю, что в одном моменте немного схалтурила, когда взяла готовую модель для персонажа. За моими плечами только два видео по локации, а делать персонажа с нуля — это уже сложный путь, отдельно курс надо проходить. В данный момент я бы хотела сосредоточиться на лёгких деталях, таких как окружение (комната, природа, какие небольшие детали мебели). Однако это не облегчило мне задачу, так как все готовые модели были без костей, а настроить их нетрудно — но! Большинство моделей были сделаны в виде полноценного объекта (не знаю, как правильно назвать, но там не было разделения на отдельные части). И я не могла, например, к руке кость прикрепить, так как отдельной руки не было. В результате нашла другое модель спустя вечность, она уже была хорошая. Если кому-то нужна сама модель, могу скинуть ссылку на автора, ему огромное спасибо.
Планы
В заключение могу сказать, что я немного растеряла под конец свой энтузиазм. Однако, как только ты видишь свое окончательное творение — ты будто восполняешь свои силы. И всё равно на бессонные ночи, чтобы довести до некого идеала, на мой взгляд, проект. Но это стоит того!
Пока не буду браться за курс, ведь это большое дело, хочу сделать небольшие модели. Вообще мой алгоритм осваивания Blender таков: сначала смотрю туториал, делаю по видео (разбирая в голове каждое действие) , а дальше делаю самостоятельно модель. Похожую из видео, но на свой лад, чтобы закрепить пройденное.
Так что дальше хочу сделать небольшой перерыв, ведь на ту сцену я потратила, как новичок, много времени. И теперь хочется что-то попроще — увидела стиль 2Д, но в Blender. И прям заинтриговало! Появилось желание сделать пару небольших моделей по этой теме. Есть ещё парочка целей, но пока их все раскрывать не буду — пусть будет интрига :D
Помощь
Хотелось бы, чтобы Вы поделились своими способами сохранения мотивации. Я человек, который легко строит цели, но интерес ко всему быстро пропадает. Возможно, кто-то с таким сталкивался? За детство во все возможные ходила кружки, но логично, что это было недолго. Не хотелось бы, чтоб такое и с 3D произошло.
А те, кто прочёл полностью мою статью, посылаю виртуальные обнимашки! Спасибо Вам, мне очень приятно :)
P.S. Возможно, Вы видели мои посты до этого, я их публиковала на DTF. Меня заинтересовала и эта платформа, так как никогда не сидела на ней. Если людям понравится, то перенесу сюда + новые посты буду делать. Кто-то знает меня уже?)