Créer des graphismes comme en 1993
(staniks.github.io)- Catlantean 3D est un projet parallèle visant à créer un FPS complet avec les contraintes typiques des jeux PC du début des années 1990, avec un rendu en 320x240 basé sur une palette de 256 couleurs
- Comme le moteur de rendu ne manipule que des indices de palette, il précalcule 32 niveaux de colormap pour l’assombrissement selon la distance, puis choisit à l’exécution une couleur plus sombre via une consultation en O(1)
- La production des assets se répartit entre des sprites pré-rendus à partir de Blender, des sprites et textures dessinés à la main, et des textures procédurales générées par des scripts Python
- Le HUD dessiné à la main et les règles d’échelle au pixel près sont des contraintes essentielles pour conserver netteté et lisibilité à basse résolution, avec 1 unité du monde alignée sur 64 pixels
- Au lieu d’utiliser Tiled, l’auteur développe son propre éditeur de cartes et prévoit de fournir le même outil aux joueurs après la sortie, tandis que le code source du jeu sera publié en open source sur GitHub
Objectifs et contraintes du projet
- Catlantean 3D est un side project développé lentement sur le temps libre depuis plus d’un an, avec une sortie sur Steam visée pour l’année suivante
- L’objectif est de créer un FPS jouable, complet et publiable, en utilisant les techniques courantes du début des années 1990
- Les compilateurs modernes et les couches d’abstraction de plateforme sont autorisés, mais cette abstraction se limite à un framebuffer où écrire les pixels, aux entrées clavier et souris, à un buffer audio où écrire des échantillons, et aux E/S du système de fichiers
- Le jeu, assets compris, doit être créé entièrement from scratch, et le rendu comme le mixage sonore doivent aussi être implémentés à la main
- La résolution cible est 320x240, et chaque pixel affiché ne peut utiliser qu’une seule des 256 couleurs disponibles
- La logique du jeu utilise des nombres à virgule fixe pour garantir un comportement déterministe, tandis que le rendu utilise des nombres à virgule flottante, car le déterminisme y est moins important
- Le résultat ne doit pas être une démo technique mais un vrai jeu agréable à jouer, et aucune production issue de l’IA n’est utilisée
- Tout ce qui est montré est encore en cours de réalisation et peut changer fortement
Rendu sur palette
-
Graphismes VGA
- Le Mode 13h du matériel VGA était un mode graphique 320x200 en 256 couleurs, célèbre pour avoir défini toute une génération de jeux PC
- Du point de vue du programmeur, le Mode 13h fournissait un framebuffer linéaire où chaque pixel était représenté par 1 octet, c’est-à-dire un indice dans une palette de 256 couleurs
- Pour dessiner un pixel, il suffisait d’écrire 1 octet à une adresse précise, sans avoir à gérer des concepts comme les shaders ou la VRAM
- Les assets de jeu modernes peuvent employer des millions de couleurs dans une image, mais avec la limite des 256 couleurs, chaque choix de couleur doit être réfléchi et intentionnel
- Des jeux comme Doom et Duke Nukem sont cités comme exemples où les limites techniques ont produit une image visuelle nette et lisible
- Catlantean 3D cherche à retrouver cette sensation, mais choisit du 320x240 proche du VGA Mode-X plutôt que du 320x200
- Afficher du 320x200 sur un écran 4:3 produit des pixels non carrés ; cette approche est plus authentique, mais elle est évitée par préférence
-
Palette
- La palette commence comme un bloc de 768 octets et a été choisie à travers de nombreux essais et itérations
- Une couleur rose vif est réservée à la transparence, ainsi qu’un blanc pur et un noir pur
- Il fallait beaucoup de variantes de rouge pour représenter le sang, ainsi que des verts et des bleus pour les clés rouges, vertes et bleues et pour les portes différenciées par couleur
- L’univers se déroule à Catlantis, une terre parodique proche de l’Égypte antique à cause du culte du chat, ce qui demandait de nombreuses teintes désertiques jaunes et brunes
- Comme Catlantis est occupée par des hommes-chiens cybernétiques, il fallait aussi beaucoup de gris pour représenter les installations technologiques
- Des tons beiges ont été ajoutés pour casser la monotonie des gris et servir d’alternative plus chaude lorsque les couleurs s’assombrissent
- Le reste des couleurs a été ajouté selon les besoins apparus lors de la création des textures, avec une part de jugement subjectif du type « ça avait l’air juste »
- La palette n’a pas été finalisée d’un seul coup et a continué d’être ajustée pendant la création des assets, les tests et les itérations
colormap et gestion de l’éclairage
-
Structure du raycaster
- Catlantean 3D est un raycaster traditionnel, avec une carte entièrement composée de tuiles de même taille
- Certaines tuiles sont des murs, tandis que d’autres sont des espaces vides avec seulement un sol et un plafond
- Pour chaque colonne de l’écran, le moteur parcourt la tilemap à l’aide de l’algorithme DDA afin de trouver le point d’impact avec la géométrie de la carte
- Selon la position de collision, il dessine à l’écran une colonne de mur échantillonnée aux coordonnées de texture appropriées
- Le sol et le plafond sont ensuite rendus par lignes de balayage horizontales pour remplir le reste de l’image
- Si l’on rend le monde du jeu avec la seule palette, l’image paraît plate et peu impressionnante
- Réduire la lumière avec la distance au joueur et rendre une face des tuiles un peu plus sombre que l’autre apporte de la profondeur
-
Assombrissement basé sur la palette
- Sur un moteur de rendu moderne accéléré matériellement, on peut facilement assombrir une couleur dans un shader en multipliant son vecteur de couleur par un coefficient flottant basé sur la distance au sommet
- Un moteur sur palette ne manipule pas des couleurs mais seulement des indices de palette, donc trouver une version plus sombre d’une couleur impose de parcourir toute la palette
- Comme parcourir 256 couleurs pour chaque pixel rendu serait trop lent, une table de consultation rapide est précalculée avant l’exécution
- Si l’on dispose la palette sur une ligne et qu’on choisit 32 niveaux de luminosité, chaque couleur a besoin de 31 variantes plus sombres en plus de l’originale
- Pour chaque couleur et chaque niveau d’intensité, on calcule une couleur cible plus sombre à partir de ses valeurs RGB, mais cette couleur n’existe pas forcément dans la palette réelle
- La palette est donc parcourue pour trouver la couleur de palette la plus proche de cette cible et construire la
colormap - Au départ, une distance euclidienne était utilisée, mais beaucoup de couleurs tendaient alors vers le gris, et les couleurs sombres semblaient froides et ternes
- Le système a ensuite été revu en convertissant les couleurs dans l’espace colorimétrique Oklab et en utilisant une formule de distance perceptuelle plus proche de la perception humaine des écarts de couleur
- Une idée de pixel art appelée hue shifting est aussi appliquée, en décalant légèrement les teintes vers des tons plus chauds à mesure que la couleur s’assombrit
- La
colormapest une matrice 2D d’indices de palette représentant les nuances de chaque couleur ; comme seules les couleurs de la palette restent disponibles, les dégradés ne sont pas parfaits
-
Réduction du coût à l’exécution
- Une fois l’indice de ligne de
colormapdéterminé à partir de la distance, il suffit de prendre l’élément N de cette ligne pour obtenir l’indice de palette correspondant à la version assombrie de la couleur N - Cette méthode ramène le choix d’une couleur plus sombre à une consultation en O(1) pendant l’exécution
- Pour les murs, comme les colonnes sont parfaitement verticales et que tous les pixels d’une colonne sont à la même distance de la caméra, l’indice de ligne de
colormapn’est calculé qu’une seule fois par colonne écran - Pour le sol, tous les pixels d’une même ligne horizontale étant à la même distance, le calcul n’est fait qu’une seule fois par ligne écran
- Les sprites étant des billboards plats dont tous les pixels sont à la même distance de la caméra, le calcul n’est effectué qu’une seule fois par sprite visible
- Il suffit donc de faire le calcul 320 fois pour les murs, jusqu’à 240 fois pour le sol, et une fois par sprite visible, tandis que le raycasting fournit gratuitement l’élimination des objets occultés
- Doom et plusieurs autres jeux utilisaient une approche similaire
- Une fois l’indice de ligne de
Méthodes de production des assets
-
Trois catégories d’assets
- Les textures et sprites de Catlantean 3D se répartissent en trois catégories
- La première catégorie correspond à des sprites pré-rendus à partir de modèles 3D créés dans Blender
- La deuxième regroupe les sprites et textures dessinés à la main
- La troisième est composée de textures procédurales générées par des scripts Python spécialisés combinant de l’art dessiné à la main
-
Sprites pré-rendus
- Les sprites animés complexes sont difficiles et longs à itérer, car il faut retoucher de nombreuses frames
- Une approche plus efficace consiste à créer un modèle 3D dans Blender, à le rigger et l’animer, puis à utiliser un script basé sur l’API Python de Blender pour rendre plusieurs textures
- Les modifications sont faites sur le modèle, puis le script de rendu s’occupe du reste, ce qui réduit fortement le temps d’itération
- La principale difficulté était que les sprites rendus sortaient très flous et délavés
- Le rendu en haute résolution suivi d’une réduction par filtrage a donné des résultats mitigés, car les détails se perdaient dans le filtrage et la netteté des contours disparaissait parfois
- La méthode la plus efficace et la plus réutilisable a été d’utiliser le compositing de Blender pour obtenir le bon contraste et la bonne netteté
- Une fois l’image prête, un script Python spécialisé effectue la quantification sur palette afin de produire les images à pixels sur 1 octet utilisées par le moteur
- Pour chaque pixel de l’image source, le script cherche la couleur de palette perceptuellement la plus proche selon Oklab, puis utilise l’indice correspondant comme valeur du pixel
- Le tableau d’indices et les informations de taille sont empaquetés dans un format TEX simple utilisé par le jeu
- Les sprites d’ennemis peuvent comporter plusieurs animations, et chaque animation doit inclure des frames pour les 8 directions possibles du sprite
- Le script Python fait pivoter le sprite pour chaque animation, rend toutes les frames, puis recommence après nouvelle rotation
- Les noms de fichiers des sprites suivent une convention indiquant le nom du sprite, le nom de l’action, la direction et l’indice de frame
- Les sprites rendus ne sont pas stockés dans le dépôt et sont exclus via
.gitignore; sur une autre machine, un script de compilation rend tous les modèles pour générer les sprites - Sur une RTX 3070, le traitement d’environ 15 modèles prend à peu près 10 secondes
-
Sprites et textures dessinés à la main
- Au début du développement, une tête de chat texturée avec le chat Vilko a été créée dans Blender pour servir de visage de barre d’état
- Le résultat paraissait paresseux et peu travaillé, exprimait mal les émotions, et c’était la première chose relevée par les gens dans les retours sur l’ambiance
- Certains éléments doivent impérativement être dessinés à la main, et il a été jugé qu’une version animée dessinée à la main fonctionnait bien mieux
- Vu la taille des sprites, chaque pixel doit être intentionnel, et il n’y a pas de place pour laisser le moteur de rendu de Blender décider
- La même logique s’appliquait à la plupart des objets à ramasser, et les précédents résultats pré-rendus ne donnaient pas systématiquement de bons résultats à petite échelle avec le compositeur de Blender
- Une fois retravaillés à la main, les objets à ramasser ont gagné nettement en lisibilité et en netteté
- Augmenter simplement la résolution des sprites permettrait au rasterizer du jeu de les redimensionner, mais le résultat serait mauvais à cause d’une échelle de pixel incohérente
- On s’attend inconsciemment à ce que, lorsqu’un objet se déplace vers l’avant ou l’arrière sur une même ligne ou colonne de l’écran, la taille de ses pixels reste cohérente ; si chaque sprite a sa propre échelle de pixel, le rendu paraît étrange
- Dans Catlantean 3D, 1 unité du monde équivaut à 64 pixels, et tous les sprites sont produits selon cette échelle
- Un sprite haut d’un quart d’unité du monde doit donc mesurer 64/4=16 pixels de haut
HUD et pipeline de génération procédurale
-
HUD
- Le HUD et presque tous ses composants sont placés et dessinés à la main
- La barre d’état en bas de l’écran, plusieurs panneaux et écrans de transition, ainsi que les polices, relèvent de cette catégorie de HUD dessiné à la main
- Plutôt que de tout peindre directement, le travail s’appuie largement sur les effets de calque et le compositing d’Affinity Photo
- Les effets utilisés comprennent l’emboss pour donner une impression 3D à des surfaces plates, la génération de bruit et les overlays pour un rendu rugueux, des overlays de couleur, des modes de fusion et des effets de glow
- Comme les éléments du HUD sont fréquemment modifiés, il est aussi important de pouvoir les réagencer facilement grâce aux calques
- En général, le travail commence en truecolor dans Affinity Photo, et beaucoup d’éléments consistent en simples rectangles unis auxquels sont appliqués des effets spéciaux et des modes de fusion
- Les images exportées depuis Affinity Photo présentaient d’étranges artefacts apparemment liés à l’anticrénelage, qu’il n’a pas été possible de désactiver de façon fiable
- L’outil n’étant pas adapté au travail pixel perfect, des retouches supplémentaires sont faites dans Aseprite pour le texte pixel perfect, le découpage d’éléments graphiques et le repassage de contours plus nets
-
Textures générées procéduralement
- Certaines textures sont assez simples ou spécifiques pour être dessinées directement, mais beaucoup partagent des variations d’usure, de poussière et de détail de surface sur un même matériau de base
- Dessiner chaque variante à la main serait fastidieux et donnerait des résultats peu cohérents, donc elles sont générées par scripts Python
- Le pipeline de génération prend en entrée une heightmap définissant le relief de surface, une noise map pour les variations, une grime map pour la saleté et l’usure, deux couleurs de base, et une brightmap
- La heightmap sert en pratique à produire une normal map, elle-même utilisée pour cuire un éclairage et des ombres simples
- La brightmap indique quelles zones doivent conserver leur couleur indépendamment des autres paramètres
- Le script produit la texture finale et effectue aussi la quantification sur palette pour qu’elle soit utilisable directement dans le moteur
- Modifier une texture revient alors à ajuster des paramètres plutôt qu’à redessiner les pixels, ce qui fait gagner beaucoup de temps en solo
Gibs et effets pré-rendus
-
Gibs
- Un gibbing se produit généralement lorsqu’un ennemi subit des dégâts excessifs, comme un tir de shotgun à bout portant ou une explosion
- Pour transmettre l’impact de ces gros dégâts, l’ennemi explose en morceaux sanglants via une animation dédiée
- Ce pipeline est piloté par un script Python qui prend un sprite, une palette et un ensemble de paramètres pour générer les frames d’animation intégrées aux données du jeu
- Lors de la première étape, la décomposition de Voronoi sélectionne aléatoirement K pixels graine dans la partie opaque du corps du sprite, puis assigne chaque pixel à sa graine la plus proche
- Chaque cellule ainsi obtenue devient un morceau projeté
- Lors de la deuxième étape, le saignement de la blessure marque comme blessure de profondeur 0 les pixels de bord adjacents à d’autres morceaux, puis un BFS se propage vers l’intérieur en attribuant une profondeur
- Au rendu, les pixels proches des bords sont mélangés vers une couleur de sang issue d’une rampe dérivée de la palette du jeu, tandis que les couleurs originales du sprite sont davantage préservées au centre des morceaux
- Le choix de la rampe de palette est paramétrable, ce qui permet par exemple d’utiliser du « sang » vert ou bleu pour certains ennemis
- Lors de la troisième étape, la physique attribue à chaque morceau un centre, une vitesse de dispersion aléatoire orientée vers l’extérieur depuis le centre du sprite, une vitesse de rotation, une gravité et une traînée
- Il n’y a pas de détection de collision, mais les morceaux s’arrêtent lorsqu’ils touchent le sol, ce qui donne un résultat grossier mais suffisant
- Le nombre de morceaux, la force d’explosion, la gravité, la traînée, la dispersion et la profondeur des blessures peuvent être ajustés par paramètres
- Il faut un peu de tâtonnement pour trouver de bonnes graines, mais cela reste plus rapide que de dessiner l’animation à la main
- La même technique est utilisée pour des objets de décor destructibles comme les pots, les barils ou les caisses
- Comme les animations pré-rendues, les gibs ne sont pas stockés dans le dépôt ; ils sont régénérés après checkout et leur temps d’exécution est négligeable
-
Système de particules pré-rendu
- La plupart des effets de particules sont dessinés à la main dans Aseprite, mais certains sont générés et cuits comme les gibs
- Un script Python exécute la simulation pour produire une séquence de frames PNG, ensuite quantifiée en TEX
- Il n’existe pas de système de particules à l’exécution ; tous les effets sont précuits afin que le rasterizer logiciel puisse les rendre aussi vite que possible
- Le mot « particle » est ici un peu trompeur, car il ne s’agit pas réellement d’une simulation de particules
- Chaque frame calcule un champ d’énergie radial par pixel, puis additionne plusieurs couches indépendantes pour la composition
- Le core est un disque doux qui s’étend vers l’extérieur pendant l’animation
- Les rays sont des rayons pointus autour du core ; on peut en régler la sharpness et la length, et chacun reçoit une variation de longueur fondée sur un RNG pour un aspect irrégulier
- Le ring est une onde de choc en expansion optionnelle, et le noise multiplie l’énergie globale par un value noise pour rendre la forme moins propre et plus irrégulière
- L’énergie accumulée par pixel est quantifiée selon une rampe de palette définie dans les paramètres du script
- La conception de la palette traite chaque ligne comme un dégradé allant du clair au sombre, ce qui permet d’assombrir les pixels par simple arithmétique sur les indices de palette, sans blending ni calcul d’alpha
- Au-delà d’un certain seuil, les pixels sont poussés vers le blanc afin de donner une impression de cœur white-hot
- De petits sparkle peuvent optionnellement être dispersés au-dessus ; ces formes en croix se déplacent vers l’extérieur et s’estompent au cours de leur durée de vie
- L’animation prend en charge un mode one-shot, où l’effet gonfle puis disparaît comme une explosion ou un flash de téléportation, ainsi qu’un mode loop dont la première et la dernière frame s’alignent pour une répétition sans rupture
- Le mode loop est utile pour des effets continus et répétitifs comme des plasma bolts ou des projectiles d’énergie
Éditeur de cartes et écosystème d’outils
- L’édition de cartes a commencé avec Tiled, un outil globalement raisonnable, mais qui manquait de fonctions précises nécessaires au jeu
- Tiled ne proposait ni peinture du niveau de lumière par cellule, ni cell flags, ni concept de propriétés propres au jeu, ce qui a d’abord conduit à détourner les object properties
- Un script Python était aussi nécessaire pour convertir la sortie JSON de Tiled vers le format binaire utilisé par le moteur, ajoutant un composant supplémentaire pour compenser le décalage entre l’outil et les besoins du jeu
- Si un joueur devait installer Tiled, apprendre son interface et configurer le script de conversion pour créer des cartes, la friction serait telle qu’elle empêcherait presque totalement l’éditeur d’être réellement utilisé
- L’éditeur maison prend en charge nativement la peinture des niveaux de lumière, les cell flags, ainsi que tous les types d’entities et de propriétés connus du jeu
- Une fois le jeu sorti, les joueurs recevront le même éditeur que celui utilisé pendant le développement
- L’éditeur fonctionne en plug and play, et il permet de lancer un niveau directement depuis l’éditeur
- L’auteur sait que les icônes de la barre d’outils sont terribles, et c’est précisément pour cela qu’elles sont conservées telles quelles
- L’éditeur est développé avec wxPython, jugé plus adapté que tkinter pour les widgets, la gestion des événements et la mise en page
- Le résultat obtenu avec wxPython paraît plus natif, et les itérations y sont rapides
- Une architecture centrée sur le pattern MVP sépare proprement la logique d’interface et les données de carte, ce qui est important tant que le format de carte n’est pas stabilisé et que les deux changent souvent
- Toutes les parties de l’éditeur ne sont pas écrites en Python ; une grande partie du modèle s’appuie sur la bibliothèque
pybast pybastfournit des bindings Python internes du moteur via pybind, avec lecture des archives de données du jeu, lecture des textures du jeu, classe de virgule fixe pour les coordonnées des entities, et sérialisation- Ce choix permet d’éviter de réimplémenter en Python des fonctionnalités déjà écrites en C++, et l’ensemble moteur + outils forme un petit écosystème étroitement intégré
Plan de sortie et mode de publication
- La publication de Catlantean 3D est attendue au premier trimestre 2027
- L’accent est actuellement mis sur le level design, l’ajout d’ennemis et d’armes, ainsi que sur le travail de finition en cours
- Le prix visé se situe entre 5 et 8 dollars
- Le code source du jeu doit être publié en open source sur GitHub
- Les véritables archives de données contenant les graphismes, niveaux, sons et musiques resteront liées à l’achat du jeu
- La transparence du processus est considérée comme l’un des rares moyens de construire une confiance durable
- À la différence des AAA, un jeu indé dépend d’un public plus réduit, mais ce public est aussi plus enclin à suivre le projet, à le soutenir et à en parler autour de lui
- Montrer le processus de travail est présenté comme la manière la plus honnête de prouver qu’on se soucie réellement de ce que l’on est en train de créer
1 commentaires
Commentaires sur Hacker News
Si vous voulez vous amuser avec le rendu logiciel, il existe un exemple très proche du code le plus court pour afficher efficacement à l’écran, sur toutes les plateformes, un tableau 2D ARGB8888 en mémoire principale avec SDL2 et C : https://gist.github.com/CoryBloyd/6725bb78323bb1157ff8d4175d...
Il faut faire soi-même la conversion d’un framebuffer palette 320x200x8 bits vers l’ARGB ;)
Si vous cherchez de l’inspiration sur ce qu’on peut faire avec un framebuffer palette, cliquez sur Show Options dans http://www.effectgames.com/demos/canvascycle/ ou regardez la présentation GDC de l’artiste : https://youtu.be/aMcJ1Jvtef0
Ensuite, si vous voulez une ambiance classique façon Deluxe Paint IIe, lancez https://github.com/mriale/PyDPainter ; si vous préférez un outil plus moderne, lancez https://www.aseprite.org/
Au moins dans SDL3, il n’y a plus besoin de renderer ni de texture. Il suffit de récupérer la surface avec SDL_GetWindowSurface et de l’afficher avec SDL_UpdateWindowSurface
D’après ma compréhension de la bibliothèque, c’est la méthode la plus proche du graphisme logiciel, et SDL continue malgré tout à gérer le double buffering
C’est clairement l’approche la plus basique. Pour une petite optimisation dans la boucle interne, on peut pré-calculer l’offset de ligne de scan avant d’entrer dans la boucle de pixels :
int s = y*screenRect.w;for (int x = 0; x < screenRect.w; x++) {pixels[s + x] = argb(255, frame>>3, y+frame, x+frame);}Merci pour le partage. Il existe déjà plusieurs forks populaires de Quake, mais Planimeter distribue un fork Quake-VS2026 sans modifications
L’équipe travaille sur un build x64, ce qui nécessite de remplacer l’ancienne SciTech Multi-platform Graphics Library (x86 uniquement) par SDL3. L’autre option serait de porter scitech-mgl vers x64, ce qui semble peu probable, et aux dernières nouvelles le rendu logiciel pourrait aussi disparaître
Mais il sera peut-être possible de le préserver avec le rendu logiciel et SDL_Texture
Cet article s’inspire beaucoup de Doom, mais le véritable moteur de ray casting est en réalité plus proche des prédécesseurs de Doom, notamment le plus célèbre d’entre eux, Wolfenstein 3D
Il utilise des murs verticaux et des hauteurs de sol et de plafond constantes. Wolf3D n’avait pas de sols ni de plafonds texturés pour des raisons de performances, mais d’autres jeux similaires en avaient
Si mes souvenirs sont bons, Doom et Duke Nukem utilisaient un moteur BSP bien plus flexible, permettant aux murs de se croiser sous des angles arbitraires et aux hauteurs de sol et de plafond de varier. En revanche, les niveaux restaient encore « plats », donc on ne pouvait pas créer plusieurs étages dans un même niveau ; par exemple, il était impossible de concevoir un pont praticable à la fois au-dessus et au-dessous
Le moteur Build n’utilisait pas de BSP. Il traitait les connexions entre secteurs comme des portails, faisait le clipping sur ces portails, puis rasterisait les murs comme des trapèzes tournés de 90 degrés
Cela permettait une géométrie murale dynamique, comme des trains en mouvement ou des éclairages rotatifs, et autorisait aussi des configurations de type « pièce au-dessus d’une pièce » tant qu’on ne pouvait pas voir les deux pièces en même temps
Dans Blood et Shadow Warrior, des contournements permettaient de créer des espaces plus proches de la « 3D » en fabriquant des secteurs de même forme et en utilisant le sol d’un secteur comme un portail vers le plafond d’un autre secteur. Ce n’était pas une fonctionnalité prise en charge nativement par le moteur, mais il était suffisamment flexible pour que des studios sans accès au code source y parviennent eux-mêmes
Le premier niveau de Duke Nukem 3D utilise aussi quelques astuces de Build. Par exemple, les sprites peuvent être alignés sur les axes au lieu de pivoter avec la caméra, et ils peuvent aussi avoir des collisions ; en traitant chaque sprite comme un rectangle aligné sur les axes, on peut créer une géométrie 3D rudimentaire. Dans le premier niveau, cela sert à construire le pont entre deux bâtiments juste avant le bouton de sortie
Blake Stone et Rise of the Triad utilisaient des versions tardives du moteur Wolf3D, avec sols et plafonds texturés
Le moteur Build de Duke Nukem n’utilisait pas de BSP
https://www.jonof.id.au/forum/topic-137.html#msg1548
Il me semble que c’était aussi possible plus tard dans Shadow Warrior. Si je me souviens bien, c’était implémenté avec des portails, et c’était assez pénible à configurer dans l’éditeur
Concernant les sols, à ma connaissance même DOOM ne les traitait pas de manière exacte. Pour les murs verticaux, il suffit de faire la division de perspective une seule fois par colonne de pixels pour un segment de mur donné
Pour les sols, malheureusement, on n’a pas ce luxe ; si mes souvenirs sont bons, DOOM découpait les sols en patches, calculait la perspective correcte uniquement aux coins, puis interpolait entre les deux
Au début, je pensais que ce n’était qu’un simple re-skin de Wolfenstein 3D. C’était un jugement profondément injuste, et il y a eu énormément de travail derrière
Excellent texte. J’ai particulièrement trouvé intéressante l’approche pour créer les animations de gib
C’était une démo technique, mais vers le milieu des années 90, j’avais moi aussi fait quelque chose de similaire. Un point qui n’apparaissait pas dans cet article, c’est qu’avec des lightmaps 8x8 ou 16x16 sur les textures, on pouvait facilement créer des effets comme des torches vacillantes ou des roquettes traversant un couloir en projetant de la lumière. On pouvait aussi utiliser les lightmaps pour « cuire » l’éclairage si on le souhaitait
Comme la lightmap n’était « que » du 8x8, on pouvait se permettre les calculs nécessaires pour chaque luxel, c’est-à-dire chaque unité de la lightmap, afin d’obtenir une valeur de luminosité à partir de la distance à la source lumineuse et de la ligne de visée. Lors du rendu des textures, on utilisait ensuite les luxels avec une table de correspondance pour déterminer la couleur réelle des pixels à dessiner
Pour les performances, de mémoire, les lightmaps étaient mises à jour 15 fois par seconde. Grâce à DJGPP, j’utilisais de l’assembleur inline pour le rendu et, comme les opérations en virgule flottante étaient lentes à l’époque, j’employais de l’arithmétique à virgule fixe, qui s’optimisait bien. Pour les ordinateurs de l’époque, les performances de rendu étaient étonnantes
Le programmation graphique du début et du milieu des années 90 était assez amusante. On écrivait les données de pixels dans la VRAM mappée en mémoire, et elles apparaissaient immédiatement à l’écran
Un simple pointeur vers 0xA0000 suffisait, sans avoir besoin d’aucune API. La raison du mode VGA 320×200 à pixels non carrés mentionné ici, c’est que le tampon vidéo faisait 64000 octets, donc tenait dans un segment 16 bits, ce qui facilitait l’adressage pour le code 16 bits et les CPU de l’époque
Mais cette faiblesse permettait en contrepartie de faire beaucoup plus de travail par pixel à l’écran, ce qui a rendu possibles des systèmes comme le ray casting ou les arbres BSP
Il n’y avait pas de processeurs dédiés pour les sprites et les couches d’arrière-plan, mais en échange le PC n’était pas enfermé dans une architecture rigide à fonctions fixes
Avec l’arrivée de processeurs 3D dédiés au milieu et à la fin des années 90, ce n’est plus vraiment devenu un problème, mais pendant une brève période au début des années 90, il y a eu un terrain de jeu unique pour des rendus visuels originaux
DJGPP et Free Pascal utilisaient le même extender go32 de DJ Delorie, qui ne faisait pas de mapping linéaire complet, donc il fallait bricoler un peu plus pour afficher quoi que ce soit à l’écran
Ce qu’il y a de plus intéressant, ce sont les outils internes. Des choses comme le script Python qui crée les animations de gib, ou un autre script Python qui génère des sprite sheets 2D depuis Blender
L’auteur original semble clairement être un ingénieur 10x capable aussi de produire de belles images, et je pense que ce genre de profil est vraiment rare. Le fait qu’il y ait en plus une direction artistique cohérente était assez impressionnant
Dans l’industrie du jeu de ces 15 dernières années, à part les CEO ou les lead directors, je ne connais presque plus aucun nom
Je viens de réaliser que ce jeu est peut-être l’un des rares shooters avec une protagoniste féminine. Le chat a une robe calico, et ces chats sont presque toujours des femelles (https://en.wikipedia.org/wiki/Calico_cat)
Les shooters à protagoniste féminine sont si rares que ça ? Rien qu’en pensant à des titres assez grand public, il y a Perfect Dark, Mirror's Edge, Dishonored 1 ou 2, Metroid, etc., qui sont tous, d’une manière ou d’une autre, des shooters avec une héroïne
Bon, Mirror's Edge est peut-être plus proche du « first-person » que du « shooter » à proprement parler
Et parmi les jeux « RPG + FPS », il y en a aussi beaucoup où l’on peut jouer un homme ou une femme
L’auteur semble d’ailleurs connaître le motif et la probabilité liée au sexe du chat :
After all, I do need to give the protagonist his fair share. [image] (Yes, I know it's a female, but call it convention rooted in dialect.)Ce n’est pas le jeu Perfect Dark
De nos jours, il y a pas mal de protagonistes féminines dans les boomer shooters. Par exemple Selaco[0], Supplice[1], The Citadel[2] et sa suite[3], Zortch[4] et sa suite à venir[5], Nightmare Reaper[6], COVEN[7], Viscerafest[8], Hedon[9], etc.
En fait, on a presque l’impression qu’il y a maintenant plus de boomer shooters avec une protagoniste féminine que l’inverse :-P En combinant les tags « boomer shooter » et « female protagonist » sur Steam, on obtient 143 résultats, même si cela inclut aussi des jeux où l’on peut choisir le sexe du personnage, ou des jeux où l’on joue surtout un homme mais avec quelques passages joués en femme
[0] https://store.steampowered.com/app/1592280/Selaco/
[1] https://store.steampowered.com/app/1693280/Supplice/
[2] https://store.steampowered.com/app/1378290/The_Citadel/
[3] https://store.steampowered.com/app/3371240/Beyond_Citadel/
[4] https://store.steampowered.com/app/2443360/Zortch/
[5] https://store.steampowered.com/app/3807500/Zortch_2/
[6] https://store.steampowered.com/app/1051690/Nightmare_Reaper/
Ce n’était probablement pas intentionnel, mais globalement ça ne m’impressionne pas vraiment et je n’y vois pas beaucoup de valeur. C’est pareil avec les films hollywoodiens où une femme met KO un homme deux fois plus grand qu’elle
Je trouve ça irréaliste, ridicule et nuisible.
Vraiment génial. Une autre astuce amusante qu’on utilisait dans les années 90, c’était l’animation de palette. Le simple fait de modifier la palette permettait de produire des effets incroyables avec un coût d’exécution très faible.
Oui. Pour voir un excellent exemple de cette technique, je recommande vivement ce site web
http://www.effectgames.com/demos/canvascycle/
Changer la palette au milieu d’une frame, c’est amusant aussi. Sur PC, on n’avait rien comme le copper de l’Amiga, donc il fallait faire beaucoup plus attention au timing, mais c’était quand même possible.
Si je me souviens bien, beaucoup d’ennemis dans Diablo 1 et 2 étaient en fait le même sprite avec des palettes différentes appliquées dessus. C’est le même genre d’astuce ?
Le bleu, c’est l’eau, le violet, le plasma, et le rouge/orange, le sang ou la lave.
J’ai été vraiment surpris de voir à quel point les sprites quantifiés rendaient bien après le rendu. Grâce à cette conversion rapide, ils avaient un aspect très net.
En tant que développeur travaillant lui aussi sur un moteur 3D avec des contraintes absurdement sévères, j’adore voir le niveau de détail de cette explication et le cheminement suivi.
Je bricole une démo technique de rendu d’espace voxel en homebrew sur PlayStation. Après seulement un ou deux jours de travail le week-end, j’obtiens déjà des résultats corrects autour de 10 à 15 FPS, sans encore utiliser ni le DMA, ni le GTE, ni même les primitives polylines
C’est rafraîchissant de ressortir la trigonométrie et les vieilles astuces d’optimisation bas niveau. Quand on a un scratch buffer de 1 KiB et qu’on ne peut utiliser qu’une partie de cette taille pour la pile, on se rend compte à quel point les microcontrôleurs que j’utilise au travail sont luxueux. Là, chaque thread a droit à une pile de 8 KiB, et on peut même obtenir des backtraces avec plus de 50 fonctions templates C++ empilées.