Программирование 3D-игр в DirectX на Delphi
 
         
    [Главная] [Новости] [Статьи] [Игры] [Проекты] [Автор]  
         
  Основы 3D-программирования DirectX8.1 в Delphi 6-7: теоретические и практические основы создания игр.  
     
 

Камера замкнутого пространства

 
     
  [Все уроки]  
     
  Постановка задачи: рассмотрение способов реализации 2-х основных видов камер - камеры замкнутого пространства (Indoor scene) и камеры открытого пространства (Outdoor scene). Их основное отличие - привязка камеры к определенной точке (объекту) и возможность свободного перемещения.  
     
  Сначала рассмотрим как реализована камера в игровой сцене 1 для замкнутого пространства, с привязкой к определенной точке и значительными ограничениями по перемещениям.  
     
 

В модуле Main.pas класс игровой камеры описан в виде

TD3DGameCamera = class(CD3DCamera)

Как видим он является производным от стандартной камеры описанной в модуле D3DUtil.pas.

Чтобы убедиться в этом наведите указатель мыши на слово CD3DCamera. Среда Delphi высветит название типа type D3DUtil.CD3DCamera. Если данной подсказки не появилось, то возможно модуль не скомпилирован, попробуйте дать команду Build All, а после этого Compile. После этого все модули будут перекомпилированы и указанная подсказка должна высвечиваться.

 
     
  CD3DCamera - если заглянуть в реализацию этого класса, то можно увидеть, что основное назначение - это работа с плоскими спрайтами которые широко представлены в проекте Donuts3D. В остальном же - это камера свободного, но ограниченного пространства, с привязкой к объекту игрока и без возможностей свободного вращения. Есть возможность только переключения между тремя видами: со стороны, от третьего лица, от первого лица. Всё это наглядно показано в реализации проекта Donuts3D - выбранный игроком объект (самолет, вертолет, космический странник или что-то еще) перемещается, а вместе с ним и камера.  
     
  В задачи класса TD3DGameCamera входит некоторое расширение возможностей стандартной камеры. В уроке Отображение моделей и работа с камерой уже упоминалось то, каким образом можно подойти к решению поставленной задачи. Там же упоминались возможные трудности, которые могут возникнуть в этом случае.  
     
  Теперь же, когда все трудности уже разрешены мы рассмотрим окончательный вариант того, что можно получить.  
     
 

Внутри класса игровой сцены описываем поле для камеры -

SceneCamera: TD3DIndoorCamera; Не удивляйтесь, что в качестве имени класса взято TD3DIndoorCamera. Просто поскольку мы будем делать камеру уже не для 1 игровой сцены, а для 5-ой, то и класс камеры будет несколько иной, хотя и подобный TD3DGameCamera. Конкретные детали Вы решите для себя сами, мы же здесь затронем только основные особенности реализации.

 
     
 

В методе создания игровой сцены - constructor TGameScene5.Create;

обнуляем поле камеры -

SceneCamera := nil;

 
     
 

В методе - function TGameScene5.DoInitDeviceObjects

создаем экземпляр класса игровой камеры -

SceneCamera := TD3DIndoorCamera.Create;

и сразу же устанавливаем параметры ее вида и проецирования
SceneCamera.SetViewParams( D3DXVECTOR3(0.0, 0.0, 0.0),
D3DXVECTOR3(0.0, 0.0, 1.0), D3DXVECTOR3(0.0, 1.0, 0.0) );
SceneCamera.SetProjParams( D3DX_PI/4, g_d3DApp.fAspectRatio, 0.1, 100.0 );

В методе - function TGameScene5.DoRestoreDeviceObjects

устанавливаем еще ряд важных параметров - обработчик класса окна приложения, ссылка на D3DDevice, D3DInput, размеры окна отображения и ссылку на класс игровой сцены.

with g_d3DApp do
SceneCamera.SetupCamera(m_hwndMain, m_pd3dDevice, m_DXInput, m_dwScreenWidth,
m_dwScreenHeight, Self);

 
     
 

В методе уничтожения игровой сцены - function TGameScene5.DoDeleteDeviceObjects

аккуратно очищаем ресурсы памяти от экземпляра класса камеры -

