Quanto sono orientati agli oggetti i videogiochi? [chiuso]


18

Mi sono sempre chiesto in che misura l'orientamento agli oggetti viene utilizzato dai videogiochi, in particolare dai grandi progetti 3D. Mi sa che sarebbe bello che sia trolled werewolfereditato i suoi attributi da enemye sovrascritto alcuni di loro. Ma forse le lezioni sono troppo pesanti per essere pratiche, non lo so ...


2
Come si quantificano (o si qualificano oggettivamente) l'orientamento agli oggetti? Vuoi una risposta in Meyers o Dahl-Nygaards?

Ovviamente sono così pesanti che un linguaggio fortemente basato su OO è lo standard del settore.
Il comunista Duck il

4
Se intendi C ++, è stato scelto perché è un buon linguaggio per esplorare la programmazione generica sicura dei tipi in modo consapevole della velocità; le poche funzionalità OOP sono ovviamente uno sfortunato errore di progettazione conservato solo per compatibilità con le versioni precedenti. ;)

Risposte:


20

Per venire al tuo esempio, spiegherò i miei pensieri su Entites nei videogiochi. Non discuterò il vantaggio generale e lo svantaggio di oggetti e classi, né il loro intero ruolo in Videogiochi.

Le entità in piccoli videogiochi sono per lo più definite da classi separate, nei grandi giochi sono spesso definite in file. Ci sono due approcci generali da seguire:

Estensione : nel tuo esempio Trolle Werewolfsi estenderà Enemy, il che si estende Entity. Entitysi prende cura delle cose di base come movimento, salute ecc., mentre Enemyindica le sottoclassi come nemici e fa altre cose (Minecraft segue questo approccio).

Basato sui componenti : il secondo approccio (e il mio preferito) è una Entityclasse, che ha componenti. Un componente potrebbe essere Moveableo Enemy. Questi componenti si occupano di una cosa come il movimento o la fisica. Questo metodo rompe le lunghe catene di estensione e riduce al minimo il rischio di incorrere in un conflitto. Ad esempio: come puoi creare nemici non mobili, se ereditano da un personaggio mobile? .

In grandi videogiochi come World of Warcraft o Starcraft 2, le Entità non sono classi, ma insiemi di dati, che specificano l'intera Entità. Lo scripting è anche importante, perché si definisce ciò che l'Entità non fa in una Classe, ma in uno Script (se è unico, non cose come lo spostamento).

Attualmente sto programmando un RTS e prendo l'approccio basato sui componenti con i file Descriptor e Script per entità, abilità, oggetti e tutto il resto dinamico nel gioco.


2
Sembra interessante. Comunque non mentirò, non so cosa componentsia in questo contesto. Un link sarebbe molto apprezzato.
Vemv,

Un componente è un'entità che ha la responsabilità di una singola funzionalità. L'entità basata su componenti sarà proprietaria di un componente (se è il caso) che non erediterà da esso. La proprietà implica che un'entità può disattivare uno dei suoi componenti modificandone il comportamento. Nel paradigma di estensione classica il comportamento è dovuto "appartenere a" una superclasse. Un'entità può essere un componente e un componente può essere modellato come una classe o come una struttura, se si preferisce.
FxIII,

Componente (a volte chiamato sistemi di entità): puoi cercare idee nel blog t-machine.org Ci sono varianti ma l'idea di base è che un componente è un oggetto speciale e il tuo attore lega insieme un mucchio di componenti, come costruire un modello da Legos.
Patrick Hughes,

2
Ho iniziato un blog sullo sviluppo di giochi e ho scritto il mio primo post di blog su questo. Niente di nuovo, ma con un po 'di codice: jagamedev.wordpress.com/2011/06/26/…
Marco,

23

Penso che sarebbe bello che sia il troll che il lupo mannaro abbiano ereditato i suoi attributi dal nemico e ne abbiano sovrascritto alcuni.

