Ottobre 2017

Volume 32 Numero 10

Il presente articolo è stato tradotto automaticamente.

Sviluppo di giochi - Fisica di rete multiplayer per lo sviluppo di giochi Web

Da Gary Weiss | Ottobre 2017

Giochi Web hanno shoddy reputazione. Giochi ben scritti sono disponibili nella console di gioco avanzate e per i computer con processori moderni grafici. Giochi per dispositivi mobili sono disponibili in modo poiché il canonico "serpente." Ma giochi sul Web, ad esempio Rodney Dangerfield, non viene rispettata. Che di recenti progressi browser moderni possono portare qualità giochi nel browser. La tecnologia più importante a questo proposito è WebGL, che consente l'accesso diretto alla grafica con accelerazione hardware.

In questo articolo verrà esaminare giochi Web da un punto di vista specifico: implementazione di un semplice gioco multiplayer di fisica in rete. Questo esercizio esamina delle tecnologie e strumenti di sviluppo di giochi sono disponibili per l'attività e i browser moderni test di stress per rete, l'efficienza di calcolo e prestazioni per il rendering. In questo articolo può essere considerato come un'indagine di piccole dimensioni che fanno parte di un quantità maggiore tema: Uno studio di gioco professional effettivamente scrivere un gioco di fascia alta in un browser?

Il codice sorgente per il gioco è disponibile gratuitamente nel bit.ly/2toc4h4, e il gioco è possibile riprodurre in linea sprocketleague.lance.gg.

Il gioco

Il gioco che sarà modello include intercettazione negli stadi automobili. È un cerchio in stadio che può essere al mittente negli obiettivi. Questo concetto è ispirato "Rocket lega", un titolo e-sportivi più diffusi da Psyonix. Pertanto, di assemblaggio del gioco dagli oggetti seguenti: un ambito (inclusi gli obiettivi), automobili e una palla, come si può vedere figura 1. Il lettore può spostare il veicolo avanti o indietro e attivare ruote.

Automobili riproduzione calcio nel Browser

Figura 1 automobili riproduzione calcio nel Browser

L'architettura del gioco

L'implementazione del gioco utilizza il modello di server autorevole, in base al quale ogni client inoltra gli input dell'utente a un server centrale. I client non attendono il rete prima di applicare gli input localmente; piuttosto, i client eseguirà il è comunemente noto come stima sul lato client. Questa tecnica consente al client di continuare a eseguire la logica del gioco e fisica di gioco. Catch, naturalmente, è che quando gli aggiornamenti per le posizioni e velocità vengono trasmessi dal server autorevole, ogni client deve effettuare le correzioni dei propri incrementale in modo che il gioco mantiene un'esperienza visiva smooth.

Il rendering l'obiettivo principale nel rendering accede GPU. Sono disponibili diversi strumenti che è possono compilare questo slot semplificando l'interfaccia WebGL. Babylon.js e Three.js sono stati utilizzati per molti giochi Web avanzati. Per il gioco è utilizzato a-frame, una libreria relativamente nuova che consente di compilare una scena 3D utilizzando gli elementi HTML. A-frame definisce nuovi elementi HTML che è possibile utilizzare come blocchi predefiniti. Inoltre, a-frame in modo efficace di modellazione degli oggetti utilizzando un modello di progettazione entità componente del sistema. Un altro vantaggio interessano a-frame è che è stato progettato per virtual realtà, in modo è possibile effettuare facilmente il gioco in un'esperienza VR.

Fisica Cannon.js è un motore fisica relativamente facile da usare. Fornisce meccanismi rigida corpo con il rilevamento dei conflitti. È stata scritta in JavaScript da zero, ovvero che la libreria è nettamente inferiore motori transpiled, ad esempio ammo.js.

Oggetto di serializzazione e la rete per il server trasmettere le posizioni di oggetto e velocità, giochi oggetti devono essere serializzati. A questo scopo usare Lance server dei giochi (Lance.gg), un motore multiplayer Apri origine. Giochi oggetti che derivano la classe di base Lance GameObject verranno serializzati e inoltrati al client. Motori di giochi avanzati utilizzano socket UDP per migliorare le prestazioni, mentre Lance utilizza ancora il socket TCP. Questo è data la complessità del WebRTC per la comunicazione tra client e server (a differenza dei peer-to-peer). Si tratta di un'area in cui giochi Web viene ritardato in modo significativo altre piattaforme. Tuttavia, che il gioco risulta offre un'esperienza uniforme anche a ritardi di rete di 150 ms, come avviene in genere all'interno degli Stati Uniti continentali o Europa.