SAFE_DELETE(SceneCamera);

 
     
  В методе DoInvalidateDeviceObjects камера не нуждается, да и размещение установки дополнительных параметров внутри DoRestoreDeviceObjects сделано только для того, чтобы гарантировать действительные данные во всех передаваемых параметрах. Иначе может получиться, что например, окно класса приложения еще не до конца проинициализировано и указатель содержит мнимые данные, что естественно приведет к нежелательным ошибкам времени выполения. Это особенно актуально, когда класс игровой сцены запускается не посредством загрузчика TSceneLoader, а прямо из класса игрового приложения.  
     
  Итак, остаются только два метода - DoFrameMove и DoRender внутри которых для камеры нужно тоже описать какие-то действия - по аналогии со всеми игровыми объектами, которые проходят эти этапы - Create, DoInit, DoInvalidate, DoRestore, DoFrameMove, DoRender, DoDelete - (см. урок Вывод Заставки).  
     
  Поскольку камера не является отрисовываемым объектом, а сама задает параметры отрисовки являясь внутренним объектом устройства отображения DirectX, то внутри метода DoRender делать ничего не нужно.  
     
 

Остается только DoFrameMove -

Здесь всё просто - внутри метода UpdateInput, чтобы гарантировать обработку управления камерой пользователем и избежав двойного вызова метода m_DXInput.Update; о чём вы найдете строгий комментарий внутри метода UpdateInput класса камеры TD3DIndoorCamera -

где-нибудь ближе к концу реализаций метода, а лучше вообще в самом конце вызываем лишь 2 метода -

SceneCamera.UpdateInput;
SceneCamera.MoveCamera;

Первый из них производит обработку управления камерой, а второй выполняет фактические перемещения и изменения ориентации.

 
     
  Всё.  
     
 

Как видим, если разобраться, работа с камерой достаточно проста. Все особенности кроются в реализации класса камеры TD3DIndoorCamera (или как Вы там ее назовете - не суть важно).

В секции interface этот класс камеры можно описать так -

TD3DIndoorCamera = class(CD3DCamera)
private
Fxmin, Fymin, Fzmin, Fxmax, Fymax, Fzmax: Single;

m_UserInput: TCameraControl;

function ScreenToVector(sx, sy: Integer): TD3DXVector3;

procedure SetupBoundingBox(Box: TBoundingBox);

protected
procedure UpdateInput;
public

vLookAtPos: TD3DXVector3;

vPos: TD3DXVector3;
AxisAngles: TD3DXVector3;

m_qDown: TD3DXQuaternion; // Quaternion before button down
m_qNow: TD3DXQuaternion; // Composite quaternion for current drag
m_matRotation: TD3DXMatrix; // Matrix for arcball's orientation
//m_matRotationDelta: TD3DXMatrix; // Matrix for arcball's orientation
m_matTranslation: TD3DXMatrix; // Matrix for arcball's position
m_matTranslationDelta: TD3DXMatrix; // Matrix for arcball's position

m_iWidth: Integer; // ArcBall's window width
m_iHeight: Integer; // ArcBall's window height
m_fRadius: Single; // ArcBall's radius in screen coords
m_fRadiusTranslation: Single; // ArcBall's radius for translating the target
m_bRightHanded: Boolean; // Whether to use RH coordinate system

m_pd3dDevice: IDirect3DDevice8;
m_DXInput: TDXInput;
m_hWnd: HWND;

TimeLapsed: Single;

GameScene: TD3D_GDOListItem;

constructor Create;
destructor Destroy; override;
procedure SetupCamera(ph_wnd: HWND; pd3dDevice: IDirect3DDevice8; DXInput: TDXInput;
Width, Height: Integer; aGameScene: TD3D_GDOListItem);

procedure MoveCamera;
end;

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

В первую очередь, если Вы когда-нибудь заглядывали в dx-help нужно выставить начальное положение и ориентацию камеры.

Всё это можно сделать уже на этапе ее создания, т.е. внутри метода

constructor TD3DIndoorCamera.Create;
begin
inherited Create;
//вызываем унаследованный метод Create класса CD3DCamera

