Configurare il progetto di gioco

Nota

Questo argomento fa parte della serie di esercitazioni Creare un semplice gioco UWP (Universal Windows Platform) con DirectX. L'argomento in tale collegamento imposta il contesto per la serie.

Il primo passaggio nello sviluppo del gioco consiste nel creare un progetto in Microsoft Visual Studio. Dopo aver configurato un progetto specificamente per lo sviluppo di giochi, è possibile riutilizzarlo successivamente come tipo di modello.

Obiettivi

  • Creare un nuovo progetto in Visual Studio utilizzando un modello di progetto.
  • Comprendere il punto di ingresso e l'inizializzazione del gioco esaminando il file di origine per la classe App.
  • Osservare il ciclo del gioco.
  • Rivedere il file package.appxmanifest del progetto.

Creare un nuovo progetto in Visual Studio

Nota

Per informazioni sulla configurazione di Visual Studio per lo sviluppo in C++/WinRT, compresi l'installazione e l'uso dell'estensione C++/WinRT per Visual Studio (VSIX) e del pacchetto NuGet, che insieme forniscono il modello di progetto e il supporto della compilazione, vedere Supporto di Visual Studio per C++/WinRT.

Prima di tutto, installare (o aggiornare) la versione più recente di C++/WinRT Visual Studio Extension (VSIX); vedere la nota sopra. In Visual Studio creare un nuovo progetto basato sul modello di progetto Core App (C++/WinRT). Specificare come destinazione la versione più recente disponibile a livello generale (ovvero non l'anteprima) di Windows SDK.

Rivedere la classe App per comprendere IFrameworkViewSource e IFrameworkView

Nel progetto Core App aprire il file del codice sorgente App.cpp. In esso è presente l'implementazione della classe App, che rappresenta l'app e il relativo ciclo di vita. In questo caso, naturalmente, sappiamo che l'app è un gioco. Tuttavia, si farà riferimento a essa come app per parlare più in generale di come si inizializza un'app UWP (Universal Windows Platform).

La funzione wWinMain

La funzione wWinMain è il punto di ingresso per l'app. Ecco l'aspetto di wWinMain (da App.cpp).

int __stdcall wWinMain(HINSTANCE, HINSTANCE, PWSTR, int)
{
    CoreApplication::Run(winrt::make<App>());
}

Creiamo un'istanza della classe App (si tratta dell'unica e sola istanza di App che viene creata), e la trasferiamo al metodo statico CoreApplication.Run. Notare che CoreApplication.Run prevede un'interfaccia IFrameworkViewSource. La classe App deve implementare quell'interfaccia.

Le due sezioni successive di questo argomento descrivono le interfacce IFrameworkViewSource e IFrameworkView. Queste interfacce (così come CoreApplication.Run) rappresentano un modo per l'app di fornire a Windows un provider di viste. Windows utilizza tale provider di viste per connettere l'app alla shell di Windows in modo da poter gestire gli eventi del ciclo di vita dell'applicazione.

L'interfaccia IFrameworkViewSource

La classe App implementa effettivamente IFrameworkViewSource, come è possibile vedere nell'elenco seguente.

struct App : winrt::implements<App, IFrameworkViewSource, IFrameworkView>
{
    ...
    IFrameworkView CreateView()
    {
        return *this;
    }
    ...
}

Un oggetto che implementa IFrameworkViewSource è un oggetto factory del provider di viste. Il lavoro dell'oggetto consiste nel produrre e restituire un oggetto provider di viste.

IFrameworkViewSource dispone del singolo metodo IFrameworkViewSource::CreateView. Windows chiama tale funzione sull'oggetto che viene trasferito a CoreApplication.Run. Come è possibile notare sopra, l'implementazione App::CreateView di tale metodo restituisce *this. In altre parole, l'oggetto App restituisce se stesso. Poiché IFrameworkViewSource::CreateView ha un tipo di valore restituito IFrameworkView, è necessario che anche la classe App implementi tale interfaccia. Ed è possibile vedere che lo fa nell'elenco precedente.

L'interfaccia IFrameworkView