Sul lato client stima troppo, in questo caso, è basato sul Lance per eseguire l'estrapolazione di posizioni e velocità. Lance implementa insieme sul lato client delle trasmissioni di server, calcola le correzioni necessarie per ogni posizione e l'orientamento e applica la correzione in modo incrementale lo stato di avanzamento del ciclo per il rendering. Correzioni di posizione vengono applicate il calcolo della differenza di vettore e dividendo la differenza in incrementi. Correzioni di orientamento, al contrario, vengono applicate per il calcolo della differenza del quaternione. Quaternione correzione per un quaternione server qs e un quaternione client qc è dato dalla qΔ qs o qc -1 =. Il quaternione corrente può essere rappresentato come una rotazione intorno a un singolo asse e quindi divise in incrementi sull'asse.

L'architettura di gioco, quindi, include logica del server, la logica di client e logica condivisa, come illustrato nel figura 2. La logica client deve gestire tutti gli aspetti che coinvolgono l'utente, inclusi la raccolta di input e il lavoro per il rendering. La logica al server è più semplice di confronto, ma necessario eseguire alcuni passaggi autorevole, come ad accettare connessioni, la creazione di oggetti di giochi e si decide che con punteggio.

L'architettura di un gioco Multiplayer

Figura 2, l'architettura di un gioco Multiplayer

La logica condivisa è la parte più interessante dell'implementazione e viene illustrata in una sezione distinta. Include la logica di gioco principale, che deve essere eseguita nel server autorevole, ma deve anche eseguire in ogni client durante l'esecuzione del giochi tra le trasmissioni di server. Iniziamo con il dettaglio dei giochi multiplayer: il livello di comunicazione.

Comunicazione tra client e Server

Un gioco multiplayer con un server autorevole richiede il codice di comunicazione. Questo codice ha molte responsabilità.  Inizialmente, è necessario gestire il porta del server in ascolto sulle porte TCP/IP per i dati in ingresso. Successivamente, è un processo di handshake quando un client si connette al server. Operazione di handshake registra il nuovo client nel server e stabilisce un socket dedicato per il client. Alcuni giochi potrebbero richiedere un'autenticazione o un processo di individuazione agente, nonché. La connessione di un nuovo client e la disconnessione di un client esistente, è possibile avere implicazioni per il gioco. Il gioco deve ricevere una notifica in modo è possibile creare una nuova auto per il nuovo client e assegnare il lettore a un team.

Mentre è in esecuzione il gioco, il server invia le trasmissioni periodiche a tutti i client. Le trasmissioni descrivono lo stato corrente del gioco. Uno stato include la posizione, velocità, orientamento (quaternione) e velocità angolare di ciascun oggetto gioco. Inoltre, i giochi oggetti potrebbero contenere anche altri attributi non fisiche, ad esempio livello di attendibilità, power dello scudo, timeout ortografico e così via. In questo caso, il server deve essere molto efficiente e Invia come quantità ridotta di dati è assolutamente necessario. Ad esempio, un oggetto che non è stato spostato o modificato non deve essere trasmesso. D'altra parte, se un nuovo lettore è appena connessi, tale lettore necessita di una descrizione completa di tutti gli oggetti di giochi per l'avvio automatico dello stato del gioco.

Infine, la comunicazione client-server sarà necessario creare e acquisire gli eventi fuori banda, che descrivono gli eventi di giochi che non rientrano nell'ambito dello stato di avanzamento di gioco normale. Ad esempio, il server potrebbe voler comunicare a un lettore specifico che ha concluso una realizzazione speciali. Questo meccanismo può essere utilizzato anche per segnalare un messaggio a tutti i lettori.

