Séparation du dessin et de la logique dans les jeux


11

Je suis un développeur qui commence à peine à jouer avec le développement de jeux. Je suis un gars .Net, j'ai donc joué avec XNA et je joue maintenant avec Cocos2d pour l'iPhone. Ma question est cependant plus générale.

Disons que je construis un simple jeu de Pong. J'aurais une Ballclasse et une Paddleclasse. Venant du développement du monde des affaires, mon premier réflexe est de ne pas avoir de code de dessin ou de saisie dans aucune de ces classes.

//pseudo code
class Ball
{
   Vector2D position;
   Vector2D velocity;
   Color color;
   void Move(){}
}

Rien dans la classe ball ne gère l'entrée ou ne traite du dessin. J'aurais alors une autre classe, ma Gameclasse ou ma Scene.m(dans Cocos2d) qui renouvellerait la balle, et pendant la boucle de jeu, elle manipulerait la balle selon les besoins.

La chose est cependant, dans de nombreux tutoriels pour XNA et Cocos2d, je vois un modèle comme celui-ci:

//pseudo code
class Ball : SomeUpdatableComponent
{
   Vector2D position;
   Vector2D velocity;
   Color color;
   void Update(){}
   void Draw(){}
   void HandleInput(){}
}

Ma question est, est-ce vrai? Est-ce le schéma que les gens utilisent dans le développement de jeux? Cela va en quelque sorte contre tout ce à quoi je suis habitué, pour que ma classe de Ball fasse tout. De plus, dans ce deuxième exemple, où mon Ballsait comment se déplacer, comment gérer la détection de collision avec le Paddle? Serait-il Ballnécessaire de connaître le Paddle? Dans mon premier exemple, la Gameclasse aurait des références à la fois au Ballet au Paddle, puis les enverrait à un CollisionDetectiongestionnaire ou quelque chose, mais comment gérer la complexité de divers composants, si chaque composant individuel fait tout par lui-même? (J'espère que j'ai du sens .....)


2
Malheureusement, c'est vraiment ainsi que cela se fait dans de nombreux jeux. Je ne considérerais pas cela comme signifiant que c'est la meilleure pratique. De nombreux didacticiels que vous lisez pourraient s'adresser aux débutants, et ils ne vont donc pas commencer à expliquer au lecteur les modèles, vues, contrôleurs ou joug.
tenpn

@tenpn, votre commentaire semble suggérer que vous ne pensez pas que ce soit une bonne pratique, je me demandais si vous pouviez expliquer davantage? J'ai toujours pensé que c'était l'arrangement le plus approprié en raison de ces méthodes contenant du code qui est probablement très spécifique à chaque type; mais j'ai peu d'expérience moi-même donc je serais intéressé d'entendre vos pensées.
sef

1
@sebf cela ne fonctionne pas si vous êtes dans la conception orientée données, et cela ne fonctionne pas si vous aimez la conception orientée objet. Voir les principes SOLIDES d'oncle Bob: butunclebob.com/ArticleS.UncleBob.PrinciplesOfOod
tenpn

@tenpn. Cet article et ce document liés sont très intéressants, merci!
sebf

Réponses:


10

Ma question est, est-ce vrai? Est-ce le schéma que les gens utilisent dans le développement de jeux?

Les développeurs de jeux ont tendance à utiliser tout ce qui fonctionne. Ce n'est pas rigoureux, mais bon, il est expédié.

Cela va en quelque sorte contre tout ce à quoi je suis habitué, pour que ma classe de Ball fasse tout.

Donc, un idiome plus généralisé pour ce que vous avez là-bas est handleMessages / update / draw. Dans les systèmes qui font un usage intensif des messages (ce qui présente des avantages et des inconvénients comme vous vous en doutez), une entité de jeu saisit tous les messages qui lui importent, exécute la logique de ces messages, puis se dessine.

