PDA

Voir la version complète : Mon projet de RTS sans nom pour le moment



Tomaka17
27/03/2012, 20h47
Salut à tous,

Il y a quelques temps (le 6 février en fait) j'ai commencé à développer mon petit jeu "indé" pendant mon temps libre. J'ai la chance de bosser en télétravail et d'avoir des horaires flexibles, ce qui fait que j'en ai finalement pas mal, de temps libre.

Il s'agira donc d'un RTS-like avec un aspect gestion plus développé que dans les RTS moyens, pour se rapprocher pourquoi pas d'un Settlers. À ce stade, je n'ai même pas encore décidé dans quel univers il se déroule (médiéval-fantastique, deuxième guerre mondiale, post-apo) ni la liste des ressources ni le niveau de zoom, et ainsi de suite

Mais l'important est que je ne sois pas bloqué dans mon développement. Pour l'instant je bosse sur des trucs de base : affichage de la map, des personnages, etc. Il faut que je me décide rapidement sur les aspects évoqués plus haut, mais pour le moment j'ai encore du taf'

Quelques caractéristiques que je me suis fixées :
- pas de moteur 3D/moteur de jeu, tout est codé à la main
- les personnages sont affichés sous forme de sprites 2D
- le jeu doit être portable

Pas de moteur 3D
Cela signifie que je ne vais pas utiliser Ogre, Unity Engine, CryEngine, etc. mais tout coder à la main avec OpenGL.
Il est mille fois plus gratifiant de tout faire soi-même, d'autant que cela me permettra d'avoir le contrôle total sur mon appli
Pour l'instant j'utilise les librairies suivantes (mais cela peut changer) qui sont toutes gratuites : Boost (plein de trucs utiles de manière général), OpenGL (pour communiquer avec la carte graphique, bien que ce ne soit pas une lib), OpenAL (pour le son), FreeImage (charger des images), FreeType (charger des polices de caractères), libavcodec (lire des vidéos), DirectInput (les contrôles)

Personnages sous forme de sprites
La modélisation 3D et l'animation sont de vastes domaines qui sont incroyablement prise de tête. J'ai donc décidé d'afficher les personnages à l'écran sous forme de sprites, comme dans Populous 3 (http://www.game-over.net/review/november/populous3/Screen6.JPG) ou Theme Park World (http://www.macgamezone.com/images/tests/themeparkworld/02.jpg)
Non seulement c'est plus simple à faire, mais c'est également plus rapide à l'exécution

Portable
Je développe le jeu pour Windows 32 bits. Mais le jeu doit être portable, ce qui veut dire que si je décide du jour au lendemain de faire une version Linux ou une version XBox, les modifications à apporter doivent être les plus petites possibles


Cela fait donc 7 semaines et 2 jours que je bosse dessus pendant mon temps libre, et j'en suis là :

http://tof.canardpc.com/preview/96f4fcbc-d047-4c51-9a80-3137a43a58c3.jpg (http://tof.canardpc.com/view/96f4fcbc-d047-4c51-9a80-3137a43a58c3.jpg)

Vous pouvez donc voir l'ébauche de terrain (pour l'instant les reliefs sont générés aléatoirement) ainsi qu'un personnage au milieu de l'écran
Evidemment j'en suis pas très loin et ça semble un peu à des screens de kevin12ans qui vient montrer son MMO qui va révolutionner la planète. Néanmoins ce qui me bloque le plus c'est le temps, et je serais déjà beaucoup plus avancé si j'étais 35h/semaines dessus

J'envisage de poster de temps en temps des comptes rendus/mini-tutos sur comment j'ai fait tel ou tel aspect, n'hésitez pas à me dire si vous voulez savoir comment j'ai fait ceci-celà

Cuthalion
27/03/2012, 21h55
Je ne peux que te souhaiter bien du courage!

Moi aussi je m'étais crée un petit moteur avec mes petites mimines, OpenGL, GLSL avec gestion minimal de la physique (accélération, force, mais pas les rotations), et de mon expérience, je ne peux que te conseiller de passer pas mal de temps à dessiner des graphes UML de toute sorte pour bien définir le bouzin et à faire de l'héritage de PARTOUT.

Car retconné ton code toute les semaines simplement parceque tu as oublié quelque chose qui pourtant aurait du te sauter aux yeux pendant la phase de conception, cela risque de vite te souler.

Après, je ne connais pas ton niveau en programmation donc je m'excuse si je donne des conseils qui peuvent paraitre évident mais bon.

Aussi, pour la génération de terrain aléatoire, je ne peux que te conseiller de jeter un oeil sur l'algorithme de diamant-carré, les fractales, ou le bruit de perlin http://en.wikipedia.org/wiki/Fractal_landscape
http://en.wikipedia.org/wiki/Diamond-square_algorithm

Tomaka17
27/03/2012, 22h10
En fait j'ai déjà un peu d'expérience vu que j'ai déjà bossé sur des gros projets non-jeux ainsi que sur des petits jeux fait moi-même
Alors évidemment faire des petits jeux c'est différent de faire un gros truc bien fignolé, mais j'ai déjà une idée assez précise de comment tout cela va se ficeler

Je vois ce que tu veux dire en parlant de modifier son code chaque semaine parce qu'on a oublié un truc, mais j'ai tendance à coder de grosses classes avec peu de dépendances et avec très rarement de l'héritage, ce qui m'évite souvent ce genre de problèmes.

Sinon pour le terrain, en fait pour l'instant j'ai juste fait un bête "vertex.z = rand()", mais comme c'est un domaine que je ne maîtrise pas vraiment, je sais déjà que ça va être un gros morceau

Cuthalion
27/03/2012, 22h21
Après, chacun code à sa façon, mais si tu souhaites réellement faire quelque chose de portable et de facilement extensible, je ne peux que te conseiller de faire beaucoup, beaucoup de polymorphisme.

Par exemple, tu pourrais créer une classe de base abstraite dont hérite tous les objets de ton jeu (sprites, terrain, primitives etc), qui, dans ses propriétés, dispose d'un objet de la classe abstraite "DisplayMode" qui dispose d'une méthode virtuelle pure "Display()" pour afficher l'objet dans ta scène.
Ainsi, tu pourras créer autant de classe fille de "DisplayMode" que tu souhaites, pour chaque moteur ou façon d'afficher ton objet (glBegin/end, VBO, shaders, différentes version de OpenGL, DirectX, moteur maison etc) sans avoir besoin de rajouter des switch case illisible dans tout les sens.

Bon, après, c'est vraiment juste mes 2 cents, je m'emporte un peu car moi aussi j'ai déjà un petit moteur fait main en c++ qui sommeille un peu pour l'instant, et que je reprendrai peut être plus tard si j'arrive à percer dans le milieu ou à devenir self entrepreneur (rêve).

Edit : Et je te comprends tout à fait dans ta démarche et je suis content de voir que je ne suis pas le seul qui aime mettre les mains dans le cambouis et qui trouve ca bien plus gratifiant que d'utiliser des trucs déjà fait /o/

