Lägga till indata och interaktivitet i Marble Maze-exemplet

UWP-spel (Universal Windows Platform) körs på en mängd olika enheter, till exempel stationära datorer, bärbara datorer och surfplattor. En enhet kan ha en mängd indata- och kontrollmekanismer. Det här dokumentet beskriver de viktigaste metoderna att tänka på när du arbetar med indataenheter och visar hur Marble Maze tillämpar dessa metoder.

Anmärkning

Exempelkoden som motsvarar det här dokumentet finns i DirectX Marble Maze-spelexemplet.

  Här är några av de viktigaste punkterna som beskrivs i det här dokumentet när du arbetar med indata i ditt spel:

  • När det är möjligt kan du ha stöd för flera indataenheter så att ditt spel kan hantera ett bredare utbud av inställningar och förmågor bland dina kunder. Även om spelkontroll och sensoranvändning är valfria rekommenderar vi starkt att du förbättrar spelarupplevelsen. Vi har utformat spelstyrenheten och sensor-API:er för att hjälpa dig att enklare integrera dessa indataenheter.

  • Om du vill initiera touch måste du registrera dig för fönsterhändelser, till exempel när pekaren aktiveras, släpps och flyttas. Om du vill initiera accelerometern skapar du ett Windows::D enheter::Sensorer::Accelerometer-objekt när du initierar programmet. En spelkontrollant kräver inte initiering.

  • För enspelarspel bör du överväga om du vill kombinera indata från alla möjliga kontrollanter. På så sätt behöver du inte spåra vilka indata som kommer från vilken kontrollant. Eller så kan du bara spåra indata från den senast tillagda kontrollanten, som vi gör i det här exemplet.

  • Bearbeta Windows-händelser innan du bearbetar indataenheter.

  • Spelkontrollant och accelerometer stöder avsökning. Du kan alltså söka efter data när du behöver dem. För touch registrerar du touchhändelser i datastrukturer som är tillgängliga för din indatabearbetningskod.

  • Överväg om du vill normalisera indatavärden till ett gemensamt format. Detta kan förenkla hur indata tolkas av andra komponenter i ditt spel, till exempel fysiksimulering, och kan göra det enklare att skriva spel som fungerar med olika skärmupplösningar.

Indataenheter som stöds av Marble Maze

Marble Maze stöder spelkontrollant, mus och touch för att välja menyalternativ, och spelkontrollanten, musen, touchen och accelerometern för att styra spelet. Marble Maze använder Windows::Gaming::Input API:er för att ta emot indata från kontrollern. Touch gör det möjligt för program att spåra och svara på fingertoppsindata. En accelerometer är en sensor som mäter kraften som appliceras längs X-, Y- och Z-axlarna. Genom att använda Windows Runtime kan du avsöka det aktuella tillståndet för accelerometerenheten och ta emot touchhändelser via mekanismen för händelsehantering i Windows Runtime.

Anmärkning

Det här dokumentet använder touch för att referera till både pek- och musinmatning och pekare för att referera till alla enheter som använder pekarhändelser. Eftersom touch och musen använder standardpekarhändelser kan du använda någon av enheterna för att välja menyalternativ och styra spel.

 

Anmärkning

Paketmanifestet anger Liggande som den enda stödrotationen för spelet för att förhindra att skärmläget ändras när enheten roteras för att rulla marmorn. Om du vill visa paketmanifestet öppnar du Package.appxmanifest i Solution Explorer i Visual Studio.

 

Initierar indataenheterna

Spelstyrenheten kräver inte initiering. För att initiera touch måste du registrera dig för fönsterhändelser, till exempel när pekaren aktiveras (till exempel trycker spelaren på musknappen eller rör vid skärmen), släpps och flyttas. För att initiera accelerometern måste du skapa ett Windows::D enheter::Sensorer::Accelerometer-objekt när du initierar programmet.

I följande exempel visas hur metoden App::SetWindow registreras för Windows::UI::Core::CoreWindow::PointerPressed, Windows::UI::Core::CoreWindow::PointerReleasedoch Windows::UI::Core::CoreWindow::PointerMoved pekarhändelser. Dessa händelser registreras under programinitieringen och före spelloopen.

Dessa händelser hanteras i en separat tråd som anropar händelsehanterarna.

Mer information om hur programmet initieras finns i Marble Maze-programstrukturen.

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);