Purtroppo questo approccio al polimorfismo, popolare negli anni '90, si è dimostrato in pratica una cattiva idea. Immagina di aggiungere "lupo" all'elenco dei nemici - beh, condivide alcuni attributi con il lupo mannaro, quindi vorresti consolidarli in una classe base condivisa, ad es. 'WolfLike'. Ora aggiungi "Umano" all'elenco dei nemici, ma a volte i lupi mannari sono umani, quindi condividono anche attributi, come camminare su 2 gambe, essere in grado di parlare, ecc. Crei una base comune "umanoide" per umani e lupi mannari , e poi devi smettere di lupi mannari derivanti da WolfLike? O moltiplichi l'ereditarietà di entrambi - nel qual caso, quale attributo ha la precedenza quando qualcosa in Humanoid si scontra con qualcosa in WolfLike?

Il problema è che provare e modellare il mondo reale come un albero di classi è inefficace. Prendi 3 cose e probabilmente puoi trovare 4 modi diversi per fattorizzare il loro comportamento in condiviso e non condiviso. Questo è il motivo per cui molti si sono invece rivolti a un modello modulare o basato su componenti, in cui un oggetto è composto da diversi oggetti più piccoli che compongono il tutto e gli oggetti più piccoli possono essere scambiati o modificati per compensare il comportamento desiderato. Qui potresti avere solo 1 classe di nemici, e contiene oggetti secondari o componenti per come cammina (es. Bipede contro quadrupede), i suoi metodi di attacco (es. Morso, artiglio, arma, pugno), la sua pelle (verde per troll, peloso per lupi mannari e lupi), ecc.

Questo è ancora completamente orientato agli oggetti, ma la nozione di cosa sia un oggetto utile è diversa da quella che veniva comunemente insegnata nei libri di testo. Invece di avere un grande albero ereditario di varie classi astratte e diverse classi concrete sulle punte dell'albero, in genere hai solo una classe concreta che rappresenta un concetto astratto (ad esempio "nemico") ma che contiene più classi concrete che rappresentano concetti più astratti (es. attacchi, tipo di pelle). A volte queste seconde classi possono essere implementate al meglio tramite 1 livello di ereditarietà (ad es. Una classe di attacco base e diverse classi derivate per attacchi specifici) ma l'albero delle eredità profonde è sparito.

Ma forse le lezioni sono troppo pesanti per essere pratiche, non lo so ...

Nella maggior parte delle lingue moderne, le lezioni non sono "pesanti". Sono solo un altro modo di scrivere codice e di solito avvolgono semplicemente l'approccio procedurale che avresti scritto comunque, tranne che con una sintassi più semplice. Ma sicuramente, non scrivere una classe in cui una funzione farà. Le classi non sono intrinsecamente migliori, ma solo dove semplificano la gestione o la comprensione del codice.


3
Adoro questa risposta :)
Czarek Tomczak,

8

Non sono sicuro di cosa significhi "troppo pesante", ma C ++ / OOP è la lingua franca dello sviluppo del gioco per le stesse buone ragioni per cui è usato altrove.

Parlare di soffiare nella cache è un problema di progettazione, non una funzione linguistica. Il C ++ non può essere così male poiché è stato usato nei giochi negli ultimi 15-20 anni. Java non può essere così male poiché viene utilizzato negli ambienti di runtime molto stretti degli smartphone.

Passiamo ora alla vera domanda: per molti anni le gerarchie di classi sono diventate profonde e hanno iniziato a soffrire di problemi legati a questo. In tempi più recenti le gerarchie di classi si sono appiattite e la composizione e altri progetti basati sui dati sono stati piuttosto che ereditarietà basata sulla logica.

È tutto OOP ed è ancora usato come base quando si progetta un nuovo software, concentrandosi solo su ciò che le classi incarnano.


2

Le classi non comportano alcun sovraccarico di runtime. Il polimorfismo di runtime chiama semplicemente tramite un puntatore a funzione. Questi costi generali sono estremamente minimi rispetto ad altri costi come chiamate Draw, sincronizzazione della concorrenza, I / O del disco, switch della modalità kernel, scarsa localizzazione della memoria, uso eccessivo di allocazione dinamica della memoria, scarsa scelta degli algoritmi, uso dei linguaggi di scripting e elenco continua. C'è una ragione per cui l'orientamento agli oggetti ha sostanzialmente soppiantato ciò che lo precedeva, ed è perché è molto meglio.


