Share via


在 Marble Maze 中加入輸入和互動性範例

通用 Windows 平台 (UWP) 遊戲可在多種裝置上執行,例如桌上型電腦、筆記型電腦和平板電腦。 裝置可以擁有大量的輸入和控制機制。 本文件將說明使用輸入裝置時要記住的重要做法,並示範 Marble Maze 如何套用這些做法。

注意

與本文檔對應的範例程式碼可以在 DirectX Marble Maze 遊戲範例中找到。

  以下是本文件說明在遊戲中使用輸入時的一些重點:

  • 如果可能,請支援多種輸入裝置,使您的遊戲更能滿足客戶多樣化的喜好設定和功能。 雖然不強制使用遊戲控制器和感應器,但我們強烈建議使用它來增強玩家體驗。 我們設計了遊戲控制器和感應器 API,幫助您能更輕鬆地整合這些輸入裝置。

  • 若要初始化觸控,您必須註冊視窗事件,例如在啟動、放開和移動指標時。 若要初始化加速計,請在初始化應用程式時建立 Windows::Devices::Sensors::Accelerometer 物件。 遊戲控制器不需要初始化。

  • 對於單人遊戲,請考慮是否要結合所有可能控制器的輸入。 這樣一來,您就不必追蹤哪個輸入來自哪個控制器。 或者,僅追蹤來自最近新增控制器的輸入,就像我們在本範例中所做的那樣。

  • 在處理輸入裝置之前,請先處理 Windows 事件。

  • 遊戲控制器和加速計支援輪詢。 也就是說,您可以在需要時輪詢資料。 針對觸控,請在輸入處理常式碼可用的資料結構中記錄觸控事件。

  • 請考慮是否要將輸入值標準化為通用格式。 這樣做可以簡化遊戲其他元件 (例如物理模擬) 解釋輸入的方式,並且可以更輕鬆地編寫在不同螢幕解析度下執行的遊戲。

Marble Maze 支援的輸入裝置

Marble Maze 支援使用遊戲控制器、滑鼠和觸控來選擇選單項目,以及使用遊戲控制器、滑鼠、觸控和加速計來控制遊戲玩法。 Marble Maze 會使用 Windows::Gaming::Input API 輪詢控制器以取得輸入。 觸控可讓應用程式追蹤及回應指尖輸入。 加速計是一種測量沿 X、Y 和 Z 軸施加的力的感應器。 透過使用 Windows 執行階段,您可以輪詢加速計裝置的目前狀態,並透過 Windows 執行階段事件處理機制接收觸控事件。

注意

本文件使用觸控來指觸控和滑鼠輸入,使用指標來指稱使用指標事件的任何裝置。 由於觸控和滑鼠使用標準指標事件,因此您可以使用任一裝置來選擇選單項目並控制遊戲。

 

注意

套件清單將 Landscape 設定為遊戲唯一支援的旋轉,防止旋轉裝置以滾動彈珠時變更方向。 若要查看套件清單,請在 Visual Studio 的方案總管中開啟 Package.appxmanifest

 

初始化輸入裝置

遊戲控制器不需要初始化。 若要初始化觸控,您必須註冊視窗事件,例如當指標啟動 (例如,玩家按下滑鼠按鈕或觸控螢幕)、放開和移動時。 若要初始化加速計,您必須在初始化應用程式時建立 Windows::Devices::Sensors::Accelerometer 物件。

以下範例顯示 App::SetWindow 方法如何註冊 Windows::UI::Core::CoreWindow::PointerPressedWindows::UI::Core::CoreWindow::PointerReleasedWindows::UI::Core ::CoreWindow:: PointerMoved 指標事件。 這些事件會在應用程式初始化期間和遊戲迴圈之前註冊。

這些事件會在叫用事件處理常式的單獨執行緒中處理。

有關如何初始化應用程式的更多資訊,請參閱 Marble Maze 應用程式結構

window->PointerPressed += ref new TypedEventHandler<CoreWindow^, PointerEventArgs^>(
    this, 
    &App::OnPointerPressed);

window->PointerReleased += ref new TypedEventHandler<CoreWindow^, PointerEventArgs^>(
    this, 
    &App::OnPointerReleased);

window->PointerMoved += ref new TypedEventHandler<CoreWindow^, PointerEventArgs^>(
    this, 
    &App::OnPointerMoved);

