Понедельник
29.04.2024, 15:34
Приветствую Вас Гость | RSS
Главная Каталог статей Регистрация Вход
Меню сайта

Категории раздела
Уроки програмирования игр на IrrLicht [7]
Уроки програмирования на игровом движке IrrLicht
Обработка цифровых изображений [0]
Различные способы реализации графических фильтровов,работа с цветовыми пространствами
Програмирование игр с использованием DirectX [3]
Програмирование трёхмерной графики на DirectX под Windows

Наш опрос
На каком языке вы програмируете?
Всего ответов: 209

Статистика

Онлайн всего: 1
Гостей: 1
Пользователей: 0

Форма входа

Главная » Статьи » Програмирование » Уроки програмирования игр на IrrLicht

Урок 7.Пермещение по сферической поверхности.

Написанное в данной статье относится к графическому движку IrrLicht. Однако она не посвящена каким-либо особенностям данного движка.  Основная идея статьи может быть легко перенесена на любой другой графический движок.

Итак, представьте себе, что вам захотелось написать стратегию или экшен, но поверхность, на которой происходит действие не  плоская, а сферическая. Конечно, данная идея не является новой, эта идея реализована в очень многих современных компьютерных играх. Но все-таки, если мы решили сделать именно такую игру, какие проблемы могут у нас возникнуть? Для начала предлагаю определиться с целями, выбрать, что именно мы хотим сделать.
Итак в игре у нас должно быть реализовано следующее:
1.В сцене у нас имеется сферическая поверхность (например, поверхность планеты). Для простоты примем, что поверхность абсолютно круглая, без возвышенностей и углублений. Для красоты добавим туда солнышко, небольшой спутник и звездное небо.
2.Необходимо реализовать перемещение камеры по данной поверхности, а именно движение вперед, назад, влево, вправо, а так же повороты камеры влево и вправо. Управление реализуем с клавиатуры. Итого 6 клавишь.
3.Для того чтобы нам не было скучно летать по пустой планете, добавим несколько объектов со случайными координатами и направлением.
Вот скриншот нашей будущей игры
pic1 |
 Пермещение по сферической поверхности
А теперь как же нам все это реализовать? Давайте рассмотрим по порядку следующие вопросы:
1.Какую систему координат использовать? Как хранить данные о положении и ориентации в пространстве объекта?
2.Как нам собрать сцену?
3.Как нам реализовать перемещение камеры?

Система координат. Хранение данных

Я предлагаю использовать матрицы. Матрица - это двумерный массив вещественных чисел. В компьютерной графике используются матрицы размерностью 4х4. Матрица такого размера позволяет хранить данные об ориентации объекта и о положении его в пространстве. Рассмотрим небольшой пример для чайников.
pic1_ |
 Пермещение по сферической поверхности
У нас имеется глобальная система координат (оси X, Y, Z). А так же имеется некоторый объект в пространстве. Помимо того, что этот объект имеет определенные координаты в пространстве по отношению к глобальной системе координат, он еще определенным образом ориентирован в пространстве. Т.е. у него имеется своя система координат (оси X’, Y’, Z’). Для того, чтобы описать его ориентацию, достаточно описать координаты векторов X’, Y’, Z’. Вся эта информация содержится в матрице размерностью 4х4.
Если мы рассмотрим все это с точки зрения нашей задачи, то придем к выводу, что для описания положения и ориентации одного объекта нам одной матрицы более чем достаточно. Я имею в виду, что нам даже не обязательно хранить информацию о положении, оно следует из его ориентации.
pic2 |
 Пермещение по сферической поверхности
Если с этим все понятно, давайте переедем к следующему вопросу.

Сцена

Сцена включает в себя объекты, которые нам надо отобразить, камеры, системы частиц и. т.п. Но сцена – это не просто набор этих элементов. В любом уважающем себя графическом движке (к которым относится IrrLicht),  все эти элементы расположены в определенной иерархии, в виде дерева. И координаты каждого объекта задаются не относительно глобальной системы координат, а относительно родительского узла (Node).
Рассмотрим дерево нашей сцены.
pic4 |
 Пермещение по сферической поверхности