Il dispacciamento tra vari tipi polimorfici viene effettuato in modo da dare una scarsa localizzazione della memoria. Anche nelle implementazioni più leggere come la cache C ++ o la cache IMP Objective-C, se si utilizza effettivamente il polimorfismo per gestire oggetti di vario tipo, si soffia la cache. Naturalmente se non usi il polimorfismo di quanto la vtable rimanga nella tua cache o il messaggio nella cache IMP porti nella posizione corretta, ma allora perché preoccuparsi? E in Java o C # il costo è più pesante e in JavaScript o Python il costo è ancora più pesante.

1
@Joe Wreschnig: se stai usando quelle lingue più lente, il costo di OO è banale rispetto agli altri costi come l'interpretazione / JIT. Ancora più importante, è possibile teorizzare ciò che si desidera su una scarsa localizzazione della memoria, ma il fatto è che la previsione del ramo, l'esecuzione fuori servizio e funzionalità simili sulla CPU praticamente annullano completamente il costo. Altrimenti, perché così tanto software ad alte prestazioni è scritto usando l'orientamento agli oggetti?
DeadMG,

1

Una cosa da tenere presente è che i concetti di codice orientato agli oggetti sono spesso più preziosi della loro implementazione in linguaggi. In particolare, dover eseguire migliaia di chiamate di aggiornamento virtuale su entità in un ciclo principale può causare un pasticcio nella cache in C ++ su piattaforme come 360 ​​o PS3 - quindi l'implementazione potrebbe evitare parole chiave "virtuali" o addirittura "di classe" per il bene di velocità.

Spesso seguirà comunque i concetti OO di ereditarietà e incapsulamento, implementandoli in modo diverso da come fa il linguaggio stesso (ad esempio modelli curiosamente ricorsivi, composizione anziché ereditarietà (il metodo basato sui componenti descritto da Marco)). Se l'ereditarietà può sempre essere risolta in fase di compilazione, i problemi di prestazioni scompaiono, ma il codice può diventare un po 'peloso con modelli, implementazioni RTTI personalizzate (anche in questo caso quelle incorporate sono solitamente impraticabilmente lente) o logica di compilazione in macro ecc.

In Objective-C per iOS / Mac ci sono problemi simili, ma peggiori con lo schema di messaggistica (anche con le fantasiose cose di cache) ...


0

Il metodo che cercherò di utilizzare mescola l'eredità di classe e i componenti. (Prima di tutto, non trasformo Enemy in una classe - lo trasformo in un componente.) Non mi piace l'idea di utilizzare una classe Entity generica per tutto e quindi aggiungere i componenti necessari per arricchire l'entità. Preferisco invece che una classe aggiunga automaticamente componenti (e valori predefiniti) nel costruttore. Quindi le sottoclassi aggiungono i loro componenti aggiuntivi nel loro costruttore, così la sottoclasse avrebbe i suoi componenti e i suoi componenti della classe genitore. Ad esempio, una classe di armi aggiungerebbe componenti di base comuni a tutte le armi, quindi la sottoclasse di spade aggiungerebbe eventuali componenti aggiuntivi specifici per le spade. Sto ancora discutendo sull'opportunità di utilizzare risorse text / xml per definire valori per entità (o spade specifiche per esempio), o per fare tutto in codice, o qual è il giusto mix. Ciò consente ancora al gioco di utilizzare le informazioni sul tipo di linguaggio del codice e il polimorfismo, e rende ObjectFactories molto più semplice.


3
Ehi Chris. Anche se condividere la tua esperienza e i tuoi piani è fantastico, non risponde direttamente alla domanda. Penso che la domanda sia più su come queste cose vengono fatte in genere dove, mentre rispondi a come pensi di farlo . Sono domande simili, ma non sono le stesse.
MichaelHouse
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.