Notez que cet appel draw () peut ne pas réellement signifier que l'entité appelle putPixel ou quoi que ce soit à l'intérieur d'elle-même; dans certains cas, cela pourrait simplement signifier la mise à jour des données qui sont ensuite interrogées par le code responsable du dessin. Dans les cas plus avancés, il "se dessine" lui-même en appelant des méthodes exposées par un moteur de rendu. Dans la technologie sur laquelle je travaille en ce moment, l'objet se dessinait à l'aide d'appels de rendu, puis chaque image du fil de rendu organisait tous les appels effectués par tous les objets, optimisait des choses comme les changements d'état, puis dessinait le tout (tout de ce qui se passe sur un thread séparé de la logique du jeu, nous obtenons donc un rendu asynchrone).

La délégation de responsabilité appartient au programmeur; pour un jeu de pong simple, il est logique que chaque objet gère complètement son propre dessin (avec des appels de bas niveau). C'est une question de style et de sagesse.

De plus, dans ce deuxième exemple, où ma balle sait se déplacer, comment gérer la détection de collision avec la palette? Le ballon aurait-il besoin de connaître la pagaie?

Donc, une façon naturelle de résoudre le problème ici est de faire en sorte que chaque objet prenne tous les autres objets comme une sorte de paramètre pour vérifier les collisions. Cela évolue mal et peut avoir des résultats étranges.

Faire en sorte que chaque classe implémente une sorte d'interface Collidable, puis avoir un peu de code de haut niveau qui boucle simplement sur tous les objets Collidable dans la phase de mise à jour de votre jeu et gère les collisions, en définissant les positions des objets selon les besoins.

Encore une fois, il vous suffit de développer un sens (ou de prendre une décision) sur la façon dont la responsabilité des différentes choses dans votre jeu doit être déléguée. Le rendu est une activité solitaire et peut être manipulé par un objet dans le vide; les collisions et la physique ne le peuvent généralement pas.

Dans mon premier exemple, la classe Game aurait des références à la fois à la balle et à la palette, puis les expédierait à un gestionnaire de CollisionDetection ou quelque chose, mais comment gérer la complexité de divers composants, si chaque composant individuel le fait tout eux-mêmes?