Klassen MarbleMazeMain skapar också ett std::map-objekt för att lagra touch-händelser. Nyckeln för det här kartobjektet är ett värde som unikt identifierar indatapekaren. Varje tangent är kopplad till avståndet mellan varje beröringspunkt och mitten av skärmen. Marble Maze använder senare dessa värden för att beräkna hur mycket labyrinten lutas.

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

Klassen MarbleMazeMain innehåller också ett Accelerometer- objekt.

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

Objektet Accelerometer initieras i MarbleMazeMain konstruktorn, som du ser i följande exempel. Metoden Windows::Devices::Sensors::Accelerometer::GetDefault returnerar en instans av standardaccelerometern. Om det inte finns någon standardacelerometer returnerar Accelerometer::GetDefaultnullptr.

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

Du kan använda musen, touchen eller en spelkontroll för att navigera i menyerna på följande sätt:

  • Använd riktningssatsen för att ändra det aktiva menyalternativet.
  • Använd touch, A-knappen eller menyknappen för att välja ett menyalternativ eller stänga den aktuella menyn, till exempel tabellen med höga poäng.
  • Använd menyknappen för att pausa eller återuppta spelet.
  • Klicka på ett menyalternativ med musen för att välja den åtgärden.

Spåra inmatning för spelkontroll

För att hålla reda på de spelplattor som för närvarande är anslutna till enheten definierar MarbleMazeMain en medlemsvariabel , m_myGamepads, som är en samling Windows::Gaming::Input::Gamepad-objekt . Detta initieras i konstruktorn så här:

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

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

Dessutom registrerar MarbleMazeMain konstruktorn händelser för när gamepads läggs till eller tas bort:

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;
    }
});

När en spelplatta läggs till läggs den till i m_myGamepads. När en spelplatta tas bort kontrollerar vi om spelplattan finns i m_myGamepads, och om den är det tar vi bort den. I båda fallen anger vi m_currentGamepadNeedsRefresh till sant, vilket indikerar att vi måste omtilldela m_gamepad.

Slutligen tilldelar vi en spelplatta till m_gamepad och ställer in m_currentGamepadNeedsRefreshfalse:

m_gamepad = GetLastGamepad();
m_currentGamepadNeedsRefresh = false;

I metoden Uppdatera kontrollerar vi om m_gamepad behöver omtilldelas:

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

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

    m_currentGamepadNeedsRefresh = false;
}

Om m_gamepad behöver omtilldelas, tilldelar vi den den senast tillagda gamepaden med hjälp av GetLastGamepad, som definieras så här:

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

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

    return gamepad;
}

Den här metoden returnerar helt enkelt den sista gamepaden i m_myGamepads.

Du kan ansluta upp till fyra spelstyrenheter till en Windows 10-enhet. För att undvika att behöva ta reda på vilken kontrollant som är aktiv spårar vi bara den senast tillagda gamepaden. Om ditt spel har stöd för fler än en spelare måste du spåra indata för varje spelare separat.

Metoden MarbleMazeMain::Update avsöker spelplattan efter indata:

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

Vi håller reda på inmatningsläsningen som vi fick i föregående ram med m_oldReadingoch den senaste inmatningsläsningen med m_newReading, som vi får genom att anropa Gamepad::GetCurrentReading. Detta returnerar ett GamepadReading-objekt som innehåller information om spelplattans aktuella tillstånd.

För att kontrollera om en knapp bara trycktes eller släpptes definierar vi MarbleMazeMain::ButtonJustPressed och MarbleMazeMain::ButtonJustReleased, som jämför knappläsningar från den här ramen och den sista ramen. På så sätt kan vi bara utföra en åtgärd vid den tidpunkt då en knapp först trycks eller släpps, och inte när den hålls:

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-avläsningar jämförs med bitvisa operationer – vi kontrollerar om en knapp trycks med bitvis och (&). Vi avgör om en knapp bara trycktes eller släpptes genom att jämföra den gamla läsningen och den nya läsningen.

Med ovanstående metoder kontrollerar vi om vissa knappar har tryckts på och utför motsvarande åtgärder som måste utföras. När till exempel menyknappen (GamepadButtons::Menu) trycks in ändras speltillståndet från aktiv till pausad eller pausad till aktiv.

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);
    }
}