D3DXQuaternionIdentity(m_qDown); //задаем начальные ориентации - 2 кватерниона и матрица
D3DXQuaternionIdentity(m_qNow);
D3DXMatrixIdentity(m_matRotation);

D3DXQuaternionRotationMatrix(m_qNow, m_matRotation); //эти методы здесь не нужны, но демонстрируют как
D3DXQuaternionRotationMatrix(m_qDown, m_matRotation);
//выполняется переход от матрицы к кватерниону

D3DXMatrixIdentity(m_matTranslation); //также нужно выставить начальную матрицу положения и смещения
D3DXMatrixIdentity(m_matTranslationDelta);
m_fRadiusTranslation := 1.0;
//задаем радиус смещений
m_bRightHanded := False;
//левостороннюю систему координат - по правилу левой руки

ZeroMemory(@m_UserInput, SizeOf(m_UserInput)); //обнуляем поле состояний элементов управления камерой


with vPos do begin
//выставляем начальные координаты камеры в игровой сцене
x := 0.85;
y := -1.3;
z := -53.5;
end;

end;

 
     
 

При разрушении экземпляра класса камеры не забываем обнулить значимые поля -

destructor TD3DIndoorCamera.Destroy;
begin
m_pd3dDevice := nil;
m_DXInput := nil;
m_hWnd := 0;

inherited Destroy;
end;

Это уменьшает счетчики Reference Counting которые учитываются в системных объектах DirectX работающих, как известно, на принципах технологии COM. Т. е. когда на объект ссылается другой объект счетчик ссылок увеличивается, а в момент когда не нуждается - уменьшается. Нужный системный объект DirectX остается в памяти пока его счетчик ссылок не достигнет нуля. После этого он сам удаляет себя из памяти.

Всё это описано в dx-help в разделе посвященном особенностям COM-технологии.

 
     
 

Рассмотрим реализацию метода установки дополнительных параметров камеры -

procedure TD3DIndoorCamera.SetupCamera(ph_wnd: HWND; pd3dDevice: IDirect3DDevice8; DXInput: TDXInput;
Width, Height: Integer; aGameScene: TD3D_GDOListItem);

const
Radius = 20;

var
fAspect: Single;
//matProj: TD3DXMatrix;
BoundingBox: TBoundingBox;

begin
m_hwnd := ph_wnd;
//запоминаем ссылки на окно класса приложения на котором будет выполняться отрисовка
m_pd3dDevice := pd3dDevice;
//ссылку на D3DDevice и D3DInput
m_DXInput := DXInput;

GameScene := aGameScene; //ссылку на игровую сцену

// Set up the camera
m_iWidth := Width;
//размеры окна
m_iHeight := Height;
m_fRadius := 0.85;
//радиусы окружности вращений и смещений

m_fRadiusTranslation := Radius;

// Set the projection matrix
fAspect:= Width / Height;

D3DXMatrixPerspectiveFovLH(m_matProj, D3DX_PI/4, fAspect,
Radius/64.0, Radius*200);
//строим матрицу перспективного вида. Она строится только 1 раз, во время вызова данного

//метода, а дальше используется только в готовом виде никак не изменяясь, поскольку параметры перспективы на //протяжении нашего приложения остаются неизменны!

//**
m_pd3dDevice.SetTransform(D3DTS_PROJECTION, m_matProj);
//полученная матрица задается в качестве системной //матрицы проекционного проецирования для устройства D3DDevice

with BoundingBox do begin //данное поле задает ограничивающий бокс для перемещений камеры в сцене
LowerLeftCorner.x := -20;
LowerLeftCorner.y := 10;
LowerLeftCorner.z := -3*Radius;
UpperRightCorner.x := 20;
UpperRightCorner.y := -10;//4.64;
UpperRightCorner.z := -1.5*Radius;
end;
SetupBoundingBox(BoundingBox);
//а данный метод устанавливает заданные параметры в качестве ограничивающих

end;

 
     
 

Самое интересное о том, как происходит управление камерой находится внутри метода -

var
s_vDown: TD3DXVector3 = (x:0; y:0; z:0); // Button down vector