Tomaka17
27/03/2012, 22h43
Sinon pour revenir au sujet, deux/trois trucs qui n'apparaissent pas mais que j'ai déjà codé : (oui parce que j'ai pas mis 2 mois pour afficher un pauvre truc vert avec un dessins dessus)
- le son, je peux charger n'importe quel fichier musical (supporté par libavcodec) et le jouer
- de manière plus générale, je peux jouer une vidéo, également grâce à libavcodec qui me décode ça
- charger n'importe quel format d'image
- charger une police de caractères et l'envoyer sous forme de texture où tous les caractères disponibles sont côte à côte ; l'affichage du texte n'est pas encore fait mais ce sera vite fait
- j'ai commencé le code pour tout ce qui est GUI et menus

Teto
27/03/2012, 23h06
Tu as certainement plus d'expérience que moi j'en aurai jamais dans la création des jeux from scratch. Néanmoins, je trouve de mon humble point de vue que c'est plutôt dangereux que tu "finalises" les graphismes avant le gameplay. Je ne parle pas de la coiffure de ton héros, mais plutôt du type d'animation que ton sprite aura, sa grosseur, etc. Pour moi, ton gameplay et les mécanismes de ton jeu vont décider des graphismes. OK, tu vas faire du 2D/3D isométrique ou quelque chose approchant Populous 3, mais malgré tout la qualité des sprites, leurs animations etc vont dépendre du moteur. Pour prendre un exemple récent Sim City V va sortir l'année prochaine, et l'équipe des dèvs a d'abord développé le moteur de simulation. Ils ont fait des graphismes à l'arrache pour tester, n'empêche que la gueule des bâtiments, c'est ce qui va être fait en dernier (voire en DLC...).

Il me semble que si tu choisis d'abord le thème de ton jeu, la grandeur des cartes et le nombre de pnj à l'écran, les ressources à allouer aux graphismes vont couler de source, tout comme la manière de programmer tout ça. Je crains de mon point de vue de noob que si tu t'occupes trop de l'affichage et de l'interface au départ et qu'elle s'avère inadaptée au milieu du développement cela risque fort de te faire rager.

Mais ce n'est que mon avis, bonne chance dans tous les cas! ;)

war-p
27/03/2012, 23h52
Tu utilises une API quelconque pour l'affichage avec OpenGL, ou bien tu codes ça from scratch?

Tomaka17
28/03/2012, 10h28
Tu as certainement plus d'expérience que moi j'en aurai jamais dans la création des jeux from scratch. Néanmoins, je trouve de mon humble point de vue que c'est plutôt dangereux que tu "finalises" les graphismes avant le gameplay. Je ne parle pas de la coiffure de ton héros, mais plutôt du type d'animation que ton sprite aura, sa grosseur, etc. Pour moi, ton gameplay et les mécanismes de ton jeu vont décider des graphismes. OK, tu vas faire du 2D/3D isométrique ou quelque chose approchant Populous 3, mais malgré tout la qualité des sprites, leurs animations etc vont dépendre du moteur. Pour prendre un exemple récent Sim City V va sortir l'année prochaine, et l'équipe des dèvs a d'abord développé le moteur de simulation. Ils ont fait des graphismes à l'arrache pour tester, n'empêche que la gueule des bâtiments, c'est ce qui va être fait en dernier (voire en DLC...).

Il me semble que si tu choisis d'abord le thème de ton jeu, la grandeur des cartes et le nombre de pnj à l'écran, les ressources à allouer aux graphismes vont couler de source, tout comme la manière de programmer tout ça. Je crains de mon point de vue de noob que si tu t'occupes trop de l'affichage et de l'interface au départ et qu'elle s'avère inadaptée au milieu du développement cela risque fort de te faire rager.

Mais ce n'est que mon avis, bonne chance dans tous les cas! ;)

Oui tu as évidemment raison
Je ne comptais pas finaliser les graphismes maintenant, mais je sais que quelque soit ma décision sur l'univers du jeu il me faudra un code pour dessiner un perso, un code pour dessiner un terrain, etc. et c'est sur ça que je bosse. Comme tu peux le voir la gueule de mon perso c'est un truc dessiné en 20 secondes sous paint.
De même pour le GUI, je ne suis pas en train de choisir l'emplacement des boutons (c'est impossible, je ne sais même pas quels boutons je vais mettre) mais de créer un système qui va me permettre de créer facilement des boutons par la suite (d'ailleurs si mon système fonctionne, vous allez être gueule béante :trollface:). Au temps les éléments de gameplay (terrain, personnages, bâtiments, etc.) sont des choses qui changent rarement, au temps l'interface et les menus ce sont des trucs qui doivent pouvoir se modifier en deux temps trois mouvements


Tu utilises une API quelconque pour l'affichage avec OpenGL, ou bien tu codes ça from scratch?

J'utilise juste GLEW pour me faciliter la tâche (j'ai oublié de le mentionner plus haut)

En fait j'ai juste codé un "wrapper" (pour rendre transparentes certaines opérations) et mes différents éléments à afficher créent eux-mêmes leur vertexbuffer, indexbuffer, shaders et textures (avec évidemment une gestion des ressources pour ne pas charger deux fois la même texture)
Ça paraît complexe, mais en fait pas du tout

war-p
28/03/2012, 11h27
Oui oui je vois! :)

Tomaka17
30/03/2012, 17h38
1 - Les outils de travail

Comme je l'ai dit plus haut, j'envisage de faire de temps à autres des mini-"tutos" sur tels aspects du jeu, histoire de satisfaire l'instinct de professeur qui sommeille en moi
Voilà donc le premier, sur tous les outils que j'utilise dans ce projet. C'est peut être un peu orienté "noob", donc désolé pour ceux qui ont déjà un peu d'expérience là dedans

Je code donc ce jeu uniquement grâce au langage de programmation nommé C++ (http://fr.wikipedia.org/wiki/C++)

Je créé des fichiers contenant des lignes de code, puis que je vais demander à un logiciel qu'on appelle un compilateur de transformer ces lignes en un vrai programme (opération nommée la compilation).

Sublime Text 2

Pour taper mon code, n'importe quel programme comme le bloc-note suffirait. Dans la pratique néanmoins il est très utile d'avoir quelques fonctionnalités comme le code qui se met en couleur (afin de repérer plus facilement les différents éléments), l'indentation automatique (encore une fois pour s'y retrouver), un rechercher-remplacer robuste (qui utilise les expression régulières), plusieurs onglets, etc.

Ainsi j'utilise Sublime Text 2 (http://www.sublimetext.com/), qui est un éditeur assez peu connu mais très complet.
Le choix de l'éditeur c'est surtout une question de feeling personnel, et moi j'aime bien celui-ci. Il propose pas mal de raccourcis sympas, il a un superbe look, permet de sélectionner plusieurs choses simultanément, et bien d'autres encore.

Visual C++ 2010