MarbleMazeMain 類別也會建立一個 std::map 物件來保存觸控事件。 此對應物件的索引鍵是唯一識別輸入指標的值。 每個按鍵都會對應到每個觸控點與螢幕中心之間的距離。 Marble Maze 稍後會使用這些值來計算迷宮傾斜的數量。

typedef std::map<int, XMFLOAT2> TouchMap;
TouchMap        m_touches;

MarbleMazeMain 類別也會保存 Accelerometer 物件。

Windows::Devices::Sensors::Accelerometer^           m_accelerometer;

Accelerometer 物件在 MarbleMazeMain 建構函式中初始化,如以下範例所示。 Windows::Devices::Sensors::Accelerometer::GetDefault 方法會傳回預設加速計的執行個體。 如果沒有預設的加速計,Accelerometer::GetDefault 會傳回 nullptr

// Returns accelerometer ref if there is one; nullptr otherwise.
m_accelerometer = Windows::Devices::Sensors::Accelerometer::GetDefault();

您可以使用滑鼠、觸控或遊戲控制器來瀏覽選單,如下所示:

  • 使用方向鍵變更活動選單項目。
  • 使用觸控、A 按鈕或選單按鈕來選擇選單項目或關閉目前的選單,例如高分表。
  • 使用 [選單] 按鈕暫停或恢復遊戲。
  • 用滑鼠點選選單項目來選擇該動作。

追蹤遊戲控制器輸入

為了追蹤目前連接到裝置的遊戲手把,MarbleMazeMain 定義了一個成員變數 m_myGamepads,它是 Windows::Gaming::Input::Gamepad 物件的集合。 這會在建構函式中初始化,如下所示:

m_myGamepads = ref new Vector<Gamepad^>();

for (auto gamepad : Gamepad::Gamepads)
{
    m_myGamepads->Append(gamepad);
}

此外,MarbleMazeMain 建構函式會註冊新增或刪除遊戲手把時的事件:

Gamepad::GamepadAdded += 
    ref new EventHandler<Gamepad^>([=](Platform::Object^, Gamepad^ args)
{
    m_myGamepads->Append(args);
    m_currentGamepadNeedsRefresh = true;
});

Gamepad::GamepadRemoved += 
    ref new EventHandler<Gamepad ^>([=](Platform::Object^, Gamepad^ args)
{
    unsigned int indexRemoved;

    if (m_myGamepads->IndexOf(args, &indexRemoved))
    {
        m_myGamepads->RemoveAt(indexRemoved);
        m_currentGamepadNeedsRefresh = true;
    }
});

新增遊戲手把時,會將其加入 m_myGamepads 中; 當遊戲手把移除時,我們檢查遊戲手把是否在 m_myGamepads 中,如果是,我們會將其移除。 在這兩種情況下,我們都將 m_currentGamepadNeedsRefresh 設為 True,表示我們需要重新指派 m_gamepad

最後,我們為 m_gamepad 指派一個遊戲手把,並將 m_currentGamepadNeedsRefresh 設定為 False

m_gamepad = GetLastGamepad();
m_currentGamepadNeedsRefresh = false;

Update 方法中,我們要檢查 m_gamepad 是否需要重新指派:

if (m_currentGamepadNeedsRefresh)
{
    auto mostRecentGamepad = GetLastGamepad();

    if (m_gamepad != mostRecentGamepad)
    {
        m_gamepad = mostRecentGamepad;
    }

    m_currentGamepadNeedsRefresh = false;
}

如果 m_gamepad 需要重新指派,我們會使用 GetLastGamepad 為其指派最近新增的遊戲手把,其定義如下:

Gamepad^ MarbleMaze::MarbleMazeMain::GetLastGamepad()
{
    Gamepad^ gamepad = nullptr;

    if (m_myGamepads->Size > 0)
    {
        gamepad = m_myGamepads->GetAt(m_myGamepads->Size - 1);
    }

    return gamepad;
}

此方法只會傳回 m_myGamepads 中的最後一個遊戲手把。

您最多可以將四個遊戲控制器連線到 Windows 10 裝置。 為了避免找出哪個控制器是活動控制器,我們只需追蹤最近新增的遊戲手把。 如果您的遊戲支援多名玩家,則必須分別追蹤每位玩家的輸入。

MarbleMazeMain::Update 方法會輪詢遊戲手把以取得輸入:

if (m_gamepad != nullptr)
{
    m_oldReading = m_newReading;
    m_newReading = m_gamepad->GetCurrentReading();
}