procedure TD3DIndoorCamera.UpdateInput;
var
vCur: TD3DXVector3;
fDeltaX, fDeltaY: Single;
qAxisToAxis: TD3DXQuaternion;
MouseCursorPos: TPoint;

begin
if m_DXInput = nil then Exit;
if GameScene = nil then Exit;

Windows.GetCursorPos(MouseCursorPos);
Windows.ScreenToClient(m_hWnd, MouseCursorPos);

//m_DXInput.Update; //вызывается в TGameScene5.UpdateInput
//двойной вызов приводит к логическим ошибкам обработки управления!!!


{//**}
m_UserInput.bCameraRotate := (CameraRotate in m_DXInput.States) or
(CameraMMB in m_DXInput.States);


m_UserInput.bCameraZoom := (m_DXInput.Mouse.Z <> 0) or
(CameraZoom in m_DXInput.States);

m_UserInput.bCameraMove := CameraMove in m_DXInput.States;


if m_UserInput.bCameraZoom then
begin
// Normalize based on size of window and bounding sphere radius
if m_DXInput.Mouse.Z <> 0 then
//коэффициент 0.05 для того, чтобы камера не выходила за заданные пределы ограничений
fDeltaY:= ((-m_DXInput.Mouse.Z) * m_fRadiusTranslation / m_iHeight)*0.05

else
fDeltaY:= (-m_DXInput.Mouse.Y) * m_fRadiusTranslation / m_iHeight;

D3DXMatrixTranslation(m_matTranslationDelta, 0.0, 0.0, 5*fDeltaY);
D3DXMatrixMultiply(m_matTranslation, m_matTranslation, m_matTranslationDelta);

// Keep object in bounds in Z
{if (vPos.z > Fzmin) then
vPos.z := vPos.z + 5*fDeltaY
else
vPos.z := vPos.z + 0.5;

if (vPos.z < Fzmax) then
vPos.z := vPos.z + 5*fDeltaY
else
vPos.z := vPos.z - 0.5;}

//unlimited camera
//
vPos.z := vPos.z + 5*fDeltaY;

end;

if m_UserInput.bCameraMove then begin
// Normalize based on size of window and bounding sphere radius
fDeltaX:= (-m_DXInput.Mouse.X) * m_fRadiusTranslation / m_iWidth;
fDeltaY:= (-m_DXInput.Mouse.Y) * m_fRadiusTranslation / m_iHeight;

D3DXMatrixTranslation(m_matTranslationDelta, -2*fDeltaX, 2*fDeltaY, 0.0);
D3DXMatrixMultiply(m_matTranslation, m_matTranslation, m_matTranslationDelta);

// Keep object in bounds in X
{if (vPos.x > Fxmin) then
vPos.x := vPos.x + 2*fDeltaX
else
vPos.x := vPos.x + 0.5;

if (vPos.x < Fxmax) then
vPos.x := vPos.x + 2*fDeltaX
else
vPos.x := vPos.x - 0.5;

// Keep object in bounds in Y
if (vPos.y < Fymin) then
vPos.y := vPos.y - 2*fDeltaY
else
vPos.y := vPos.y - 0.5;

if (vPos.y > Fymax) then
vPos.y := vPos.y - 2*fDeltaY
else
vPos.y := vPos.y + 0.5;}

{unlimited camera}
//
vPos.x := vPos.x + 2*fDeltaX;
//
vPos.y := vPos.y - 2*fDeltaY;
end;


if not m_UserInput.bCameraRotate then
begin
s_vDown := ScreenToVector(MouseCursorPos.X, MouseCursorPos.Y);

m_qDown:= m_qNow;
end;

if m_UserInput.bCameraRotate then
begin
// recompute m_qNow

vCur := ScreenToVector(MouseCursorPos.X, MouseCursorPos.Y);

D3DXQuaternionAxisToAxis(qAxisToAxis, s_vDown, vCur);
m_qNow := m_qDown;

D3DXQuaternionMultiply(m_qNow, m_qNow, qAxisToAxis);

D3DXMatrixRotationQuaternion(m_matRotation, m_qNow);

end;


end;

В чём смысл указанных выше строк кода? В ответ на нажатия определенных, заданных в приложении клавиш или действий мышью и ее кнопок происходит изменение положения и повороты камеры. Следует заметить, что камера должна вращаться не вокруг своей оси, как это будет происходить, если для нее просто задана матрица вращения, а вокруг некоторого центра. Реализация этого способа показана далее.

 
     
 

Магический метод, который приводит к действительным результатам перемещений и вращений камеры -

procedure TD3DIndoorCamera.MoveCamera;
var
matView: TD3DXMatrix;
vEyePt, vLookAtPt, vUpVec: TD3DXVector3;
qEye: TD3DXVector4;

begin

D3DXQuaternionIdentity(TD3DXQuaternion(qEye)); //задаем начальный кватернион положения глаза наблюдателя
D3DXVec3Transform(qEye, vPos, m_matRotation);
//трансформируем вектор положения матрицей получая кватернион

//ориентации

vEyePt.x := qEye.x; //задаем полученные параметры в качестве положений глаза и точки зрения
vEyePt.y := qEye.y;
vEyePt.z := qEye.z;

vLookAtPt.x := vPos.x;
vLookAtPt.y := vPos.y;
vLookAtPt.z := 0;

vUpVec := D3DXVECTOR3( 0.0, 1.0, 0.0); //вектор Up камеры остается постоянным

D3DXMatrixLookAtLH(matView, vEyePt, vLookatPt, vUpVec); //строим матрицу вида для левосторонней координатной //системы

D3DXMatrixMultiply(matView, matView, m_matRotation); //перемножаем матрицу вида на матрицу момента вращения

// Set up view matrix
m_pd3dDevice.SetTransform(D3DTS_VIEW, matView);
//устанавливаем системную матрицу вида из камеры

//SetViewParams
D3DXMatrixInverse(m_matBillboard, nil, matView);
//готовим матрицу для плоских спрайтов выводимых как Billboarding-//объекты
m_matBillboard._41 := 0.0;
m_matBillboard._42 := 0.0;
m_matBillboard._43 := 0.0;
end;

 
     
 

И остается показать реализацию 2-х дополнительных методов -

procedure TD3DIndoorCamera.SetupBoundingBox(Box: TBoundingBox);
begin
Fxmin := Box.LowerLeftCorner.x;
Fymin := Box.LowerLeftCorner.y;
Fzmin := Box.LowerLeftCorner.z;

Fxmax := Box.UpperRightCorner.x;
Fymax := Box.UpperRightCorner.y;
Fzmax := Box.UpperRightCorner.z;
end;

Данный метод демонстрирует как экранные координаты переводятся в векторные


function TD3DIndoorCamera.ScreenToVector(sx, sy: Integer): TD3DXVector3;
var
x, y, z, mag, scale: Single;
begin
// Scale to screen
x := -(sx - m_iWidth/2) / (m_fRadius*m_iWidth/2);
y := (sy - m_iHeight/2) / (m_fRadius*m_iHeight/2);

if m_bRightHanded then
begin
x := -x;
y := -y;
end;

z := 0.0;
mag := x*x + y*y;

if (mag > 1.0) then
begin
scale := 1.0 / Sqrt(mag);
x := x*scale;
y := y*scale;
end
else
z := Sqrt(1.0 - mag);

// Return vector
Result:= D3DXVector3(x, y, z);
end;

 
     
 

Если Вы испробовали код описанный выше и разобрались в чём суть работы камеры у Вас должен возникнуть вопрос: Почему же данная камера не совсем удовлетворяет поставленной цели - она не вращается вокруг указанной точки пространства (0, 0, 0).

Исправим этот недостаток -

откройте реализацию метода

procedure TD3DIndoorCamera.MoveCamera

и найдите строку кода -

D3DXMatrixMultiply(matView, matView, m_matRotation);

закомментируйте ее и перекомпилируйте код.

Поставленная задача решена.

 
     
  Следует отметить, что конкретные параметры расположения и ограничения перемещений Вы должны подобрать самостоятельно для своей камеры. Выше приведены рабочие параметры без ограничения перемещений камеры.  
     
     
     
  [Назад] [Все уроки]  
     
    [Главная] [Новости] [Статьи] [Игры]    
         
(c) Мега Информатик 2006-2008    
Hosted by uCoz