В самом верху находится корневая нода – глобальная система координат.
К ней мы привяжем солнце в виде билбоарда с какими-нибудь ненулевыми координатами, например (300, 300, 300).
Так же к корневой ноде приделаем сутую ноду, к которой приделаем меш астероида – спутника. При чем пустая нода будет иметь нулевые координаты, а меш не нулевые (70, 0, 0). Все это делается для того, чтобы анимировать вращение спутника вокруг планеты. Т.е. мы будем вращать пустую ноду,  и спутник будет вращаться вместе с ним. Для красоты добавим еще вращение спутника вокруг своей оси. Для того, чтобы нам не заморачиваться со всеми этими вращениями используем такой элемент, как ISceneNodeAnimator. Мы просто добавим его в самом начале и благополучно забудем.
И конечно не забудем приделать к корневой ноде саму сферу – нашу планету. Она будет иметь нулевые координаты. Используем меш, который содержит сферу единичного радиуса, а потом используем setScale, чтобы растянуть нашу сферу до нужного размера SPHERE_RADIUS. Потом можно будет поиграться с этим значением.
Создадим скайбокс. Это будат наше звездное небо.
Камеру мы тоже не будем прибавлять непосредственно к корневой ноде. Создадим пустую ноду, а затем уже к ней добавим камеру. Пустая нода будет иметь нулевые координаты, камера имеет координаты (0, 15, -2). Так же нам потребуется еще одна пустая нода, чтобы указать, куда должна смотреть камера. Пускай у неё будут координаты (0, 10, 3). Я специально сделал Z = 3, а не 0, потому что это гораздо удобнее, когда камера вращается не вокруг того места, на которое смотрит. Вы сами можете в этом убедиться. Тут нужно еще пояснение по поводу того, зачем же нам создавать эту ноду. Дело в том что при создании камеры мы указываем её координаты и координаты того места, куда она смотрит. Но почему-то в IrrLicth сделано так, что её координаты привязаны к родительской ноде (что нам собственно и нужно), а координаты её цели указываются в глобальных координатах. В этом есть определенная гибкость, например, мы можем камерой следить за объектом, который привязан к совершенно другой ветке нашего дерева. Постараюсь объяснить, что мы собственно сделали. Мы привязали пустую ноду цели к родительской ноде камеры. Т.е. эта нода будет двигаться в зависимости от поворота родительской ноды камеры, как собственно и сама камера. На каждом кадре мы будем запрашивать глобальные координаты целевой ноды и устанавливать эти координаты для камеры в качестве цели.
Запомните, в нашей программе ось X показывает в сторону, Y – вверх, а Z – вперед. Хотя это очень условно и могло бы быть совсем по другому.
pic5 |
 Пермещение по сферической поверхности
И еще один момент, на который стоит обратить внимание. Помимо координат камеры и координат её цели, ориентацию определяет так называемый вертикальный вектор. Если у нас имеется какой-нибудь шутер, в котором мы движемся по плоскости, то «верх» для нас всегда находится вверху и мы можем даже не задумываться над этим. Хотя если нам вдруг понадобиться чтобы камера наклонилась набок, допустим нас убили или мы просто выглядываем из-за угла, то нам уже придется поработать с этим самым вертикальным вектором. Когда мы движемся по сфере, то без вертикального вектора нам не обойтись, т.к. направление «верх» всегда меняется, в зависимости от того, где мы находимся. В нашей программе в качестве вертикального вектора вполне подойдут координаты цели камеры.
И последнее, что у нас имеется в сцене – это несколько произвольных объектов. Их иерархия будет похожа на иерархию камеры, которую мы только что рассмотрели, только в ней нам не понадобится лишняя пустая нода. Я на всякий случай повторюсь и объясню еще раз. Взглянем на рисунок.
pic6 | 
Пермещение по сферической поверхности
К глобальной ноде (которая здесь не нарисована) мы добавляем пустую ноду с нулевыми координатами. К пустой ноде добавляем наш объект. Координаты объекта (0, SHPERE_RADIUS, 0). Они не меняются за время игры, если мы конечно не хотим создать неровный ландшафт. Единственное, что меняется это ориентация нашей пустой ноды, которую мы будем хранить в отдельной матрице. На приведенном ниже рисунке показано, как меняется положение объекта в зависимости от ориентации корневой пустой ноды.
pic7 |
 Пермещение по сферической поверхности
Большие стрелочки - это глобальные координаты, а маленькие – это корневая пустая нода объекта.

Перемещение камеры

Взгляните на картинку. Если вы вспомните, что перемещение нашей камеры – это всего лишь поворот нашей корневой ноды вокруг соответствующей оси, то сразу все поймете.
pic8 | 
Пермещение по сферической поверхности

Текст программы

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

#include <irrlicht.h>
#include <deque>
using namespace std;
using namespace irr;

#pragma comment(lib, "Irrlicht.lib")
typedef core::vector3df vec; // Переопределяем тип, чтобы попроще было писать

#define SPHERE_RADIUS 10 // Радиус планеты
#define OBJECT 10 // Количество объектов