我們使用 m_oldReading 追蹤最後一個影格中獲得的輸入讀數,並使用 m_newReading 追蹤最新的輸入讀數,這是透過呼叫 Gamepad::GetCurrentReading 取得的。 這會傳回 GamepadReading 物件,其中包含遊戲手把目前狀態的相關資訊。

若要檢查按鈕是否剛按下或放開,我們會定義 MarbleMazeMain::ButtonJustPressedMarbleMazeMain::ButtonJustReleased,它們會比較此影格和上一個影格的按鈕讀數。 如此一來,我們只能在最初按下或放開按鈕時執行動作,而不是在按住按鈕時執行動作:

bool MarbleMaze::MarbleMazeMain::ButtonJustPressed(GamepadButtons selection)
{
    bool newSelectionPressed = (selection == (m_newReading.Buttons & selection));
    bool oldSelectionPressed = (selection == (m_oldReading.Buttons & selection));
    return newSelectionPressed && !oldSelectionPressed;
}

bool MarbleMaze::MarbleMazeMain::ButtonJustReleased(GamepadButtons selection)
{
    bool newSelectionReleased = 
        (GamepadButtons::None == (m_newReading.Buttons & selection));

    bool oldSelectionReleased = 
        (GamepadButtons::None == (m_oldReading.Buttons & selection));

    return newSelectionReleased && !oldSelectionReleased;
}

GamepadButtons 讀數使用位元運算進行比較 - 我們使用位元與 (&) 檢查按鈕是否按下。 我們透過比較舊讀數和新讀數來確定按鈕是剛按下還是放開。

使用上述方法,我們會檢查是否已按下特定按鈕,並執行任何必須發生的相應動作。 例如,當按下 [選單] 按鈕 (GamepadButtons::Menu) 時,遊戲狀態會從進行中變更為暫停,或從暫停變更為進行中。

if (ButtonJustPressed(GamepadButtons::Menu) || m_pauseKeyPressed)
{
    m_pauseKeyPressed = false;

    if (m_gameState == GameState::InGameActive)
    {
        SetGameState(GameState::InGamePaused);
    }  
    else if (m_gameState == GameState::InGamePaused)
    {
        SetGameState(GameState::InGameActive);
    }
}

我們也會檢查玩家是否按下 [檢視] 按鈕,在這種情況下,我們會重新啟動遊戲或清除高分表:

if (ButtonJustPressed(GamepadButtons::View) || m_homeKeyPressed)
{
    m_homeKeyPressed = false;

    if (m_gameState == GameState::InGameActive ||
        m_gameState == GameState::InGamePaused ||
        m_gameState == GameState::PreGameCountdown)
    {
        SetGameState(GameState::MainMenu);
        m_inGameStopwatchTimer.SetVisible(false);
        m_preGameCountdownTimer.SetVisible(false);
    }
    else if (m_gameState == GameState::HighScoreDisplay)
    {
        m_highScoreTable.Reset();
    }
}

如果主選單處於使用中狀態,則當按下十字鍵的向上或向下按鍵時,活動選單項目會發生變化。 如果使用者選擇目前的選項,則對應的 UI 元素將標記為已選擇。

// Handle menu navigation.
bool chooseSelection = 
    (ButtonJustPressed(GamepadButtons::A) 
    || ButtonJustPressed(GamepadButtons::Menu));

bool moveUp = ButtonJustPressed(GamepadButtons::DPadUp);
bool moveDown = ButtonJustPressed(GamepadButtons::DPadDown);

switch (m_gameState)
{
case GameState::MainMenu:
    if (chooseSelection)
    {
        m_audio.PlaySoundEffect(MenuSelectedEvent);
        if (m_startGameButton.GetSelected())
        {
            m_startGameButton.SetPressed(true);
        }
        if (m_highScoreButton.GetSelected())
        {
            m_highScoreButton.SetPressed(true);
        }
    }
    if (moveUp || moveDown)
    {
        m_startGameButton.SetSelected(!m_startGameButton.GetSelected());
        m_highScoreButton.SetSelected(!m_startGameButton.GetSelected());
        m_audio.PlaySoundEffect(MenuChangeEvent);
    }
    break;

case GameState::HighScoreDisplay:
    if (chooseSelection || anyPoints)
    {
        SetGameState(GameState::MainMenu);
    }
    break;

case GameState::PostGameResults:
    if (chooseSelection || anyPoints)
    {
        SetGameState(GameState::HighScoreDisplay);
    }
    break;

case GameState::InGamePaused:
    if (m_pausedText.IsPressed())
    {
        m_pausedText.SetPressed(false);
        SetGameState(GameState::InGameActive);
    }
    break;
}

