Je me suis toujours demandé dans quelle mesure l'orientation des objets était utilisée par les jeux vidéo, en particulier les grands projets 3D. Je pense que ce serait cool que les deux troll
et a werewolf
hérité ses attributs de enemy
et réenregistrés certains d'entre eux. Mais peut-être que les cours sont trop lourds pour être pratiques, je ne sais pas ...
18
Réponses:
Pour revenir à votre exemple, je vais vous expliquer mes réflexions sur Entites dans les jeux vidéo. Je ne discuterai pas de l'avantage et des inconvénients généraux des objets et des classes, ni de leur rôle dans les jeux vidéo.
Les entités dans les petits jeux vidéo sont principalement définies par des classes distinctes, dans les grands jeux, elles sont souvent définies dans des fichiers. Il y a deux approches générales à suivre:
Extension : Dans votre exemple
Troll
etWerewolf
prolongeraEnemy
, qui se prolongeEntity
.Entity
prend soin des choses de base comme le mouvement, la santé, etc., tout enEnemy
déclarant les sous-classes comme des ennemis et faisant d'autres choses (Minecraft suit cette approche).Basé sur les composants : La deuxième approche (et ma préférée) est une
Entity
classe, qui a des composants. Un composant pourrait êtreMoveable
ouEnemy
. Ces composants s'occupent d' une chose comme le mouvement ou la physique. Cette méthode rompt les longues chaînes d'extension et minimise le risque de se heurter à un conflit. Par exemple: Comment pouvez-vous faire des ennemis non mobiles, s'ils héritent d'un personnage mobile? .Dans les grands jeux vidéo comme World of Warcraft ou Starcraft 2, les entités ne sont pas des classes, mais des ensembles de données, qui spécifient l'entité entière. Les scripts sont également importants, car vous définissez ce que l'entité ne fait pas dans une classe, mais dans un script (s'il est unique, pas des choses comme le déplacement).
Je programme actuellement un RTS et j'utilise l'approche basée sur les composants avec des fichiers de descripteur et de script pour les entités, les compétences, les éléments et tout le reste dynamique dans le jeu.
la source
component
c'est dans ce contexte. Un lien serait très apprécié.Malheureusement, cette approche du polymorphisme, populaire dans les années 90, s'est révélée être une mauvaise idée dans la pratique. Imaginez que vous ajoutez «loup» à la liste des ennemis - eh bien, il partage certains attributs avec le loup-garou, donc vous voudriez les consolider dans une classe de base partagée, par exemple. «WolfLike». Maintenant, vous ajoutez «Humain» à la liste ennemie, mais les loups-garous sont parfois des humains, ils partagent donc également des attributs, comme marcher sur 2 jambes, pouvoir parler, etc. Créez-vous une base commune «humanoïde» pour les humains et les loups-garous , et devez-vous alors arrêter les loups-garous dérivant de WolfLike? Ou multipliez-vous l'héritage des deux - dans ce cas, quel attribut a priorité lorsque quelque chose dans Humanoid entre en conflit avec quelque chose dans WolfLike?
Le problème est qu'essayer et modéliser le monde réel comme un arbre de classes est inefficace. Prenez 3 choses et vous pouvez probablement trouver 4 façons différentes de factoriser leur comportement en partagé et non partagé. C'est pourquoi beaucoup se sont plutôt tournés vers un modèle modulaire ou basé sur des composants, où un objet est composé de plusieurs objets plus petits qui composent l'ensemble, et les objets plus petits peuvent être échangés ou modifiés pour créer le comportement que vous souhaitez. Ici, vous ne pouvez avoir qu'une seule classe ennemie, et elle contient des sous-objets ou des composants pour la façon dont elle marche (par exemple. Bipède contre Quadrupède), ses méthodes d'attaque (par exemple, morsure, griffe, arme, poing), sa peau (verte pour les trolls, à fourrure pour les loups-garous et les loups), etc.
C'est toujours complètement orienté objet, mais la notion de ce qui est un objet utile est différente de ce qui était communément enseigné dans les manuels. Plutôt que d'avoir un grand arbre d'héritage de diverses classes abstraites et plusieurs classes concrètes aux extrémités de l'arbre, vous n'avez généralement qu'une seule classe concrète représentant un concept abstrait (par exemple, «ennemi») mais qui contient des classes plus concrètes représentant des concepts plus abstraits (p. ex. attaques, type de peau). Parfois, ces deuxièmes classes peuvent être mieux implémentées via 1 niveau d'héritage (par exemple une classe d'attaque de base et plusieurs classes dérivées pour des attaques spécifiques) mais l'arbre d'héritage profond a disparu.
Dans la plupart des langues modernes, les cours ne sont pas «lourds». Ils ne sont qu'une autre façon d'écrire du code, et ils enveloppent généralement l'approche procédurale que vous auriez écrite de toute façon, sauf avec une syntaxe plus facile. Mais par tous les moyens, n'écrivez pas une classe où une fonction fera l'affaire. Les classes ne sont pas intrinsèquement meilleures, seulement là où elles rendent le code plus facile à gérer ou à comprendre.
la source
Je ne sais pas trop ce que vous entendez par «trop lourd», mais C ++ / OOP est la lingua franca du développement de jeux pour les mêmes bonnes raisons qu'il est utilisé ailleurs.
Parler de souffler des caches est un problème de conception, pas une fonctionnalité de langue. Le C ++ ne peut pas être trop mauvais car il est utilisé dans les jeux depuis 15-20 ans. Java ne peut pas être trop mauvais car il est utilisé dans les environnements d'exécution très serrés des téléphones intelligents.
Passons maintenant à la vraie question: pendant de nombreuses années, les hiérarchies de classes sont devenues profondes et ont commencé à souffrir de problèmes liés à cela. Plus récemment, les hiérarchies de classes se sont aplaties et la composition et d'autres conceptions basées sur les données sont plutôt que l'héritage logique.
Tout est POO et toujours utilisé comme base de référence lors de la conception de nouveaux logiciels, juste un accent différent sur ce que les classes incarnent.
la source
Les classes n'impliquent aucune surcharge d'exécution. Le polymorphisme au moment de l'exécution appelle simplement un pointeur de fonction. Ces frais généraux sont extrêmement minimes par rapport à d'autres coûts tels que les appels Draw, la synchronisation simultanée, les E / S disque, les commutateurs en mode noyau, la mauvaise localisation de la mémoire, la surutilisation de l'allocation dynamique de la mémoire, le mauvais choix d'algorithmes, l'utilisation de langages de script et la liste continue. Il y a une raison pour laquelle l'orientation objet a essentiellement supplanté ce qui l'a précédé, et c'est parce que c'est beaucoup mieux.
la source
Une chose à savoir est que les concepts de code orienté objet sont souvent plus précieux que leur implémentation dans les langages. En particulier, devoir effectuer des milliers d'appels de mise à jour virtuelle sur des entités dans une boucle principale peut provoquer un gâchis dans le cache en C ++ sur des plates-formes comme 360 ou PS3 - donc l'implémentation peut éviter des mots clés "virtuels" ou même "classe" pour le plaisir de vitesse.
Souvent, il suivra toujours les concepts OO d'héritage et d'encapsulation - les implémentant simplement différemment de la façon dont le langage se fait (par exemple, des modèles curieusement récursifs, la composition au lieu de l'héritage (la méthode basée sur les composants décrite par Marco)). Si l'héritage peut toujours être résolu au moment de la compilation, les problèmes de performances disparaissent, mais le code peut devenir un peu poilu avec les modèles, les implémentations RTTI personnalisées (là encore, les intégrées sont généralement peu pratiques) ou la logique de compilation dans macros etc.
Dans Objective-C pour iOS / Mac, il y a des problèmes similaires, mais pires avec le schéma de messagerie (même avec les trucs de mise en cache de fantaisie) ...
la source
La méthode que j'essaierai d'utiliser l'héritage de classes mixtes et les composants. (Tout d'abord, je ne fais pas de l'ennemi une classe - j'en fais un composant.) Je n'aime pas l'idée d'utiliser une classe d'entité générique pour tout, puis d'ajouter les composants nécessaires pour étoffer l'entité. Au lieu de cela, je préfère qu'une classe ajoute automatiquement des composants (et des valeurs par défaut) dans le constructeur. Ensuite, les sous-classes ajoutent leurs composants supplémentaires dans leur constructeur, afin que la sous-classe ait ses composants et ses composants de classe parent. Par exemple, une classe d'armes ajouterait des composants de base communs à toutes les armes, puis la sous-classe des épées ajouterait des composants supplémentaires spécifiques aux épées. Je suis toujours en train de débattre de l'utilisation des ressources text / xml pour définir les valeurs des entités (ou des épées spécifiques par exemple), ou de faire tout cela dans le code, ou quel est le bon mélange. Cela permet toujours au jeu d'utiliser les informations de type et le polymorphisme du langage de code, et rend ObjectFactories beaucoup plus simple.
la source