適用於遊戲的移動視角控制項
瞭解如何將傳統的滑鼠和鍵盤移動視角控制項 (亦稱為滑鼠視角控制項) 新增至 DirectX 遊戲。
我們也會討論適用於觸控裝置的移動視角支援功能,將定向輸入行為的移動控制器定義為螢幕左下區,並將螢幕其餘部分定義為視角控制器,而相機位於該區中央玩家最後觸控到的位置。
如果您對這個概念不熟悉,不妨這樣想:在這個 3D 空間中,鍵盤 (或觸控型定向輸入方塊) 控制了您的雙腳,且雙腳只能往前/往後移動,或是側身左右移動。 滑鼠 (或觸控指標) 則控制了您的頭部。 頭部可朝某個方向觀看:往左或往右、向上或向下,或看向平面的某處。 如果檢視中有目標,則可使用滑鼠將相機檢視聚焦於該目標,然後按下往前鍵朝目標移動,或按返回鍵朝反方向離開。 若要圍繞目標,請將相機檢視聚焦於目標上,同時向左或向右移動。 如您所見,以此控制方式瀏覽 3D 環境非常有效率!
這些控制項在遊戲中通常稱為 WASD 控制項,其中 W、A、S 和 D 鍵用於 x-z 平面的固定相機移動,而滑鼠則用來控制相機繞 x 軸和 y 軸旋轉。
目標
- 將適用於滑鼠、鍵盤及觸控螢幕的基本移動視角控制項新增至 DirectX 遊戲。
- 實作用來瀏覽 3D 環境的第一人稱視角相機。
實作觸控控制項的注意事項
我們會針對觸控控制項實作兩組控制器:一個是移動控制器,處理 x-z 平面相對於相機視點的移動;另一個是視角控制器,瞄準相機視點。 移動控制器對應至鍵盤的 WASD 鍵,而視角控制器則對應至滑鼠。 但若是觸控控制項,則需要定義作為方向輸入或虛擬 WASD 按鈕的螢幕區域,螢幕其餘部分則定義為視角控制項的輸入空間。
螢幕畫面看起來像這樣:
移動螢幕左下角的觸控指標 (不是滑鼠!),任何向上的動作都會使相機往前移動。 任何向下的動作會使相機往後移動。 移動控制器指標空間內的左右移動相同。 在該空間以外的區域,則為視角控制器,您可以用觸控方式或將相機拖曳到任何想朝向的地方。
設定基本輸入事件基礎結構
首先,我們必須建立控制項類別,用來處理滑鼠和鍵盤的輸入事件,以及根據該輸入更新相機視角。 我們要實作的是移動視角控制項,因此稱之為 MoveLookController。
using namespace Windows::UI::Core;
using namespace Windows::System;
using namespace Windows::Foundation;
using namespace Windows::Devices::Input;
#include <DirectXMath.h>
// Methods to get input from the UI pointers
ref class MoveLookController
{
}; // class MoveLookController
現在,我們要建立標頭,定義移動視角控制器及其第一人稱視角相機的狀態,以及實作控制項和更新相機狀態的基本方法及事件處理常式。
#define ROTATION_GAIN 0.004f // Sensitivity adjustment for the look controller
#define MOVEMENT_GAIN 0.1f // Sensitivity adjustment for the move controller
ref class MoveLookController
{
private:
// Properties of the controller object
DirectX::XMFLOAT3 m_position; // The position of the controller
float m_pitch, m_yaw; // Orientation euler angles in radians
// Properties of the Move control
bool m_moveInUse; // Specifies whether the move control is in use
uint32 m_movePointerID; // Id of the pointer in this control
DirectX::XMFLOAT2 m_moveFirstDown; // Point where initial contact occurred
DirectX::XMFLOAT2 m_movePointerPosition; // Point where the move pointer is currently located
DirectX::XMFLOAT3 m_moveCommand; // The net command from the move control
// Properties of the Look control
bool m_lookInUse; // Specifies whether the look control is in use
uint32 m_lookPointerID; // Id of the pointer in this control
DirectX::XMFLOAT2 m_lookLastPoint; // Last point (from last frame)
DirectX::XMFLOAT2 m_lookLastDelta; // For smoothing
bool m_forward, m_back; // States for movement
bool m_left, m_right;
bool m_up, m_down;
public:
// Methods to get input from the UI pointers
void OnPointerPressed(
_In_ Windows::UI::Core::CoreWindow^ sender,
_In_ Windows::UI::Core::PointerEventArgs^ args
);
void OnPointerMoved(
_In_ Windows::UI::Core::CoreWindow^ sender,
_In_ Windows::UI::Core::PointerEventArgs^ args
);
void OnPointerReleased(
_In_ Windows::UI::Core::CoreWindow^ sender,
_In_ Windows::UI::Core::PointerEventArgs^ args
);
void OnKeyDown(
_In_ Windows::UI::Core::CoreWindow^ sender,
_In_ Windows::UI::Core::KeyEventArgs^ args
);
void OnKeyUp(
_In_ Windows::UI::Core::CoreWindow^ sender,
_In_ Windows::UI::Core::KeyEventArgs^ args
);
// Set up the Controls that this controller supports
void Initialize( _In_ Windows::UI::Core::CoreWindow^ window );
void Update( Windows::UI::Core::CoreWindow ^window );
internal:
// Accessor to set position of controller
void SetPosition( _In_ DirectX::XMFLOAT3 pos );
// Accessor to set position of controller
void SetOrientation( _In_ float pitch, _In_ float yaw );
// Returns the position of the controller object
DirectX::XMFLOAT3 get_Position();
// Returns the point which the controller is facing
DirectX::XMFLOAT3 get_LookPoint();
}; // class MoveLookController
程式碼包含 4 個私人欄位群組。 我們一起瞭解各群組用途。
首先,定義一些實用欄位,以保存相機檢視的更新資訊。
- m_position 是使用場景座標在 3D 場景中的相機位置 (因此為檢視平面)。
- m_pitch 是相機的俯仰 (圍繞檢視平面的 x 軸上下旋轉),以弧度為單位。
- m_yaw 是相機的偏航 (圍繞檢視平面的 y 軸左右旋轉),以弧度為單位。
接著,我們要定義用來儲存控制器狀態和位置相關資訊的欄位。 首先,定義觸控式移動控制器的必要欄位。 (移動控制器的鍵盤實作沒有什麼特殊需求,我們只讀取有特定處理常式的鍵盤事件)。
- m_moveInUse 可指出移動控制器是否使用中。
- m_movePointerID 是目前移動指標的唯一識別碼。 檢查指標識別碼值時,其可來區分視角指標和移動指標。
- m_moveFirstDown 是玩家在螢幕上第一次觸控移動控制器指標區域的點。 我們稍後會使用此值設定死區,以防微小的動作造成檢視抖動。
- m_movePointerPosition 是玩家目前在螢幕上將指標移至特定位置的點。 該欄位會檢查相對於 m_moveFirstDown 的值,以判斷玩家想移動的方向。
- m_moveCommand 是移動控制器的最終計算命令:上 (往前)、下 (往後)、左或右。
接下來我們定義用於視角控制器的欄位,包括滑鼠和觸控實作。
- m_lookInUse 指出視角控制項是否使用中。
- m_lookPointerID 是目前視角指標的唯一識別碼。 檢查指標識別碼值時,其可來區分視角指標和移動指標。
- m_lookLastPoint 是上一個畫面格中擷取的最後一個點 (場景座標)。
- m_lookLastDelta 是目前的 m_position 和 m_lookLastPoint 之間的相差值。
最後,我們為 6 個移動角度定義 6 個布爾值,指示各方向移動動作的目前狀態 (開啟或關閉):
- m_forward、m_back、m_left、m_right、m_up 和 m_down。
我們使用 6 個事件處理常式,擷取用於更新控制器狀態的輸入資料:
- OnPointerPressed。 玩家在遊戲畫面中使用指標按下滑鼠左鍵或觸控螢幕。
- OnPointerMoved。 玩家在遊戲畫面中使用指標移動滑鼠,或拖曳螢幕上的觸控指標。
- OnPointerReleased。 玩家在遊戲畫面中使用指標放開滑鼠左鍵,或停止觸控螢幕。
- OnKeyDown。 玩家按下按鍵。
- OnKeyUp。 玩家放開按鍵。
最後,我們使用這些方法和屬性,初始化、存取和更新控制器的狀態資訊。
- Initialize。 應用程式會呼叫此事件處理常式以初始化控制項,並將其附加至描述顯示視窗的 CoreWindow 物件。
- SetPosition。 應用程式會呼叫此方法,以在場景空間中設定控制項的 (x、y 和 z) 座標。
- SetOrientation。 應用程式會呼叫此方法,以設定相機的俯仰和偏航。
- get_Position。 應用程式會存取此屬性,以取得場景空間中相機的目前位置。 您可以使用此屬性向應用程式傳遞目前的相機位置。
- get_LookPoint。 應用程式會存取此屬性,以取得控制器相機目前朝向的點。
- 更新。 讀取移動和視角控制器的狀態,並更新相機位置。 您持續從應用程式的主要迴圈呼叫此方法,以重新整理相機控制器資料和相機在場景空間中的位置。
現在,所有實作移動視角控制項的必要元件都已準備好。 接著,我們來連結這些元件。
建立基本輸入事件
Windows 執行階段事件發送器提供 5 個事件,我們希望由 MoveLookController 類別的執行個體處理這些事件:
這些事件會在 CoreWindow 類型實作。 我們假設您有要使用的 CoreWindow 物件。 如果您不知道如何取得,請參閱<如何設定通用 Windows 平台 (UWP) C++ 應用程式以顯示 DirectX 檢視>。
如果應用程式執行時引發這些事件,則處理常式會更新私用欄位中定義的控制器狀態資訊。
首先,我們填入滑鼠和觸控指標事件處理常式。 在第一個事件處理常式 OnPointerPressed() 中,我們從 CoreWindow 取得指標的 x-y 座標,使用者點按滑鼠或觸控螢幕上的視角控制器區域時,該座標可用來管理顯示結果。
OnPointerPressed
void MoveLookController::OnPointerPressed(
_In_ CoreWindow^ sender,
_In_ PointerEventArgs^ args)
{
// Get the current pointer position.
uint32 pointerID = args->CurrentPoint->PointerId;
DirectX::XMFLOAT2 position = DirectX::XMFLOAT2( args->CurrentPoint->Position.X, args->CurrentPoint->Position.Y );
auto device = args->CurrentPoint->PointerDevice;
auto deviceType = device->PointerDeviceType;
if ( deviceType == PointerDeviceType::Mouse )
{
// Action, Jump, or Fire
}
// Check if this pointer is in the move control.
// Change the values to percentages of the preferred screen resolution.
// You can set the x value to <preferred resolution> * <percentage of width>
// for example, ( position.x < (screenResolution.x * 0.15) ).
if (( position.x < 300 && position.y > 380 ) && ( deviceType != PointerDeviceType::Mouse ))
{
if ( !m_moveInUse ) // if no pointer is in this control yet
{
// Process a DPad touch down event.
m_moveFirstDown = position; // Save the location of the initial contact.
m_movePointerPosition = position;
m_movePointerID = pointerID; // Store the id of the pointer using this control.
m_moveInUse = TRUE;
}
}
else // This pointer must be in the look control.
{
if ( !m_lookInUse ) // If no pointer is in this control yet...
{
m_lookLastPoint = position; // save the point for later move
m_lookPointerID = args->CurrentPoint->PointerId; // store the id of pointer using this control
m_lookLastDelta.x = m_lookLastDelta.y = 0; // these are for smoothing
m_lookInUse = TRUE;
}
}
}
此事件處理常式會檢查指標是否為滑鼠 (此範例支援滑鼠和觸控),以及是否位於移動控制器區域內。 如果這兩個條件都為 true,則檢查指標是否剛按下,具體作法是測試 m_moveInUse 是否為 false,以檢查該點擊是否與前一個移動或視角輸入無關。 若是如此,處理常式會擷取移動控制器區域中,按下動作發生時所在的點,並將 m_moveInUse 設為 true,這樣當系統再次呼叫該處理常式時,就不會覆寫移動控制器輸入互動的開始位置。 該處理常式也會將移動控制器的指標識別碼更新為目前指標的識別碼。
如果指標是滑鼠,或者觸控指標不在移動控制器區域內,則其須位於視角控制器區域。 系統會將 m_lookLastPoint 設為使用者按下滑鼠按鈕或觸控和按下的目前位置、重設差異值,並將視角控制器的指標識別碼更新為目前的指標識別碼。 也會將視角控制器的狀態設定使用中。
OnPointerMoved
void MoveLookController::OnPointerMoved(
_In_ CoreWindow ^sender,
_In_ PointerEventArgs ^args)
{
uint32 pointerID = args->CurrentPoint->PointerId;
DirectX::XMFLOAT2 position = DirectX::XMFLOAT2(args->CurrentPoint->Position.X, args->CurrentPoint->Position.Y);
// Decide which control this pointer is operating.
if (pointerID == m_movePointerID) // This is the move pointer.
{
// Move control
m_movePointerPosition = position; // Save the current position.
}
else if (pointerID == m_lookPointerID) // This is the look pointer.
{
// Look control
DirectX::XMFLOAT2 pointerDelta;
pointerDelta.x = position.x - m_lookLastPoint.x; // How far did pointer move
pointerDelta.y = position.y - m_lookLastPoint.y;
DirectX::XMFLOAT2 rotationDelta;
rotationDelta.x = pointerDelta.x * ROTATION_GAIN; // Scale for control sensitivity.
rotationDelta.y = pointerDelta.y * ROTATION_GAIN;
m_lookLastPoint = position; // Save for the next time through.
// Update our orientation based on the command.
m_pitch -= rotationDelta.y; // Mouse y increases down, but pitch increases up.
m_yaw -= rotationDelta.x; // Yaw is defined as CCW around the y-axis.
// Limit the pitch to straight up or straight down.
m_pitch = (float)__max(-DirectX::XM_PI / 2.0f, m_pitch);
m_pitch = (float)__min(+DirectX::XM_PI / 2.0f, m_pitch);
}
}
指標移動時就會觸發 OnPointerMoved 事件處理常式 (在此情況下是指拖曳觸控螢幕指標,或在按下滑鼠左鍵後同時拖曳指標)。 如果指標識別碼與移動控制器指標的識別碼相同,則為移動指標,否則便檢查視角控制器是否為使用中的指標。
如果是移動控制器,則只更新指標位置。 只要 PointerMoved 事件持續觸發,我們就會不斷更新,這是為了比較最終位置與使用 OnPointerPressed 事件處理常式擷取的第一個位置。
若為視角控制器,流程會稍微複雜一點。 我們需要計算新的視點並將相機聚焦於該點,因此計算最後一個視點與目前螢幕位置之間的差異,然後比較相乘與縮放比例的結果,接著進行調整,以使視角移動少於或多於螢幕移動的距離。 使用該值時,我們會計算俯仰和偏航。
最後,玩家停止移動滑鼠或觸控螢幕時,我們需要停用移動或視角控制器行為。 我們使用 OnPointerReleased (PointerReleased 觸發時呼叫) 將 m_moveInUse 或 m_lookInUse 設為 FALSE,然後關閉相機的平移動作,並將指標識別碼歸零。
OnPointerReleased
void MoveLookController::OnPointerReleased(
_In_ CoreWindow ^sender,
_In_ PointerEventArgs ^args)
{
uint32 pointerID = args->CurrentPoint->PointerId;
DirectX::XMFLOAT2 position = DirectX::XMFLOAT2( args->CurrentPoint->Position.X, args->CurrentPoint->Position.Y );
if ( pointerID == m_movePointerID ) // This was the move pointer.
{
m_moveInUse = FALSE;
m_movePointerID = 0;
}
else if (pointerID == m_lookPointerID ) // This was the look pointer.
{
m_lookInUse = FALSE;
m_lookPointerID = 0;
}
}
截至目前,我們已處理所有觸控螢幕事件。 接著,我們要處理鍵盤型移動控制器的按鍵輸入事件。
OnKeyDown
void MoveLookController::OnKeyDown(
__in CoreWindow^ sender,
__in KeyEventArgs^ args )
{
Windows::System::VirtualKey Key;
Key = args->VirtualKey;
// Figure out the command from the keyboard.
if ( Key == VirtualKey::W ) // Forward
m_forward = true;
if ( Key == VirtualKey::S ) // Back
m_back = true;
if ( Key == VirtualKey::A ) // Left
m_left = true;
if ( Key == VirtualKey::D ) // Right
m_right = true;
}
只要按下其中一個按鍵,此事件處理常式就會將對應的方向移動狀態設為 true。
OnKeyUp
void MoveLookController::OnKeyUp(
__in CoreWindow^ sender,
__in KeyEventArgs^ args)
{
Windows::System::VirtualKey Key;
Key = args->VirtualKey;
// Figure out the command from the keyboard.
if ( Key == VirtualKey::W ) // forward
m_forward = false;
if ( Key == VirtualKey::S ) // back
m_back = false;
if ( Key == VirtualKey::A ) // left
m_left = false;
if ( Key == VirtualKey::D ) // right
m_right = false;
}
按鍵放開時,此事件處理程式會將其設回 false。 當我們呼叫 Update,其會檢查這些方向移動狀態,並據以移動相機。 這比觸控實作更簡單一點!
初始化觸控控制項及控制器狀態
現在我們要連結這些事件,並初始化所有控制器狀態欄位。
Initialize
void MoveLookController::Initialize( _In_ CoreWindow^ window )
{
// Opt in to receive touch/mouse events.
window->PointerPressed +=
ref new TypedEventHandler<CoreWindow^, PointerEventArgs^>(this, &MoveLookController::OnPointerPressed);
window->PointerMoved +=
ref new TypedEventHandler<CoreWindow^, PointerEventArgs^>(this, &MoveLookController::OnPointerMoved);
window->PointerReleased +=
ref new TypedEventHandler<CoreWindow^, PointerEventArgs^>(this, &MoveLookController::OnPointerReleased);
window->CharacterReceived +=
ref new TypedEventHandler<CoreWindow^, CharacterReceivedEventArgs^>(this, &MoveLookController::OnCharacterReceived);
window->KeyDown +=
ref new TypedEventHandler<CoreWindow^, KeyEventArgs^>(this, &MoveLookController::OnKeyDown);
window->KeyUp +=
ref new TypedEventHandler<CoreWindow^, KeyEventArgs^>(this, &MoveLookController::OnKeyUp);
// Initialize the state of the controller.
m_moveInUse = FALSE; // No pointer is in the Move control.
m_movePointerID = 0;
m_lookInUse = FALSE; // No pointer is in the Look control.
m_lookPointerID = 0;
// Need to init this as it is reset every frame.
m_moveCommand = DirectX::XMFLOAT3( 0.0f, 0.0f, 0.0f );
SetOrientation( 0, 0 ); // Look straight ahead when the app starts.
}
Initialize 會將應用程式的 CoreWindow 執行個體作為參數進行參照,然後將我們開發的事件處理常式註冊到 CoreWindow 上的適當事件。 其會初始化移動和視角指標的識別碼、將觸控螢幕移動控制器實作的命令向量設為零,並在應用程式啟動時將相機設為直視前方。
取得及設定相機的位置和方向
我們來定義一些方法,以取得和設定相機相對於檢視區的位置。
void MoveLookController::SetPosition( _In_ DirectX::XMFLOAT3 pos )
{
m_position = pos;
}
// Accessor to set the position of the controller.
void MoveLookController::SetOrientation( _In_ float pitch, _In_ float yaw )
{
m_pitch = pitch;
m_yaw = yaw;
}
// Returns the position of the controller object.
DirectX::XMFLOAT3 MoveLookController::get_Position()
{
return m_position;
}
// Returns the point at which the camera controller is facing.
DirectX::XMFLOAT3 MoveLookController::get_LookPoint()
{
float y = sinf(m_pitch); // Vertical
float r = cosf(m_pitch); // In the plane
float z = r*cosf(m_yaw); // Fwd-back
float x = r*sinf(m_yaw); // Left-right
DirectX::XMFLOAT3 result(x,y,z);
result.x += m_position.x;
result.y += m_position.y;
result.z += m_position.z;
// Return m_position + DirectX::XMFLOAT3(x, y, z);
return result;
}
更新控制器狀態資訊
現在我們會執行計算,將 m_movePointerPosition 中追蹤的指標座標資訊各自轉換成世界座標系統的新座標資訊。 每次主要應用程式迴圈重新整理時,應用程式都會呼叫此方法。 因此,此時將計算要傳遞至應用程式的新視點位置資訊,以便先更新檢視矩陣再投影至檢視區。
void MoveLookController::Update(CoreWindow ^window)
{
// Check for input from the Move control.
if (m_moveInUse)
{
DirectX::XMFLOAT2 pointerDelta(m_movePointerPosition);
pointerDelta.x -= m_moveFirstDown.x;
pointerDelta.y -= m_moveFirstDown.y;
// Figure out the command from the touch-based virtual joystick.
if (pointerDelta.x > 16.0f) // Leave 32 pixel-wide dead spot for being still.
m_moveCommand.x = 1.0f;
else
if (pointerDelta.x < -16.0f)
m_moveCommand.x = -1.0f;
if (pointerDelta.y > 16.0f) // Joystick y is up, so change sign.
m_moveCommand.y = -1.0f;
else
if (pointerDelta.y < -16.0f)
m_moveCommand.y = 1.0f;
}
// Poll our state bits that are set by the keyboard input events.
if (m_forward)
m_moveCommand.y += 1.0f;
if (m_back)
m_moveCommand.y -= 1.0f;
if (m_left)
m_moveCommand.x -= 1.0f;
if (m_right)
m_moveCommand.x += 1.0f;
if (m_up)
m_moveCommand.z += 1.0f;
if (m_down)
m_moveCommand.z -= 1.0f;
// Make sure that 45 degree cases are not faster.
DirectX::XMFLOAT3 command = m_moveCommand;
DirectX::XMVECTOR vector;
vector = DirectX::XMLoadFloat3(&command);
if (fabsf(command.x) > 0.1f || fabsf(command.y) > 0.1f || fabsf(command.z) > 0.1f)
{
vector = DirectX::XMVector3Normalize(vector);
DirectX::XMStoreFloat3(&command, vector);
}
// Rotate command to align with our direction (world coordinates).
DirectX::XMFLOAT3 wCommand;
wCommand.x = command.x*cosf(m_yaw) - command.y*sinf(m_yaw);
wCommand.y = command.x*sinf(m_yaw) + command.y*cosf(m_yaw);
wCommand.z = command.z;
// Scale for sensitivity adjustment.
wCommand.x = wCommand.x * MOVEMENT_GAIN;
wCommand.y = wCommand.y * MOVEMENT_GAIN;
wCommand.z = wCommand.z * MOVEMENT_GAIN;
// Our velocity is based on the command.
// Also note that y is the up-down axis.
DirectX::XMFLOAT3 Velocity;
Velocity.x = -wCommand.x;
Velocity.z = wCommand.y;
Velocity.y = wCommand.z;
// Integrate
m_position.x += Velocity.x;
m_position.y += Velocity.y;
m_position.z += Velocity.z;
// Clear movement input accumulator for use during the next frame.
m_moveCommand = DirectX::XMFLOAT3(0.0f, 0.0f, 0.0f);
}
我們不希望在玩家使用觸控式移動控制器時出現抖動,所以在指標周圍設定一個直徑為 32 像素的虛擬死區。 我們也會新增速度,也就是命令值加上移動增益率。 (您可以根據指標在移動控制器區域中移動的距離,依喜好調整此行為,以減緩或加快移動速度)。
我們計算速度時,也會將移動和視角控制器接收到的座標,轉譯為要傳送給方法 (用於計算場景檢視矩陣) 的實際視點。 首先,我們會反轉 x 座標,若使用視角控制器點按移動或左右拖曳,視點會在場景中反方向旋轉,因為相機可能會繞其中心軸擺動。 接著,我們會交換 y 和 z 軸,因為移動控制器的向上/向下鍵按下或觸控拖曳動作 (讀取為 Y 軸行為),應轉譯為將視點移入或移出螢幕 (z 軸) 的相機動作。
玩家的最後一個視點位置是最後位置加上已計算的速度,這將是轉譯器呼叫 get_Position 方法時所讀取的內容 (最有可能發生在每格畫面的設定期間)。 之後,我們會將 move 命令重設為零。
使用新的相機位置更新檢視矩陣
我們可以取得相機聚焦的場景空間座標,且每當您指示應用程式執行此動作時 (例如,在主要應用程式迴圈中每 60 秒一次),該座標都會更新。 此虛擬程式碼提供可實作的呼叫行為建議:
myMoveLookController->Update( m_window );
// Update the view matrix based on the camera position.
myFirstPersonCamera->SetViewParameters(
myMoveLookController->get_Position(), // Point we are at
myMoveLookController->get_LookPoint(), // Point to look towards
DirectX::XMFLOAT3( 0, 1, 0 ) // Up-vector
);
恭喜! 您已在遊戲中實作觸控螢幕和鍵盤/滑鼠輸入觸控控制項的基本移動視角控制項!