Condividi tramite


Aggiunta di input e interattività all'esempio di Marble Maze

I giochi UWP (Universal Windows Platform) vengono eseguiti su un'ampia gamma di dispositivi, ad esempio computer desktop, portatili e tablet. Un dispositivo può avere una pletora di meccanismi di input e controllo. Questo documento descrive le procedure principali da tenere presenti quando si lavora con i dispositivi di input e illustra come Marble Maze applica queste procedure.

Annotazioni

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

  Ecco alcuni dei punti chiave che questo documento illustra quando si lavora con l'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. Anche se l'utilizzo del controller di gioco e del sensore è facoltativo, consigliamo vivamente di migliorare l'esperienza del giocatore. Abbiamo progettato le API del controller e del sensore del gioco per aiutarti a integrare più facilmente questi dispositivi di input.

  • Per inizializzare il tocco, è necessario registrarsi per gli eventi della finestra, ad esempio quando il puntatore viene attivato, rilasciato e spostato. Per inizializzare l'accelerometro, creare un oggetto Windows::Devices::Sensors::Accelerometer quando si avvia l'applicazione. Un controller di gioco non richiede l'inizializzazione.

  • Per i giochi a giocatore singolo, valutare se combinare l'input da tutti i controller possibili. In questo modo, non è necessario tenere traccia dell'input proveniente da quale controller. In alternativa, è sufficiente tenere traccia dell'input solo dal controller aggiunto più di recente, come illustrato in questo esempio.

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

  • Il controller di gioco e l'accelerometro supportano il polling. È possibile richiedere i dati quando ne hai bisogno. Per il tocco, registrate gli eventi di tocco nelle strutture dati disponibili per il codice di elaborazione dell'input.

  • Valutare se normalizzare i valori di input in un formato comune. In questo modo è possibile semplificare la modalità di interpretazione dell'input da parte di altri componenti del gioco, ad esempio la simulazione della fisica, e può semplificare la scrittura di giochi che funzionano su 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 e il controller di gioco, il mouse, il tocco e l'accelerometro per controllare il gioco. Marble Maze utilizza le API Windows::Gaming::Input per rilevare l'input del controller. Il tocco consente alle applicazioni di tenere traccia e rispondere all'input della punta del dito. Un accelerometro è un sensore che misura la forza applicata lungo gli assi X, Y e Z. Usando Windows Runtime, puoi interrogare lo stato corrente del dispositivo accelerometro, nonché ricevere eventi touch tramite il meccanismo di gestione degli eventi di Windows Runtime.

Annotazioni

Questo documento utilizza il termine "tocco" per indicare sia l'input da tocco che quello del mouse, e "puntatore" per riferirsi a qualsiasi dispositivo che utilizza eventi puntatore. Poiché il tocco e il mouse usano eventi di puntatore standard, è possibile usare entrambi i dispositivi per selezionare le voci di menu e gestire il gioco.

 

Annotazioni

Il manifest 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, aprire Package.appxmanifest nel esplora soluzioni di in Visual Studio.

 

Inizializzazione dei dispositivi di input

Il controller di gioco non richiede l'inizializzazione. Per inizializzare il tocco, è necessario registrarsi agli eventi di gestione delle finestre, come quando il puntatore viene attivato (ad esempio, quando il giocatore preme il pulsante del mouse o tocca lo schermo), rilasciato o 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 gli eventi puntatore Windows::UI::Core::CoreWindow::PointerPressed, Windows::UI::Core::CoreWindow::PointerReleasede 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, vedere 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 gli eventi di tocco. La chiave per questo oggetto mappa è un valore che identifica in modo univoco il puntatore di input. Ogni tasto mappa la distanza tra ogni punto di tocco e il centro dello schermo. Marble Maze usa successivamente questi valori per calcolare la quantità in base alla quale il labirinto è inclinato.

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

La classe MarbleMazeMain contiene anche un oggetto Accelerometer.

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

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

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

È possibile usare il mouse, il tocco o un controller di gioco per spostarsi nei menu, come indicato di seguito:

  • Usare il riquadro direzionale per modificare la voce di menu attiva.
  • Usare il tocco, il pulsante A o il pulsante Menu per selezionare una voce di menu o chiudere il menu corrente, ad esempio la tabella con punteggio elevato.
  • Usa il pulsante Menu per sospendere o riprendere il gioco.
  • Fare clic su una voce di menu con il mouse per scegliere l'azione.