Vi kontrollerar också om spelaren trycker på knappen Visa, i vilket fall vi startar om spelet eller rensar tabellen med höga poäng:

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();
    }
}

Om huvudmenyn är aktiv ändras det aktiva menyalternativet när riktningssatsen trycks upp eller ned. Om användaren väljer den aktuella markeringen markeras lämpligt gränssnittselement som valt.

// 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;
}

Spåra pek- och musinmatning

För pek- och musinmatningar väljs ett menyalternativ när användaren rör vid eller klickar på det. I följande exempel visas hur MarbleMazeMain::Update-metoden bearbetar pekarindata för att välja menyalternativ. Den m_pointQueue medlemsvariabeln spårar de platser där användaren rörde vid eller klickade på skärmen. Det sätt på vilket Marble Maze samlar in pekarindata beskrivs mer detaljerat senare i det här dokumentet i avsnittet Bearbetning av pekarindata.

// 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();
}

Metoden UserInterface::HitTest avgör om den angivna punkten finns inom gränserna för något gränssnittselement. Alla gränssnittselement som klarar det här testet markeras som vidrörda. Den här metoden använder hjälpfunktionen PointInRect för att avgöra om den angivna punkten finns inom gränserna för varje UI-element.

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));
        }
    }
}

Uppdatera speltillståndet

Efter att MarbleMazeMain::Update bearbetar kontroll- och pekinmatning, uppdateras speltillståndet om någon knapp trycks in.

// 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);
}

Styra spelupplevelse

Spelslingan och MarbleMazeMain::Update-metoden fungerar tillsammans för att uppdatera tillståndet för spelobjekt. Om ditt spel accepterar indata från flera enheter kan du samla indata från alla enheter till en uppsättning variabler så att du kan skriva kod som är enklare att underhålla. Metoden MarbleMazeMain::Update definierar en uppsättning variabler som ackumulerar rörelse från alla enheter.

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

Indatamekanismen kan variera från en indataenhet till en annan. Pekarindata hanteras till exempel med hjälp av händelsehanteringsmodellen för Windows Runtime. Däremot söker du efter indata från spelkontrollanten när du behöver dem. Vi rekommenderar att du alltid följer den inmatningsmekanism som föreskrivs för en viss enhet. Det här avsnittet beskriver hur Marble Maze läser indata från varje enhet, hur den uppdaterar de kombinerade indatavärdena och hur den använder de kombinerade indatavärdena för att uppdatera spelets tillstånd.

Bearbetning av pekarindata

När du arbetar med pekarindata anropar du Windows::UI::Core::CoreDispatcher::ProcessEvents metoden för att bearbeta fönsterhändelser. Anropa den här metoden i din spelloop innan du uppdaterar eller renderar scenen. Marble Maze anropar detta i metoden 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);
    }
}

Om fönstret är synligt skickar vi CoreProcessEventsOption::P rocessAllIfPresent till ProcessEvents för att bearbeta alla köade händelser och returnera omedelbart; Annars skickar vi CoreProcessEventsOption::P rocessOneAndAllPending för att bearbeta alla köade händelser och vänta på nästa nya händelse. När händelserna har bearbetats renderas och visas nästa bildruta i Marble Maze.

Windows Runtime anropar den registrerade hanteraren för varje händelse som inträffade. Metoden App::SetWindow registrerar för händelser och vidarebefordrar pekarinformation till klassen 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);
}

Klassen MarbleMazeMain reagerar på pekarhändelser genom att uppdatera kartobjektet som innehåller touch-händelser. Metoden MarbleMazeMain::AddTouch anropas när pekaren först trycks in, till exempel när användaren först rör skärmen på en pekaktiverad enhet. Metoden MarbleMazeMain::UpdateTouch anropas när pekarpositionen flyttas. Metoden MarbleMazeMain::RemoveTouch anropas när pekaren släpps, till exempel när användaren slutar röra skärmen.

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);
}

Funktionen PointToTouch översätter den aktuella pekarpositionen så att ursprunget är i mitten av skärmen och skalar sedan koordinaterna så att de ligger ungefär mellan -1.0 och +1.0. Detta gör det enklare att beräkna labyrintens lutning på ett konsekvent sätt över olika indatametoder.

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);
}

Metoden MarbleMazeMain::Update uppdaterar de kombinerade indatavärdena genom att öka lutningsfaktorn med ett konstant skalningsvärde. Det här skalningsvärdet fastställdes genom att experimentera med flera olika värden.

