Condividi tramite


Aggiunta di input e interattività all'esempio Marble Maze

I giochi UWP (Universal Windows Platform) vengono eseguiti su un'ampia varietà di dispositivi, come computer desktop, laptop e tablet. Un dispositivo può avere numerosi meccanismi di input e controllo. Questo documento descrive le pratiche chiave da tenere a mente quando lavori con dispositivi di input e mostra come Marble Maze applica queste pratiche.

Nota

Il codice di esempio corrispondente a questo documento è disponibile nell'esempio di gioco Marble Maze DirectX.

  Ecco alcuni dei punti chiave che questo documento illustra quando si lavora con input nel gioco:

  • Quando possibile, supporta più dispositivi di input per consentire al tuo gioco di soddisfare una gamma più ampia di preferenze e abilità tra i tuoi clienti. Sebbene l'utilizzo del controller di gioco e del sensore sia facoltativo, lo consigliamo vivamente per migliorare l'esperienza del giocatore. Abbiamo progettato il controller di gioco e le API dei sensori per aiutarti a integrare più facilmente questi dispositivi di input.

  • Per inizializzare il tocco, è necessario registrarsi per gli eventi della finestra come quando il puntatore viene attivato, rilasciato e spostato. Per inizializzare l'accelerometro, crea un oggetto Windows::Devices::Sensors::Accelerometer quando inizializzi l'applicazione. Il controller di gioco non richiede inizializzazione.

  • Per i giochi per giocatore singolo, valuta se combinare l'input di tutti i controller possibili. In questo modo, non è necessario tenere traccia di quale input proviene da quale controller. In alternativa, tieni semplicemente traccia dell'input solo dal controller aggiunto più di recente, come facciamo in questo esempio.

  • Elabora gli eventi di Windows prima di elaborare i dispositivi di input.

  • Il controller di gioco e l'accelerometro supportano il polling. Cioè, puoi eseguire il polling dei dati quando ne hai bisogno. Per il tocco, registra gli eventi tocco nelle strutture dati disponibili per il codice di elaborazione dell'input.

  • Considera se normalizzare i valori di input in un formato comune. Ciò può semplificare il modo in cui l'input viene interpretato da altri componenti del gioco, come la simulazione fisica, e può rendere più semplice la scrittura di giochi che funzionano con risoluzioni dello schermo diverse.

Dispositivi di input supportati da Marble Maze

Marble Maze supporta il controller di gioco, il mouse e il tocco per selezionare le voci di menu, nonché il controller di gioco, il mouse, il tocco e l'accelerometro per controllare il gioco. Marble Maze utilizza le API Windows::Gaming::Input per eseguire il polling del controller per l'input. Il tocco consente alle applicazioni di monitorare e rispondere all'input con la punta delle dita. L'accelerometro è un sensore che misura la forza applicata lungo gli assi X, Y e Z. Utilizzando Windows Runtime, puoi eseguire il polling dello stato corrente del dispositivo accelerometro, nonché ricevere eventi tocco tramite il meccanismo di gestione degli eventi di Windows Runtime.

Nota

Questo documento utilizza il tocco per fare riferimento sia all'input tramite tocco sia al mouse e al puntatore per fare riferimento a qualsiasi dispositivo che utilizza eventi del puntatore. Poiché il tocco e il mouse utilizzano eventi del puntatore standard, puoi utilizzare entrambi i dispositivi per selezionare le voci di menu e controllare il gioco.

 

Nota

Il manifesto del pacchetto imposta Orizzontale come unica rotazione supportata per il gioco per impedire che l'orientamento cambi quando si ruota il dispositivo per far rotolare la biglia. Per visualizzare il manifesto del pacchetto, apri Package.appxmanifest in Esplora soluzioni in Visual Studio.

 

Inizializzazione dei dispositivi di input

Il controller di gioco non richiede inizializzazione. Per inizializzare il tocco, è necessario registrarsi per eventi di finestra come quando il puntatore viene attivato (ad esempio, il giocatore preme il pulsante del mouse o tocca lo schermo), rilasciato e spostato. Per inizializzare l'accelerometro, è necessario creare un oggetto Windows::Devices::Sensors::Accelerometer quando si inizializza l'applicazione.

L'esempio seguente mostra come il metodo App::SetWindow viene registrato per eventi del puntatore Windows::UI::Core::CoreWindow::PointerPressed, Windows::UI::Core::CoreWindow::PointerReleased e Windows::UI::Core::CoreWindow::PointerMoved. Questi eventi vengono registrati durante l'inizializzazione dell'applicazione e prima del ciclo del gioco.

Questi eventi vengono gestiti in un thread separato che richiama i gestori eventi.

