Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

Projets Discussion :

[WE-JV6] Swimming Brick


Sujet :

Projets

  1. #41
    Membre éprouvé
    Si vous parlez de l'emboîtement des dents entre les engrenages et les barres en haut et en bas, il est possible qu'ils ne soient pas parfaitement calés.

    Si vous parlez des deux engrenages sur la porte, en fait ils n'étaient même pas censés se toucher, mais je me suis rendu compte qu'ils étaient trop gros une fois que je voulais en caser 2 sur chaque porte, et je ne les ai pas rapetissés :p Mais c'est vrai que j'aurai pu les décaler, les rapprocher un peu et les faire s'emboîter...

  2. #42
    Membre éprouvé
    Allez, pour le plaisir:


  3. #43
    Expert éminent sénior
    trop fort, avec la correction
    Les 4 règles d'airain du développement informatique sont, d'après Michael C. Kasten :
    1)on ne peut pas établir un chiffrage tant qu'on a pas finalisé la conception
    2)on ne peut pas finaliser la conception tant qu'on a pas complètement compris toutes les exigences
    3)le temps de comprendre toutes les exigences, le projet est terminé
    4)le temps de terminer le projet, les exigences ont changé
    Et le serment de non-allégiance :
    Je promets de n’exclure aucune idée sur la base de sa source mais de donner toute la considération nécessaire aux idées de toutes les écoles ou lignes de pensées afin de trouver celle qui est la mieux adaptée à une situation donnée.

  4. #44
    Membre éprouvé
    Bonjour,

    Je suis toujours sur le chantier de la compilation complète des maps: pour l'instant, j'ai la géométrie statique, les collisions statiques (ces deux-là n'ont pas changé depuis la dernière fois), les mouvements de caméra, les entités "dynamiques" (que ce soit les brushes ou les modèles, avec le collider correspondant quand l'entité en question est solide), ce qui comprend les triggers, les collectibles, les "portes" et les leviers (les 3 derniers sont grosso modo juste des types particuliers de triggers), et les scripts. Les mécanismes sont toujours décrits et lus au runtime dans un fichier .json.

    J'ai aussi fait en sorte que toutes les données qui peuvent changer (que ce soit la position des entités, leur état ou d'autres infos) soient contigües, pour pouvoir les sauvegarder d'un seul bloc. Mais pour l'instant, je trouve la façon dont j'ai fait ça assez crade. De plus, ce sont seulement les données appartenant aux maps qui sont rassemblées, d'autres infos, comme la position et la vitesse du joueur n'ont pas bougé.

    J'ai fini par créer un petit langage de script simple, pour remplacer les arbres dégueux décrits en json que j'avais avant. Ça a commencé en voulant refactorer la "machine virtuelle" (c'est un bien grand mot :p ) de mes scripts, car la structure en arbre n'était pas adaptée à ce que je faisais (il y avait rarement des branchements, donc j'avais un des deux pointeurs qui la plupart du temps ne servait à rien...). Maintenant, ça ressemble plus à un mini processeur, auquel on fournit une liste de fonctions à exécuter à la suite, et certaines de ces instructions peuvent être des jump pour faire les branchements. Du coup, je peux potentiellement faire des boucles :p Ça sera sans doute pour une future version.

    Avec ces nouvelles possibilités, ça avait encore moins de sens de garder mes vieux arbres en JSON, donc je me suis lancé. Même si mes cours de théorie du langage et de lex/yacc remontent à loin, ça m'a permis au moins de savoir d'où partir.

    Je n'ai pas besoin d'arithmétique, à part d'un peu d'arithmétique booléenne pour faire des tests, je n'ai pas besoin de créer de variables locales dans les scripts, du coup le langage est assez simple.
    J'ai choisi de faire quelque chose qui ressemble à du C. Je sais que certains studios utilisent des langages basés sur le Lisp, et ça marcherait sans doute aussi bien pour ce que je fais, mais comme de mon côté j'ai le nez dans du C/C++ toute la journée, autant rester sur du familier. Je m’emmêlais déjà les pinceaux à mon ancien boulot quand je passais du Lua au C++ ou du C# au Java :p

    Voilà le code de la première salle de test, qui gère ce qu'il se passe quand on active les deux manivelles:
    Code :Sélectionner tout -Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    //Code de la manivelle de gauche
    script onLeftManivelle()
    {
    	lockPlayer()
    	{
    		playToggleMechanism(2);
    		launchToggleMechanism(0);
    		playCameraPath("camerapath0");
    		/*
    		Si les 2 portes sont ouvertes,
    		On ouvre la grille
    		*/
    		if(isMechanismAtEnd(1) && isMechanismAtEnd(0))
    		{
    			launchToggleMechanism(4);
    			playCameraPath("camerapathFloorGrid");
    		}
    		else if(isMechanismAtEnd(4))
    		{
    			//Sinon, si la grille est ouverte,
    			//on la ferme
    			launchToggleMechanism(4);
    			playCameraPath("camerapathFloorGrid");
    		}
    	}
    }
     
    script onRightManivelle()
    {
    	lockPlayer()
    	{
    		playToggleMechanism(3);
    		launchToggleMechanism(1);
    		playCameraPath("camerapath1");
    		if(isMechanismAtEnd(0) && isMechanismAtEnd(1))
    		{
    			launchToggleMechanism(4);
    			playCameraPath("camerapathFloorGrid");
    		}
    		else if(isMechanismAtEnd(4))
    		{
    			launchToggleMechanism(4);
    			playCameraPath("camerapathFloorGrid");
    		}
    	}
    }

    J'avais mis quelques commentaires au milieu pour vérifier qu'ils fonctionnaient bien :p
    Vous remarquerez qu'il y a pas mal de code qui se ressemble, et ces deux scripts sont presque identiques à quelques paramètres près. Je songe à ajouter un système de macro et/ou de fonction pour remédier à ça.

    Vous avez peut-être remarqué les lignes "lockPlayer(){...}": c'est une feature de "sécurité" que j'ai ajouté au langage: on peut obliger certaines fonctions à être utilisées en étant suivies d'un scope plutôt que d'un point-virgule: ça permet, par exemple dans ce cas, de ne pas pouvoir laisser le joueur bloqué après la fin du script parce qu'on a oublié la fonction qui permet de le débloquer: le déblocage sera automatiquement ajouté à la fin du scope.

    Utiliser un index pour accéder à un mécanisme c'est pas terrible, il faudrait que je puisse utiliser un id. C'était moins urgent que pour d'autres infos, car les mécanismes sont dans les .json, que je peux lire pour voir à quelle position ils sont, alors que toutes les autres infos sont dans les .map, dans lequel je ne peux pas savoir si d'une sauvegarde à l'autre les entités seront dans le même ordre.

    J'aurais sûrement pu implémenter un langage de script existant à la place. J'ai d'ailleurs fait mon petit état de l'art avant. Au final, non seulement j'avais une "machine virtuelle" assez simple qui fonctionnait déjà quand je me suis lancé dans le langage lui-même, mais en plus j'avais des besoins beaucoup plus simple que ce que tous ces langages permettent.

    J'ai pu faire en sorte d'inclure le code généré par flex/bison en tant que bibliothèque dans mon moteur, sans que ce soit impossible d'ajouter d'autres compilateurs faits avec les mêmes outils plus tard - flex/bison ne sont pas faits pour ça au départ, les noms des fonctions générées sont toujours les mêmes et il y a plein de globales... - Au final c'était super simple, j'avais juste à entourer tout ce code d'un "namespace ... {...}" :p Ça me permet de compiler les scripts avec le reste de la map.

    Dans mon prochain post, j'espère pouvoir vous annoncer que les sauvegardes fonctionnent, et aussi bien que je l'espérais.

  5. #45
    Membre éprouvé
    Bon, j'ai un peu dévié...



    Pour changer un peu de tout ce qui est gestion du chargement du niveau et des sauvegardes, j'ai décidé de me pencher un peu sur le rendu de l'eau, que je n'avais pas du tout attaqué.
    Je suis plutôt satisfait du résultat, même pour les bords de l'eau, pour lesquels j'ai été surpris de ne pas trouver de "vraie" solution après avoir fait des recherches (mes recherches n'étaient peut-être pas bonnes... De plus, dans tout ce que je trouvais, un seul côté de l'eau pouvait être affiché, alors que j'ai besoin de pouvoir voir sur et sous l'eau), et celle que je retrouve partout était plus adaptée pour des bords en pente, pas pour des bords bien verticaux comme j'en ai ici. Au final, j'ai opté pour décaler légèrement l'angle de vue du rendu de la réfraction et de la réflexion, tout en diminuant légèrement leur fov, pour "étirer" l'image pour qu'elle ait plus de chance de dépasser le bord de l'eau. Sur mes maps de test, ça marche mieux que simplement décaler le plan de clip de chacun de ces rendus, parce qu'avec cette solution, j'avais un bout du dessous de l'eau qui se retrouvait sur le reflet, et comme j'ai mis des textures complètement différentes de chaque côté de l'eau, le problème se voyait vite, mais peut-être que dans un cas réel, ça ne se verrait pas du tout :p

    Et tout ça m'a aussi appris que mettre un "discard" tout au début d'un shader après un simple test était beaucoup moins performant que de compter sur le zbuffer pour éliminer des fragments :p

    Du coup, je me suis amusé à refaire mon gif précédent avec l'eau à jour :p


    Pour avoir un gif pas trop gros tout en restant propre, j'ai désactivé les caustiques et tweaké la luminosité et le brouillard, sur ce gif comme le précédent. J'ai essayé d'avoir les mêmes réglages que dans le gif précédent :p

  6. #46
    Membre éprouvé
    Bonjour,

    Les sauvegardes, c'est vraiment un sujet à part entière: pour l'instant j'ai juste besoin de pouvoir charger/sauvegarder rapidement, et être capable de charger une sauvegarde de la version 64-bit du jeu dans la version 32-bit, et vice versa (l'exécutable de la version DirectX12 est 64-bit, celui de la version OpenGL 32-bit. Il n'y a pas vraiment de raison pour ça mais bon :p ).
    Mais à terme, je devrai être en plus capable de charger une sauvegarde d'une version antérieure dans l'exécutable d'une version plus récente, que la mise à jour concerne la structure des données ou les data. Curieusement, d'après mes recherches, toutes les API de serialization que j'ai trouvé sont faites pour gérer les changements de structures, mais pas forcément les changements de data, ce dont j'aurai encore plus besoin: par exemple si dans une mise à jour du jeu je décide d'ajouter une entité au milieu du niveau, il ne faut pas que ça foute le bordel dans les entités existantes dans la sauvegarde. Et c'est encore plus problématique pour les scripts (vu qu'avec les entités, on peut s'en sortir avec des ID): je veux être capable de sauvegarder vraiment n'importe où, même si une coroutine est en train de tourner. Je sauvegarde donc l'index de l'instruction représentant la coroutine, mais comme il n'y a pas moyen d'"identifier" chaque ligne de code d'un script, comment charger une ancienne sauvegarde dans une nouvelle version, dans laquelle le corps du même script a changé, sans qu'après le chargement on pointe sur une mauvaise instruction? À part faire une table de correspondance à la main à chaque mise à jour, je ne vois pas.

    Bref, pour l'instant, je laisse les problèmes de mise à jour de côté :p

    Ma première implémentation des sauvegardes, qui marchait bien, consistait à rassembler toutes les données qu'on aurait besoin de sauvegarder (et même un peu plus), pour pouvoir les sauvegarder en bloc. À la sauvegarde, comme il y a aussi des pointeurs dans ces données, je sauvegardais aussi l'adresse du début de cette zone, ce qui permettait au chargement de décaler les pointeurs sauvegardés, si la zone n'est plus au même endroit au moment où on charge la sauvegarde.
    Malheureusement, comme vous pouvez vous en douter, impossible d'ouvrir une sauvegarde faite en 64-bit dans l'exécutable 32-bit :/ Du moins sans se faire chier à tout décaler au chargement.
    Je me dis qu'il y a quand même moyen de s'en sortir avec cette solution, en créant un générateur de code qui s'occupe de ce problème. Le truc, c'est que cette solution était la plus rapide pour la sauvegarde et le chargement dans le cas le plus courant (où on sauvegarde/charge avec le même exécutable, sur la même machine), donc ça me fait mal de devoir me séparer de cette efficacité pour pouvoir gérer tous les cas à la con :p
    Au final, j'ai implémenté une solution très proche, mais en remplaçant les pointeurs par des espèces d'indices (sur 4 octets sur les 2 versions), avec un peu de magie pour que le code du jeu reste très proche. Donc au final le l'exécutable doit convertir les indices en pointeurs en permanence, juste pour que les sauvegardes fonctionnent simplement :/

    À part ça: je sors une nouvelle version (la première depuis décembre ^^ ): http://lhuillia.iiens.net/wejv6/livrables/WEJV6_Guntha_07_2018.zip

    Les différences par rapport à la précédente:
    ->Le niveau de test refait avec TrenchBroom: certaines salles ont été quelque peu modifiées, d'autres sont méconnaissables, d'autres encore n'ont quasiment pas bougé.
    ->Le rendu: la spotlight du joueur, l'eau, les ombres (qui méritent un peu plus de travail)
    ->La caméra a un feeling légèrement différent (elle reste en chantier)
    ->Le modèle du joueur est 2 fois plus petit (pour qu'il soit à l'échelle par rapport à ce que je veux en faire plus tard :p )
    ->J'ai écrit un importeur glTF simple (.gltf et .glb). Je ne connaissais pas ce format quand j'ai sorti la version précédente, il est simple à parser (surtout que j'avais déjà un parseur json dans mon projet). Donc plus besoin d'assimp
    ->Les nouveaux scripts. D'ailleurs, ne vous étonnez pas si parfois, en utilisant une clé, vous pouvez encore bouger, et parfois non: c'est juste que les scripts des salles ont quelques petites différences entre eux. Je sais, c'est idiot
    ->Je ne suis pas encore satisfait de mon système de collision
    ->La plupart des collisions avec les mécanismes sont maintenant vraiment faites avec les brushes qui les composent. Attention: je n'update les collisions qu'à la fin d'une animation de mécanisme :p
    ->Et donc, les mécanismes qui peuvent maintenant utiliser les brushes.

    Allez, une petite image mystérieuse pour la route:



    Les chantiers pour la suite:
    ->Toujours les sauvegardes :p
    ->Toujours les collisions :p
    ->Il n'y a toujours pas d'éditeur de mécanisme :p
    ->Améliorer le feeling de la "nage"
    ->Un vrai game design, ou alors utiliser tout le travail que j'ai fait pour ce projet pour une autre jeu?
    ->Il va falloir que je me penche sérieusement sur les perfs (actuellement, il y a zéro occlusion, aucun partitionnement des salles, et j'ai un vieux PC portable qui commence à ne pas apprécier mon fillrate... Et ça, c'est juste le rendu. J'ai fait un partitionnement simple des collisions, mais il faut que j'aille plus loin).

  7. #47
    Membre éprouvé
    Houlà, je m'aperçois que j'ai oublié des infos essentielles:

    =>Appuyez sur F4 pour sauvegarder (vraiment n'importe quand), et F3 pour charger la dernière sauvegarde.
    =>Sur la version DirectX12, j'ai parfois un crash aléatoire au lancement, au premier ExecuteCommandLists(); je ne sais pas si ça vient d'une modif de ma part ou d'une mise à jour de Windows, en tout cas pour essayer d'en venir à bout je me suis débarrassé de toutes les erreurs que la couche de Debug de DirectX12 me sortait, mais ça n'a pas suffi.

  8. #48
    Membre éprouvé
    Et évidemment ce n'est que maintenant que je m'aperçois qu'il y a un bug qui fait qu'après un chargement, les collisions ne sont updatées que dans la 1ère salle x) J'ai réuploadé la version à la même URL.

  9. #49
    Responsable 2D/3D/Jeux

    Bonjour,

    J'ai lu votre message sur les sauvegardes. Je pense que vous cherchez à faire trop de choses à la fois. Notamment, vous devriez vous faciliter la vie en ne supportant pas nécessairement une ancienne version. Il est inévitable d'avoir des incompatibilité, et c'est encore plus vrai pour une version en développement.
    Toutefois, une ressource qui peut vous donner une piste : http://preshing.com/20171218/how-to-...p-game-engine/ (la troisième partie).

    De même, pour les scripts, est-ce vraiment nécessaire de reprendre exactement là où il se sont arrêtés ?

    En tout cas, c'est un immense travail, très impressionnant
    Vous souhaitez participer à la rubrique 2D/3D/Jeux ? Contactez-moi

    Ma page sur DVP
    Mon Portfolio

    Qui connaît l'erreur, connaît la solution.

  10. #50
    Membre éprouvé
    Salut LittleWhite,

    Il y a plusieurs choses dans lesquelles je me suis reconnu dans l'article que tu as donné:
    • Je me suis rendu compte qu'en effet, la sérialisation est un vrai gros sujet :p
    • J'ai eu du mal à trouver des ressources qui en parlent (8ème paragraphe), surtout orienté jeu.
    • Après avoir implémenté ma solution actuelle, j'ai aussi pensé à implémenter une forme de réflexion pour pouvoir revenir au système que j'ai implémenté en premier: en plus des data elles-même, sauvegarder le layout des structures que je sauvegarde, comme ça si j'ouvre une sauvegarde faite en 64-bit dans l'exécutable 32-bit (et vice-versa, et ça marche aussi pour l'endianness), je peux me servir de cette info pour faire tous les décalages nécessaires, sans avoir à écrire de code spécifique pour la conversion (même si l'idée de plutôt générer du code pour ça permettrait de légèrement gagner en perf? :p Mais après tout, c'est déjà un cas peu commun, tant que l'utilisateur ne partage pas ses sauvegardes, ou essaie de les transférer entre des machines différentes......) Et bien entendu, ça peut aussi servir pour la rétro-compatibilité.
    • Toutefois, comme pour les autres ressources que j'ai trouvé, pour ce qui est de la rétro-compatibilité, ça ne parle que des changements de structures, pas des changements de data que, dans l'idéal, je voudrais être capable de pouvoir gérer aussi. Mais comme je l'ai dit plus haut, en dehors des scripts, tout est gérable.


    Pour les scripts, j'ai demandé un avis sur un autre forum, on m'a suggéré de faire en sorte de ne pas avoir à sauvegarder de coroutine, en ayant une machine à état que le script doit suivre et en sauvegardant l'état actuel, plutôt que de sauvegarder la coroutine, et d'interdire la sauvegarde quand des coroutines tournent, pour les cas où je ne peux pas m'en passer.
    (Actuellement, vous pouvez sauvegarder au milieu d'une cutscene où une porte s'ouvre, et après le chargement, vous vous retrouverez exactement au même point dans cette cutscene :p)

    En effet, je ne songe pas à avoir un système de sauvegarde rétrocompatible que j'utiliserai pendant le développement, mais j'aimerais bien avoir le système complet en place, et ne plus avoir à m'en soucier pour la suite

    Il y a aussi cette ressource que j'ai trouvé, sur la sérialisation dans les Little Big Planet: https://yave.handmade.network/blogs/p/2723-how_media_molecule_does_serialization#15607

    Merci pour les encouragements ^^

  11. #51
    Responsable 2D/3D/Jeux

    Pour les scripts, j'ai demandé un avis sur un autre forum, on m'a suggéré de faire en sorte de ne pas avoir à sauvegarder de coroutine, en ayant une machine à état que le script doit suivre et en sauvegardant l'état actuel, plutôt que de sauvegarder la coroutine, et d'interdire la sauvegarde quand des coroutines tournent, pour les cas où je ne peux pas m'en passer.
    Oui. Après, la tendance actuelle est de ne plus laisser le joueur contrôler les sauvegardes, mais de faire des sauvegardes automatiques (à des moments précis). Comme je l'ai dis et je continue à penser, vous allez très loin dans votre système (après, c'est super intéressant, mais cela dépend aussi de est-ce que vous voulez finir votre jeu/projet ou pousser très profond dans chacun des aspects).
    Vous souhaitez participer à la rubrique 2D/3D/Jeux ? Contactez-moi

    Ma page sur DVP
    Mon Portfolio

    Qui connaît l'erreur, connaît la solution.