Di seguito è riportata l'implementazione. Sul lato server, visualizzata un server node.js express e un servizio socket.io. Il codice illustrato in figura 3 configura il server HTTP sulla porta 3000, inizializza il motore server e le classi del motore di giochi e indirizza tutte le richieste HTTP per la sottodirectory dist in cui vengono memorizzate tutti i HTML e risorse. Ciò significa che il server node.js ha due ruoli. Il primo ruolo, ma funge da server HTML che ha contenuto, inclusi i file CSS, immagini, audio e i modelli 3D in formato GLTF HTML. Il secondo ruolo, il server HTTP funge da server gioco che accetta le connessioni in ingresso in socket.io. Si noti che è consigliabile memorizzare nella cache tutti gli asset statici dietro una rete di consegna di contenuti (CDN) per prestazioni ottimali. Questa configurazione non è illustrata nell'esempio di figura 3, ma l'utilizzo di una rete CDN aumenta notevolmente le prestazioni del backup portare gioco, riducendo il carico non necessario dal server di gioco. Infine, all'ultima riga del codice, viene avviato il gioco.

Figura 3 punto di ingresso del Server

const express = require('express'); 
const socketIO = require('socket.io'); 
 
// Constants 
const PORT = process.env.PORT || 3000; 
const INDEX = path.join(__dirname, './dist/index.html'); 
 
 
// Network servers 
const server = express(); 
const requestHandler = server.listen(PORT, () => console.log(`Listening on ${PORT}`)); 
const io = socketIO(requestHandler); 
 
 
// Get game classes 
const SLServerEngine = require('./src/server/SLServerEngine.js'); 
const SLGameEngine = require('./src/common/SLGameEngine.js'); 
 
 
// Create instances 
const gameEngine = new SLGameEngine({ traceLevel: 0 }); 
const serverEngine = 
  new SLServerEngine(io, gameEngine, { debug: {}, updateRate: 6, timeoutInterval: 20 }); 
 
 
// HTTP routes 
server.get('/', (req, res) => { res.sendFile(INDEX); }); 
server.use('/', express.static(path.join(__dirname, './dist/'))); 
 
// Start the game 
serverEngine.start();

Il server è stato avviato, ma non è stato ancora gestire le nuove connessioni. La classe di base ServerEngine dichiara un metodo del gestore per le nuove connessioni chiamato onPlayerConnected e un gestore onPlayerDisconnected corrispondente. Questo è il luogo in cui i metodi di sottoclasse di un server autorevole possono indicare al motore di gioco per creare una nuova auto o rimuovere un'automobile esistente. Frammento di codice in figura 4 dalla classe di base viene illustrato come utilizzare socket.io per garantire a tali gestori vengono chiamati quando è connesso un nuovo lettore.

Implementazione del gestore di connessione nella figura 4 con socket.io

class ServerEngine {

  // The server engine constructor registers a listener on new connections
  constructor(io, gameEngine, options) {
      this.connectedPlayers = {};
      io.on('connection', this.onPlayerConnected.bind(this));
  }

  // Handle new player connection
  onPlayerConnected(socket) {
    let that = this;

    // Save player socket and state
    this.connectedPlayers[socket.id] = {
      socket: socket,
      state: 'new'
    };
    let playerId = socket.playerId = ++this.gameEngine.world.playerCount;
 
    // Save player join time, and emit a `playerJoined` event
    socket.joinTime = (new Date()).getTime();
    this.resetIdleTimeout(socket);
    let playerEvent = { id: socket.id, playerId, 
      joinTime: socket.joinTime, disconnectTime: 0 };
    this.gameEngine.emit('playerJoined', playerEvent);
    socket.emit('playerJoined', playerEvent);

    // Ensure a handler is called when the player disconnects
    socket.on('disconnect', function() {
      playerEvent.disconnectTime = (new Date()).getTime();
      that.onPlayerDisconnected(socket.id, playerId);
      that.gameEngine.emit('playerDisconnected', playerEvent);
    });


  }

  // Every server step starts here
  step() {

    // Run the game engine step
    this.gameEngine.step();

    // Broadcast game state to all players
    if (this.gameEngine.world.stepCount % this.options.updateRate === 0) {
      for (let socketId of Object.keys(this.connectedPlayers)) {
        this.connectedPlayers[socketId].socket.emit(
          'worldUpdate', this.serializeUpdate());
      }
    }
  }
}

Quando si connette un lettore, questo codice genera l'evento playerJoined due volte. Il primo evento viene generato nel controller di eventi del motore di giochi, in altre parti del gioco codice può essere stato registrato listener per questo evento specifico. Il secondo evento verrà inviato tramite socket del lettore. In questo caso, è possibile acquisire l'evento sul lato client il socket e opera come una conferma che il server è consentito il lettore creare un join del gioco.

