Structuration du code/ressources - Divers - Programmation
Marsh Posté le 10-04-2012 à 11:17:06
Y'a pas mal de documentation sur les pratiques standard dans l'industrie du jeu vidéo. Tu devrais trouver facilement des livres à ce sujet.
L'approche en général consiste à avoir des entités modulaires (actor/components) et tes composants qui référencent des ressources, elles-mêmes gérées par un manager dédié (SoundManager, TextureManager, ...). Les composants doivent juste gérer la logique de jeu. La logique d'affichage (ou pour jouer des sons, peu importe) doit être gérée ailleurs (dans les managers ? dans un système équivalent à un scenegraph ? ...)
Dans l'idéal, la manière dont tu construis ton arborescence de fichier ne devrait pas nécessairement avoir de rapport avec l'implémentation. Ca devrait être simplement ce qui est le plus logique pour le projet sur lequel tu bosses. Si ca te semble pertinent de regrouper les objets par type ou par utilisation, il faut que ca reste ton choix.
Après, le point auquel tu veux pousser le système dépend de ton ambition. Peut-être que tes redondances sont acceptables pour un jeu qui doit ne tourner que sur PC. Par contre, si tu vises des machines avec ressources plus limitées (genre, smartphones, à tout hasard), ca pourrait devenir un handicap conséquent.
En tout cas, ton histoire de rendre à coup de "if" m'inquiète un peu sur le cheminement que tu envisages ...
Bref, ta question étant assez ouverte, je ne suis pas sur d'avoir d'y avoir répondu ...
Marsh Posté le 10-04-2012 à 18:45:55
le rendu a coup de if :
c'était pour exprimer le SceneManager un truc qui parse la mêmoire et en fonction de l'état des objets affiche les sprites.
Cette approche 'standard' est plustôt cool et à l'avantage d'être modulable a souhait (indépendamment de la logique de jeu). par contre je trouve toujours plus "objet" d'implémenter une fonction draw a chacun de mes objets qui renvoie le bon sprite en fonction de son état...
je sais pas je trouve que ca fait redondant d'un côté changer l'état de lobjet et ensuite faire une logique qui déduit sont apparence en fonction de son état... mais a défaut d'avoir mieux
Marsh Posté le 10-04-2012 à 22:08:49
la programmation orientée objet, c'est marrant, mais la programmation orientée données, c'est mieux
Pour ton cas, dans l'idée, tu devrais avoir des batchs de sprites à afficher.
Tu peux assez simplement trouver ceux qu'il est pertinent d'envoyer au rendu ... Au final, tu fais SpriteManager->draw( batch ) ... Ton manager sait afficher un sprite. Si tu as 1000 sprites à afficher, ton approche te fait faire 1000 appels à Draw() alors qu'ici, tu n'en as qu'un seul. C'est une des raisons pour lesquelles utiliser des managers pour tes ressources a du sens. Après, suivant les ambitions de ton projet, tu pourras voir d'autres intérêts.
Marsh Posté le 11-04-2012 à 14:14:12
theshockwave a écrit : la programmation orientée objet, c'est marrant, mais la programmation orientée données, c'est mieux |
qu'entend tu par batchs ?
ca ne reviens pas a lire un par un les status de mes entités pour en déduire quel sprite afficher ?
car entre 1000 appels a Draw() qui te donne un id du sprite a afficher par le manager
30000 test par boucle de jeu pour connaitre l'état de chacun de mes objets... j'ai du mal a voir le gain.. hors la séparation des tâches (et donc la possiblitée de sous traiter la partie de rendu)
dans les 2 cas on peut tout a fait optimiser le rendu , avec un observer sur les objets dans mon champ de vue pour signaler si il faut ou non actualiser le rendu.
Après je suis dans une logique de jeu de plateau (Roguelike) donc d'une division par cases, prenant des états... peut etre que ca influe sur ma vision de comment structurer le bousin... j'imagine mal une logique identique sur un FPS ou une porte n'est pas un objet a part entiere
Marsh Posté le 11-04-2012 à 15:02:33
Je me suis mal exprimé : dans la mesure où je ne connais pas vraiment l'état de ton programme et que cette discussion est absolument abstraite, je n'ai pas de "devrais" à utiliser, je ne fais que transposer les pratiques que je connais et qui me semblent applicables ici.
Avoir la logique de mise à jour du jeu, qui influence tes structures affichables (déplacer un sprite, par exemple) n'a pas de raison d'être mélangée avec la logique d'affichage (déterminer ce qui va être effectivement envoyé à l'écran ou non, faire les appels au système d'affichage pour tracer, ...)
Une bonne pratique que j'ai pu rencontrer, c'est même :
* la logique de jeu ne fait qu'empiler des commandes sur les structures de rendu
* la logique de rendu ne fait que tracer avec l'état actuel
* tu as une phase de mise à jour des positions quelque part dans ta mainloop qui sert juste à consommer les commandes de rendu.
Partant de là, ton code qui se charge de faire le draw() sur un objet n'est pas du tout mêlé à ta logique de jeu. Il n'a aucune raison de l'être. Plutôt que d'avoir ce Draw() qui va sans doute tester sa visibilité (ou pire, un truc style : if( object->isVisible() ) object->draw() ) tu vas préférer parcourir tous tes objets pour construire un batch (un ensemble quoi) d'objets à afficher et demander à ton manager d'afficher cet ensemble d'objets.
Pour en revenir aux ressources .. Ces objets vont potentiellement partager des données. Chaque case de ton sol va pouvoir avoir la même texture qu'une quelconque autre case. Les objets dont je parlais doivent donc être des sortes d'instances de ces ressources dans ta scène de jeu et avoir une manière de référencer cette ressource (pointeur, ID ou quoi que ce soit), et c'est à ton manager de ressource de tracer chaque ressource à l'endroit décrit par chaque objet de ton batch.
Qui plus est, ce système de batch laisse ton manager choisir l'ordre dans lequel les objets vont être traités (si jamais tu veux un jour te soucier de rendre d'abord les objets pleins et ensuite les objets transparents, ca te facilitera la vie .. peut-être que tu préfèreras remplir deux batchs différents, je ne sais pas ... mais la structure est là et ca aura probablement du sens)
Marsh Posté le 12-04-2012 à 08:36:36
c'est chouette ça comme méthode, d'autant que cela m'était totalement inconnue.
C'est donc de cela que découle le scenegraph. Après avoir regroupé tes entitées par propriété communes à afficher tu ordonnance les groupes dans ton graph de manière à appliquer tes effets qu'une seul fois par objet et permettre l'héritage d'effets.
dans un autre registre comment dans l'industrie du jeu vidéo les ordres sont gérées ?
après plusieurs petit jeux fait dans mon coin, je m'oriente maintenant vers la solution suivante.
chaque acteur à une pile d'ordre qu'il consume en continue, tandis que la fonction d'IA de l'acteur elle empile les ordres ou les réorganisent en fonction de l'instant du jeu et du temps qui lui est alloué. Pour les ordres j'essaye de les faires les plus macroscopique possible, souvent un TOKEN associé a une position. Genre MOVE + Pos , OPEN + Pos ....
Dans l'ensemble ca marche plustôt bien, tant que la pile d'ordre ne deviens pas trop volumineuse (dans le cas d'un IA trop prédictrice) J'avais tenté de regrouper certains series d'ordres en groupe mais ca deviens vite bordélique. Surtout quand les interuptions sont trop nombreuses.
c'est un sujet sur le quel hormis dans les cas des STR en réseau ou cette approche est perainne de par l'environnement totalement prédictif et de l'IA limité (http://www.gamasutra.com/view/feature/3094/1500_archers_on_a_288_network_.php, http://gafferongames.com/networkin [...] tworking/) je n'ais pas trouvé d'articles super intéressant
Marsh Posté le 12-04-2012 à 14:27:32
Je ne connais pas tous les détails pour le contrôle des IA, mais globalement, ca doit se faire à coup de FSM (finite state machine / automate à états fini) dans lequel tu décris tes états et transition, et oui, globalement, tu donnes des ordres ... Globalement, oui, ton idée d'ordre macro semble pertinente.
Même pour du FPS, on peut faire des trucs similaires. Rien ne t'empêche d'avoir un ordre dont l'effet est réévalué de temps en temps (toutes les demi secondes ou je ne sais quoi) et ton IA sera capable de tenir compte de son environnement, en toute logique. Mais bon, encore une fois, c'est un peu abstrait, tout ca, or tu sembles avoir déjà du code qui roule.
Marsh Posté le 21-04-2012 à 10:33:45
Bon j'ai appliqué vos conseils avec succès c'est cool de pouvoir avoir de nouveau point de vues
me voila bloqué sur une nouvelle problématique de la division du plateau de jeu, je suis partit sur un mode cellules avec des bords,
mais plus ca viens plus je me rend compte que ce choix est dur à gerrer, bien que j'ai réussit a faire mon éclairage dynamique, mon calcul de champs de vision, et de propagation du son.
je me rend compte que j'aurrais dut peut être diviser mon espace en case unitaire (mur,vide ...) et ensuite y aller à coup de quadtree pour mes collisions.
au lieu de faire des cellules avec des bords
ensuite gerrer un espace sous forme de grille ca me rassure, (d'autant que je proramme un RL qui par définition se déroule sur une grille) mais je ne pense pas que ce soit la meilleur solution (voir le code source de Doom ou déjà le partitionnement par case comme le faisait wolfenschtein a été abandonné pour un binarytree qui ne stoque que l'espace praticable sous forme de polygone)
En bref, j'aimerais bien garder ce concept de mur "cloison" sans pour autant exploser mon nombre de cases.
car au final actuellement j'ai un damier de 40*40 avec un partionement par case unitaire, sachant que l'épaisseur de mes murs font 2/16 de cases il faudrais que je multiplie ma surface par 8 . ce qui me semble assez "énorme", mon raycaster vas lui aussi prendre 8x plus de temps.
Et donc cela m'oblige a optimiser tout cela dans un quadtree
Marsh Posté le 03-05-2012 à 11:28:12
Bon je crois que je suis bon pour tout reprendre depuis le début. J'ai de nouveau merdé dans le moteur de rendu, je ne sais décidément pas comment programmer cette partie... =(
mon monde est découpé en étages
au début j'avais un boucle qui redessinait systématiquement tout l'étage ou se situais le joueur. C'était très simple et ca marchait pas mal.
Ensuite pour optimiser, j'ai pris le partit que chaque Acteur informait le moteur de rendu d'un changement de son état, en lui envoyant au travers d'une liste de tocken.
Avantages :
- si liste de tocken vide, rien a recalculer
- possibilité de réorganiser la liste de tocken à chaud (pour optimiser le rendu)
Inconvénients :
- obligé de connaitre la position précédente de l'objet (
dans le cas d'un déplacement) pour "effacer celui ci"; (et c'est super chiant cette partie)
- un volume de code qui explose.
Je suis donc désormait orienté vers un mix des 2.
j'ai une série de calques par niveau,
- des calques statiques qui ne changerons jamais d'état pendant le jeu, que je raffiche a chaque boucle.
- des calques dynamique qui sont effacés completement et recalculé (par demande).
Le demande de recalcul des calques dynamiques se font par ma pile d'ordre, en gros se sont que des ordres de Redraw
Marsh Posté le 03-05-2012 à 12:55:05
J'imagine que tu fais de la 2D, vu la manière dont tu présentes les choses.
Globalement, redessinner l'intégralité de ton étage (statique et dynamique) doit avoir un coût négligeable sur la plupart des machines sur lesquelles tu voudras faire tourner ton appli. Si ca n'a pas un coût négligeable, c'est peut-être là qu'est le problème, non ?
Remplir un écran en copiant des cases depuis une autre zone mémoire ne devrait pas être long et il semble assez facile de déterminer quelles cases sont visibles sur ton écran pour ne demander à tracer que celles-ci.
Quel est ton problème au final ? Tu trouves que ton approche est insuffisante ou tu as vraiment pu mesurer un problème du style "mon rendu prend 300ms" ?
Marsh Posté le 08-10-2012 à 12:18:17
Bonjour,
De retour après plusieurs semaines de dev intermittentes.
J'ai dans mon petit jeu finalement opté pour redessiner la totalité des sprites, chacun des objet emportant l'id du sprite à afficher.
Donc d'un côté j'ai le ressource manager qui fournit des ressources (sprites,animations,musique,sons) en fonction d'un id.
donc a chaque redraw :
- redessine en bloc, tout les objets/représentation statiques
- ensuite je parcours tout mes objets dont la représentation est dynamique je les redessinent un par un.
ensuite je me suis attaqué à l'externialisation des comportement de mes armes dans le jeu (qui je rapel est un rogueLike)
- j'ai isolé tout les effets/comportements
feu, eau, triggers ..
- j'ai crée 2 classes d'armes distant/Cac
et ensuite ca donne a peut pres cela dans mon fichier de conf
<item fleche>
<effet feu>
<item mine trigger=sound>
<effet gaz>
<effet eau>
donc la c'est un objet pour arme distant (une flèche)
qui vas
enflamer (feu) + lacher une mine (qui se déclenchera au son, et qui lancera du gaz) + mouiller (eau)
cette série d'effet est apliqué à une position (là ou on tire) et on applique les effets a chacun des objets
Il faut que je restructure le tout pour nettoyer/optimiser mais dans l'ensemble ca marche
Marsh Posté le 09-04-2012 à 22:00:30
Développant depuis quelques année en amateur, je retombe régulièrement sur le problème de structuration de mes données (quel arborescence choisir, ou faut t'il lier fortement ou non les ressources aux classe ... ) Venant de me lancer dans un petit projet de RogueLike me voila de nouveau confronté à cette problématique. Et donc je me suis dis que ce serrait peut être intéressant de comparer nos approches de structuration de ressources code.
Dans l'exemple d'un petit jeu je serrais tenter de structurer mon projet ainsi
un dossier source, contenant
un pakage entites pour mes entitées avec comme sous pakage (mur,joueur,bonus...)
un pakage core pour la mécanique du jeu (et les classes abstraites de mes entitées)
un pakage utils pours des fonctions/classes statiques utiles de ci de la (genre le loader de son, le logeur...)
un dossier res de ressources (pour mes sons/images)
jusque la rien de bien sorcier (je pose tout de même une question pourquoi beaucoup de personnes encapsulent leur package dans un package com ?)
ensuite devient la problématique que je me pose toujours dans le cas d'un jeu
faut t'il fortement lier mes entités à mes ressources, CAD est-ce intélligent que l'objet stocke sa visualisation, ou ses sons... ou mieux vaut t'il reléguer cela dans la partie core , genre on lis l'état du jeu case par case et on rend le tout a coup de if..
d'un côté le fait de reléguer cela a l'objet avec un design pattern genre poid plume me parait plutôt chouette , ainsi il suffit de faire monObjet.draw() sans se soucier de ce qu'il y a derrière tout en évitant la surcharge des objets. tout en permettant d'avoir des entités vraiment indépendantes (on pourrais même aller jusqu'a mettre dans le package corespondant à l'entitée sa représentation et son son). l'inconvénient on se retrouve avec pas mal de doublons, et un peut de tout dans tout les sens, l'avantage chaque entitée est un petit module et donc c'est facile de s'y retrouver traquer les ressources mortes ...
dans un de mes gros projet de PHP, arrivé a une certaine taille mon modèle MVC global n'éttait vraiment plus viable, j'ai finalement découpé mon projet en petit modules (chacun embarquant sa logique MVC et ses ressources ) , et finalement ainsi je m'en suis sortit indeme et quand je reprend le code pour régler un bug 6mois plus tard, c'est vraiment plus facile de cerner la zone/module ou se situe le bug. par contre j'ai pas mal de ressources redondantes and co...
Comment gérez vous le problème ? comment découpez vous cela ?
Message édité par une IA le 09-04-2012 à 22:01:18