Программирование на Xtreme3D Создание игры от третьего лица Урок I: камера Создание качественного движка от третьего лица - задача совсем непростая. Необходимо уделить внимание всему: от камеры до искусственного интеллекта персонажей. Если говорить о коммерческих программах, таких как Blitz или Dark Basic, то тут особо напрягаться не нужно - в сети довольно много исходников и уроков на эту тему, но вот пользователи Xtreme3D почему-то обделены. Настало время исправить эту несправедливость! Предполагается, что Вы уже скачали Xtreme3D V2 для Game Maker (если нет, то сделать это можно по ссылке http://www.sentinelgames.com/download/Xtreme3D/Xtreme3DV2.zip). Подробное описание всех возможностей движка Вы можете прочитать в предыдущем номере FPS. Для начала работы Вы можете выбрать один из примеров или начать движок "с нуля". Последнее более предпочтительно, так как любое программирование осуществляется по принципу "от простого - к сложному". А все ненужные на данном этапе системы и процедуры будут лишь мешать и запутывать. Поэтому я все же предлагаю создать новый файл GM6 и в нем писать движок. В этом случае будет необходимо предварительно импортировать все скрипты с функциями Xtreme3D. Для тех, кто не знает или забыл, поясню: выбираете меню Scripts > Import Scripts и загружаете файл x3d.gml, который содержит все скрипты и поставляется вместе с движком. Да, чуть не забыл. Для работы Вам потребуется зарегистрированная версия Game Maker 5.x или 6.x (наверняка также поддерживается 7.x, но я лично не тестировал, так что не вполне уверен). Весь последующий программный код был написан мной под Game Maker 6.1. Все демонстрационные примеры, поставляемые с движком, используют Game Maker 6.х. Итак, начинаем. Я все буду писать в одном объекте - так легче, да и для статьи удобнее. Создадим новый объект o_engine, без спрайта, невидимый, глубина (depth) - 100. В событии Create прописываем: dll_init(); EngineCreate(window_handle()); matlib=MaterialLibraryCreate(); MaterialLibraryActivate(matlib); view=ViewerCreate(0,0,640,480); ViewerEnableVSync(view,vsmSync); Это стандартные процедуры, без которых либо движок не будет работать, либо Вы ничего не увидите. Первые две строки создают движок, еще две - Библиотеку материалов (Material Library), в которую по умолчанию будут грузиться текстуры. Последние две строки создают Вид (View) - окно рендеринга, в котором будет отрисовано трехмерное пространство. К слову сказать, существует возможность использовать несколько Видов, например, для создания мультиплеера, но об этом будет рассказано в будущих уроках. Здесь я создал Вид разрешением 640х480. Далее можно совершить некоторые настройки: ViewerSetLighting(view,true); ViewerSetBackgroundColor(view,$000000); ViewerEnableFog(view,1); ViewerSetFogColor(view,$000000); ViewerSetFogDistance(view,50,200); В настройках Вида Вы можете указать, использовать ли освещение (строка 1), какой цвет использовать для фона (строка 2), использовать ли туман (строка 3), цвет тумана (строка 4) и дальность тумана (строка 3). 50 - это минимальное расстояние, при котором начинается затуманивание, 200 - расстояние, на котором все объекты полностью исчезают в тумане. Вы можете указать любые значения по желанию. Теперь еще немного рутинных процедур: global.back=DummycubeCreate(0); global.scene=DummycubeCreate(0); global.front=DummycubeCreate(0); light=LightCreate(lsOmni,0); ObjectSetPosition(light,0,20,-10); Первые три строчки создают так называемые Манекены (Dummycube) - невидимые объекты, применяемые в качестве материнских для трех основных групп объектов: back (задний план - Небесный купол, Небесная коробка), scene (сценический план - все активные трехмерные объекты, включая декорации и персонажей), front (передний план - двумерные изображения и текст, отрисовываемые поверх экрана). Это требует некоторого пояснения. Структура многих интерфейсов, включая и OpenGL, основана на использовании зависимостей "материнский объект > дочерний объект". Это значит, что дочерние объекты "наследуют" настройки материнского и, таким образом, можно легко манипулировать одновременно многими дочерними объектами, управляя только одним материнским. Это просто удивительный по своей простоте и мощности механизм, значительно упрощающий создание игровой логики. По ходу уроков мы еще не раз встретимся с ним, это я гарантирую. Последние две строки создают источник света и ставят его на нужное место. В принципе, на данном этапе свет можно было вообще не использовать, но мне лично так больше нравится. К тому же, свет необходим для Теневой плоскости, о которой сейчас пойдет речь. В будущем мы еще коснемся вопроса освещения и его настроек. А сейчас начинается самое интересное. Мы познакомимся с таким классным объектом Xtreme3D, как Теневая плоскость. Это плоскость, на которую другие видимые объекты могут отбрасывать динамические тени. Напишите следующий код, а потом я объясню, как она работает: global.shadow_caster=DummycubeCreate(global.scene); plane=ShadowplaneCreate(128,128,10,10, global.shadow_caster,light,c_black,0.6,global.scene); MaterialCreate('mPlane','DATA/plane.jpg'); ObjectSetMaterial(plane,'mPlane'); ObjectPitch(plane,90); Мы создаем общий материнский Манекен для всех объектов, которые будут отбрасывать тени. Затем создаем непосредственно саму Плоскость и Материал для нее (тут понадобится файл с какой-нибудь текстурой - думаю, с этим у Вас проблем не возникнет). Материалом в Xtreme3D называется одна или несколько текстур с настройками отображения (маппинга) и реакции на освещение. В качестве текстуры может быть использовано любое изображение формата, поддерживаемого Xtreme3D, и разрешения, поддерживаемого видеокартой. Мой совет: используйте системные ресурсы как можно более рационально. Для текстур выбирайте наиболее подходящий формат (в большинстве подойдет png, но для больших размеров лучше использовать jpg) и наиболее выйгрышное разрешение (не больше, чем будет видно на экране, и притом желательно с размерами сторон, равными степеням двойки, т.е. 256х256, 512х256, 1024х1024 и т.д.) Это относится не только к текстурам, но и любым изображениям в игре. Причина тому кроется в особенностях распределения памяти: все изображения хранятся в оперативке с вышеупомянутыми разрешениями, соостветственно, изображение шириной или высотой хотя бы на 1 пиксель больше, чем 1024, будет храниться как 2048, занимая в два раза больше памяти, чем оно требуется! Нижеследующий код создаст иерархию из персонажа, которым игрок будет управлять, и Камеры, которая будет за ним следить. В качестве условного персонажа используется простой куб. down=DummycubeCreate(0); ObjectSetAbsoluteDirection(down,0,-1,0); d=DummycubeCreate(global.shadow_caster); ObjectSetPosition(d,0,0,-50); camera=CameraCreate(global.scene); CameraSetViewDepth(camera,800); CameraSetFocal(camera,80); ViewerSetCamera(view,camera); ObjectSetPositionOfObject(camera,d); actor=CubeCreate(4,4,4,d); MaterialCreate('mActor','DATA/ball.jpg'); ObjectSetMaterial(actor,'mActor'); ObjectSetPosition(actor,0,2,0); ObjectRotate(actor,0,0,0); target=DummycubeCreate(d); ObjectSetPosition(target,0,10,-30); CameraSetTargetObject(camera,d); jump=0; grav=0; Ну, и в конце события Create ставим одну последнюю строчку: set_automatic_draw(false); Это отменит автоматическое перекрашивание экрана. Поскольку мы все равно не увидим то, что будет отрисовано в 2D-окне Game Maker, то и тратить силы видеокарты на него и не нужно. Xtreme3D имеет собственную функцию, которая будет рендерить графику в каждом шаге. Вообще-то, говоря откровенно, рендеринг в каждом шаге - не есть хороший тон, так как это непростительное пренебрежение потенциалом системы. Но на данный момент мы никаких особо ресурсоемких эффектов использовать не будем, поэтому и так сойдет. А об оптимизации поговорим в другой раз. Итак, в событии Step помещаем такой код: cx=ObjectGetAbsolutePosition(camera,0); cy=ObjectGetAbsolutePosition(camera,1); cz=ObjectGetAbsolutePosition(camera,2); tx=ObjectGetAbsolutePosition(target,0); ty=ObjectGetAbsolutePosition(target,1); tz=ObjectGetAbsolutePosition(target,2); dx=tx-cx; dy=ty-cy; dz=tz-cz; ObjectTranslate(camera,dx*0.05,dy*0.05,dz*0.05); ObjectLift(d,-grav+jump); jump-=0.1; if jump<0 jump=0; if ObjectGetDistance(camera,target)>30 ObjectLift(camera,1); ObjectSetPosition(down,d); dy=ObjectRaycast(down,plane); ty=ObjectGetAbsolutePosition(d,1)-2; if ty>dy grav+=0.1; else grav=0; if keyboard_check(vk_left) ObjectRotate(d,0,3,0); if keyboard_check(vk_right) ObjectRotate(d,0,-3,0); if keyboard_check(vk_up) ObjectMove(d,0.8); if keyboard_check(vk_down) ObjectMove(d,-0.8); if keyboard_check(ord('A')) ObjectStrafe(d,-0.5); if keyboard_check(ord('D')) ObjectStrafe(d,0.5); if (keyboard_check(vk_space) && ty<=dy) jump=2.6; Update(); ViewerRender(view); Я не буду разбирать его по кускам (тот, кому это нужно - сам разберется), только поясню, что в основном это управление камерой и персонажем. Стрелки "вправо" и "влево" поворачивают персонаж, "вверх" и "вниз" - двигают его вперед и назад. Клавишей Space можно подпрыгнуть, а A и D - двигаться вбок. Логика Камеры устроена так, что ее наиболее дальняя дистанция от персонажа - при движении вперед (чтобы можно было хорошо видеть, что творится вокруг), а самая близкая - при движении назад. При повороте персонажа Камера позволяет рассмотреть его сбоку. Примерно такой же прием используется в гоночных симуляторах, так что на основе этого примера вполне можно сделать движок гонок. Это все! Можно ставить объект в комнате, запускать игру и наслаждаться "умной" камерой и красивой тенью. Этот пример использует лишь один из многих способов решения камеры Third Person, но все они используют общий принцип. Немного подредактировав код, можно получить любой движок от третьего лица. В следующий раз мы создадим настоящего персонажа из анимированной 3D-модели! Использовать будем MD2, и в качестве домашнего задания я предлагаю Вам заранее найти подходящую модель в Интернете. Желательно такую, которая была сделана для Quake II (просто потому что в них обычно есть все нужные анимации - бег, стрельба, прыжок и т.д.) Таких в свое время было понаделано немало, так что с поиском проблем возникнуть не должно. Gecko "FPS" №2 2008 Некоторые части кода были взяты из примера Dynamic 3rd Person.pb (автор: Xception), входившего в дистрибутив Xtreme3D PureBasic