Si noti il metodo di passaggio del motore del server, in questo caso, lo stato del gioco è broadcast per tutti i lettori. La trasmissione si verifica solo a intervalli fissi. Il gioco si è scelto di configurare la pianificazione in modo che 60 operazioni vengono eseguite ogni secondo e una trasmissione viene inviata ogni sesto passaggio del processo o 10 volte al secondo.

Ora è possibile implementare i metodi nella sottoclasse, perché sono validi in modo specifico per il gioco. Come illustrato nella figura 5, questo codice gestisce le nuove connessioni creando una nuova auto e l'aggiunta della macchina per il team blu o il team di colore rosso. Quando si disconnette un lettore, si rimuove car che del lettore.

Gestione della connessione nella figura 5 Server

// Game-specific logic for player connections
onPlayerConnected(socket) {
  super.onPlayerConnected(socket);

  let makePlayerCar = (team) => {
    this.gameEngine.makeCar(socket.playerId, team);
  };

  // Handle client restart requests
  socket.on('requestRestart', makePlayerCar);
  socket.on('keepAlive', () => { this.resetIdleTimeout(socket); });
}

// Game-specific logic for player dis-connections
onPlayerDisconnected(socketId, playerId) {
  super.onPlayerDisconnected(socketId, playerId);
  this.gameEngine.removeCar(playerId);
}

Comunicazione può verificarsi anche fuori banda, che significa che un evento deve a volte essere inviato a uno o più client, in modo asincrono o i dati devono essere inviati che non fa parte i gioco stati di oggetti. L'esempio seguente viene illustrato come utilizzare socket.io sul lato server: 

// ServerEngine: send the event monsterAttack! with data 
// monsterData = {...} to all players
this.io.sockets.emit('monsterAttack!', monsterData);

// ServerEngine: send events to specific connected players
for (let socketId of Object.keys(this.connectedPlayers)) {
  let player = this.connectedPlayers[socketId];
  let playerId = player.socket.playerId;

  let message = `hello player ${playerId}`;
  player.socket.emit('secret', message);
}

Sul lato client, l'attesa di eventi è semplice:

this.socket.on('monsterAttack!', (e) => {
  console.log(`run for your lives! ${e}`);
});

La logica del gioco

La logica del gioco fondamentale prevede l'applicazione di input utente allo stato di gioco, come illustrato nella figura 6. Ad esempio, premendo per gas, applicare una forza indirizzate forward su un'automobile. Premendo freni deve applicare una forza con le versioni precedenti indirizzate un'automobile, sarà potenzialmente in ordine inverso. Attivare la rotellina governo si applica una velocità angolare e così via.

Figura 6 il passaggio GameEngine (metodo)

// A single Game Engine Step
step() {
  super.step();

  // Car physics
  this.world.forEachObject((id, o) => {
    if (o.class === Car) {
      o.adjustCarMovement();
    }
  });

  // Check if we have a goal
  if (this.ball && this.arena) {

    // Check for ball in Goal 1
    if (this.arena.isObjInGoal1(this.ball)) {
      this.ball.showExplosion();
      this.resetBall();
      this.metaData.teams.red.score++;
    }

    // Check for ball in Goal 2
    if (this.arena.isObjInGoal2(this.ball)) {
      this.ball.showExplosion();
      this.resetBall();
      this.metaData.teams.blue.score++;
    }
  }
}

Queste operazioni vengono implementate come chiamate al motore di fisica con l'avanzamento del tempo. Un torsione interessante è che l'applicazione corretta forza fisico per una macchina offre un'esperienza di gioco insufficienti. Se si applicano le forze fisiche corrette come funzionano nel mondo reale, il controllo e la gestione dell'automobile non "difficoltà" in mani del lettore. L'azione di gioco risulta è troppo lento. Per il gioco sia semplice, che necessaria per applicare un incremento artificiale quando il veicolo accelera dalla velocità molto bassa, in caso contrario l'azione di gioco è troppo lenta.

Infine, la logica del gioco deve verificare se la palla passato i post obiettivo e aggiornare il punteggio.

Stima sul lato client