Rilevamento dell'input del controller di gioco

Per tenere traccia dei gamepad attualmente connessi al dispositivo, MarbleMazeMain definisce una variabile membro, m_myGamepads, che è una raccolta di oggetti Windows::Gaming::Input::Gamepad. Questa operazione viene inizializzata nel costruttore in questo modo:

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

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

Inoltre, il MarbleMazeMain costruttore registra gli eventi relativi a 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 game pad, viene aggiunto a m_myGamepads; quando un game pad viene rimosso, controlliamo se il game pad è in m_myGamepads e, in caso affermativo, lo rimuoviamo. In entrambi i casi, abbiamo impostato m_currentGamepadNeedsRefresh su true, il che indica che è necessario riassegnare m_gamepad.

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

m_gamepad = GetLastGamepad();
m_currentGamepadNeedsRefresh = false;

Nell'aggiornamento del metodo , verifichiamo se il 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ù di recente, usando GetLastGamepad, che è definito come segue:

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, è sufficiente tenere traccia del game pad aggiunto più di recente. Se il gioco supporta più giocatori, devi tenere traccia dell'input per ogni giocatore separatamente.

Il metodo MarbleMazeMain::Update sonda il gamepad per l'input:

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

Teniamo traccia della lettura dell'input ricevuta nell'ultimo fotogramma con m_oldReading, e della lettura dell'input più recente con m_newReading, che otteniamo chiamando Gamepad::GetCurrentReading. Viene restituito 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, è possibile eseguire un'azione solo al momento in cui un pulsante viene inizialmente premuto o rilasciato e non quando viene mantenuto:

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 dei GamepadButtons vengono confrontate usando operazioni bit per bit: controlliamo se un pulsante viene premuto usando l'operazione bitwise AND (&). Determiniamo se un pulsante è stato premuto o rilasciato confrontando la lettura precedente e la nuova lettura.

Usando i metodi precedenti, controlliamo se alcuni pulsanti sono stati premuti ed eseguono le azioni corrispondenti che devono verificarsi. Ad esempio, quando viene premuto il pulsante Menu (GamepadButtons::Menu), lo stato del gioco passa da attivo a sospeso o sospeso in 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 cancellamo la tabella dei punteggi 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 di menu attiva cambia quando viene premuto il riquadro direzionale 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, una voce di menu viene selezionata quando l'utente la tocca o ci clicca sopra. 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 raccoglie l'input del puntatore viene descritto in modo più dettagliato 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 specificato 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 helper PointInRect per determinare se il punto specificato si trova nei confini di ogni 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 l'input del controller e del tocco, aggiorna lo stato del gioco se viene premuto un qualsiasi 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 del gioco e il metodo MarbleMazeMain::Update interagiscono per aggiornare lo stato degli oggetti del gioco. Se il gioco accetta input da più dispositivi, puoi accumulare l'input da tutti i dispositivi in un unico set di variabili in modo da poter scrivere codice più semplice da gestire. Il metodo MarbleMazeMain::Update definisce un insieme di variabili che accumula lo spostamento proveniente da tutti i dispositivi.

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

Il meccanismo di input può variare da un dispositivo di input a un altro. Ad esempio, l'input del puntatore viene gestito usando il modello di gestione degli eventi di Windows Runtime. Al contrario, richiedi i dati di input dal controller di gioco quando ne hai bisogno. È consigliabile seguire sempre il meccanismo di input prescritto per un determinato dispositivo. Questa sezione descrive come Marble Maze legge l'input da ogni dispositivo, come aggiorna i valori di input combinati e come usa i valori di input combinati per aggiornare lo stato del gioco.

Elaborazione dell'input del puntatore