scene::IAnimatedMeshSceneNode* sphere = 0; // Меш планеты
scene::IAnimatedMeshSceneNode* sphere2 = 0; // Меш спутника
IrrlichtDevice* device = 0;
video::IVideoDriver* driver;
scene::ISceneManager* smgr;
irr::scene::IBillboardSceneNode *sun; // Билбоард солнца

#define DEG (3.14/180) // константа для перевода из градусов в радианы и наоборот

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

class Object
{
public:
scene::ISceneNode* node; // Меш самого объекта
scene::ISceneNode* cSys; // Корневая нода, которая определяет положение объекта
Object()
{
cSys = smgr->addEmptySceneNode();
node = smgr->addAnimatedMeshSceneNode(smgr->getMesh("obj1.3ds"));
node->setMaterialTexture(0, driver->getTexture("tex1.jpg"));
node->setMaterialFlag(video::EMF_LIGHTING, false);
node->setPosition(vec(0,SPHERE_RADIUS,0));
cSys->addChild(node);
cSys->setRotation(vec(rand()%360,rand()%360,rand()%360));
}
};

Об этом уже было подробно расписано выше. Создаем пустую ноду, создаем ноду с мешем объекта, к пустой ноде приделываем ноду с объектом. Ноде с мешем задаем координаты. А пустой ноде задаем случайную ориентацию.
Далее создаем класс камеры, в которой опишем все создаваемые ноды и обработку перемещений камеры.

class Camera
{
public:
float x, y, xt, yt, z, zt;
scene::ICameraSceneNode *node;
scene::ISceneNode *cSys, *target;
core::matrix4 mat;
vec pos;
Camera()
{
x = 0;
y = 0;
z = 0;
xt = 0;
yt = 0;
zt = 0;
node = smgr->addCameraSceneNode(NULL, vec(0,15,-2), vec(0,0,0));
cSys = smgr->addEmptySceneNode();
target = smgr->addEmptySceneNode();
target->setPosition(vec(0, 10, 3));
cSys->addChild(node);
cSys->addChild(target);

mat.makeIdentity();
}
void move()
{
vec v;
core::matrix4 m;

x+=xt;
y+=yt;
z+=zt;
x*=0.99f;
y*=0.99f;
z*=0.99f;
m.setRotationDegrees(vec(x,0,0));
mat *= m;
m.setRotationDegrees(vec(0,y,0));
mat *= m;
m.setRotationDegrees(vec(0,0,z));
mat *= m;

v = mat.getRotationDegrees();
cSys->setRotation(v);

pos = target->getAbsolutePosition();
node->setUpVector(pos);
node->setTarget(pos);

}
};

Переменная mat – это наша самая главная матрица, которая хранит положение нашей камеры, а точнее ориентацию корневой ноды.
Переменные xt, yt, zt  - это что-то вроде ускорения в определенном направлении. Они равны чему-то если соответствующая клавиша нажата и равны 0 если отпущена. Переменные x, y, z - это текущая скорость. Конструкция типа
x*=0.99f;
введена для того, чтобы создать эффект инерции. Далее следует
m.setRotationDegrees(vec(x,0,0));
mat *= m;
Здесь мы создаем матрицу поворота вокруг определенной оси и умножаем нашу самую главную матрицу на только что созданную.
  v = mat.getRotationDegrees();
  cSys->setRotation(v);
В этом фрагменте мы преобразуем нашу супер-матрицу в углы Эйлера и задаем ориентацию корневой ноды с помощью этих углов. Все это делается для потому, что в IrrLicht нельзя задать ориентацию объекта непосредственно из матрицы (во всяком случае я не нашел такой функции). Вот и приходится делать одно лишнее преобразование.
  pos = target->getAbsolutePosition();
  node->setUpVector(pos);
  node->setTarget(pos);
Об этом уже было сказано выше. Мы определяем глобальные координаты целевой ноды, и задаем этот вектор в качестве цели для камеры. Затем задаем вертикальный вектор для камеры, для чего идеально подходит тот же самый вектор pos.
Что происходит в конструкторе поясню вкратце. Создаем ноду камеры, корневую пустую ноду, и пустую ноду цели. Ноду камеры и ноду цели прикрепляем к корневой пустой ноде. Задаем им координаты.

class MyEventReceiver : public IEventReceiver
{
public:
Camera *camera;
MyEventReceiver()
{
camera = NULL;
}
void init(Camera *c)
{
camera = c;
}
virtual bool OnEvent(SEvent event)
{
if(event.EventType == EET_KEY_INPUT_EVENT)
if(event.KeyInput.PressedDown)
switch(event.KeyInput.Key)
{
case KEY_KEY_W:
camera->xt=.0009f;
break;
case KEY_KEY_S:
camera->xt=-.0006f;
break;
case KEY_KEY_Q:
camera->zt=.0009f;
break;
case KEY_KEY_E:
camera->zt=-.0006f;
break;
case KEY_KEY_A:
camera->yt=-.0016f;
break;
case KEY_KEY_D:
camera->yt=.0016f;
break;
}
else
switch(event.KeyInput.Key)
{
case KEY_KEY_Q:
case KEY_KEY_E:
camera->zt = 0;
break;
case KEY_KEY_A:
case KEY_KEY_D:
camera->yt = 0;
break;
case KEY_KEY_W:
case KEY_KEY_S:
camera->xt = 0;
break;
}
return false;
}
};