Ogni gioco multiplayer ha requisiti diversi per le stime sul lato client e deve essere configurato in modo analogo. Tipi di oggetto gioco possono anche avere una configurazione specifica. Il codice che segue mostra un piccolo subset di configurazione del gioco. Gli input dell'utente vengono posticipati da tre passaggi o 50 ms, in modo che corrisponda meglio l'ora in cui gli input verranno applicati nel server. Questa operazione viene eseguita impostando delayInputCount su 3. syncOptions.sync è impostata su "estrapolare" per abilitare la stima del client; localObjBending è impostato su 0,6, che indica che le posizioni di oggetto controllato in locale devono essere corretta del 60 percento prima della trasmissione server successiva viene stimata arrivo; remoteObjBending è impostato più elevato all'80%, poiché tali oggetti sono più probabile che si differenziano notevolmente. Infine, si imposta bendingIncrements su 6, che indica che ogni correzione è necessario non applicare in una sola volta, ma piuttosto oltre 6 con incrementi di loop di rendering:

const options = {
  delayInputCount: 3,
  syncOptions: {
    sync: 'extrapolate',
    localObjBending: 0.6,
    remoteObjBending: 0.8,
    bendingIncrements: 6
  }
};

La palla ha il proprio parametro piegatura velocità (non illustrato) impostato su zero. Infatti, quando i punteggi di un team, la palla Ottiene teleported al centro del campo e qualsiasi tentativo di piegatura la velocità risulta provocherà effetti indesiderati.

La logica di stima sul lato client stesso è troppo grande da includere in questo caso, in modo che presentato come generale pseudocodice in figura 7. È il segreto, che rende possibile un gioco multiplayer. Il primo passaggio analizza l'ultima trasmissione server e controlla se sono stati creati nuovi oggetti. Gli oggetti esistenti memorizzano lo stato corrente prima di essi è sincronizzati con lo stato del server. Il secondo passaggio è la fase reenactment, che ripete la logica di gioco per tutti i passaggi che si sono verificati dopo la procedura descritta nella trasmissione. È necessario riapplicare gli input e viene eseguito il passaggio di motore di gioco in modalità reenactment. Nel terzo passaggio, ogni oggetto registra le correzioni necessarie e viene ripristinato lo stato memorizzato. Infine, nell'ultimo passaggio, è possono rimuovere gli oggetti contrassegnati per la distruzione.

Figura 7 semplificato pseudocodice di stima sul lato Client

applySync(lastSync):

  // Step 1: sync to all server objects in the broadcast
  for each obj in lastSync.objects
    localObj = getLocalObj(obj.id)
    if localObj
      localObj.rememberState()
      localObj.syncTo(obj)
    else
      addLocalObj(obj)

  // Step 2: re-enactment using latest state information
  for step = lastSync.serverStep; step < clientStep; step++
    processInputsForStep(step)
    gameEngine.step(reenactment=true)

  // Step 3: record bending and revert
  for each obj in world.obects
     obj.bendToCurrentState()
     obj.revertToRememberedState()

  // Step 4: remove destroyed objects
  for each obj in world.obects
    objSyncEvents = lastSync.getEvents(obj.id)
    for each event in objSyncEvents
      if event.name == ‘objectDestroy’
        gameEngine.removeObjectFromWorld(obj.id)

Importante sottolineare i dettagli

È molto più lavoro in questo gioco non viene visualizzato a prima vista. Non trattato videocamera automobile, le differenze nell'input tra i dispositivi mobili e desktop (nei dispositivi mobili spostamento Auto è controllato da inclinazione del dispositivo) e gli argomenti come la corrispondenza dei profili di rilevamento e di debug. Tutti questi argomenti non rientrano nell'ambito di questo articolo. Tuttavia, è importante ricordare che alcune di queste operazioni comportano salti road significativi in modalità di gioco Web. Ad esempio, è possibile eseguire un gioco Web su un desktop o su un tablet o un dispositivo mobile, a volte una GPU è disponibile e in alcuni casi non è, mentre la probabilità di bassa aumenta di larghezza di banda della rete.

Ancora un altro problema è il flusso di lavoro richiesto per la generazione di modelli 3D compatibili degli standard esistente. I set di strumenti di sviluppo di giochi per la piattaforma Web non sono come avanzata delle rispettive controparti in altre piattaforme. Ultimo, giochi sul Web non dispone di spazio di archiviazione locale allo stesso modo che forniscono altre piattaforme. Pertanto, è necessario caricare frugally asset. Un notevole realizzazione in quest'area è il gioco in linea "Galaxy urbano" (bit.ly/2uf3iWB).