Per ulteriori informazioni su come viene inizializzata l'applicazione, consulta Struttura dell'applicazione 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);

La classe MarbleMazeMain crea anche un oggetto std::map per contenere eventi tocco. La chiave per questo oggetto della mappa è un valore che identifica in modo univoco il puntatore di input. Ogni tasto corrisponde alla distanza tra ogni punto di contatto e il centro dello schermo. Marble Maze successivamente utilizza questi valori per calcolare il livello di inclinazione del labirinto.

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

La classe MarbleMazeMain contiene anche un oggetto Accelerometro.

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

L'oggetto Accelerometro viene inizializzato nel costruttore MarbleMazeMain, come mostrato nell'esempio seguente. Il metodo Windows::Devices::Sensors::Accelerometer::GetDefault restituisce un'istanza dell'accelerometro predefinito. Se non è presente un accelerometro predefinito, Accelerometer::GetDefault restituisce nullptr.

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

Puoi utilizzare il mouse, il tocco o un controller di gioco per navigare nei menu, come segue:

  • Utilizza il pad direzionale per modificare la voce di menu attiva.
  • Usa il tocco, il pulsante A o il pulsante Menu per selezionare una voce di menu o chiudere il menu corrente, ad esempio la tabella dei punteggi più alti.
  • Usa il pulsante Menu per mettere in pausa o riprendere il gioco.
  • Fai clic su una voce di menu con il mouse per scegliere l'azione corrispondente.

Monitoraggio dell'input del controller di gioco

Per tenere traccia dei gamepad attualmente collegati al dispositivo, MarbleMazeMain definisce una variabile membro, m_myGamepads, che è una raccolta di oggetti Windows::Gaming::Input::Gamepad. Questo è inizializzato nel costruttore in questo modo:

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

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

Inoltre, il costruttore MarbleMazeMain registra gli eventi per quando i gamepad vengono aggiunti o rimossi:

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

Quando viene aggiunto un gamepad, questo viene aggiunto a m_myGamepads; quando un gamepad viene rimosso, controlliamo se il gamepad è in m_myGamepads e, in caso sia presente, lo rimuoviamo. In entrambi i casi, impostiamo m_currentGamepadNeedsRefresh su vero, indicando che dobbiamo riassegnare m_gamepad.

Infine, assegniamo un gamepad a m_gamepad e impostiamo m_currentGamepadNeedsRefresh su falso:

m_gamepad = GetLastGamepad();
m_currentGamepadNeedsRefresh = false;

Nel metodo Aggiornamento, controlliamo se m_gamepad deve essere riassegnato:

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

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

    m_currentGamepadNeedsRefresh = false;
}

Se m_gamepad deve essere riassegnato, gli assegniamo il gamepad aggiunto più recentemente, utilizzando GetLastGamepad, che è definito in questo modo:

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

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

    return gamepad;
}

Questo metodo restituisce semplicemente l'ultimo gamepad in m_myGamepads.

Puoi connettere fino a quattro controller di gioco a un dispositivo Windows 10. Per evitare di dover capire quale controller è quello attivo, tracciamo semplicemente solo il gamepad aggiunto più di recente. Se il gioco supporta più di un giocatore, devi tenere traccia dell'input per ciascun giocatore separatamente.

Il metodo MarbleMazeMain::Update esegue il polling del gamepad per l'input:

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

Teniamo traccia della lettura dell'input ottenuta nell'ultimo fotogramma con m_oldReading e dell'ultima lettura dell'input con m_newReading, che otteniamo chiamando Gamepad::GetCurrentReading. Ciò restituisce un oggetto GamepadReading, che contiene informazioni sullo stato corrente del gamepad.

Per verificare se un pulsante è stato appena premuto o rilasciato, definiamo MarbleMazeMain::ButtonJustPressed e MarbleMazeMain::ButtonJustReleased, che confrontano le letture dei pulsanti da questo fotogramma e dall'ultimo fotogramma. In questo modo possiamo eseguire un'azione solo nel momento in cui un pulsante viene inizialmente premuto o rilasciato e non quando viene tenuto premuto:

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

Le letture di GamepadButtons vengono confrontate utilizzando operazioni bit per bit: controlliamo se un pulsante viene premuto utilizzando bit per bit e (&). Determiniamo se un pulsante è stato appena premuto o rilasciato confrontando la vecchia lettura e la nuova lettura.

Utilizzando i metodi di cui sopra, controlliamo se determinati pulsanti sono stati premuti ed eseguiamo le azioni corrispondenti che devono verificarsi. Ad esempio, quando viene premuto il pulsante Menu (GamepadButtons::Menu), lo stato del gioco cambia da attivo a in pausa o da in pausa ad attivo.

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