Voir ci-dessus pour une exploration de ceci. Si vous êtes dans un langage qui prend facilement en charge les interfaces (par exemple, Java, C #), c'est facile. Si vous êtes en C ++, cela peut être ... intéressant. Je préfère utiliser la messagerie pour résoudre ces problèmes (les classes gèrent les messages qu'ils peuvent, ignorer les autres), d'autres personnes comme la composition des composants.

Encore une fois, tout ce qui fonctionne et est le plus simple pour vous.


2
Oh, et rappelez-vous toujours: YAGNI (vous n'en aurez pas besoin) est vraiment important ici. Vous pouvez perdre beaucoup de temps à essayer de trouver le système flexible idéal pour gérer tous les problèmes possibles, sans jamais rien faire. Je veux dire, si vous vouliez vraiment une bonne dorsale multijoueur pour tous les résultats possibles, vous utiliseriez CORBA, non? :)
ChrisE

5

J'ai récemment fait un simple jeu Space Invadors en utilisant un «système d'entités». C'est un modèle qui sépare extrêmement bien les attributs et les comportements. Il m'a fallu quelques itérations pour bien le comprendre, mais une fois que vous avez conçu quelques composants, il devient extrêmement simple de composer de nouveaux objets en utilisant vos composants existants.

Vous devriez lire ceci:

http://t-machine.org/index.php/2007/09/03/entity-systems-are-the-future-of-mmog-development-part-1/

Il est mis à jour fréquemment par un gars extrêmement compétent. C'est aussi la seule discussion sur le système d'entités avec des exemples de code concrets.

Mes itérations se sont déroulées comme suit:

La première itération avait un objet "EntitySystem" qui était comme Adam le décrit; cependant, mes composants avaient encore des méthodes - mon composant «rendable» avait une méthode paint (), et mon composant position avait une méthode move () et etc. Quand j'ai commencé à étoffer les entités, j'ai réalisé que je devais commencer à passer un message entre composants et ordonner l'exécution des mises à jour des composants .... trop désordonné.

Alors, je suis retourné et relu le blog T-machines. Il y a beaucoup d'informations dans les fils de commentaires - et dans ceux-ci, il souligne vraiment que les composants n'ont pas de comportements - les comportements sont fournis par les systèmes d'entités. De cette façon, vous n'avez pas besoin de passer de messages entre les composants et de commander les mises à jour des composants car l'ordre est déterminé par l' ordre global d'exécution du système. D'accord. C'est peut-être trop abstrait.

Quoi qu'il en soit pour l'itération # 2, voici ce que j'ai glané du blog:

EntityManager - agit comme la "base de données" des composants, qui peut être interrogée pour les entités qui contiennent certains types de composants. Cela peut même être soutenu par une base de données en mémoire pour un accès rapide ... voir t-machine partie 5 pour plus d'informations.

EntitySystem - Chaque système est essentiellement juste une méthode qui fonctionne sur un ensemble d'entités. Chaque système utilisera les composants x, y et z d'une entité pour faire son travail. Vous devez donc interroger le gestionnaire pour les entités avec les composants x, y et z, puis transmettre ce résultat au système.

Entité - juste un identifiant, comme un long. L'entité est ce qui regroupe un ensemble d'instances de composants en une «entité».

Composant - un ensemble de champs .... pas de comportements! lorsque vous commencez à ajouter des comportements, cela commence à devenir désordonné ... même dans un simple jeu Space Invadors.

Edit : au fait, 'dt' est le temps delta depuis la dernière invocation de la boucle principale

Donc, ma boucle Invadors principale est la suivante:

Collection<Entity> entitiesWithGuns = manager.getEntitiesWith(Gun.class);
Collection<Entity> entitiesWithDamagable =
manager.getEntitiesWith(BulletDamagable.class);
Collection<Entity> entitiesWithInvadorDamagable = manager.getEntitiesWith(InvadorDamagable.class);

keyboardShipControllerSystem.update(entitiesWithGuns, dt);
touchInputSystem.update(entitiesWithGuns, dt);
Collection<Entity> entitiesWithInvadorMovement = manager.getEntitiesWith(InvadorMovement.class);
invadorMovementSystem.update(entitiesWithInvadorMovement);

Collection<Entity> entitiesWithVelocity = manager.getEntitiesWith(Velocity.class);
movementSystem.move(entitiesWithVelocity, dt);
gunSystem.update(entitiesWithGuns, System.currentTimeMillis());
Collection<Entity> entitiesWithPositionAndForm = manager.getEntitiesWith(Position.class, Form.class);
collisionSystem.checkCollisions(entitiesWithPositionAndForm);

Ça a l'air un peu bizarre au début, mais c'est incroyablement flexible. Il est également très facile à optimiser; pour différents types de composants, vous pouvez avoir différentes banques de données de support pour accélérer la récupération. Pour la classe «forme», vous pouvez la faire appuyer sur un arbre quadruple pour accélérer l'accès pour la détection de collision.

Je suis comme vous; Je suis un développeur chevronné mais je n'avais aucune expérience dans l'écriture de jeux. J'ai passé un certain temps à rechercher des modèles de développement et celui-ci a attiré mon attention. Ce n'est en aucun cas le seul moyen de faire les choses, mais je l'ai trouvé très intuitif et robuste. Je crois que le modèle a été officiellement discuté dans le livre 6 de la série "Game Programming Gems" - http://www.amazon.com/Game-Programming-Gems/dp/1584500492 . Je n'ai lu aucun des livres moi-même mais j'entends qu'ils sont la référence de facto pour la programmation de jeux.


1

Il n'y a aucune règle explicite à suivre lors de la programmation de jeux. Je trouve les deux modèles parfaitement acceptables, tant que vous suivez le même modèle de conception dans tout votre jeu. Vous seriez surpris qu'il existe de nombreux autres modèles de conception pour les jeux, dont plusieurs ne sont même pas au-dessus de la POO.

Maintenant, selon ma préférence personnelle, je préfère séparer le code par comportement (une classe / thread à dessiner, une autre à mettre à jour), tout en ayant des objets minimalistes. Je trouve plus facile de paralléliser, et je me retrouve souvent à travailler sur moins de fichiers en même temps. Je dirais que c'est plus une façon procédurale de faire les choses.

Mais si vous venez du monde des affaires, vous êtes probablement plus à l'aise pour écrire des cours qui savent tout faire par eux-mêmes.

Encore une fois, je vous recommande d'écrire ce qui vous semble le plus naturel.


Je pense que vous avez mal compris ma question. Je dis que je n'aime pas avoir des cours qui font tout eux-mêmes. Je suis tombé comme un ballon devrait juste être une représentation logique d'un ballon, mais il ne devrait rien savoir de la boucle de jeu, de la gestion des entrées, etc.
BFree

Pour être honnête, dans la programmation d'entreprise, si vous le résumez, il y a deux trains: les objets métier qui se chargent, persistent et exécutent la logique sur eux-mêmes, et DTO + AppLogic + Repository / Persitance Layer ...
Nate

1

l'objet 'Ball' a des attributs et des comportements. Je trouve qu'il est logique d'inclure les attributs et comportements uniques de l'objet dans leur propre classe. La façon dont un objet Ball est mis à jour est différente de la façon dont une pagaie se mettrait à jour, il est donc logique que ce soient des comportements uniques qui méritent d'être inclus dans la classe. Idem avec le dessin. Il y a souvent une différence unique dans la façon dont une pagaie est dessinée par rapport à une balle et je trouve plus facile de modifier la méthode de tirage d'un objet individuel pour répondre à ces besoins que de travailler sur des évaluations conditionnelles dans une autre classe pour les résoudre.

Pong est un jeu relativement simple en termes de nombre d'objets et de comportements, mais lorsque vous entrez dans des dizaines ou des centaines d'objets de jeu uniques, vous pouvez trouver votre 2ème exemple un peu plus facile à tout modulariser.

La plupart des gens utilisent une classe d'entrée dont les résultats sont disponibles pour tous les objets de jeu via un service ou une classe statique.


0

Je pense que ce que vous utilisez probablement dans le monde des affaires est de séparer l'abstraction et la mise en œuvre.

Dans le monde des développeurs de jeux, vous voulez généralement penser en termes de vitesse. Plus vous avez d'objets, plus il y aura de changements de contexte, ce qui peut ralentir les choses en fonction de la charge actuelle.

Ce n'est que ma spéculation puisque je suis un développeur de jeux en herbe mais un développeur professionnel.


0

Non, ce n'est pas «bien» ou «mal», c'est juste une simplification.

Certains codes d'entreprise sont exactement les mêmes, sauf si vous travaillez avec un système qui sépare de force la présentation et la logique.

Ici, un exemple VB.NET, où la "logique métier" (en ajoutant un nombre) est écrite dans un gestionnaire d'événements GUI - http://www.homeandlearn.co.uk/net/nets1p12.html

comment gérer la complexité des différents composants, si chaque composant individuel fait tout lui-même?

Si et quand cela devient aussi complexe, vous pouvez le prendre en compte.

class Ball
{
    GraphicalModel ballVisual;
    void Draw()
    {
        ballVisual.Draw();
    }
};

0

D'après mon expérience en développement, il n'y a pas de bonne ou de mauvaise façon de faire les choses, à moins qu'il n'y ait une pratique acceptée dans le groupe avec lequel vous travaillez. J'ai rencontré l'opposition de développeurs qui sont réticents à séparer les comportements, les skins, etc. Et je suis sûr qu'ils diront la même chose de ma réservation à écrire une classe qui comprend tout sous le soleil. N'oubliez pas que vous devez non seulement l'écrire, mais être capable de lire votre code demain et à une date ultérieure, le déboguer et éventuellement le développer lors de la prochaine itération. Vous devez choisir un chemin qui fonctionne pour vous (et votre équipe). Choisir un itinéraire que les autres utilisent (et est contre-intuitif pour vous) vous ralentira.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.