Questi problemi presentano nuove sfide per sviluppatori di giochi sulla piattaforma Web. Tuttavia, esistono alcuni vantaggi per la piattaforma Web che devono essere indicate. L'ambiente di debug nel browser è molto avanzato. La natura dinamica della programmazione Web comporta la creazione di prototipi molto rapido e il recapito continuo di patch. Community di sviluppatori Web è molto grande.

Probabilmente il vantaggio più significativo per i giochi Web è il modo in cui l'avvio. Un gioco Web accessibili semplicemente facendo clic su un collegamento Web e non richiede una pre-download dell'intero set di risorse del gioco nel dispositivo client prima che venga avviato. Scaricando asset solo quando sono necessari, l'esperienza di gioco è possibile iniziare in pochi secondi, invece di minuti, e laddove possibile, supporto offline può anche essere sfruttato per migliorare gioco successive.

Accesso al sito NBA Web visitatori potrebbe ad esempio, gli uni un gioco basket contro giocatori di tutto il mondo, destro del mouse nel browser.

Tecnologie Web per lo sviluppo di giochi

Negli ultimi due anni, si è verificato lo stato di avanzamento significativo nella possibilità di browser. L'avanzamento primario è lo sviluppo di WebGL, che hanno accesso diretto alle funzionalità GPU nel browser. WebGL è un'interfaccia di livello molto basso, in modo da sviluppatori di giochi si basano su interfacce di alto livello da librerie come Babylon.js, three.js e a-frame per compilare scene complesse.

Altre tecnologie Web recenti includono WebAudio, che fornisce funzionalità avanzate di audio; Media origine Extensions (MSE), ottimizzare l'accesso ai video e consentono la manipolazione dei video in tempo reale; e WebVR, che è relativamente nuovo e fornisce un'interfaccia per l'hardware virtuale realtà incluso cuffie VR e controller VR. Utilizza WebVR insieme WebAudio, è possibile implementare ambisonic esperienze di audio 3D in una scena VR basate sul Web.

Anche il linguaggio JavaScript è stato aggiornato in modo utile con la versione più recente di ECMA-262 6a edizione. L'ecosistema di JavaScript è ampia, che fornisce molti Framework per sviluppatori. In realtà, esistono molte strutture e metodologie che il numero elevato determina talvolta confusione.

Nella parte anteriore di rete, con il protocollo HTTP, WebSockets e WebRTC sono due standard disponibili per le applicazioni. WebSocket sono flussi di dati bidirezionale semplice, mentre WebRTC fornisce un meccanismo più complesso per la comunicazione peer-to-peer. Per lo sviluppo di gioco multiplayer, Lance.gg è una libreria open source che gestisce la rete e la sincronizzazione degli oggetti di giochi.

Un'altra tecnologia interessano è WebAssembly, che ha raggiunto recente multibrowser consenso, e che sia in grado di offrire un vantaggio significativo delle prestazioni eseguendo compilato un formato binario in un browser.

È possibile trovare facilmente ulteriori informazioni su tutte queste tecnologie sul Web.

Conclusioni

Creazione di un gioco multiplayer per la piattaforma Web è diversa. Naturalmente è indirizzato Web un'ampia gamma di dispositivi e ciò costituisce una vera sfida di sviluppo di giochi. L'assenza di una semplice rete UDP nel browser ha un impatto tempi di risposta multiplayer. Tuttavia, recenti progressi nel Web tecnologie hanno reso possibile scrivere una demo multiplayer fisica in un tempo relativamente breve. Librerie e i flussi di lavoro usati in questo esercizio avanzamento rapido e può essere utilizzati già oggi. Con risorse aggiuntive e l'ora, è possibile sviluppare un gioco di qualità in un browser.

Desidero ringraziare i co-autore del gioco descritto nell'articolo, Opher Vishnia, cofondatore di Lance e un creatore multidisciplinare di sviluppo di giochi, linguaggio di programmazione, composizione di progettazione e musica.


Gary Weiss è un software architect e fondatore di Lance, il gruppo che sviluppa il progetto open source Lance.gg. È possibile contattarlo in garyweiss74@gmail.com

Grazie per il seguente esperto tecnico di Microsoft per la revisione dell'articolo:  Raanan Weber


Viene illustrato in questo articolo nel forum di MSDN Magazine