Controlliamo anche se il giocatore preme il pulsante Visualizza, nel qual caso riavviamo il gioco o cancelliamo la tabella dei punteggi più alti:

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

Se il menu principale è attivo, la voce del menu attivo cambia quando il pad direzionale viene premuto verso l'alto o verso il basso. Se l'utente sceglie la selezione corrente, l'elemento dell'interfaccia utente appropriato viene contrassegnato come scelto.

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

Rilevamento dell'input tramite tocco e mouse

Per l'input tramite tocco e mouse, viene scelta una voce di menu quando l'utente la tocca o fa clic su di essa. L'esempio seguente mostra come il metodo MarbleMazeMain::Update elabora l'input del puntatore per selezionare le voci di menu. La variabile membro m_pointQueue tiene traccia delle posizioni in cui l'utente ha toccato o fatto clic sullo schermo. Il modo in cui Marble Maze acquisisce l'input del puntatore è descritto in maggiore dettaglio più avanti in questo documento nella sezione Elaborazione dell'input del puntatore.

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

Il metodo UserInterface::HitTest determina se il punto fornito si trova nei limiti di qualsiasi elemento dell'interfaccia utente. Tutti gli elementi dell'interfaccia utente che superano questo test vengono contrassegnati come toccati. Questo metodo usa la funzione di aiuto PointInRect per determinare se il punto fornito si trova nei limiti di ciascun elemento dell'interfaccia utente.

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

Aggiornamento dello stato del gioco

Dopo che il metodo MarbleMazeMain::Update elabora il controller e l'input touch, aggiorna lo stato del gioco se è stato premuto un pulsante.

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

Controllo del gioco

Il ciclo di gioco e il metodo MarbleMazeMain::Update lavorano insieme per aggiornare lo stato degli oggetti di gioco. Se il gioco accetta input da più dispositivi, puoi accumulare gli input da tutti i dispositivi in un unico set di variabili in modo da poter scrivere un codice più facile da gestire. Il metodo MarbleMazeMain::Update definisce un set di variabili che accumula il movimento da tutti i dispositivi.

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

Il meccanismo di input può variare da un dispositivo di input all'altro. Ad esempio, l'input del puntatore viene gestito utilizzando il modello di gestione degli eventi di Windows Runtime. Al contrario, esegui il polling dei dati di input dal controller di gioco quando ne hai bisogno. Ti consigliamo di seguire sempre il meccanismo di input prescritto per un determinato dispositivo. Questa sezione descrive come Marble Maze legge l'input da ciascun dispositivo, come aggiorna i valori di input combinati e come utilizza i valori di input combinati per aggiornare lo stato del gioco.

Elaborazione dell'input del puntatore

Quando lavori con l'input del puntatore, chiama il metodo Windows::UI::Core::CoreDispatcher::ProcessEvents per elaborare gli eventi della finestra. Chiama questo metodo nel ciclo di gioco prima di aggiornare o eseguire il rendering della scena. Marble Maze lo chiama nel metodo 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);
    }
}

Se la finestra è visibile, passiamo da CoreProcessEventsOption::ProcessAllIfPresent a ProcessEvents per elaborare tutti gli eventi in coda e tornare immediatamente; in caso contrario, passiamo a CoreProcessEventsOption::ProcessOneAndAllPending per elaborare tutti gli eventi in coda e attendere il nuovo evento successivo. Dopo che gli eventi sono stati elaborati, Marble Maze esegue il rendering e presenta il fotogramma successivo.

Windows Runtime chiama il gestore registrato per ogni evento che si è verificato. Il metodo App::SetWindow registra gli eventi e inoltra le informazioni sul puntatore alla classe 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);
}

La classe MarbleMazeMain reagisce agli eventi del puntatore aggiornando l'oggetto della mappa che contiene gli eventi tocco. Il metodo MarbleMazeMain::AddTouch viene chiamato quando il puntatore viene premuto per la prima volta, ad esempio, quando l'utente tocca inizialmente lo schermo su un dispositivo abilitato al tocco. Il metodo MarbleMazeMain::UpdateTouch viene chiamato quando la posizione del puntatore si sposta. Il metodo MarbleMazeMain::RemoveTouch viene chiamato quando il puntatore viene rilasciato, ad esempio, quando l'utente smette di toccare lo schermo.

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

La funzione PointToTouch traduce la posizione corrente del puntatore in modo che l'origine sia al centro dello schermo, quindi ridimensiona le coordinate in modo che siano comprese tra -1.0 e +1.0. Ciò semplifica il calcolo dell'inclinazione del labirinto in modo coerente tra diversi metodi di input.

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