La prochaine étape est de compiler (http://fr.wikipedia.org/wiki/Compilateur) le projet. Pour cela, je donne tous mes fichiers à un compilateur, je lui indique ce qu'il doit faire avec, et il me sort un programme en .exe prêt à être exécuté.

Il existe de nombreux compilateurs C++ sur le marché, mais pour ma part j'utilise Visual C++ 2010 (dont la version express (http://msdn.microsoft.com/fr-fr/express/aa700735) est gratuite). Celui-ci a l'avantage d'être très performant et de proposer un débuggeur (logiciel permettant de diagnostiquer les erreurs dans un programme) très simple à utiliser.

La version 11 de Visual C++ est actuellement en beta. Je compte passer à cette version dès qu'elle sortira en version définitive.

CMake

Dans le processus de création d'un fichier exécutable, il ne suffit pas simplement d'exécuter le compilateur, mais il faut également configurer celui-ci selon certaines préférences. Par exemple je peux demander au compilateur d'inclure ou non les informations permettant de débugger le programme, ou alors lui demander d'activer certaines optimisations ou non.

Le problème c'est que chaque compilateur se configure différemment, il n'existe pas de "fichier de configuration universel" qui fonctionnerait avec plusieurs compilateurs. Si jamais je voulais changer de compilateur (par exemple Visual C++ n'existe pas pour Linux, donc si je voulais compiler pour Linux il faudra que j'en change) il faudrait donc que j'écrive un deuxième fichier de configuration propre à ce second compilateur.

Pour outrepasser ce problème, j'utilise CMake. Le principe est simple : j'écris un fichier de configuration en "langage CMake", et le logiciel CMake va le lire et créer un fichier de configuration pour le compilateur de mon choix.

Si je donne mon code source à quelqu'un pour qu'il le compile, la première chose qu'il va devoir faire c'est lancer le logiciel CMake et ouvrir un fichier nommé "CMakeLists.txt" qui est fourni avec mon code source et qui contient toute la configuration de mon projet.

CMake va alors demander à la personne quel compilateur il souhaite utiliser, puis va générer un fichier qui pourra alors être lu par le compilateur choisi.

Git

Il vous est certainement déjà arrivé de vouloir modifier un fichier sur un réseau, et de ne pas pouvoir le faire car quelqu'un d'autre a déjà ouvert ce même fichier. Comment font alors les studios de développement pour que plusieurs personnes puissent travailler sur le même projet ?

Tout simplement ils utilisent un logiciel de gestion de versions (http://fr.wikipedia.org/wiki/Logiciel_de_gestion_de_versions). Un logiciel de ce type permet synchroniser de manière intelligente les fichiers. Plusieurs personnes peuvent modifier le même fichier simultanément, et le logiciel va essayer de fusionner les modifications. Cela ne fonctionne évidemment que pour les fichiers texte (cela tombe bien, le code source est du texte) et pas sur les images ou les vidéos par exemple.

Évidemment cet aspect ne me sert pas à grand chose puisque je suis seul à travailler dessus. Néanmoins, les logiciels de ce type ont d'autres gros avantages, comme la conservation de tout l'historique de modification de chaque fichier, une copie de sauvegarde (évitant de perdre son travail si jamais on supprime un fichier par erreur), ainsi qu'un système de branches, qui permet par exemple de maintenir plusieurs versions différentes d'un projet

Git est le logiciel de gestion de versions que j'utilise, car c'est actuellement celui qu'on considère le "plus abouti".

GIMP et Inkscape

Il va me falloir dessiner des choses en 2D, et comme je n'ai pas de budget :tired:, j'utilise GIMP et Inkscape, les deux logiciels de dessin gratuits les plus connus.

Peut être d'autres...

J'ai peut être oublié d'autres outils, auquel cas je les rajouterai ici


Comment ça se passe en pratique ?

Pour commencer mon projet, je créé donc un petit fichier texte "monProjet.cpp". J'ouvre celui-ci avec Sublime Text 2 et je tape mon code en langage C++.

Je créé ensuite un fichier CMakeLists.txt qui contient toute la configuration du projet. J'ouvre ensuite le logiciel CMake, j'ouvre CMakeLists.txt, je choisis "Visual C++ 2010" et CMake me créé plein de fichiers que je peux ouvrir avec Visual C++. Je clique alors sur "compiler" Visual C++, et il me sort un fichier .exe correspondant à mon programme.

Content de mon travail, je créé un "repository" Git (un espace de stockage) et je sauvegarde mes fichiers dessus. Ces fichiers sont désormais en lieu sûr.

http://tof.canardpc.com/preview/ff2d937c-1e54-4fcc-b1ac-43a67670f431.jpg (http://tof.canardpc.com/view/ff2d937c-1e54-4fcc-b1ac-43a67670f431.jpg) http://tof.canardpc.com/preview/57747338-2691-48ac-96c4-c61659cb6717.jpg (http://tof.canardpc.com/view/57747338-2691-48ac-96c4-c61659cb6717.jpg) http://tof.canardpc.com/preview/e39e2242-9e5f-4759-bfff-4188126ba310.jpg (http://tof.canardpc.com/view/e39e2242-9e5f-4759-bfff-4188126ba310.jpg) http://tof.canardpc.com/preview/097862c4-7639-4f24-8b32-5775a00cc6fc.jpg (http://tof.canardpc.com/view/097862c4-7639-4f24-8b32-5775a00cc6fc.jpg)

war-p
30/03/2012, 17h48
Pourquoi tu codes pas directement sous visual studio? T'aime pas l'interface?

Tomaka17
30/03/2012, 17h56
Pourquoi tu codes pas directement sous visual studio? T'aime pas l'interface?

L'interface est bien, mais alors qu'est ce c'est lent à démarrer et qu'est ce que ça bouffe comme mémoire
Et toutes les 10mn j'ai un popup "visual c++ packet manager has crashed"
C'est une grosse usine à gaz

De plus j'utilise également sublime text 2 pour d'autres trucs (comme le web developpment) et donc je préfère utiliser un seul éditeur

war-p
30/03/2012, 18h20
Okay! Bon, perso j'utilise visual studio 2010 ultimate, et j'ai pas de soucis particulier, l'intérêt étant principalement de développer avec .NET, après, c'est vrai que si tu fais que du C++, ça te sert à rien.

Cuthalion
30/03/2012, 21h17
Sinon en alternative à Visual studio, il y a Qt Creator qui est bien plus léger, dispose de tout ce qu'on est en droit d'attendre d'un IDE moderne, possibilité (forcément) d'utiliser ses cmake ou makefile assez facilement, et une interface que je trouve plutôt bien pensé pour coder en C++

Son principal inconvénient à mon sens se trouve dans son nom, Qt Creator, et dans l'absence d'onglets qui peut rebuter au début.

Tomaka17
03/04/2012, 17h11
Juste pour préciser, si je poste rien c'est pas que je glande, c'est juste que j'ai rien à montrer

J'ai dit plus haut que je bossais sur le système de GUI et que vous alliez être gueule béante
En fait c'est tout simplement parce que je suis en train d'écrire un petit moteur de rendu CSS (et que celui-ci commence à fonctionner)
Une fois terminé je ferai un petit compte-rendu sur comment il fonctionne

Tomaka17
05/04/2012, 19h01
Après un peu de réflexion, je suis parti sur les éléments suivants
Il y a des chances que je change en court de route, mais dites moi ce que vous en pensez pour l'instant

- le jeu se déroule dans un univers médiéval-fantastique
- chaque joueur commence avec une citadelle (un bâtiment), la perte de la citadelle c'est le game over
- dans la plupart des RTS, les parties sont des escarmouches, par exemple dans age of empires le scénario pourrait être "les français et les espagnols se battent pour le contrôle de ces terres fertiles", alors que dans ce jeu ce serait plus un conflit global, c'est à dire "france vs espagne, il n'en restera qu'un"

La map :
- le jeu se jouera "de loin" au niveau du zoom, c'est à dire plus comme RUSE que comme SC2/AoE
- la map comportera des montagnes, des fleuves et des arbres ; les montagnes empêchent le passage, les fleuves et les forêts sont uniquement décoratifs
- on y trouvera des emplacements prédéterminés où peuvent se construire des mines, avec des emplacements facilement accessibles par chaque joueur, et d'autres emplacements plus contestés

Les unités :
- contrairement à warcraft 3, ici les unités seront nombreuses et fragiles, un peu comme les zerglings de starcraft 2
- les unités : villageois, infanterie, archer, cavalier, catapulte, l'espion
- l'infanterie se bat à pied, coûte très peu
- l'archer est très fragile, coûte un peu plus cher et a l'avantage de tirer à distance
- le cavalier coûte cher mais pas excessivement, en faible nombre ils peuvent harass, en grand nombre ils font office d'infanterie sous stéroïdes
- une catapulte coûte très cher
- les unités ne sont pas directement contrôlables mais doivent être liées à une armée dirigée par un général
- les généraux sont recrutables dans la citadelle
- je ne sais pas encore comment vont se présenter les batailles, mais j'ai quelques idées que je détaillerai en temps et en heures
- peut être une autre unité : la milice ; on pourrait convertir toute la population en milice pendant une courte durée

Bâtiments :
- il y a deux types de bâtiments : les gros bâtiments (citadelle par exemple) qui sont chères et ne se construisent qu'une fois chacun, et les petits bâtiments qu'on construit de nombreuses fois
- les gros bâtiments sont accessibles directement via raccourcis, comme dans company of heroes
- le centre de commerce (gros bâtiment) permet d'échanger d'acheter du fer à prix exorbitant ou recruter des soldats contre de l'or
- la caserne (gros bâtiment) permet de recruter les unités
- la forge (gros bâtiment) permet d'upgrader les unités contre du fer et de l'or
- le centre d'espionnage (gros bâtiment) permet de recruter des espions
- la banque (gros bâtiment) fournit chaque seconde un petit montant d'or ; en payant une grosse somme on peut "upgrader" la banque pour augmenter le montant gagné
- le petit bâtiment "mine de fer" ne se construit que sur les emplacements de mine de fer (merci captain obvious) et ramène du fer
- la maison (petit bâtiment) se construit par pelletées et ne coûte presque rien
- des tours de garde (petit bâtiment)
- peut être des murs

Les ressources :
- l'or, la population (c'est une ressource qui monte et qui descend, pas juste un maximum d'unité), le fer
- à intervalle régulier, la population augmente d'un montant égal au nombre de maisons que possède le joueur
- chaque seconde, l'or augmente d'un montant égal à la quantité de population moins le nombre de soldats que l'on possède
- l'or augmente également si le joueur possède une banque
- il y a deux styles d'économie : celle basée sur de grosses quantités d'or (on recrute ses soldats au centre de commerce) et celle basée sur l'or et le fer
- les unités coûtent soit un peu d'or, de pop et de fer dans la caserne, soit beaucoup d'or dans le centre de commerce
- les bâtiments consomment de l'or et de la population pour se construire

Divers :
- j'aimais bien le système de decks de AoE3, c'est à voir

war-p
05/04/2012, 19h11
Dans tes choix de game design tu parles de conflit global + RUSE. Tu vas faire un système de conquête de zone, ou bien, ça se passera autrement?

Tomaka17
05/04/2012, 19h23
Jvais faire ça comme dans les RTS classiques, si l'armée ennemie détruit ta mine ben t'as moins de ressources et si elle arrive dans ta base principale ben t'es un peu dans la merde

Touze
05/04/2012, 19h42
On va pouvoir convertir les catapultes à une quelconque religion comme dans age of empire ?:ninja:

Tomaka17
07/04/2012, 13h03
J'ai doucement commencé à travailler sur le terrain ce matin, mais mon debugger OpenGL plante quand j'essaye de voir les textures, du coup je suis obligé de travailler à l'aveuglette :tired:

Tomaka17
07/04/2012, 23h33
Bon ben cet après-midi j'ai fait le système d'inputs bruts, c'est à dire que je peux maintenant détecter quand on appuie/relâche une touche du clavier, quand on bouge/scroll/clique avec la souris ou quand on appuie sur un bouton/bouge un axe d'un joystick/joypad/volant quelconque, même si ce dernier aspect ne servira pas à grand chose pour un RTS

En fait j'ai découvert que DirectInput (le truc que je voulais utiliser au départ) est déconseillé pour les nouveaux projets, et qu'ils recommandent plutôt d'utiliser l'API raw input (http://msdn.microsoft.com/en-us/library/windows/desktop/ms645536%28v=vs.85%29.aspx), du coup c'est ce que j'ai fait

À noter que la détection des inputs (c'est à dire les boutons appuyés et les mouvements de souris) et les inputs de texte sont deux choses différentes. Par exemple quand j'appuie sur 'A', je reçois un message qui me dit qu'on a appuyé sur la touche numéro je-sais-pas-combien (chaque touche du clavier a un numéro appelé "scancode"), puis un autre message qui me dit qu'on a écrit la lettre 'a'

Si maintenant j'appuie sur '`' par exemple je vais recevoir un seul message me disant qu'on a appuyé sur la touche numéro je-sais-pas-combien. Ensuite si j'appuie sur 'U' je vais recevoir un message me disant qu'on appuyé sur la touche numéro trucmuche et un deuxième message me disant qu'on écrit un 'ù'.


Heureusement on peut toujours demander à windows de nous indiquer le nom correspondant à un scancode (afin d'afficher ce nom dans la fenêtre de configuration par exemple), et à ce moment là windows va répondre différemment selon si c'est du qwerty, du azerty ou autre chose


Au niveau du terrain j'étais parti sur un truc à base de heightmap, mais je me demande si je vais pas tout bêtement indiquer les emplacements/tailles des montagnes, des rivières, etc. dans un simple fichier et générer le terrain pseudo-aléatoire au moment du lancement de la map (évidemment la seed (http://fr.wikipedia.org/wiki/Graine_al%C3%A9atoire) serait toujours la même pour éviter que la map soit différente à chaque chargement)

Tomaka17
10/04/2012, 17h43
Le moteur de rendu CSS

Comme dit plus haut, je suis en train de coder un moteur de rendu CSS pour ce jeu, en mode "mains dans le cambouis"
Ça peut paraître très complexe vu comme ça, mais en découpant bien les différentes facettes, on s'en sort très bien

Le moteur prend donc en entrée un document XML, va lire les styles qui lui sont associés, va prendre en compte les mouvements/clics de la souris, et va dessiner le tout à l'écran

(note : si vous lisez ce post sans avoir jamais fait de XHTML ou CSS, vous n'allez rien comprendre)


La hiérarchie XML

Un document XML est une hiérarchie de noeuds (ce n'est pas moi qui le dit, c'est le DOM (http://www.w3.org/TR/dom/)). Chaque noeud est soit un élément, soit un attribut, soit un texte, soit un CDATA, soit un commentaire. On va ignorer les attributs et les commentaires, et traiter les CDATA et le texte indifféremment. Il ne reste donc plus que deux types de noeuds : les éléments et le texte.

Par exemple dans le code XML suivant "<p>Je m'appelle <strong>Tomaka</strong></p>" on a :
* Un noeud de type élément <p>
* Cet élément a deux fils : un noeud texte "Je m'appelle " et un noeud élément <strong>
* L'élément <strong> a lui même un fils, un noeud texte "Tomaka"

J'ai une classe "XMLDocument" qui se charge d'analyser le code XML et de le découper en différents noeuds. Parser du XML c'est assez "simple" à condition de passer beaucoup de temps sur les expressions régulières.


Lire les styles

L'une des choses que devra faire notre moteur de rendu est de déterminer quels styles sont appliqués à quels éléments. Le standard définit de manière très précise (http://www.w3.org/TR/CSS2/cascade.html#cascade) d'où viennent les propriétés appliquées à un élément.

On va donc coder une fonction que j'appelle "buildSpecifiedValues" qui va prendre comme paramètre un noeud XML et qui va renvoyer un tableau de toutes les valeurs CSS spécifiées par le créateur du XML ou de la feuille de style.
Cette fonction va commencer par lire l'attribut "style" du noeud, s'il existe. Elle va également lire tous les sélecteurs de la feuille de style et filter ceux qui correspondent à notre noeud. J'ai pour cela une autre fonction nommée "doesSelectorMatch" qui va, grâce à des expression régulières, dire si oui ou non le noeud XML correspond au sélecteur demandé. Pour vous donner une idée, voici le début du code source de cette fonction (http://pastebin.com/4ktUXLqX) (pas encore terminée).

Une fois l'attribut "style" et les sélecteurs lus, la fonction calcule la "spécificité" de chaque élément, puis nous créé un tableau (de type "std::unordered_map<CSSProperty,CSSValue>" avec CSSProperty une énumération) associant à chaque propriété CSS une unique valeur. Seules les propriétés CSS ayant été écrites par l'utilisateur se trouve dans les tableaux générés par la fonction.


Parser les valeurs des propriétés CSS

Mais on va devoir à un moment donné utiliser ces valeurs, c'est à dire les interpréter pour appliquer leur effet. Si je vous dis "3px solid black", vous en tant qu'humain comprenez que c'est une bordure de 3px, de style "solid" et de couleur noire. Mais il peut y avoir énormément de combinaisons possibles, par exemple "attr(width px) solid hsl(160,50,50)". Il nous faut trouver une méthode pour interpréter cela.

Nous avons d'abord ce que j'appelle une "valeur CSS", par exemple "3px solid black". Cette valeur est composée de trois "valeurs individuelles" (c'est le nom que je leur donne) séparées par des espaces : "3px", "solid" et "black".
On peut remarquer qu'en CSS, à l'exception d'une seule propriété qui utilise le slash (je ne sais plus laquelle), toutes les valeurs peuvent s'écrire "x" ou "x x x x" ou encore "x x x, x x x, x, x x" avec 'x' une valeur individuelle.
En C++, j'ai donc un type "CSSValue" qui est un "std::vector<std::vector<CSSIndividualValue>>". Il s'agit d'un tableau à deux dimensions, la première dimension est pour la séparation par virgule, et la deuxième est pour la séparation par espaces.
J'ai codé une fonction nommée "parseValue (http://pastebin.com/qFLP0tk4)" qui prend comme paramètre une string et qui me renvoie une CSSValue.

Quant aux valeurs individuelles, cela peut être "5%", "15px", "rgb(50,50,50)", etc. J'ai donc une fonction "parseIndividualValue" (utilisée par parseValue notamment) qui va m'analyser ce truc et me sort un objet de type "CSSIndividualValue".

Mais comment faire quand on a "attr(width px)" par exemple, qui pour rappel signifie "la valeur de l'attribute width en pixels" ? Et bien l'objet "CSSIndividualValue" est composé de plusieurs objets de type "std::function", en fait une fonction par type de donnée possible (longueur, couleur, image, durée, etc.)
Pour connaître le type d'une valeur individuelle, il suffit de regarder quelles fonctions sont définies, puis d'appeler la fonction en question en passant le noeud concerné comme pointeur.

Par exemple si j'appelle parseIndividualValue("5px");, la fonction va me renvoyer un objet CSSIndividualValue. Cet objet a tous ses membres à 0, mis à part la fonction "toLength", ce qui permet d'informer du type de la valeur. Ensuite j'appelle valeur->toLength(nodePtr); et la fonction me renvoie 5. De cette façon, je peux traiter tous les cas particuliers à base de "attr", "calc" ou "cycle".

Les mots-clés sont gérés de la même façon, avec une fonction "toKeyword" qui renvoie une valeur prise dans un enum de tous les mots-clés du CSS.


Specified, computed et used values

Chaque propriété CSS passe par trois étapes. Lorsqu'on lit la valeur d'une propriété depuis la feuille de style, on appelle cela une "specified value" (en français : valeur spécifiée).

La deuxième étape consiste à convertir toutes les specified values en "computed values" (en français : valeurs calculées). Cela signifie concrètement que l'on va remplacer tous les mots-clés "inherit" et "initial" par leur véritable valeur, qu'on va remplacer toutes les valeurs relatives (par exemple les valeurs en em) par des valeurs absolues, ainsi que différents autres traitements en fonction de la propriété.

Toutes les propriétés qui ne sont pas dans la liste des propriétés "specified" sont ajoutées, afin que les computed values soient l'ensemble des propriétés CSS existantes. On supprime également les raccourcis (comme "border", "background", "font", "margin", etc.) en les découpant.

En fait vous remarquerez que pour créer la liste des "computed values", on a besoin des computed values du noeud parent. C'est là même la définition des "computed values", c'est la transformation des valeurs en prenant en compte les valeurs du noeud parent.

La troisième étape consiste à convertir les "computed values" en "used values" (en français : valeurs utilisées). Pour calculer les used values, on va avoir besoin des used values des enfants cette fois-ci, ce qui signifie qu'il faut calculer les used values de bas en haut.

La plupart des propriétés ne changent pas de valeur lors de la conversion en used values. Ce sont principalement les propriétés "width", "height", "margin", etc. quand elles sont mises sur auto qui sont calculées à ce moment là.

Le calcul des valeurs se fait donc en plusieurs étapes : d'abord on créé la liste de toutes les specified values pour chaque noeud. Ensuite on descend la hiérarchie en calculant tous les computed values. Enfin on fait un deuxième passage, mais à l'envers cette fois (de bas en haut), pour calculer les used values.

Dans la pratique, plutôt que de calculer tous les computed values d'un coup et tous les used values d'un coup, j'ai codé des fonctions "getComputedValue" et "getUsedValue" qui vont calculer la valeur d'une seule propriété à la fois. Une fois le calcul effectué, elles stockent le résultat dans un cache de façon à ce que la prochaine demande soit beaucoup plus rapide. Il ne faut en revanche pas oublier de vider le cache lorsque le document XML est modifié.


La hiérarchie dans notre moteur CSS

L'autre tâche que devra effectuer notre moteur de rendu CSS, c'est de transformer la hiérarchie XML qu'on lui donne en une hiérarchie pouvant être dessinée à l'écran.

Il y a deux types de noeuds XML (élément et texte) et il y a également deux types de noeuds CSS : block-level et inline-level. Un noeud texte en XML est forcément inline-level, et un noeud élément peut être les deux (cela dépend de la valeur de sa propriété display).
Par exemple les éléments de type <strong> sont typiquement inline-level, alors que les <div> sont typiquement block-level. À noter que les noeuds dont la propriété est "inline-block" sont inline.

Il y a un troisième type de noeud CSS : les lignes. Ces noeuds ne sont pas visibles et servent en fait à nous faciliter la tâche.

Voici les deux règles importantes à suivre :
- un noeud block-level ne peut contenir que des enfants de type block-level ou que des enfants de type ligne, mais jamais de inline-level ou de mix de plusieurs types.
- un noeud ligne ne peut contenir que des enfants inline-level

Si on reprend l'exemple plus haut "<p>Je m'appelle <strong>Tomaka</strong></p>", cela sera découpé comme suit :
- un noeud block-level qui correspond au <p>
- celui-ci contient un noeud ligne
- le noeud ligne contient deux enfants inline : le "Je m'appelle " et le <strong>

Si jamais on redimensionne la fenêtre pour la rendre toute petite, on pourra créer d'autres noeuds lignes, et mettre le <strong> dans le deuxième noeud ligne plutôt que dans le premier.

Si on prend un autre exemple : "<div>je suis texte <p>je suis block</p></div>"
Que va contenir le <div> ? Des blocks ou des lignes ? Et bien on va en fait englober "je suis texte " dans un block anonyme (c'est à dire qui ne correspond à aucun noeud XML).
Le <div> contiendra donc deux enfants de type block : le premier anonyme qui contient des lignes qui contiennent "je suis texte ", et le second qui correspond à <p>, qui contient lui aussi des lignes qui elles contiennent "je suis block".

Les blocks anonymes servent également aux pseudo-éléments CSS, comme ::before, ::after, ou pour rajouter le petit marqueur devant les éléments d'une liste <ul> ou <ol>.

J'utilise le même principe de cache que pour les valeurs, c'est à dire que j'ai une variable nommée "childrenAreUpToDate", qui si elle est mise sur false, forcera mon programme à recréer la liste des enfants d'un noeud.

Pour créer cette hiérarchie de noeuds CSS, on utilise la récursivité. On créé d'abord les enfants d'un noeud en particulier, puis on appelle la même fonction pour chaque enfant. Ainsi si je suis de type "block-level" et que je n'ai pas d'enfant XML de type block, je vais m'orienter vers des lignes. Si en revanche j'ai un enfant de type block, je vais m'orienter vers des blocks.
Lorsque la fonction est appelée sur une ligne, celle-ci calcule sa longueur, et si celle-ci est trop élevée, se divise en deux.

Cette méthode de découpage permet également de gérer le cas du ":first-line"


Le dessin

Dessiner la hiérarchie de noeuds CSS est la dernière étape qui n'est pas tout à fait au point dans mon moteur, d'où l'absence de screens.

Il y a quatre choses à dessiner pour chaque noeud : l'arrière-plan, les bordures, le contenu, et l'outline (j'ai zappé ce dernier pour le moment, ça ne sert pratiquement à rien).
Il suffit pour cela d'interpréter les used values des propriétés concernées et de créer des fonctions de dessin correspondantes. Encore une fois j'utilise un système de cache avec mes std::function. Lorsque le noeud change, je mets la fonction à 0, ce qui force un rafraichissement.


Interaction avec la souris

Ça je n'ai pas encore commencé ^_^
En fait grâce aux matrices il est facile de déterminer sur quel élément se trouve la souris.
Il suffit alors de vider le cache de toutes les valeurs et de recréer les specified values, en prenant cette fois en compte le :hover.
Évidemment je vais stocker dans une variable une information selon si oui ou non il y a un changement quand la souris passe sur tel élément, sinon les performances risquent de chûter.


Conclusion

J'en suis à 835 lignes de code pour mon .cpp et 331 pour mon .hpp
Je pense avoir bien cerné le sujet, c'est à dire que je ne pense pas me retrouver bloqué devant un obstacle

D'ici la fin de la semaine je pense avoir un rendu correct (même si certains aspects comme les marges qui se collapsent, les images, etc. ne fonctionneront pas encore) mis à part peut être le texte, puisque celui-ci ne fonctionne même pas encore en stand-alone

Bonne lecture

---------- Post added at 16h43 ---------- Previous post was at 16h05 ----------

Par contre je sens que je vais devoir faire péter du screenshot si jveux ramener du monde sur ce topic :rolleyes:

alecool
11/04/2012, 19h12
En tout cas, il y a l'air d'avoir un sacré boulot derrière. C'est clair que pouvoir faire des GUI sur base de HTML/CSS, ca m'aurait fait gagner pas mal de temps sur certains projets. Je ne pourrait que te conseiller de faire de ton parser/afficheur une bibliothèque séparée redistribuable.

Tomaka17
11/04/2012, 20h24
Ça peut se faire, mais il faut déjà que ça marche tout court :p

Pour l'instant je suis en train de repenser l'algo qui créé la hiérarchie CSS, parce qu'il y a pas mal de cas qui bug à mort actuellement, comme "<em>Ceci est du <p>texte</p></em>", c'est à dire un block dans un inline

J'hésite entre utiliser un seul shader pour bordure + arrière plan, ou bien faire deux dessins l'un au dessus de l'autre, mais ça c'est des détails

Tomaka17
17/04/2012, 00h55
Par contre je rencontre un petit problème, c'est que ma méthode de gros bourrin est carrément vachement lente
Alors certes je compile en debug et j'ai quelques grosses couilles niveau perf dans mon code, mais là ça met environ une demi seconde pour parser cette feuille de style (http://www.w3.org/TR/CSS2/sample.html) qui n'est pourtant pas bien grande
Et pire, ça met environ 2 secondes à créer toute l'arborescence et les fonctions d'affichage pour "<html>Je m'appelle <em>Tomaka</em></html>"

Je crois qu'il va falloir que je fasse un peu de benchmark et que je multiplie les caches

Bref, en attendant je pense que je vais passer sur autre chose, parce que mine de rien ça commence à faire beaucoup de temps passé là dessus

EDIT : en fait j'ai déjà corrigé 2 "grosses couilles" à l'instant et c'est déjà beaucoup plus rapide

war-p
17/04/2012, 12h18
Tu pourrais montrer le bout de code avant et après optimisation? (Je suis très intéressé par des méthodes de parsing maison efficaces...)

Tomaka17
17/04/2012, 12h52
En fait les grosses couilles dont je parle sont assez simples

J'avais un bout de code comme ça :


static CSSPropertiesSet defaultValues;
static std::once_flag onceFlag;

std::call_once(onceFlag, [&]() {
defaultValues.insert(std::make_pair(CSS_PROPERTY_B ACKGROUND_ATTACHMENT, parseValue("scroll")));
defaultValues.insert(std::make_pair(CSS_PROPERTY_B ACKGROUND_CLIP, parseValue("border-box")));
defaultValues.insert(std::make_pair(CSS_PROPERTY_B ACKGROUND_COLOR, parseValue("transparent")));
etc.
});

return defaultValues.at(valeurDemandee);

Normalement le système avec le once_flag fait que les insert sont appelés une seule fois pour remplir le CSSPropertiesSet une bonne fois pour toutes, et on utilise le même tout au long du programme

Mais Visual C++ a un bug qui fait que ça ne marche pas (bug de compilation parce qu'il ne reconnaît pas mon énumération CSS_PROPERTY_machin, bref c'est un vieux bug à la con)
Du coup j'ai temporairement viré mon système de once_flag, et les insert se faisaient à chaque fois ainsi que les appels de "parseValue"

J'avais le même problème sur une autre fonction, et rien qu'en mettant un "if (properties.empty())" autour, le parsing de la feuille de style dure maintenant grosso modo un dixième de seconde (on sent un tout petit accrochage, j'ai pas mesuré précisément)

Pour ce qui est de la création de la hiérarchie qui dure 2 secondes, j'ai pas encore regardé ce qui causait ça, mais ça me surprend car mine de rien je n'ai pas tant d'opérations que ça à faire


Sinon niveau parsing là j'utilise pas mal d'expressions régulières qui sont ultra-lentes
Mais je vais par exemple écrire un petit dérivé de find_if (http://www.cplusplus.com/reference/algorithm/find_if/) qui va me trouver le premier caractère demandé qui ne soit pas à l'intérieur de guillemets/parenthèses/crochets, et ça va me permettre de remplacer pas mal d'expressions régulières par ça

Je vais aussi remplacer tous les paramètres "const std::string&" par "std::string::const_iterator, std::string::const_iterator" comme ça je lis à chaque fois la même chaîne de caractères, plutôt que de créer des string
C'est toujours ça de gagné

De toutes manières la méthode la plus rapide pour parser un truc c'est d'avancer lettre par lettre dans la chaîne de caractères, tu peux pas faire mieux je pense

Tomaka17
17/04/2012, 15h35
Mais pour ceux que ça intéresse, le code source actuel du mon moteur CSS (http://www.mediafire.com/?aez8776wo2615ps)

Tomaka17
17/04/2012, 18h42
En tout cas j'ai fait un petit profiling
99.27 % du CPU consommé par mon moteur CSS c'est pour les regexp, au moins maintenant j'en suis certain

Tomaka17
17/04/2012, 23h44
Bon ben après avoir supprimé toutes les expressions régulières : ~90ms pour parser la feuille de style et ~40ms pour créer toute la hiérarchie des trucs à afficher
En fait plus haut j'ai dit que ça mettait 2 secondes à créer la hiérarchie, mais il y avait en réalité un bug, et une fois corrigé je me suis rendu compte que ça durait en réalité une trentaine de secondes (oui j'ai bien dit secondes, pas millisecondes), ce qui est colossal ; à mon avis l'implémentation des expressions régulières est foireuse parce que là c'est juste pas possible

Je pense que les temps que j'ai maintenant sont pas mauvais ; je compile en debug (ce qui ralentit l'exécution) mais en contrepartie j'ai 4 Ghz dans le moteur, donc j'imagine que sur un PC un peu plus vieux les deux s'annulent
EDIT : cela dit en release c'est tellement rapide qu'il me dit "0.00 secondes" pour le tout, la précision n'est pas assez grande
L'inconvénient maintenant c'est que je vais devoir écrire quelques tests pour voir si tout est bien parsé comme prévu, car là c'est un peu plus acrobatique

Faut voir aussi que là j'ai tous les caches que j'ai mis en place qui sont vides, c'est à dire que je créé tous les specified values, computed values, used values, etc. de tous les éléments. Lorsqu'on bougera simplement la souris il n'y aura pas tout à modifier, et donc avec un peu de bidouillage supplémentaire j'ai des chances d'atteindre le temps idéal qui serait de 3-4ms max lorsqu'on bouge la souris ou qu'on clique (une frame dure 16.6ms pour rappel)


Mais quoiqu'il en soit je crois que je vais mettre ça en pause et commencer vraiment l'apparence du terrain, histoire d'avoir un truc visuel

Sahnvour
18/04/2012, 18h44
Super intéressant ton "carnet de développeur", continue comme ça ;)
Hâte de voir ce que ça peut donner à l'usage ton moteur de rendu CSS !

Tomaka17
18/04/2012, 19h32
Merci bien ;)


J'ai commencé la création du terrain
Avec ma petite heightmap faite en 30 secondes sous paint :
http://tof.canardpc.com/preview2/8d31af76-74e2-4926-9fce-bcc4aa5db3eb.jpg (http://tof.canardpc.com/view/8d31af76-74e2-4926-9fce-bcc4aa5db3eb.jpg)

En appliquant un petit noise et un gouraud shading tout simple, j'obtiens ce résultat :
http://tof.canardpc.com/preview2/aaca96c8-26f7-496f-8c91-a16069430eda.jpg (http://tof.canardpc.com/view/aaca96c8-26f7-496f-8c91-a16069430eda.jpg)

Maintenant le "défi" ça va être :
- créer une heightmap correct
- créer des textures correctes à mettre dessus

Je vais essayer de créer les deux de manière procédurale, c'est à dire que j'indique la position des montagnes, des rivières, etc. et la heightmap/texture se génèrent tout seuls

Pour l'instant c'est tout vilain, mais ça va vite s'améliorer (j'espère)

ElGato
19/04/2012, 10h47
Je suis super curieux de voir ce qu'on pourrait faire avec le moteur de rendu CSS, même si 4 ms ça fait encore un peu beaucoup.

Pour OpenGL, je me demandais où tu trouvais des infos intéressantes pour apprendre...Les tutos de Nehe sont encore une référence ?

Tomaka17
19/04/2012, 11h18
En fait j'ai dit 4 ms comme le temps maximum quand tu bouges la souris
Faut bien voir que tu peux avoir des règles CSS du genre "p { display: block; } p:hover { display: inline; }", et donc quand tu passes la souris sur le "p" ça doit tout recalculer

Mais comme dit sur mon post plus haut, quand je compile en release la précision de mon profiler n'est pas assez grande, il indique 0.00 secondes pour parser la feuille de style et créer la hiérarchie de mon mini document
Du coup je pense que je vais être bien en dessous des 4ms



Pour OpenGL, je me demandais où tu trouvais des infos intéressantes pour apprendre...Les tutos de Nehe sont encore une référence ?

Les tutos de NeHe sont très vieux et utilisent des fonctions de OpenGL 2.1
Maintenant on a une conception légèrement différente du rôle de la carte graphique (plus générique, on peut lui dire de faire à peu près n'importe quel calcul, ce qui ouvre un large champ de possibilités)
D'ailleurs si tu vas sur son site il a mis "legacy tutorials"

Le problème c'est que tout s'est complexifié au moment du passage entre la version 2 et la version 3
Si tu programmais déjà avec OpenGL avant, le passage était pas très compliqué, mais si tu débutes c'est assez chaud

ElGato
19/04/2012, 11h38
Ok pour les règles CSS, donc j'attends ça avec encore plus d'impatience !

Pour OpenGL, en fait, c'est exactement mon problème : j'ai appris 2-3 trucs il y a très longtemps, et je pensais reprendre from scratch en utilisant quelque chose de moderne...Sauf que voilà, j'ai pas trouvé d'équivalent moderne aux tutos de Nehe. Je sais bien que tout a beaucoup changé entre-temps, mais à part me taper des forums entiers, des tonnes d'articles éparpillés un peu partout, impossible de trouver de référence, ne serait-ce que pour détailler la bonne version d'OpenGL à utiliser en fonction des besoins, les "bonnes pratiques" en fait.

Du coup j'imagine que toi a appris il y a longtemps et que tu t'es maintenu à jour ?

Tomaka17
19/04/2012, 12h10
En fait il y a trois grosses versions d'OpenGL : la version 2.1, la version 3.3 et la version 4.2 (la dernière)
Tous les drivers graphiques supportent l'une des trois versions, je ne crois pas que ça existe une carte qui supporterait par exemple la version 3.2 mais pas la version 3.3

La version 2.1 fonctionne sur à peu près tous les PC du monde mais est vieille, la 3.3 c'est la version actuelle pour les développeurs (celle que j'utilise) car elle est supportée par toutes les cartes graphiques depuis 2007/2008, et la version 4.2 c'est la version pour ceux qui veulent expérimenter les trucs de demain, elle est déjà supportée par les cartes graphiques modernes mais si tu codes un truc qui l'utilise ça ne fonctionnera que sur les geeks hardware qui mettent à jour leur matos régulièrement

En faisant quelques recherches sur le net je suis tombé là dessus (http://duriansoftware.com/joe/An-intro-to-modern-OpenGL.-Table-of-Contents.html), ça a l'air pas mal
Mais c'est vrai qu'une bonne référence OpenGL 3 pour débutant ça manque vraiment

Le fonctionnement d'une carte graphique c'est grosso-modo ça :

http://duriansoftware.com/joe/media/gl1-pipeline-01.png

Tu indiques à la carte graphique une liste de points, et une liste de figures à dessiner (des triangles en général) qui relient les points que tu lui as donnés. L'objet que tu dessines est composé de toutes les figures indiquées.

Ensuite tu lui dis également "effectue ce calcul que je t'indique sur chaque point", c'est ce qu'on appelle vertex shader. Le vertex shader est exécuté une fois pour chaque point que comporte l'objet à dessiner. Par exemple si tu fais "coordonnees += vec3(1,0,0);" ça décale tous les points de (1,0,0) et donc l'objet entier est lui aussi déclaré de cette quantité. Le vertex shader est d'ailleurs presque tout le temps utilisé pour positionner/redimensionner/tourner/etc. l'objet.
Tu pourrais faire ces calculs toi-même (en tant que programmeur) mais en le faisant faire à la carte graphique c'est non seulement plus rapide mais tu n'as pas besoin de lui renvoyer les points à chaque fois

Ensuite la carte graphique détermine quels sont les pixels à l'intérieur de chaque triangle que tu dessines (la rastérisation).
Tu lui dis alors "effectue ce calcul pour chaque pixel à afficher à l'écran", c'est le fragment shader. Le fragment shader est exécuté une fois pour chaque pixel à remplir sur l'écran, prend en entrée diverses informations sur ceux-ci, et a pour rôle d'indiquer à la carte graphique quelle couleur aura le pixel concerné. Par exemple si tu veux ajouter une texture ou une ombre, c'est dans le fragment shader qu'il faut le faire.

Bref, c'est un résumé très rapide mais bon

Tomaka17
20/04/2012, 21h14
Rien à voir avec mon jeu mais :


http://www.youtube.com/watch?v=SvLsOF-c0_0

Tomaka17
22/04/2012, 22h17
Bon, histoire de donner des nouvelles je suis en train de bosser sur le terrain

Sauf que là je viens de perdre 4 heures à debugger mon truc OpenGL qui se comportait super bizarrement parce que j'ai mis une coordonnée w à 0 :tired:
En gros la carte graphique faisait des divisions par zéro, renvoyait je-ne-sais-quel résultat, et moi comme un con je comprenais que dalle à ce qu'il se passait

Je dessinais l'objet à un endroit, ça m'affichait un petit rectangle vert
Je le dessinais 3 pixels plus loin (je faisais la modif directement dans le shader), ça m'affichait en plein écran un dégradé jaune

Et aucun avertissement, aucune erreur :tired:

war-p
22/04/2012, 22h22
:haha:

Pardon...

Tomaka17
23/04/2012, 16h43
Hop, première version fonctionnelle du code qui génère une heightmap

Donc pour ceux qui savent pas ce que c'est une heightmap, ça c'est le terrain vu de dessus : ce qui est rouge clair représente une altitude élevée, ce qui est rouge foncé représente une altitude faible
C'est généré de manière totalement aléatoire au moment où le programme démarre, mais en informatique ce qui est aléatoire ne l'est pas vraiment. Je peux facilement contrôler le processus pour que le terrain soit le même à chaque fois, parce que sinon ce serait un peu déroutant que la map change quand on charge une partie, ou que tous les joueurs en multi n'aient pas la même map

Dans la version finale l'aléatoire sera juste là pour dessiner les petits reliefs (les collines), et les emplacements des montagnes seront choisies à l'avance

Maintenant il me reste à dessiner le terrain en utilisant cette heightmap, et à créer la texture qui va le recouvrir

http://tof.canardpc.com/preview2/d9ede9c5-e575-4180-95ec-f47343bb346f.jpg (http://tof.canardpc.com/view/d9ede9c5-e575-4180-95ec-f47343bb346f.jpg)

---------- Post added at 15h43 ---------- Previous post was at 15h26 ----------

Bon ben ce ne fut pas long pour afficher le terrain en fonction de cette heightmap

Je n'ai pas encore codé dans la caméra, donc je peux pas trop explorer pour voir si tout est correct, mais je pense que c'est bon

Ci-dessous le terrain vu de loin et de côté, c'est pas très explicit pour vous mais chez moi ça tourne donc je comprends mieux le truc
Les reliefs semblent inversés (montagnes = en bas, prairie = en haut), je sais pas si c'est parce que je regarde d'en dessous ou si c'est un bug

http://tof.canardpc.com/preview2/1a55de2e-7b0f-48d5-bfbb-a8a9819bb8f1.jpg (http://tof.canardpc.com/view/1a55de2e-7b0f-48d5-bfbb-a8a9819bb8f1.jpg)

yourykiki
23/04/2012, 16h57
Salut, je poste pas souvent mais je suis les différents projets du forum avec intérêt.
Tu as l'habitude d'expliquer un peu les différentes étapes de ton développement, mais la je reste un peu sur ma faim. Je me demandais quel algo tu utilisais pour générer ta heightmap. Il semble (mais c'est peut être du aux captures) qu'il y ai beaucoup de bruit sur le rendu de la 2eme capture contrairement à la premiere. C'est volontaire ou tu es encore en phase d'expérimentation ?

Tomaka17
23/04/2012, 19h03
Je n'ai pas mis de bruit
Je ne sais pas exactement à quoi sont dus ces petits "pics" mais je suis en train d'expérimenter, à mon avis c'est tout bêtement la heightmap qui a trop de petites sautes de couleurs
Par exemple là j'ai fait quelques modifs dans des constantes et les pics ont disparu

Pour l'algo j'utilise simplement le "diamond-square algorithm"

Voilà même mon code source (http://pastebin.com/9EqrPyAW) pour générer la heightmap, certes un peu bordélique (jvous conseille de copier coller le raw en bas dans un éditeur un peu plus correct que pastebin, parce que là j'arrive pas non plus à me lire)
À noter que c'est pas forcément bien implémenté, encore une fois c'est juste la première version qui marche bien

---------- Post added at 16h43 ---------- Previous post was at 16h21 ----------

J'ai mis la caméra à peu près au bon endroit, j'ai peint tout ce qui était à une certaine altitude en blanc et le reste en vert, et ça commence doucement à ressembler à quelque chose

http://tof.canardpc.com/preview2/d9d9a607-73b0-41c6-bda5-4220f1abf432.jpg (http://tof.canardpc.com/view/d9d9a607-73b0-41c6-bda5-4220f1abf432.jpg)

---------- Post added at 18h03 ---------- Previous post was at 16h43 ----------

En modifiant un peu l'algorithme ça ressemble un peu plus à une map :

http://tof.canardpc.com/preview2/6f0b74c1-89ed-4c68-b6c1-dd58729f341a.jpg (http://tof.canardpc.com/view/6f0b74c1-89ed-4c68-b6c1-dd58729f341a.jpg)

yourykiki
24/04/2012, 14h43
Ok thx :)

Tomaka17
24/04/2012, 20h47
Quand je bosse sur un truc, j'aime bien avancer un peu sur chaque aspect l'un après l'autre, plutôt que de bosser des heures et des heures sur le même code
Du coup là après avoir commencé mon moteur CSS et après avoir commencé mon terrain, je vais commencer "l'état du jeu"
Vous inquiétez pas, j'aime beaucoup aussi la sensation du travail terminé


En fait dans beaucoup de petits jeux vidéos, les informations concernant le jeu et le code de dessin/collision/etc. sont mélangées
Par exemple tu as souvent une classe "Personnage" que tu utilises à chaque fois que tu veux dessiner un personnage, sauvegarder la partie, tester les collisions, etc. c'est un peu la classe à tout faire

Moi j'aime bien séparer tout ce qui est représentation visuelle et tout ce qui est données pures
Si vous connaissez le principe modèle-vue-controleur (http://fr.wikipedia.org/wiki/Mod%C3%A8le-Vue-Contr%C3%B4leur), c'est le même principe : j'ai un code qui contient les données (le modèle), un code qui affiche ces données à l'écran (la vue) et un code qui va traiter les demandes du joueur (le contrôleur)


En pratique, j'ai trois classes : une classe GameState qui contient l'état d'une partie, une classe ControlledGameState qui sert à "protéger" le GameState, et une classe GameDisplayer qui affiche cette partie à l'écran (dans afficher j'inclue la partie son) selon le point de vue d'un joueur.

Prenons par exemple une partie en solo. J'ai une instance de chacune de ces classes. Le GameDisplayer lit le GameState au travers du ControlledGameState (il peut tout lire, le controlled machin laisse tout passer niveau lecture)
Lorsque le joueur clique sur le bouton pour créer un soldat, la vue appelle la fonction "requeteDeCreationDeSoldat" du ControlledGameState
La classe ControlledGameState vérifie alors que le joueur en question ait effectivement le droit de faire ça (s'il a assez de ressources, etc.) et modifie le GameState en tenant compte des règles du jeu

On a donc :
- le GameState qui contient les données et qui autorise n'importe quelle lecture et n'importe quelle écriture mais qui s'assure toujours de la cohérence des données (par exemple s'il me dit que le joueur A est dans l'équipe X, mais que quand je lui demande les membres de l'équipe X, A n'y est pas, c'est que les données ne sont pas cohérentes, et ça c'est pas bien)
- le ControlledGameState qui contient en interne un pointeur vers un GameState ; il autorise n'importe quelle lecture du GameState mais interdit toute écriture directe ; il accepte des requêtes de la part des joueurs, et vérifie si les requêtes respectent les règles du jeu
- le GameDisplayer qui lit le GameState, affiche tout à l'écran, et traduit les actions souris/clavier/manette en requêtes à passer au ControlledGameState


Dans le cas d'une partie multijoueurs, par exemple un serveur dédié et deux joueurs connectés dessus.
Sur l'ordi de chacun des deux joueurs on a une instance de GameState, une instance de ControlledGameState et une instance de GameDisplayer
Sur le serveur on a une instance de GameState et une instance de ControlledGameState
On a de plus des classes qui vont se charger de synchroniser les GameStates du serveur vers le client ; en gros chaque modification du GameState effectuée sur le serveur est transférée vers le client
De plus les ControlledGameState de chaque client vont transmettre toutes les requêtes qu'ils recoivent au serveur, qui seront alors traitées par le ControlledGameState du serveur.

Pour faire un netcode sympa, on a les ControlledGameState du client qui traitent également directement la plupart des requêtes en plus de les transmettre, ce qui évite la sensation de lag
De même les classes qui font la synchronisation n'écrasent pas le GameState du client, ils vont faire les modifs par palliers, encore une fois pour éviter les saccades dûes au lag
Et ces mêmes classes de synchronisation ne synchronisent pas tout ; par exemple dans le GameState de chaque client il n'y aura pas d'information sur les ressources possédées par les autres joueurs, pour éviter des hacks à ce niveau là


Quand j'ai le temps j'écris la suite, sur comment je vais représenter l'état du jeu dans une seule classe sans que cette classe fasse 5000 lignes de code

---------- Post added at 19h47 ---------- Previous post was at 18h10 ----------

Petit détail mais je viens de laisser tomber OpenAL et je vais utiliser XAudio2, tout simplement car j'ai voulu faire tourner mon .exe sur le PC de quelqu'un d'autre et que le runtime OpenAL n'était pas installé
Les APIs sonores sont assez bidon (c'est un gros cran en dessous du graphisme) donc c'est pas une grosse charge de travail

ElGato
25/04/2012, 11h38
Merci pour les détails, c'est super intéressant.

Pour le son, quitte à utiliser une API avec des fonctionnalités assez haut niveau comme XAudio2, pourquoi carrément ne pas utiliser un vrai moteur audio comme Fmod qui est meilleur à peu près à tous les niveaux ? Ou carrément un vrai truc cross-platform, open source mais plus bas niveau comme PortAudio ?

Tomaka17
25/04/2012, 15h03
C'est un peu un choix au pif que j'ai fait pour la biblio son
Je n'utilise pas les fonctionnalités haut niveau, j'ai juste besoin d'un truc qui transmette ce que je lui donne au chipset sonore

À part OpenAL je connaissais pas de biblio son portable
Bizarrement quand je recherche PortAudio sur google tout est déjà marqué en visité, ainsi que des liens à l'intérieur de leur site
Donc à mon avis j'y ai jeté un coup d'oeil il y a longtemps et ça m'a pas plu, mais je me souviens plus pourquoi

---------- Post added at 14h03 ---------- Previous post was at 12h45 ----------

En fait je viens de jeter un coup d'oeil et PortAudio a l'air pas mal du tout et simple qui plus est, jvais un peu étudier ça

Sahnvour
25/04/2012, 15h14
Sinon tu as aussi le module audio de SFML qui doit être pas mal. Il y a pas mal de trucs haut niveau mais ça doit pouvoir aussi permettre de faire ce que tu veux assez simplement. Ça vaut peut-être le coup d’œil.

Tomaka17
25/04/2012, 19h07
Je reviens sur mon histoire de GameState

Comme dit plus haut, la classe "GameState" me sert à stocker en mémoire l'état complet d'une partie en cours
Par exemple si je codais un Pong, je devrais stocker la position de la palette du joueur 1, la position de la palette du joueur 2 et la position de la balle
Sauf qu'ici c'est nettement plus complexe que dans pong

Requirements

La classe doit :
- être thread-safe, c'est à dire que si je fais simultanément 3 lectures et 2 modifications, ça ne doit pas poser de problèmes
- proposer des fonctions qui permettent de lire les données souhaitées
- proposer des fonctions qui permettent de modifier n'importe quoi, tout en gardant la cohérence dans les données ; par exemple si je supprime un joueur, toutes ses unités/bâtiments doivent aussi être détruits (ou passer neutre), et le nombre de joueurs dans la partie doit diminuer
- proposer un système de callback qui permet de savoir quand une information a été modifiée ; par exemple mon GUI va demander au gamestate : "préviens moi quand la quantité de ressources possédées par le joueur X change, comme ça je peux mettre à jour les valeurs affichées à l'écran"
- être sérialisable, c'est à dire qu'on peut la transformer en texte qu'on va stocker dans un fichier (pour sauvegarder)
- être extensible, c'est à dire que si je veux rajouter par la suite un élément de gameplay, ça doit se faire le plus facilement possible

Tomaka17
26/04/2012, 11h53
Bon ben erratum

Ce que j'ai décrit plus haut (et supprimé depuis) c'était le système auquel j'avais réfléchi il y a quelques jours mais pas encore essayé en pratique
Dans la pratique, c'est juste beaucoup trop lourd à utiliser et à implémenter :
- en fait si je donne une entité au hasard à quelque chose, il ne peut pas savoir ce que c'est ; donc soit je créé plein de propriétés du genre "modèle", "texture", "animable", etc. et c'est très chiant à faire, soit je mets une propriété "type" mais qui me casse totalement l'intérêt d'avoir tout regroupé en entités
- même chose pour créer une entité : soit je lui indique son type, soit je lui indique 36000 machins bien loudingues
- depuis l'extérieur quand j'appelle la fonction "get" je suis obligé de mettre le namespace, c'est à dire que je dois écrire "state.get<State::GameState::PROPERTY_POSITION>(entity)" ce qui contribue à la lourdeur
- je peux pas mettre de callbacks pour les "actions instantanées", par exemple écrire du texte sur le chat ça ce serait fait en lisant une propriété "texteDuChat", en rajoutant le truc à la fin, et en écrivant la même valeur, ce qui encore une fois est lourd
- plein d'autres trucs que j'ai plus en tête

Du coup, je suis parti sur un système beaucoup plus simple (http://pastebin.com/BQ4Zbz37)

ElGato
26/04/2012, 12h30
C'est dommage d'enlever tout ce que t'as mis, du coup ça devient plus difficile de savoir "ce qu'il ne faut pas faire"...J'aime bien l'idée que t'essayes des trucs en direct, c'est beaucoup plus instructif que simplement se contenter de donner la solution idéale ; surtout que tu décortiques bien tes méthodes, c'est intéressant.

Tomaka17
27/04/2012, 19h48
J'vais continuer un peu mon "dev diary"

Là j'ai à peu près terminé la première version de ma classe GameState
Comme dit plus haut, la classe GameState sert à contenir l'état actuel d'une partie
Elle contient uniquement des setters et des getters qui permettent de lire ou modifier le status d'une unité, d'un bâtiment, etc.

Cette classe sert uniquement de base de données, elle ne contient pas de fonctions permettant de savoir ce qu'il se passe, ni de fonction permettant de donner un ordre à une unité par exemple. Ici c'est juste le status de la partie qui m'intéresse
D'ailleurs la classe pourrait avoir un opérateur et constructeur de copie, que je ne vais pas coder car c'est long et je ne vais pas l'utiliser, mais c'est encore une fois pour pointer du doigt le fait que je ne stocke même pas de pointeur vers une partie externe du programme, je ne stocke que des données brutes


La classe GameState ne connaît pas non plus les règles du jeu
Supposons que je code un RPG et que je décide que les points de vie max d'un personnage ou d'un monstre sont toujours égaux à son endurance multipliée par 10. On pourrait se dire "dans GameState je vais rajouter une fonction getMaxHitPoints() { return getEndurance() * 10; }"
Mais je ne vais pas faire comme ça. Même si j'ai décidé de mettre en place une règle "points de vie max = endurance * 10", je vais quand même séparer l'endurance d'un perso et ses points de vie max dans deux variables différentes. En effet je vais centraliser toutes les règles du jeu (par règle du jeu j'entends les formules de calcul et les constantes) dans une seule classe, pour pouvoir facilement les modifier. De plus rien ne dit que je ne vais pas créer un "boss" qui n’obéit pas à ces règles


Cette classe en question qui connait toutes les règles du jeu, elle s'appelle "LiveGameState" et remplace le "ControlledGameState" dont je parlais plus haut. Le nom est pas forcément très explicite, ça pourrait aussi s'appeller "RunningGameState" par exemple.

Cette classe va proposer un pointeur constant vers son GameState associé. Attention, pointeur constant, donc uniquement pour les lectures. Le LiveGameState n'autorise pas les écritures directes vers son GameState.

Le LiveGameState propose néanmoins une fonction "sendCommand" surchargée. On peut lui envoyer des commandes qui prennent la forme de structures. Par exemple pour déplacer une unité quelque part : "MoveUnitCommand c; c.unit = blabla; c.destination = blabla; liveGameState.sendCommand(c);"

Cette commande va non seulement être exécutée, mais aussi être stockée dans un tableau lisible par tous. Toutes les parties du programme possédant un pointeur vers le LiveGameState* vont donc pouvoir savoir quels événements se sont produits dans la partie.

En fonction de la commande, d'autres commandes peuvent être générées. Par exemple en envoyant la commande "construire un bâtiment", la liste d'événements contiendra non seulement "construire un bâtiment" mais aussi "ressources du joueur modifiées" (coût de construction), "nouveau bâtiment est apparu" et "unité villageois a reçu l'ordre de construire"
Les parties du programme qui écoutent ces changements vont alors répercuter tout ce qu'ils lisent dans cette liste. Par exemple le "UnitDisplayer" qui gère le villageois va remarquer que son villageois a reçu un ordre de construire et va donc le faire s'animer correctement. La classe qui gère l'interface va remarquer que les ressources du joueur sont modifiées et va changer la quantité de ressources affichée à l'écran


Cette classe connaît également toutes les règles du jeu. Quand un joueur veut lui demander quelque chose, elle vérifie que tout est bon et l'applique.
Elle possède également un thread qui va mettre à jour à intervalle régulier tout ce qui change à intervalle régulier : position des unités en cours de déplacement, pourcentage de construction des bâtiments qui augmente, ressources gagnées grâce au minage, etc.


Voilà
Tout cela n'est pas très visuel, mais bon
La prochaine étape c'est donc d'avoir un jeu qui tourne "pour de vrai", avec tout ce qu'il se passe d'affiché à l'écran

Tomaka17
08/05/2012, 16h47
Hop, pour donner des nouvelles

J'ai pas trop bossé dessus ces derniers temps (j'ai lâché au moment de la beta de Guild Wars 2, et je m'y suis pas vraiment remis depuis)

Pas que vous pensiez que j'ai abandonné

Tomaka17
07/10/2012, 22h06
Hop, histoire de donner des nouvelles
Le jeu en soi est plus ou moins figé, par contre j'ai bien bossé sur le moteur CSS aujourd'hui

cf : http://forum.canardpc.com/threads/56721-Le-topic-de-la-programmation-string-cha%C3%AEnes-cuir-et-compagnie?p=5918359&viewfull=1#post5918359

KiKine
13/02/2013, 16h19
Git est le logiciel de gestion de versions que j'utilise, car c'est actuellement celui qu'on considère le "plus abouti".

http://tof.canardpc.com/preview/097862c4-7639-4f24-8b32-5775a00cc6fc.jpg

Git avec un screen de tortoise SVN ??? ¬¬

Tomaka17
13/02/2013, 16h23
Euh, bonne remarque tiens.

Je me souviens plus mais c'était peut être un vieux screenshot ressorti du placard.
En fait j'utilisais TortoiseGit à un moment, dont l'interface est quasi la même que TortoiseSVN. Finalement j'ai laissé tomber tortoisegit vu qu'il m'a foutu une grosse merde sur un autre projet.

KiKine
13/02/2013, 16h26
rien ne vaut la bonne vieille ligne de commande
sinon TFS est directement intégré à VS