Здесь мы создаем класс, который будет обрабатывать все наши нажатия клавишь. Значения скоростей он будет записывать непосредственно в объект класса Camera. Тут все просто, нажали клавшу - придали ускорение. Отпустили – сбросили ускорение.

И последний фрагмент на сегодня. Он побольше, но не на много сложнее. Читайте комментарии.

int main()
{
MyEventReceiver receiver;

device = createDevice(video::EDT_OPENGL, core::dimension2d<s32>(640, 480),
16, false, false, false, &receiver);

driver = device->getVideoDriver();
smgr = device->getSceneManager();

// инициализируем переменные
Camera camera;
Object obj[OBJECT];
receiver.init(&camera);
driver->setTextureCreationFlag(video::ETCF_CREATE_MIP_MAPS, false);

// создаем звездное небо
smgr->addSkyBoxSceneNode(
driver->getTexture("tex2.jpg"),
driver->getTexture("tex2.jpg"),
driver->getTexture("tex2.jpg"),
driver->getTexture("tex2.jpg"),
driver->getTexture("tex2.jpg"),
driver->getTexture("tex2.jpg"));

// создаем билбоард с солнышком
sun = smgr->addBillboardSceneNode (NULL, core::dimension2d< f32 >(500.0f, 500.0f), vec(300,300,300));
sun->setMaterialTexture(0, driver->getTexture("tex6.jpg"));
sun->setMaterialType(video::EMT_TRANSPARENT_ADD_COLOR);
sun->setMaterialFlag(video::EMF_LIGHTING, false);

// создаем меш планеты
sphere = smgr->addAnimatedMeshSceneNode(smgr->getMesh("sphere.3ds"));
sphere->setScale(vec(SPHERE_RADIUS,SPHERE_RADIUS,SPHERE_RADIUS));
sphere->setMaterialTexture(0, driver->getTexture("tex5.jpg"));
sphere->setMaterialFlag(video::EMF_LIGHTING, false);

// создаем меш спутника и аниматоры к нему
scene::ISceneNode *n = smgr->addEmptySceneNode();
sphere2 = smgr->addAnimatedMeshSceneNode(smgr->getMesh("sphere.3ds"));
sphere2->setScale(vec(SPHERE_RADIUS,SPHERE_RADIUS,SPHERE_RADIUS));
sphere2->setMaterialTexture(0, driver->getTexture("tex3.jpg"));
sphere2->setMaterialFlag(video::EMF_LIGHTING, false);
sphere2->setPosition(vec(70,0,0));
n->addChild(sphere2);
scene::ISceneNodeAnimator* anim = smgr->createRotationAnimator(vec(0.03f, 0, 0.03f));
n->addAnimator(anim);
anim->drop();
anim = smgr->createRotationAnimator(vec(0.3f, 0.0f, 0.0f));
sphere2->addAnimator(anim);
anim->drop();

int lastFPS = -1;
while(device->run())
{
driver->beginScene(true, true, video::SColor(255,113,113,133));

smgr->drawAll();
driver->endScene();

camera.move(); // обрабатываем движение камеры

// стандартный фрагмент с выводом fps в заголовок окна
int fps = driver->getFPS();
if (lastFPS != fps)
{
wchar_t tmp[1024];
swprintf(tmp, 1024, L"Sphere - Irrlicht Engine [%ls] fps: %d",
driver->getName(), fps);
device->setWindowCaption(tmp);
lastFPS = fps;
}
}
device->drop();
return 0;
}
Категория: Уроки програмирования игр на IrrLicht | Добавил: mannn (25.07.2010)
Просмотров: 2442 | Комментарии: 4 | Рейтинг: 5.0/1
Всего комментариев: 2
2 ExoloN32  
0
я конечно могу ошибаться, но по моему в иррлихте есть константа для перевода радиан в градусы.

1 equarmage  
0
почему бы и нет :)

Имя *:
Email *:
Код *:
Поиск

Друзья сайта
  • Самый свежий NET.Framework
  • Програмирование игр на OpenGL
  • FAQ по системе
  • Инструкции для uCoz


  • Copyright by ZHABIN GRAD © 2024