Un oggetto che implementa IFrameworkViewSource è un oggetto provider di viste. E abbiamo ora fornito Windows con quel provider di viste. È lo stesso oggetto App che abbiamo creato in wWinMain. La classe App funge quindi sia da factory del provider di viste che da provider di viste.

Ora Windows può chiamare le implementazioni della classe App dei metodi di IFrameworkView. Nelle implementazioni di quei metodi, l'app ha la possibilità di eseguire attività quali l'inizializzazione, per iniziare a caricare le risorse necessarie, per connettere i gestori di eventi appropriati e ricevere la CoreWindow che verrà utilizzata dall'app per visualizzarne l'output.

Le implementazioni dei metodi di IFrameworkView vengono chiamate nell'ordine illustrato di seguito.

Ed ecco lo scheletro della classe App (in App.cpp), che mostra le firme di tali metodi.

struct App : winrt::implements<App, IFrameworkViewSource, IFrameworkView>
{
    ...
    void Initialize(Windows::ApplicationModel::Core::CoreApplicationView const& applicationView) { ... }
    void SetWindow(Windows::UI::Core::CoreWindow const& window) { ... }
    void Load(winrt::hstring const& entryPoint) { ... }
    void OnActivated(
        Windows::ApplicationModel::Core::CoreApplicationView const& applicationView,
        Windows::ApplicationModel::Activation::IActivatedEventArgs const& args) { ... }
    void Run() { ... }
    void Uninitialize() { ... }
    ...
}

Questa è stata solo un'introduzione a IFrameworkView. Esaminiamo in dettaglio questi metodi e come implementarli, in Definire il framework dell'app UWP di un gioco.

Riordinare il progetto

Il progetto Core App creato dal modello di progetto contiene funzionalità che dobbiamo a questo punto riordinare. In seguito, possiamo utilizzare il progetto per ricreare il gioco sparatutto (Simple3DGameDX). Apportare le seguenti modifiche alla classe App in App.cpp.

  • Eliminare i relativi membri di dati.
  • Eliminare OnPointerPressed, OnPointerMoved e AddVisual.
  • Eliminare il codice da SetWindow.

Il progetto verrà realizzato ed eseguito, ma verrà visualizzato solo un colore a tinta unita nell'area client.

Il ciclo di gioco

Per avere un'idea dell'aspetto di un ciclo di gioco, cercare nel codice sorgente il gioco di esempio Simple3DGameDX scaricato.

La classe App dispone di un membro dati, denominato m_main, di tipo GameMain. E quel membro viene utilizzato in App::Run in questo modo.

void Run()
{
    m_main->Run();
}

È possibile trovare GameMain::Run in GameMain.cpp. È il ciclo principale del gioco, ed ecco un suo profilo molto approssimativo che mostra le caratteristiche più importanti.

void GameMain::Run()
{
    while (!m_windowClosed)
    {
        if (m_visible)
        {
            CoreWindow::GetForCurrentThread().Dispatcher().ProcessEvents(CoreProcessEventsOption::ProcessAllIfPresent);
            Update();
            m_renderer->Render();
            m_deviceResources->Present();
        }
        else
        {
            CoreWindow::GetForCurrentThread().Dispatcher().ProcessEvents(CoreProcessEventsOption::ProcessOneAndAllPending);
        }
    }
}

Ed ecco una breve descrizione di ciò che fa questo ciclo di gioco principale.

Se la finestra per il gioco non è chiusa, invia tutti gli eventi, aggiorna il timer e quindi esegue il rendering e presenta i risultati della pipeline grafica. C'è molto di più da dire su tali problematiche e lo faremo negli argomenti Definire il framework dell'app UWP di un gioco, Framework di rendering I: Introduzione al rendering e Framework di rendering II: Rendering del gioco. Ma questa è la struttura del codice di base di un gioco UWP DirectX.

Rivedere e aggiornare il file package.appxmanifest

Il file Package.appxmanifest contiene i metadati relativi a un progetto UWP. Questi metadati vengono utilizzati per la creazione di pacchetti e l'avvio del gioco, nonché per l'invio a Microsoft Store. Il file contiene anche informazioni importanti utilizzate dal sistema del giocatore per fornire l'accesso alle risorse di sistema necessarie per l'esecuzione del gioco.