Quando si usa l'input del puntatore, si chiama il metodo Windows::UI::Core::CoreDispatcher::ProcessEvents per elaborare gli eventi della finestra. Chiama questo metodo nel ciclo del gioco prima di aggiornare o eseguire il rendering della scena. Marble Maze chiama questo metodo 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 CoreProcessEventsOption::ProcessAllIfPresent a ProcessEvents per elaborare tutti gli eventi in coda e terminare immediatamente; in caso contrario, passiamo CoreProcessEventsOption::ProcessOneAndAllPending per elaborare tutti gli eventi in coda e attendere il prossimo evento. Dopo l'elaborazione degli eventi, 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 del 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 gestisce gli eventi di 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 per il tocco. Il metodo MarbleMazeMain::UpdateTouch viene chiamato quando si sposta la posizione del puntatore. 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 converte la posizione corrente del puntatore in modo che l'origine si trova al centro dello schermo e quindi ridimensiona le coordinate in modo che siano comprese approssimativamente tra -1,0 e +1,0. In questo modo è più semplice calcolare l'inclinazione del labirinto in modo coerente in 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 in base a 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 una lettura dell'accelerometro. Le proprietà Windows::Devices::Sensors::AccelerometerReading::AccelerationX e Windows::Devices::Sensors::AccelerometerReading::AccelerationY rappresentano rispettivamente l'accelerazione g-force lungo gli assi X e Y.

L'esempio seguente mostra come il metodo MarbleMazeMain::Update controlla l'accelerometro e aggiorna i valori di input combinati. Quando si inclina 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 è possibile assicurarsi della presenza di un accelerometro nel computer dell'utente, assicurarsi sempre di disporre di un oggetto Accelerometer valido prima di interrogare l'accelerometro.

Elaborazione dell'input del controller di gioco

Nel metodo MarbleMazeMain::Update, utilizziamo m_newReading per elaborare l'input dal bastone analogico sinistro.

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 del joystick analogico sinistro si trova all'esterno della zona morta e, in caso affermativo, lo aggiungiamo a CombinedTiltX e CombinedTiltY (moltiplicato per un fattore di scala) per inclinare il palco.

Importante

Quando si lavora con un controller di gioco, tenere sempre conto della zona morta. La zona morta si riferisce alla varianza tra i game pad nella loro sensibilità al movimento iniziale. In alcuni controller, un piccolo movimento potrebbe non generare alcuna lettura, ma in altri può generare una lettura misurabile. Per tenere conto di questo problema nel tuo gioco, crea una zona di non movimento per il movimento iniziale della levetta. Per ulteriori informazioni sulla zona morta, consulta Leggere i joystick.

 

Applicazione dell'input allo stato del gioco

I dispositivi segnalano i valori di input in modi diversi. Ad esempio, l'input del puntatore potrebbe essere in coordinate dello schermo, mentre l'input del controller potrebbe essere in un formato completamente diverso. Una delle sfide nel combinare gli input di più dispositivi in un unico insieme di valori di input è la normalizzazione, ovvero 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 che variano approssimativamente tra -1,0 e +1,0.

Suggerimento

Anche se l'applicazione usa un metodo di input, è consigliabile normalizzare sempre i valori di input. In questo modo è possibile semplificare la modalità di interpretazione dell'input da parte di altri componenti del gioco, ad esempio la simulazione della fisica, e semplifica la scrittura di giochi che funzionano su risoluzioni dello schermo diverse.

 

Dopo che il metodo MarbleMazeMain::Update elabora l'input, crea un vettore che rappresenta l'effetto dell'inclinazione del labirinto sulla sfera. Nell'esempio seguente viene illustrato come Marble Maze usa la funzione XMVector3Normalize per creare un vettore di gravità normalizzato. La variabile maxTilt limita l'inclinazione del labirinto e impedisce che si inclini 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 frame precedente e aggiorna la posizione e l'orientamento della biglia. Se la biglia è caduta attraverso il labirinto, il metodo MarbleMazeMain::Update posiziona la biglia all'ultimo checkpoint che la biglia ha toccato e reimposta 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 informazioni dettagliate, vedere Physics.h e Physics.cpp nei sorgenti di Marble Maze.

Passaggi successivi

Leggi Aggiunta di audio all'esempio di Marble Maze per informazioni su alcune delle procedure principali da tenere a mente quando si lavora con l'audio. Il documento illustra in che modo Marble Maze usa Microsoft Media Foundation e XAudio2 per caricare, combinare e riprodurre risorse audio.