Ciao a tutti! Oggi daremo una rapida occhiata dietro le quinte ad alcune delle tecnologie alla base del motore di Hytale con un focus specifico su uno dei framework che abbiamo recentemente annunciato: Flecs—un sistema entity component (ECS) leggero e potente.
Qualche tempo fa, abbiamo parlato della nostra decisione di riavviare il motore di Hytale, passando da un server Java e un client C# alla costruzione di entrambi in C++. C'erano molte ragioni per apportare questa modifica: volevamo assicurarci di poter rilasciare il gioco su più piattaforme; volevamo migliorare le nostre prestazioni quando ci rivolgevamo a dispositivi con specifiche inferiori; e volevamo costruire un motore principale abbastanza robusto da poter patchare e mantenere il gioco in futuro.
ECS è uno dei tanti strumenti su cui facciamo affidamento per aiutarci a raggiungere questi obiettivi. In questo post, discuteremo perché abbiamo scelto Flecs come base ECS del nuovo motore di Hytale e come, esattamente, ci aiuta a raggiungerli.
Ma prima di approfondire Flecs stesso, dobbiamo dare un'occhiata a ECS—il modello di sistema entity component.
DA UN ACRONIMO DI TRE LETTERE A UN ALTRO
ECS non è un concetto particolarmente nuovo, né è così raro nello sviluppo di giochi come lo era solo pochi anni fa. Anche così, può ancora essere complesso e sconosciuto quando viene incontrato per la prima volta. Per parlarne correttamente, dobbiamo contestualizzare il concetto di ECS nell'ambito dello sviluppo di giochi.
Gran parte dello sviluppo di giochi tradizionale si basa sul secolare modello di programmazione orientata agli oggetti (OOP) o sulle architetture entity-component (o actor-component!). La programmazione orientata agli oggetti è prevalente in tutto lo sviluppo software in generale e suddivide i problemi in strutture familiari che possono essere ragionate come oggetti. Potresti avere un tipo di oggetto Character generale che fornisce una logica di gioco comune a tutti i Character, che viene poi ereditato o specializzato da un oggetto Player, vari oggetti NPC e qualsiasi altro tipo che potrebbe essere considerato Character.
Questo albero può diventare molto ampio.
Entity-component ci porta un passo più vicino a ECS ed è l'architettura principale utilizzata da molti motori di gioco popolari come Unreal e Unity. In questo paradigma, ora abbiamo entità—unità individuali come un ‘player’, un ‘NPC’, una ‘sedia’—e componenti—una combinazione di dati e funzionalità che possono essere collegati a queste entità. Ogni entità è composta da un numero di componenti. Se hai familiarità con OOP, il paradigma entity-component si appoggia pesantemente al principio di ‘composizione sull'ereditarietà’. Ad esempio: un player potrebbe avere una posizione nel mondo, la capacità di leggere gli input del controller e un inventario; un NPC ha anche una posizione, potrebbe avere un inventario e ha una qualche forma di logica comportamentale; la nostra sedia, sfortunatamente, ha solo una posizione.
Fino a quando non aggiungi la logica comportamentale e diventa una creatura [disclaimer: questo è a scopo dimostrativo!]
Immediatamente, dovrebbe diventare evidente quanto liberatoria possa essere una tale struttura per il modding. Non solo facilita funzionalità altamente data-driven attraverso la configurazione degli asset, dove semplicemente cambiare i componenti collegati a un NPC o a un oggetto si traduce in un comportamento notevolmente diverso, ma teoricamente consente la creazione di funzionalità completamente nuove senza la necessità di armeggiare con il codice esistente. Con un linguaggio di scripting potresti creare e aggiungere il tuo componente e allegarlo alle entità che vuoi eseguire quel comportamento. Si aprono una varietà di possibilità.
Un'entità può essere composta in molti modi diversi!
Ma questo non è ancora ECS. ECS—entity component system—prende questi concetti e li fa progredire ulteriormente. Mentre nel modello entity-component la funzionalità (ad es. metodi e funzioni) risiede all'interno del componente stesso, ECS disaccoppia questa funzionalità dai dati e dallo stato che elabora. Invece di avere ogni componente con la propria logica di aggiornamento interna, abbiamo sistemi che abbinano entità con set definiti di componenti e agiscono su di essi. Ciò significa che con ECS sblocchiamo ancora la stessa capacità di comporre entità da componenti diversi, ma il disaccoppiamento si traduce in un'architettura di dati e logica che è significativamente più efficiente per l'hardware da eseguire e quindi più performante. Molti dei dettagli su come ECS raggiunge questi vantaggi in termini di prestazioni sono altamente tecnici, ma è sufficiente dire che comporta lo sfruttamento dell'architettura della CPU, la strutturazione dei dati in modo compatto per beneficiare della sua località nei modelli di accesso e l'utilizzo di tali modelli di accesso per parallelizzare quanta più logica possibile.
I sistemi possono abbinare qualsiasi combinazione di componenti.
ECS NEL MOTORE LEGACY
Sapevamo che volevamo passare all'utilizzo di un'architettura ECS anche quando stavamo ancora sviluppando il motore legacy, a causa dell'aumento delle prestazioni e della scalabilità che ci avrebbe dato, insieme al suo allineamento naturale con il nostro approccio data-driven alla costruzione e alla configurazione di entità e attori di gioco. Di conseguenza, abbiamo sviluppato la nostra implementazione Java del concetto e abbiamo iniziato a integrarlo in tutto il server legacy. All'epoca, non avevamo equivalenti per il client C#, il che significava che la nostra implementazione era strettamente solo server.
Parte di questo lavoro ha comportato la rifattorizzazione di aspetti della logica esistente per seguire il modello ECS, anche quando abbiamo iniziato a sviluppare nuove funzionalità insieme ad esso. Abbiamo imparato molte lezioni durante quel periodo, soprattutto tra queste che l'implementazione di un framework ECS robusto e performante da zero è uno sforzo incredibilmente impegnativo e dispendioso in termini di tempo. Esistono innumerevoli gusti diversi di ECS, ognuno con i propri vantaggi e svantaggi, ma tutti richiedono una profonda comprensione e specializzazione tecnica per essere eseguiti a un livello elevato. Java non è sempre il linguaggio di programmazione più performante e abbiamo fatto molte concessioni e scelte progettuali a causa delle sue stranezze uniche.
Anche allora, la nostra nascente implementazione ECS ha fornito notevoli vantaggi in termini di prestazioni, insieme a un nuovo approccio all'architettura di sistema che incarnava i principi di progettazione data-driven che volevamo raggiungere. Se fatto correttamente, potremmo semplificare per i modder la fornitura di dati che influenzerebbero il comportamento del gioco praticamente senza conoscenze tecniche.
UNA BASE AFFIDABILE PER UN NUOVO MOTORE
Quando abbiamo riavviato il motore, sapevamo che volevamo continuare a utilizzare ECS, ma anche che volevamo estenderlo anche al client, assicurandoci di raccogliere i benefici in ogni aspetto fattibile. Sapevamo anche che il nostro passaggio a C++ avrebbe significato che potrebbero esserci altri framework là fuori—quelli che non dovremmo costruire e mantenere da soli, con funzionalità all'avanguardia che spingerebbero i confini del paradigma.
Dopo aver valutato tutte le opzioni disponibili, ci siamo stabiliti su Flecs—un framework ECS altamente raffinato scritto e mantenuto da un esperto ECS: Sander Mertens.
Out of the box, ci dà accesso a una varietà di funzionalità comuni alla maggior parte delle implementazioni ECS, insieme a prestazioni eccellenti e compatibilità multi-piattaforma. Essendo scritto interamente in C con un'API C++, è significativamente più veloce di qualsiasi implementazione in C# o Java potrebbe sognare di essere, permettendoci di sfruttare appieno la sua implementazione intelligente di parallelizzazione e multi-threading. Un altro vantaggio ovvio è che non abbiamo bisogno di mantenerlo noi stessi—Flecs è testato sul campo, riceve aggiornamenti e correzioni di bug frequenti e, con la sua suite completa di test, possiamo essere relativamente certi della sua stabilità.
Ma forse l'aspetto più allettante era l'ampio set di funzionalità che si estendono oltre ciò che un tradizionale framework ECS offre e forniscono una flessibilità che porta ECS al livello successivo. Un esempio di questo è il concetto di ‘relazioni’. Come i componenti, le relazioni sono dati che puoi collegare a un'entità, ma questi dati vengono utilizzati per connettere un'entità a un'altra. Una relazione genitore-figlio è un buon esempio di questo, dove un'entità player potrebbe avere un'entità telecamera come figlio che la segue. Un altro esempio potrebbe essere ancora più letterale, dove l'entità A ama l'entità B. Usando questa struttura, possiamo facilmente eseguire query come ‘trovami tutte le entità che amano l'entità B’ o ‘trovami tutte le entità che l'entità B ama’.
In molti modi, un ECS è simile a un database e Flecs sfrutta appieno questo fatto. Il motore di regole sottostante è un potente strumento che supporta l'interrogazione dei dati in vari modi, che vanno dalla semplice corrispondenza per i sistemi (ad es. un sistema che aggiorna la crescita delle colture in base a un numero di componenti collegati) a complesse ricerche per la logica di gioco o il debug (ad es. trovami tutti gli NPC che portano spade che sono aggressivi nei confronti del player). Oltre a questo, il meccanismo di condivisione dei componenti ci consente di creare un tipo di attore di base come un Character e dire che un NPC o un Player è un Character, dandoci accesso all'ereditarietà in stile OOP ma costruito per beneficiare delle ottimizzazioni ECS.
A volte, alcune di queste funzionalità possono essere difficili da ragionare, come spesso accade quando si apprende un'architettura completamente nuova. Anche così, una volta compresi e padroneggiati, forniscono strumenti di sviluppo di giochi estremamente potenti.
PENSIERO PIÙ VELOCE
Per concludere, esamineremo un tale esempio. In passato abbiamo introdotto un miglioramento agli NPC di Hytale chiamato Combat Action Evaluator. Questo è un framework progettato per consentire agli NPC di prendere decisioni più intelligenti e ‘fuzzy’ su quale attacco usare e su quale bersaglio usarlo, in base a una serie di input altamente configurabili. Sebbene originariamente implementato nel motore legacy, è stato progettato per essere data-driven fin dalla concezione, con ogni singolo input collegato in un modo simile ai componenti in un ECS.
Sebbene abbia svolto il suo scopo in modo ammirevole nel motore legacy e abbia fornito NPC da combattimento che a volte potevano essere scambiati per player umani, sono state imposte limitazioni a causa del suo potenziale impatto sulle prestazioni generali del gioco. Dopotutto, consentire agli NPC di prendere decisioni ‘fuzzy’ basate su un'enorme quantità di dati di input in un ambiente OOP significa un significativo onere di elaborazione—uno che il nostro motore pre-ECS basato su Java non era attrezzato per gestire. Pertanto, eseguiremmo l'evaluator di azioni di combattimento solo a intervalli irregolari. Ciò potrebbe significare che eviteremmo qualsiasi potenziale rallentamento del server da un gran numero di NPC in combattimento attivo, ma significava anche che avrebbero preso decisioni più lente in generale—abbastanza lente da essere percepibili per il player.
Con Flecs, le nostre opzioni di progettazione del gameplay non sono quasi altrettanto limitate dai problemi di prestazioni. Riprogettando il framework interno seguendo i modelli ECS e facendo un uso abbondante delle funzionalità di Flecs, finiamo con un equivalente che non ha più bisogno di elaborare questi dati in modo così inefficiente. Possiamo invece raggruppare in modo intelligente tutte le query che controllano informazioni specifiche e parallelizzarle, ottenendo tempi di elaborazione molto più rapidi e rimuovendo qualsiasi necessità di limitazioni artificiali alla frequenza di valutazione. Mentre nel motore legacy avremmo eseguito sequenzialmente i nostri controlli (dando la priorità a quelli costosi prima in modo da poter uscire prima se fallivano!), questi possono ora accadere tutti contemporaneamente, con il motore di query di Flecs che si occupa del lavoro pesante.
In sostanza, questo significa che gli NPC possono pensare più velocemente—reagendo ai cambiamenti nell'ambiente e nei loro dintorni in modo molto più reattivo di quanto avrebbero mai potuto nel motore legacy.
VERSO IL FUTURO
In definitiva, questo graffia solo la superficie di ciò che è possibile con Flecs e ECS. Molte altre parti del motore offrono interessanti opportunità per ottimizzare intorno a ECS, dal database degli asset alla generazione di mondi a stadi, e man mano che sia Flecs che il motore di Hytale continuano a evolversi, ci aspettiamo solo che le possibilità crescano.
Fonte: Hytale.com (Traduzione AI)