// 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;
}

Bearbeta accelerometerindata

För att bearbeta accelerometerindata anropar MarbleMazeMain::Update metoden Windows::Devices::Sensors::Accelerometer::GetCurrentReading. Den här metoden returnerar ett Windows::Devices::Sensors::AccelerometerReading-objekt, som motsvarar en accelerometeravläsning. Windows::D enheter::Sensorer::AccelerometerReading::AccelerationX och Windows::D enheter::Sensorer::AccelerometerReading::AccelerationY egenskaper innehåller g-force acceleration längs X- respektive Y-axlarna.

I följande exempel visas hur metoden MarbleMazeMain::Update avsöker accelerometern och uppdaterar de kombinerade indatavärdena. När du lutar enheten gör gravitationen att marmorn rör sig snabbare.

// 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;
    }
}

Eftersom du inte kan vara säker på att en accelerometer finns på användarens dator måste du alltid se till att du har ett giltigt Accelerometer-objekt innan du avsöker accelerometern.

Bearbetning av spelkontrollantindata

I metoden MarbleMazeMain::Update använder vi m_newReading för att bearbeta indata från den vänstra analoga stickan:

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;
}

Vi kontrollerar om indata från den vänstra analoga spaken ligger utanför den döda zonen, och om den är det lägger vi till den i kombineradeTiltX och kombineradeTiltY (multiplicerat med en skalningsfaktor) för att luta scenen.

Viktigt!

När du arbetar med en spelkontrollant tar du alltid hänsyn till den döda zonen. Den döda zonen refererar till variansen bland gamepads i deras känslighet för inledande rörelse. I vissa kontrollanter kan en liten rörelse generera ingen läsning, men i andra kan det generera en mätbar läsning. För att ta hänsyn till detta i ditt spel skapar du en zon med icke-rörelse för inledande tumsticksrörelse. Mer information om den döda zonen finns i Läsa tumpinnarna.

 

Tillämpa indata på speltillståndet

Enheter rapporterar indatavärden på olika sätt. Pekarindata kan till exempel finnas i skärmkoordinater och styrenhetsindata kan ha ett helt annat format. En utmaning med att kombinera indata från flera enheter till en uppsättning indatavärden är normalisering eller konvertering av värden till ett gemensamt format. Marble Maze normaliserar värden genom att skala dem till intervallet [-1.0, 1.0]. Funktionen PointToTouch , som beskrivs tidigare i det här avsnittet, konverterar skärmkoordinater till normaliserade värden som ligger ungefär mellan -1.0 och +1.0.

Tips/Råd

Även om ditt program använder en indatametod rekommenderar vi att du alltid normaliserar indatavärden. Detta kan förenkla hur indata tolkas av andra komponenter i ditt spel, till exempel fysiksimulering, och gör det enklare att skriva spel som fungerar med olika skärmupplösningar.

 

När metoden MarbleMazeMain::Update bearbetar indata skapar den en vektor som representerar effekten av labyrintens lutning på marmorn. I följande exempel visas hur Marble Maze använder funktionen XMVector3Normalize för att skapa en normaliserad gravitationsvektor. MaxTilt-variabeln begränsar hur mycket labyrinten lutar och hindrar labyrinten från att luta på sin sida.

const float maxTilt = 1.0f / 8.0f;

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

gravity = XMVector3Normalize(gravity);

För att slutföra uppdateringen av scenobjekt skickar Marble Maze den uppdaterade gravitationsvektorn till fysiksimuleringen, uppdaterar fysiksimuleringen för den tid som har förflutit sedan föregående bildruta och uppdaterar marmorns position och orientering. Om marmorn har fallit genom labyrinten placerar metoden MarbleMazeMain::Update tillbaka marmorn vid den sista kontrollpunkten som marmorn rörde vid och återställer fysiksimuleringens tillstånd.

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);
}

Det här avsnittet beskriver inte hur fysiksimuleringen fungerar. Mer information om detta finns i Physics.h och Physics.cpp i Marble Maze-källorna.

Nästa steg

Läs Lägga till ljud i Marble Maze-exemplet för information om några av de viktigaste metoderna att tänka på när du arbetar med ljud. I dokumentet beskrivs hur Marble Maze använder Microsoft Media Foundation och XAudio2 för att läsa in, blanda och spela upp ljudresurser.