Il metodo MarbleMazeMain::Update aggiorna i valori di input combinati incrementando il fattore di inclinazione di un valore di ridimensionamento costante. Questo valore di ridimensionamento è stato determinato sperimentando diversi valori.

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

Elaborazione dell'input dell'accelerometro

Per elaborare l'input dell'accelerometro, il metodo MarbleMazeMain::Update chiama il metodo Windows::Devices::Sensors::Accelerometer::GetCurrentReading. Questo metodo restituisce un oggetto Windows::Devices::Sensors::AccelerometerReading, che rappresenta la lettura dell'accelerometro. Le proprietà Windows::Devices::Sensors::AccelerometerReading::AccelerationX e Windows::Devices::Sensors::AccelerometerReading::AccelerationY mantengono rispettivamente l'accelerazione della forza G lungo gli assi X e Y.

L'esempio seguente mostra come il metodo MarbleMazeMain::Update esegue il polling dell'accelerometro e aggiorna i valori di input combinati. Quando inclini il dispositivo, la gravità fa sì che la biglia si muova più velocemente.

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

Poiché non puoi essere sicuro che un accelerometro sia presente sul computer dell'utente, assicurati sempre di avere un oggetto Accelerometro valido prima di eseguire il polling dell'accelerometro.

Elaborazione dell'input del controller di gioco

Nel metodo MarbleMazeMain::Update, utilizziamo m_newReading per elaborare l'input dalla levetta analogica sinistra:

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

Controlliamo se l'input dalla levetta analogica sinistra è al di fuori della zona morta e, in tal caso, lo aggiungiamo a combinedTiltX e combinedTiltY (moltiplicato per un fattore di scala) per inclinare la scena.

Importante

Quando lavori con un controller di gioco, tieni sempre conto della zona morta. La zona morta si riferisce alla variazione tra i gamepad nella loro sensibilità al movimento iniziale. In alcuni controller, un piccolo movimento potrebbe non generare alcuna lettura, ma in altri potrebbe generare una lettura misurabile. Per tenere conto di ciò nel tuo gioco, crea una zona di non movimento per il movimento iniziale della levetta. Per ulteriori informazioni sulla zona morta, consulta Lettura delle levette.

 

Applicazione dell'input allo stato del gioco

I dispositivi segnalano i valori di input in diversi modi. Ad esempio, l'input del puntatore potrebbe essere nelle coordinate dello schermo e l'input del controller potrebbe essere in un formato completamente diverso. Un problema legato alla combinazione dell'input da più dispositivi in un unico set di valori di input è la normalizzazione o la conversione dei valori in un formato comune. Marble Maze normalizza i valori ridimensionandoli nell'intervallo [-1.0, 1.0]. La funzione PointToTouch, descritta in precedenza in questa sezione, converte le coordinate dello schermo in valori normalizzati compresi approssimativamente tra -1.0 e +1.0.

Suggerimento

Anche se l'applicazione utilizza un metodo di input, ti consigliamo di normalizzare sempre i valori di input. Ciò può semplificare il modo in cui l'input viene interpretato da altri componenti del gioco, come la simulazione fisica, e rende più semplice la scrittura di giochi che funzionano su risoluzioni dello schermo diverse.

 

Dopo che il metodo MarbleMazeMain::Update ha elaborato l'input, crea un vettore che rappresenta l'effetto dell'inclinazione del labirinto sulla biglia. L'esempio seguente mostra come Marble Maze utilizza la funzione XMVector3Normalize per creare un vettore di gravità normalizzato. La variabile maxTilt limita la quantità di inclinazione del labirinto e impedisce al labirinto di inclinarsi su un lato.

const float maxTilt = 1.0f / 8.0f;

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

gravity = XMVector3Normalize(gravity);

Per completare l'aggiornamento degli oggetti della scena, Marble Maze passa il vettore di gravità aggiornato alla simulazione fisica, aggiorna la simulazione fisica per il tempo trascorso dal fotogramma precedente e aggiorna la posizione e l'orientamento della biglia. Se la biglia è caduta nel labirinto, il metodo MarbleMazeMain::Update riporta la biglia all'ultimo punto di controllo che ha toccato e ripristina lo stato della simulazione fisica.

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

Questa sezione non descrive il funzionamento della simulazione fisica. Per dettagli a riguardo, consulta Physics.h e Physics.cpp nei sorgente di Marble Maze.

Passaggi successivi

Leggi l'esempio Aggiunta di audio all'esempio di Marble Maze per informazioni su alcune delle pratiche chiave da tenere a mente quando lavori con l'audio. Il documento illustra come Marble Maze utilizza Microsoft Media Foundation e XAudio2 per caricare, mixare e riprodurre risorse audio.