追蹤觸控和滑鼠輸入

對於觸控和滑鼠輸入,當使用者觸碰或點選選單項目時,會選擇該選單項目。 以下範例顯示 MarbleMazeMain::Update 方法如何處理指標輸入,以選擇選單項目。 m_pointQueue 成員變數會追蹤使用者在螢幕上觸控或點擊的位置。 Marble Maze 收集指標輸入的方式將在本文後面的處理指標輸入一節中進一步說明。

// Check whether the user chose a button from the UI. 
bool anyPoints = !m_pointQueue.empty();
while (!m_pointQueue.empty())
{
    UserInterface::GetInstance().HitTest(m_pointQueue.front());
    m_pointQueue.pop();
}

UserInterface::HitTest 方法會判斷提供的點是否位於任何 UI 元素的邊界內。 通過此測試的任何 UI 元素都會標記為已觸控。 此方法會使用 PointInRect 協助程式函數來判斷提供的點是否位於每個 UI 元素的邊界內。

void UserInterface::HitTest(D2D1_POINT_2F point)
{
    for (auto iter = m_elements.begin(); iter != m_elements.end(); ++iter)
    {
        if (!(*iter)->IsVisible())
            continue;

        TextButton* textButton = dynamic_cast<TextButton*>(*iter);
        if (textButton != nullptr)
        {
            D2D1_RECT_F bounds = (*iter)->GetBounds();
            textButton->SetPressed(PointInRect(point, bounds));
        }
    }
}

更新遊戲狀態

MarbleMazeMain::Update 方法處理控制器和觸控輸入後,如果按下任何按鈕,它就會更新遊戲狀態。

// Update the game state if the user chose a menu option. 
if (m_startGameButton.IsPressed())
{
    SetGameState(GameState::PreGameCountdown);
    m_startGameButton.SetPressed(false);
}
if (m_highScoreButton.IsPressed())
{
    SetGameState(GameState::HighScoreDisplay);
    m_highScoreButton.SetPressed(false);
}

控制遊戲玩法

遊戲迴圈和 MarbleMazeMain::Update 方法會一起運作,以更新遊戲物件的狀態。 如果您的遊戲接受來自多個裝置的輸入,您可以將所有裝置的輸入累積到一組變數,以便撰寫更容易維護的程式代碼。 MarbleMazeMain::Update 方法會定義一組變數,用於累積所有裝置的移動。

float combinedTiltX = 0.0f;
float combinedTiltY = 0.0f;

不同輸入裝置的輸入機制可能有所不同。 例如,指標輸入是透過使用 Windows 執行階段事件處理模型來處理。 相反地,您可以在需要時從遊 戲控制器輪詢輸入資料。 建議您遵循為指定裝置規定的輸入機制。 本節介紹 Marble Maze 如何從每個裝置讀取輸入、如何更新組合輸入值以及如何使用組合輸入值更新遊戲狀態。

處理指標輸入

當您使用指標輸入時,請呼叫 Windows::UI::Core::CoreDispatcher::ProcessEvents 方法來處理視窗事件。 在更新或轉譯場景之前,請在遊戲迴圈中呼叫此方法。 Marble Maze 在 App::Run 方法中呼叫此方法:

while (!m_windowClosed)
{
    if (m_windowVisible)
    {
        CoreWindow::GetForCurrentThread()->
            Dispatcher->ProcessEvents(CoreProcessEventsOption::ProcessAllIfPresent);

        m_main->Update();

        if (m_main->Render())
        {
            m_deviceResources->Present();
        }
    }
    else
    {
        CoreWindow::GetForCurrentThread()->
            Dispatcher->ProcessEvents(CoreProcessEventsOption::ProcessOneAndAllPending);
    }
}

如果視窗可見,我們會將 CoreProcessEventsOption::ProcessAllIfPresent 傳遞給 ProcessEvents 來處理所有佇列事件並立即傳回; 否則,我們會透過 CoreProcessEventsOption::ProcessOneAndAllPending 來處理所有佇列事件,並等待下一個新事件。 處理事件後,Marble Maze 會轉譯並呈現下一個影格。

Windows 執行階段會為每個發生的事件呼叫已註冊的處理程序。 App::SetWindow 方法會註冊事件,並將指標資訊轉送到 MarbleMazeMain 類別。