Avviare il Manifest Designer facendo doppio clic sul file Package.appxmanifest in Solution Explorer.

screenshot of the package.appx manifest editor.

Per maggiori informazioni sul file package.appxmanifest e sulla creazione di pacchetti, vedere Manifest Designer. Per il momento, esaminare la scheda Capabilities e osservare le opzioni fornite.

screenshot with the default capabilities of a direct3d app.

Se non di selezionano le funzionalità utilizzate dal gioco, quali ad esempio l'accesso a Internet per la scheda del punteggio più alto globale, non sarà possibile accedere alle risorse e alle funzionalità corrispondenti. Quando si crea un nuovo gioco, accertarsi di selezionare le funzionalità necessarie per le API chiamate dal gioco.

Esaminiamo ora il resto dei file inclusi nel gioco di esempio Simple3DGameDX.

Rivedere altre librerie e file di codice sorgente importanti

Se si intende creare un tipo di modello di progetto di gioco per sé stessi, in modo da poterlo riutilizzare come punto di partenza per eventuali progetti futuri, si vorrà copiare GameMain.h e GameMain.cpp fuori dal progetto Simple3DGameDX scaricato e aggiungerli al nuovo progetto Core App. Studiare questi file, imparare cosa fanno e rimuovere tutto ciò che è specifico di Simple3DGameDX. Impostare inoltre come commenti qualsiasi elemento che dipende dal codice non ancora copiato. Solo a titolo di esempio, GameMain.h dipende da GameRenderer.h. Sarà possibile rimuovere i commenti man mano che si copiano altri file da Simple3DGameDX.

Ecco un breve studio di alcuni dei file presenti in Simple3DGameDX che risulterà utile includere in un modello, nel caso se ne stia creando uno. In ogni caso, sono ugualmente importanti per comprendere il funzionamento dello stesso Simple3DGameDX.

File di origine Cartella del file Descrizione
DeviceResources.h/.cpp Utilities Definisce la classe DeviceResources, che controlla tutte le risorse del dispositivo DirectX. Definisce anche l'interfaccia IDeviceNotify, utilizzata per notificare all'applicazione che il dispositivo della scheda grafica è stato perso o ricreato.
DirectXSample.h Utilities Implementa funzioni helper quali ConvertDipsToPixels. ConvertDipsToPixels converte una lunghezza in pixel indipendenti dal dispositivo (DIP) in una lunghezza in pixel fisici.
GameTimer.h/.cpp Utilities Definisce un timer ad alta risoluzione utile per i giochi o le app di rendering interattivo.
GameRenderer.h/.cpp Rendering Definisce la classe GameRenderer, che implementa una pipeline di rendering di base.
GameHud.h/.cpp Rendering Definisce una classe per eseguire il rendering di un heads up display (HUD) per il gioco, utilizzando Direct2D e DirectWrite.
VertexShader.hlsl e VertexShaderFlat.hlsl Shaders Contiene il codice HLSL (High Level Shader Language) per gli shader di vertici di base.
PixelShader.hlsl e PixelShaderFlat.hlsl Shaders Contiene il codice HLSL (High Level Shader Language) per gli shader di pixel di base.
ConstantBuffers.hlsli Shaders Contiene definizioni di struttura dei dati per buffer costanti e strutture shader utilizzate per trasferire matrici MVP (Model-View-Projection) e dati per vertice allo shader di vertici.
pch.h/.cpp N/A Contiene elementi comuni di C++/WinRT, Windows e DirectX.

Passaggi successivi

A questo punto, abbiamo illustrato come creare un nuovo progetto UWP per un gioco DirectX, abbiamo esaminato alcuni dei pezzi in esso contenuti e abbiamo iniziato a pensare a come trasformare il progetto in un tipo di modello riutilizzabile per giochi. Abbiamo anche esaminato alcuni dei pezzi importanti del gioco di esempio Simple3DGameDX.

La prossima sezione è Definire il framework dell'app UWP di un gioco. Esamineremo in modo più approfondito il funzionamento di Simple3DGameDX.