void App::OnPointerPressed(
    Windows::UI::Core::CoreWindow^ sender, 
    Windows::UI::Core::PointerEventArgs^ args)
{
    m_main->AddTouch(args->CurrentPoint->PointerId, args->CurrentPoint->Position);
}

void App::OnPointerReleased(
    Windows::UI::Core::CoreWindow^ sender, 
    Windows::UI::Core::PointerEventArgs^ args)
{
    m_main->RemoveTouch(args->CurrentPoint->PointerId);
}

void App::OnPointerMoved(
    Windows::UI::Core::CoreWindow^ sender, 
    Windows::UI::Core::PointerEventArgs^ args)
{
    m_main->UpdateTouch(args->CurrentPoint->PointerId, args->CurrentPoint->Position);
}

MarbleMazeMain 類別會透過更新儲存觸控事件的對應物件,來對指標事件做出反應。 當第一次按下指標 (例如,當使用者首次觸碰支援觸控之裝置上的畫面),將會呼叫 MarbleMazeMain::AddTouch 方法。 當指標位置移動時,將呼叫 MarbleMazeMain::UpdateTouch 方法。 當放開指標時 (例如,當使用者停止觸碰螢幕時),將呼叫 MarbleMazeMain::RemoveTouch 方法。

void MarbleMazeMain::AddTouch(int id, Windows::Foundation::Point point)
{
    m_touches[id] = PointToTouch(point, m_deviceResources->GetLogicalSize());

    m_pointQueue.push(D2D1::Point2F(point.X, point.Y));
}

void MarbleMazeMain::UpdateTouch(int id, Windows::Foundation::Point point)
{
    if (m_touches.find(id) != m_touches.end())
        m_touches[id] = PointToTouch(point, m_deviceResources->GetLogicalSize());
}

void MarbleMazeMain::RemoveTouch(int id)
{
    m_touches.erase(id);
}

PointToTouch 函數會平移目前指標位置,使原點位於螢幕中心,然後縮放座標,使它們的範圍大約在 -1.0 和 +1.0 之間。 這可讓您更輕鬆地在不同的輸入方法之間,以一致的方式計算迷宮的傾斜度。

inline XMFLOAT2 PointToTouch(Windows::Foundation::Point point, Windows::Foundation::Size bounds)
{
    float touchRadius = min(bounds.Width, bounds.Height);
    float dx = (point.X - (bounds.Width / 2.0f)) / touchRadius;
    float dy = ((bounds.Height / 2.0f) - point.Y) / touchRadius;

    return XMFLOAT2(dx, dy);
}

MarbleMazeMain::Update 方法會透過將傾斜因子增加恆定的縮放值來更新組合輸入值。 這個縮放值是透過試驗幾個不同的值來確定的。

// Account for touch input.
for (TouchMap::const_iterator iter = m_touches.cbegin(); 
    iter != m_touches.cend(); 
    ++iter)
{
    combinedTiltX += iter->second.x * m_touchScaleFactor;
    combinedTiltY += iter->second.y * m_touchScaleFactor;
}

處理加速計輸入

為了處理加速計輸入,MarbleMazeMain::Update 方法會呼叫 Windows::Devices::Sensors::Accelerometer::GetCurrentReading 方法。 此方法會傳回 Windows::Devices::Sensors::AccelerometerReading 物件,該物件表示加速計讀數。 Windows::Devices::Sensors::AccelerometerReading::AccelerationXWindows::Devices::Sensors::AccelerometerReading::AccelerationY 屬性分別保存沿 X 軸和 Y 軸的重力加速度。

以下範例顯示 MarbleMazeMain::Update 方法如何輪詢加速計,並更新組合輸入值。 當您傾斜裝置時,重力會使彈珠移動得更快。

// Account for sensors.
if (m_accelerometer != nullptr)
{
    Windows::Devices::Sensors::AccelerometerReading^ reading =
        m_accelerometer->GetCurrentReading();

    if (reading != nullptr)
    {
        combinedTiltX += 
            static_cast<float>(reading->AccelerationX) * m_accelerometerScaleFactor;

        combinedTiltY += 
            static_cast<float>(reading->AccelerationY) * m_accelerometerScaleFactor;
    }
}

由於您無法確定使用者電腦上是否存在加速計,因此在輪詢加速計之前,請務必確保您擁有有效的 Accelerometer 物件。

處理遊戲控制器輸入

MarbleMazeMain::Update 方法中,我們使用 m_newReading 處理來自左側類比搖桿的輸入:

float leftStickX = static_cast<float>(m_newReading.LeftThumbstickX);
float leftStickY = static_cast<float>(m_newReading.LeftThumbstickY);

auto oppositeSquared = leftStickY * leftStickY;
auto adjacentSquared = leftStickX * leftStickX;

if ((oppositeSquared + adjacentSquared) > m_deadzoneSquared)
{
    combinedTiltX += leftStickX * m_controllerScaleFactor;
    combinedTiltY += leftStickY * m_controllerScaleFactor;
}

我們會檢查左側模擬搖桿的輸入是否在死區之外,如果是,則將其新增到 combinedTiltXcombinedTiltY (乘以縮放因子) 以傾斜平台。

重要

當您使用遊戲控制器時,請務必考慮死區。 死區是指遊戲手把對初始移動的敏感度差異。 在某些控制器中,微小的移動可能不會產生讀數,但在其他控制器中,可能會產生可測量的讀數。 若要在您的遊戲中考慮這一點,請為初始搖桿移動建立一個非移動區域。 如需死區的詳細資訊,請參閱讀取搖桿

 

將輸入套用至遊戲狀態

裝置會以不同方式報告輸入值。 例如,指標輸入可能採用螢幕座標,而控制器輸入可能採用完全不同的格式。 將多個裝置的輸入組合成一組輸入值的其中一項挑戰是標準化,或將值轉換為通用格式。 Marble Maze 會透過將值縮放到範圍 [-1.0, 1.0] 來標準化值。 本節先前介紹的 PointToTouch 函數會將螢幕座標轉換為範圍大約在 -1.0 和 +1.0 之間的標準化值。

提示

即使您的應用程式使用一種輸入法,我們也建議您一律將輸入值標準化。 這樣做可以簡化遊戲其他元件 (例如物理模擬) 解釋輸入的方式,並且能更輕鬆地編寫在不同螢幕解析度下執行的遊戲。

 

MarbleMazeMain::Update 方法會處理輸入後,它會建立一個向量來表示迷宮傾斜對彈珠的影響。 以下範例顯示 Marble Maze 如何使用 XMVector3Normalize 函數建立標準化重力向量。 maxTilt 變數會限制迷宮傾斜的量,並防止迷宮側向傾斜。

const float maxTilt = 1.0f / 8.0f;

XMVECTOR gravity = XMVectorSet(
    combinedTiltX * maxTilt, 
    combinedTiltY * maxTilt, 
    1.0f, 
    0.0f);

gravity = XMVector3Normalize(gravity);

為了完成場景物件的更新,Marble Maze 將更新的重力向量傳遞給物理模擬,更新自上一個影格以來經過的時間物理模擬,並更新彈珠的位置和方向。 如果彈珠從迷宮中掉下來,MarbleMazeMain::Update 方法會將彈珠放回彈珠接觸的最後一個檢查點,並重設物理模擬的狀態。

XMFLOAT3A g;
XMStoreFloat3(&g, gravity);
m_physics.SetGravity(g);

if (m_gameState == GameState::InGameActive)
{
    // Only update physics when gameplay is active.
    m_physics.UpdatePhysicsSimulation(static_cast<float>(m_timer.GetElapsedSeconds()));

    // ...Code omitted for simplicity...

}

// ...Code omitted for simplicity...

// Check whether the marble fell off of the maze. 
const float fadeOutDepth = 0.0f;
const float resetDepth = 80.0f;
if (marblePosition.z >= fadeOutDepth)
{
    m_targetLightStrength = 0.0f;
}
if (marblePosition.z >= resetDepth)
{
    // Reset marble.
    memcpy(&marblePosition, &m_checkpoints[m_currentCheckpoint], sizeof(XMFLOAT3));
    oldMarblePosition = marblePosition;
    m_physics.SetPosition((const XMFLOAT3&)marblePosition);
    m_physics.SetVelocity(XMFLOAT3(0, 0, 0));
    m_lightStrength = 0.0f;
    m_targetLightStrength = 1.0f;

    m_resetCamera = true;
    m_resetMarbleRotation = true;
    m_audio.PlaySoundEffect(FallingEvent);
}

本節不會說明物理模擬的運作原理。 有關詳細資訊,請參閱 Marble Maze 來源中的 Physics.hPhysics.cpp

下一步

請閱讀在 Marble Maze 中加入音訊範例,以了解有關使用音訊時要記住的一些重要做法資訊。 該文件會說明 Marble Maze 如何使用 Microsoft Media Foundation 和 XAudio2 載入、混合和播放音訊資源。