Obligement - L'Amiga au maximum

Vendredi 29 mars 2024 - 06:41  

Translate

En De Nl Nl
Es Pt It Nl


Rubriques

Actualité (récente)
Actualité (archive)
Comparatifs
Dossiers
Entrevues
Matériel (tests)
Matériel (bidouilles)
Points de vue
En pratique
Programmation
Reportages
Quizz
Tests de jeux
Tests de logiciels
Tests de compilations
Trucs et astuces
Articles divers

Articles in english


Réseaux sociaux

Suivez-nous sur X




Liste des jeux Amiga

0, A, B, C, D, E, F,
G, H, I, J, K, L, M,
N, O, P, Q, R, S, T,
U, V, W, X, Y, Z,
ALL


Trucs et astuces

0, A, B, C, D, E, F,
G, H, I, J, K, L, M,
N, O, P, Q, R, S, T,
U, V, W, X, Y, Z


Glossaire

0, A, B, C, D, E, F,
G, H, I, J, K, L, M,
N, O, P, Q, R, S, T,
U, V, W, X, Y, Z


Galeries

Menu des galeries

BD d'Amiga Spécial
Caricatures Dudai
Caricatures Jet d'ail
Diagrammes de Jay Miner
Images insolites
Fin de jeux (de A à E)
Fin de Jeux (de F à O)
Fin de jeux (de P à Z)
Galerie de Mike Dafunk
Logos d'Obligement
Pubs pour matériels
Systèmes d'exploitation
Trombinoscope Alchimie 7
Vidéos


Téléchargement

Documents
Jeux
Logiciels
Magazines
Divers


Liens

Associations
Jeux
Logiciels
Matériel
Magazines et médias
Pages personnelles
Réparateurs
Revendeurs
Scène démo
Sites de téléchargement
Divers


Partenaires

Annuaire Amiga

Amedia Computer

Relec


A Propos

A propos d'Obligement

A Propos


Contact

David Brunet

Courriel

 


Programmation : Amiga E - réaliser un jeu de construction de Lego
(Article écrit par Sébastien Rozé - octobre 2007)


Bonjour et bienvenue à ce cours de E pour Amiga AGA. Le but est de réaliser un jeu de construction de légo. Nous verrons, étape par étape, comment réaliser ce projet.

Zeami


Étape 1 : présentation

Première étape : organiser son plan de travail. Café chaud et Amiga E installé, créer un répertoire "Lego" (chez moi c'est Max:Baba/Lego). Créer avec un éditeur de texte (par exemple CygnusEd) un fichier texte dans le répertoire S: que l'on va nommer "l". Dans ce fichier "l", écrire :

cd max:baba/lego
ced Lego.e

Enregistrer, quitter.

Ouvrir une fenêtre Shell (facultatif sur AmigaOS 3.9).
Copier le fichier "c:execute" en "c:ex" (avec la commande "copy c:execute as c:ex").
Taper "ex l" (taper simplement "l" sur AmigaOS 3.9).
CygnusEd se charge et ouvre le fichier "Lego.e". Ceci est un gain de temps parce que nous allons utiliser des techniques qui peuvent faire planter l'Amiga, donc c'est utile de pourvoir vite redémarrer (il vaut mieux sortir l'icône Shell sur le bureau si ce n'est pas déjà fait).

Donc redémarrage de l'Amiga, double-clic sur l'icône Shell, taper "ex l" et on est prêt (22 secondes maxi chrono en main).

Ok, on peut écrire maintenant notre programme.

PROC main()
WriteF('Bienvenue à Lego\n')
ENDPROC

Enregistrer et taper "ec lego" dans le Shell.
passing, compiling... etc.
Un message apparaît : "Bienvenue à Lego".
Ok, tout va bien, vous pouvez passer au deuxième cours !

Nous avons vu qu'une procédure en E s'écrit "PROC nom_de_la_procedure()" et elle se termine par "ENDPROC".

Tous les noms de procédures ou variables doivent s'écrire en minuscules, les majuscules étant réservées pour les constantes ou les mots-clés E, et les mixées comme "WriteF" sont des procédures en ROM ou internes au compilateur E. Pour plus d'explications consultez le Beginer.guide, il m'a permis d'en apprendre pas mal sur le E, et le E.guide (qui se trouvent dans l'archive amigae33a.lha. Procurez-vous aussi GFXMaster sur Aminet.

Télécharger le code source à l'issue de l'étape 1.

Étape 2 : création d'un écran Intuition

Introduction

Le but de cette deuxième partie est de créer un écran Intuition. Ce n'est pas forcément utile mais fortement recommandé car nous allons rapidement nous débarrasser d'Intuition et des fonctions en ROM du système d'exploitation pour attaquer directement les circuits Amiga. Malheureusement, en attaquant les circuits Amiga, on peut modifier l'affichage, jouer des sons, lire le clavier, la souris, la manette, mais comme on est dans un environnement multitâche, tout ce que l'on fait dans notre jeu aura un effet sur le Workbench.

Je m'explique : je lance Lego, Lego met en place son écran, et attend la touche "a" pour continuer. Lorsque je quitte Lego, je constate que mon "a" a "traversé" le programme Lego, et a continué sa route pour atterrir dans la fenêtre Shell. Il en va de même pour la souris, on remarque que tous les clics ont traversé le programme et on a pu ouvrir un ficher ou activer un menu du Workbench sans s'en rendre compte pendant que le programme Lego tournait. Je crée un écran et une fenêtre Intuition, qui servira à recevoir et retenir les messages Intuition et servira de protection au Workbench. La deuxième mesure de sécurité est d'utiliser la procédure Forbid() ou Disable(). Ces deux procédures coupent le multitâche de l'Amiga, donc le Workbench et les autres programmes se figent et ne peuvent plus recevoir de touches ou de clics de souris. Cette sécurité est à elle seule suffisante, mais on est obligé d'activer le multitâche pour pouvoir lire ou sauver des fichiers. Donc cet écran sert de protection pendant ce temps-là.

Le programme

Nous allons créer un deuxième fichier source. Ce fichier est un module et se nomme "Interface.e". Nous allons y mettre toutes les fonctions de bas niveau de l'interface. Nous allons y mettre de l'assembleur et des accès aux circuits de l'Amiga AGA, donc c'est cette partie qui sera à modifier pour faire une version non AGA, AmigaOS 4, UAE en UAEGfx ou autre chose.

Sur la première ligne de l'interface, c'est écrit "OPT MODULE". Cela veux dire que le fichier compilé sera un module et donnera naissance à un fichier "interface.m" et non un fichier exécutable.

Nous voyons aussi une ligne "MODULE" : ce sont les modules extérieurs utilisés (sauvés dans EMODULES:).

La ligne suivante : "DEF" décrit les variables globales à l'unité. Elles sont visibles par toutes les procédures de ce module. On y trouve "ecran" et "win", notre écran et notre fenêtre.

J'y ai mis les procédures suivantes :
  • text_out : sert à écrire du texte dans la fenêtre de notre écran Intuition.
  • wait_vbl : sert à attendre que le faisceau de balayage vidéo arrive en bas de l'écran. En clair, comme on tourne à 50 images secondes, cette procédure attend le moment où l'on passe d'une image à l'autre.
  • no_click() : attend que l'on relâche le bouton de la souris. Il y a un appel à "wait_vbl" de manière à faire un filtre antirebond. En effet, quand on appuie ou relâche un bouton de la souris, il y a d'abord de très courtes impulsions électriques dues aux frottement des contacts ou du rebond du microswitch avant d'avoir un signal stable. C'est du moins le sentiment que j'ai. Ou alors ma souris déconne.
  • charge_tout() : est une pseudo-procédure qui fait semblant de charger des fichiers mais en fait elle ne fait qu'écrire du texte à l'écran.
  • init_stuff() : est la procédure qui ouvre l'écran Intuition.
  • clean_up() : ferme l'écran et la fenêtre.
Le programme Lego.e devient :

MODULE '*interface'

PROC main()
  init_stuff()
  charge_tout()
  clean_up()
ENDPROC

La première ligne décrit qu'on utilise le module externe "interface". le symbole "*" devant le nom du module signifie qu'il n'est pas dans "emodules:" mais dans le même répertoire que ce fichier source.

Pour compiler, il faut compiler dans l'ordre :
Premièrement : le module interface.e en tapant "ec interface".
Deuxièmement : le jeu Lego.e en tapant "ec Lego".

A chaque fois que l'on recompile interface.e il faut recompiler Lego.e.

Télécharger le code source à l'issue de l'étape 2.

Étape 2.5 (annexe) : introduction aux composants de l'Amiga

La RAM, la ROM, Intuition, le Blitter, le Copper et quoi encore ? Le processeur, bien sûr.

La ROM est la mémoire morte de l'Amiga. Elle contient une bonne partie du système d'exploitation de l'Amiga et beaucoup de procédures et fonctions contenues dans des bibliothèques (Library). Les bibliothèques qui ne sont pas en ROM sont dans le système, dans LIBS:. On y fait accès de la même manière en utilisant la fonction OpenLibrary qui elle même est une fonction de la bibliothèque "exec" qui est toujours ouverte et qui a sa base dans execbase. Quand on ouvre une bibliothèque, OpenLibrary renvoie la base de la bibliothèque. Chaque fonction est référencée par un décalage. Donc pour connaître l'adresse de la fonction à utiliser, il faut ajouter la base et le décalage. C'est très simple.

Le processeur sert à exécuter des programmes.

Le Copper sert à afficher de la mémoire vers l'écran de l'Amiga. Quand le faisceau lumineux balaye le canon à électrons du moniteur, le Copper calcule la couleur du point à afficher en fonction de ce qui se trouve en mémoire.

Le Blitter sert à faire des copies de blocs de mémoires pour faire des sprites, ou plus précisément sur Amiga : les BOB (Blitter OBjectS). Les sprites sur Amiga sont en fait gérés par le Copper car il permet de mélanger plusieurs sources de mémoire vidéo vers la sortie RVB.

La RAM est divisée en deux : la Chip et la Fast. C'est la mémoire vive de l'Amiga, elle contient tous les programmes et les données en cous d'exécution. La mémoire Chip est accessible par tous les circuits de l'Amiga et souffre de ce partage. Elle est donc plus lente que la mémoire Fast. La mémoire Fast n'est accessible que par le processeur. Par défaut, tout est chargé en mémoire Fast, donc attention, il faut charger les données graphiques en mémoire Chip, où elle ne seront pas exploitables ni par le Copper ni par le Blitter.

Étape 3 : utilisation du Copper

Nous allons maintenant afficher l'image du premier niveau à l'écran. La première méthode consisterait à utiliser Intuition et ses structures screen, windows, rastport, bitmap, layer, IDCMP, etc. pour l'affichage et BltMaskBitMapRastPort pour faire des BOB. Malheureusement, ces techniques ne permettent pas l'efficacité maximale, c'est très complexe puisque très détaillé.

La seconde solution, qui est retenue ici, est d'utiliser directement les registres des circuits AGA.

L'Amiga structure sa mémoire graphique en "bitplan" (ou plan de bits). Cela fonctionne un peu comme des intercalaires colorés transparents. Si on découpe un bonhomme dans un intercalaire bleu et dans un jaune on pourra avoir 4 couleurs :

Couleur 0 : pas de bonhomme du tout.
Couleur 1 : Bonhomme bleu.
Couleur 2 : Bonhomme Jaune.
Couleur 3 : Bonhomme vert : jaune + bleu.

A chaque intercalaire rajouté, on double le nombre de couleurs possibles. L'Amiga AGA gère jusqu'à huit intercalaires pour un résultat de 256 couleurs.

Sur l'Amiga, les couleurs sont programmables, on n'a pas l'effet d'optique naturelle. On pourrait dire que le jaune + le bleu donne du rouge au lieu du vert. C'est la palette de l'Amiga que l'on peut retrouver dans Deluxe Paint ou Brilliance.

Pour illustrer ceci, voici un BOB smiley :

Plan de bits 0 : tête du bonhomme

L00:..*****.
L01:.*******
L02:.*******
L03:.**...**
L04:..*****.
Plan de bits 1 : le reste
L10:........
L11:...*.*.. <- les yeux
L12:........
L13:...***.. <- la bouche
L14:........
Les "." valent 0 et les "*" valent 1. Donc pour le plan de bits 0 : 00111110,01111111,01111111,01100011,00111110, soit 3E 7F 7F 63 3E en hexadécimal.

Pour copier ce BOB, il faut copier les deux plans de bits les uns après les autres. Puisque pour le jeu Lego on sera en 8 plans de bits, il faudrait faire huit copies de plan de bits pour faire un affichage : c'est trop lourd ! On peut faire ces huit copies en une seule à condition d'organiser sa mémoire comme suit :

Les plans de bits :
L00:..*****.
L10:........
L01:.*******
L11:...*.*..  <- les yeux
L02:.*******
L12:........
L03:.**...**
L13:...***..  <- la bouche
L04:..*****.
L14:........
On a entrelacé les plans de bits et, de cette manière, on pourra tout copier d'un coup ! Donc nous allons utiliser un écran en mode RawBlit.

Il faut tout d'abord convertir notre image IFF au format RawBlit, il faut donc utiliser le logiciel GFXMaster.

Ouvrir GFXMaster. Charger le fichier "Level_1.iff" (qui est dans l'archive du code source) en glissant l'icône dans la fenêtre GFXMaster. A côté de "save" faire apparaître "Palette" dans le gadget "cycle" et cliquer sur "config". Choisir "Data", "Aga24" et "Binary". Sauver le fichier en "Level_1.col" dans le répertoire "gfx" de Lego.

A côté de "save" faire apparaître "Raw" dans le gadget "cycle" et cliquer sur "config". Choisir "RawBlit". Sauver le fichier en "Level_1.raw" dans le répertoire "gfx" de Lego.

Ok, il n'y a plus qu'à récupérer tout ça en E !

Nous allons mettre en place une Copper-list personnalisée. La Copper-list est le "programme" que le Copper va exécuter cinquante fois par seconde pour afficher l'écran. Bien sûr il ne se programme pas comme un processeur, mais par balise comme le HTML : c'est un langage descriptif. Une Copper-list complète est très longue, je vais uniquement m'attarder sur les paramètres que l'on va utiliser.

Mais pour le moment, regardons de plus près interface.e. J'ai rajouté trois variables : "plans_ecran:PTR TO LONG", "oldcpol" et "copchip".

plans_ecran est le pointeur vers les plans de bits entrelacés de l'image. La taille de cette image est de 512 pixels de large sur 348 de haut. Comme un pixel est représenté par un bit dans un plan de bits, un plan de bits fait 512 bits de large sur 348 de haut. Un octet vaut 8 bits on a donc : 512/8 = 64 octets de large et 348 lignes de haut. Donc une taille de 64x348 = 22272 octets pour un plan de bits. Nous avons 8 plans de bits, donc notre image pèse 22272x8 octets c'est-à-dire 178176 octets. C'est exactement la taille du fichier "Level_1.raw" généré par GFXMaster.

oldcopl est la variable où est sauvegardée l'ancienne Copper-list. Effectivement, comme nous allons mettre en place notre Copper-list, il faut sauvegarder l'ancienne Copper-list (celle du Workbench) pour pouvoir la restaurer à la fin du programme.

copchip est la variable qui pointera vers notre nouvelle Copper-list installée en mémoire Chip pour être visible du Copper.

Regardons les procédures :

text_out, wait_vbl(), no_click(), wait_clic() : pas de problèmes.

lit_fichier(s:PTR TO CHAR) : cette procédure est en fait une fonction. Elle lit un fichier en allouant la mémoire nécessaire pour le recevoir. Ce fichier lu est logé en mémoire Chip grâce au drapeau "MEMF_CHIP" utilisé lors de l'appel de NewM(). Le retour de cette fonction est le pointeur vers l'endroit mémoire où se situe ce fichier lu.

charge_ecran(type_ecr) : cette procédure charge l'écran du niveau désiré. Pour le moment, nous ne chargeons que le premier niveau donc cette procédure charge le fichier "level1.raw". L'image chargée est sauvée dans la variable globale "plans_ecran". C'est cette partie que l'on va afficher.

charge_palette(type_ecr) : cette procédure charge et met en place la palette de couleurs de l'écran type_ecr. Pour le moment, cette procédure charge la palette "Level_1.col" grâce à "lit_fichier()". Il y a ensuite une boucle "FOR" pour mettre à jour les couleurs, puis la zone temporaire où sont stockée les données lues est libérée avec "Dispose()". Le contenu de la boucle "FOR" va mettre à jour les couleurs de la Copper-list située à l'adresse "copchip". Le point de repère où commence les couleurs dans la Copper-list est "coulcopper". Cette zone est remplie en séparant les poids faibles des poids forts comme il convient de le faire dans une Copper-list AGA.

info_charge() : cette procédure affiche un message pour faire patienter l'utilisateur pendant le chargement des données. Le message est affiché vers la fenêtre Intuition que l'on a ouverte.

info_charge_terminé() : cette procédure affiche un message pour inviter l'utilisateur à cliquer pour démarrer le jeu. Le message est affiché vers la fenêtre Intuition que l'on a ouverte.

init_stuff() : cette procédure ouvre toujours l'écran et la fenêtre Intuition. Ensuite, elle charge le premier niveau grâce à l'appel de "charge_ecran(1)". La ligne "copchip := NewM(taille,MEMF_CHIP OR MEMF_CLEAR)" permet d'allouer de la mémoire en Chip pour pouvoir recevoir notre Copper-list. Le "CopyMem(...)" copie la Copper-list qui démarre à "new_copper:" en mémoire Chip. En effet, la partie de fin du fichier "interface.e" qui commence à "newcopper:" est notre Copper-list. Elle sera compilée par le compilateur E et inclus dans l'exécutable. Cette exécutable est chargé en mémoire Fast s'il y en a. Il faut donc copier la Copper-list en mémoire Chip pour qu'elle puisse être exploitée par le Copper.

Ensuite, la procédure renseigne les pointeurs des plans de bits de la liste Copper. Ces pointeurs sont marqués de lo1 à lo8 pour les poids faibles et de hi1 à hi8 pour les poids forts. Pour réaliser ceci, chaque adresse de plan de bits est mis temporairement dans la variable locale image grâce à l'instruction :
image := plans_ecran, pour le premier plan de bits, et :
image := plans_ecran + (64 x (numéro du plan de bits)).
Pourquoi multiplier par 64 ? Parce que notre image fait 64 octets de large, et comme les plans de bits sont entrelacés, chaque bilan commence tous les 64 octets à partir de l'adresse plans_ecran.

Plus bas dans la procédure on charge la palette grâce à l'appel à la procédure charge_palette(). Ensuite, l'utilisateur est informé que tout est prêt, puis on coupe le multitâche de l'Amiga grâce à "Forbid()". Nous mettons ensuite à jour le registre de pointeur de Copper-list en cours situé à l'adresse (gfxbase+$32) après avoir sauver son contenu dans la variable "oldcop". C'est à ce moment précis que notre image apparaît ! L'utilisation de "Forbid()" est obligatoire car dans un environnement multitâche, le pointeur de Copper-list(gfxbase+$32) peut être mis à jour par le Workbench.

clean_up() : cette procédure remet la Copper-list du Workbench en place, ferme la fenêtre et l'écran Intuition.

newcopper: : c'est ici que commence notre Copper-list. Cette partie n'est pas dans une procédure mais sera quand même compilée par le E. La liste Copper est commentée. Les premières lignes paramètrent un écran en 320x256 pixels AGA en 256 couleurs. Il faut absolument paramétrer les modulos des plans marqués par $0108 "modulos plans pairs" et $010A "modulos plans impairs". Le modulo du plan de bits c'est le nombre d'octets pour passer de la fin d'une ligne visible vers le début de la prochaine. En effet, notre image de 512 pixels de large sur 348 est plus grande que l'image visible à l'écran. Quand le Copper a terminé d'afficher la première ligne, il passe à la suivante, donc il faut qu'il se synchronise avec la position où est située en mémoire la ligne suivante. Entre deux lignes, nous avons le reste de la première ligne + sept lignes des autres plans de bits entrelacés. Le reste de la première ligne vaut 64-40 (en effet 512 pixel = 64 octets et 320 pixels déjà affiché = 40 octets). Les sept autres lignes valent 7x64 (512 pixels). On a donc un modulo de (64-40)+7x64 = 472. Ceci vaut pour les plans pairs et impairs, bien sûr.

Voilà, vous savez maintenant mettre en place votre Copper-list et afficher une image !

Télécharger le code source à l'issue de l'étape 3.

Étape 4 : utilisation du Blitter

Maintenant que le décor est planté, il faut créer le personnage. L'image est trop large pour l'écran on va donc utiliser un défilement (scrolling). Pour déplacer le personnage, on va utiliser ces fameux "BOB" de l'Amiga.

Regardons l'image Bob_1.iff : on observe notre personnage, nommé "Zeami", dans ses quatre directions. L'ordre choisi est l'ordre standard de la manette en binaire : Haut = 1, Bas = 2, Gauche = 4, Droite = 8, Feu = 16. On peut les combiner en les ajoutant (exemple : Haut+Gauche = 9). Pour pouvoir être copié au pixel près par le Blitter, il y a deux règles :

1) Il faut compter une bande vide de 16 pixels de large à droite du BOB.
En effet, cette place est réservée pour les rotations de bits du Blitter qui permettent de copier la mémoire vidéo en décalant les bits vers la droite pour une précision au pixel (bit) plutôt qu'au mot (16 bits = 16 pixels). Les pixels perdus à droite se retrouvent à gauche : il faut qu'ils soient vides.

2) La largeur du BOB est divisible par 16.
En effet, un des paramètres du Blitter est la moitié de la largeur en octet, donc 16 bits = 16 pixels.

Les images font 48x48, on rajoute 16 pixels sur la droite, ça fait 64x48. Les images doivent donc être alignées dans une grille de 64x48 et ont une taille de 48x48 avec une bande de 16x48 vide à droite.

Regardons l'image bob_1.msk.iff : on observe l'ombre des BOB. C'est, en effet, le pochoir que va utiliser le Blitter pour faire sa copie de façon à obtenir un effet de transparence et ne pas avoir un personnage encadré, mais découpé et posé sur un décor. La couleur transparente est le 0. La couleur pleine est le 255.

Vu ? Ok, maintenant on va convertir ces images avec GFXMaster :

Ouvrir GfxMaster. Charger Level_1.iff, en glissant l'icône dans la fenêtre GFXMaster. A côté de "save" faire apparaître "Palette" dans le gadget "cycle" et cliquer sur "config". Choisir "Data", "Aga24" et "Binary". Sauver en "Level_1.col" dans le répertoire "gfx" de Lego. A côté de "save" faire apparaître "Raw" dans le gadget "cycle" et cliquer sur "config". Choisir "RawBlit". Sauver en "Level_1.raw" dans le répertoire "gfx" de Lego. Charger "Bob_1.iff", en glissant l'icône dans la fenêtre GFXMaster. Sauver en "Bob_1.raw" dans le répertoire "gfx" de Lego. Charger "Bob_1.msk.iff", en glissant l'icône dans la fenêtre GFXMaster. Sauver en "Bob_1.msk" dans le répertoire "gfx" de Lego.

Tout est prêt pour le compilateur E.

Regardons le fichier interface.e qui a un peu grandi pour digérer ces BOB et ce défilement.

J'ai rajouté les constantes des adresses des registres du Blitter. Déclaré avec "CONST : NOM_DU_REGISTRE = $ADRESSE". C'est quand même plus parlant d'utiliser "BLTSIZE" plutôt que "$DFF058" pour entrer la taille des données à copier.

Pour les variables globales, on retrouve notre écran, fenêtre, Copper-list, mais en plus :
"bob_bmp" qui contiendra le bitmap des BOB (fichier "bob_1.raw").
"bob_msk" qui contiendra le masque des BOB (fichier "bob_1.msk").
plan_ecran1 et plan_ecran2 qui permettent d'utiliser le double buffering.

Eh oui, j'ai bien dit double buffering : c'est une technique qui consiste à utiliser deux images. Une est affichée à l'écran, et pendant ce temps on prépare la deuxième en dessinant dessus. Quand l'image suivante est prête, on échange les deux écrans et la deuxième image devient visible et on va retravailler la première image qui deviendra en fait la troisième au prochain échange d'écran, et ainsi de suite.

La procédure charge_ecran a évoluée. Elle peut être appelée plusieurs fois de suite parce qu'elle libère la mémoire qui était utilisée avant de charger le fichier. Ce sont les tests :

IF bmp_ecran <> NIL THEN Dispose(bmp_ecran)
Et aussi pour bob_bmp et bob_msk et l'instruction "bmp_ecran:=NIL" et les deux autres. Ensuite, on utilise toujours la fonction "lit_fichier()" pour lire et affecter les fichiers level_1.raw, bob_1.raw et bob1.msk.

La procédure init_stuff() a un peu évolué : les variables bmp_ecran, bob_msk et bob_bmp sont initialisées à NIL. On alloue la mémoire pour les deux images du double buffer grâce à "plans_ecran1:=NewM(64*348*8,MEMF_CHIP OR MEMF_CLEAR)", eh oui, il y a toujours 512 pixels de large donc 64 octets x 348 pixels de haut et 8 plans de bits. A ce moment, je ne savais pas encore que la hauteur aurait dû être de 384 et non 348, mais ça ne gêne pas pour le moment. La mise en place de la Copper-list n'a pas changée.

J'ai rajouté la procédure wait_blitter(). Le Blitter travaille en parallèle par rapport au 68000. Donc avant de s'en servir, il faut attendre qu'il termine son travail en cours avant de modifier ses registres. C'est de l'assembleur.

J'ai créé la procédure efface_zone(xz,yz,lz,hz). Cette procédure "efface" ou plutôt restaure le décor d'origine provenant de l'image level_1.raw vers l'écran en cours de travail. Les paramètres "xz" et "yz" sont en coordonnées pixels de la zone rectangulaire (coin supérieur haut), mais "x" sera arrondi à 8 pixels près. "lz" et "hz" sont les largeurs et hauteurs de cette zone. "lz" est exprimée en octet, donc par paquet de 8 pixels. Tout d'abord, quelques variables de type REG (registre assembleurs de D0 à D7 ou A0 à A7 suivant les disponibilités). L'adresse de la zone est :
adrz := (yz*512) +(xz/8)

Une ligne mesure 512 pixels divisés par 8 pour avoir la valeur en octets et multipliés par 8 plans de bits, donc 512 octets. Je multiplie cette valeur par "yz" et j'ajoute la valeur "xz" convertie en octet. b_size contient la taille du bloc mémoire à copier : la taille de notre zone. La formule appliquée ici, c'est la largeur du BOB en octet divisée par 2, auquel on ajoute 64 fois la hauteur :
taille_blitter = (largeur/2) + (64* hauteur)

Ici je mets 512xhauteur, car il faut encore multiplier par 8 plans de bits qui augmentent notre hauteur en pixel. Le modulo du Blitter est le nombre d'octets entre la fin de la première ligne et le début de la deuxième. Ils seront ignorés par le Blitter. Ce modulo vaut la largeur de l'écran en octet, donc 64 - la largeur de la zone à copier. On calcule ensuite l'adresse de la source et de la destination, stockées dans bplans et plan. Ensuite, les instructions MOVE mettent à jour les registres Blitter. Je mets l'adresse bplans dans BLTAPTH, la source A du Blitter, plan dans la destination D. BLTAPTL est mis à jour aussi puisque j'utilise un MOVE.L au lieu de deux MOVE.W qui séparent les poids forts et les poids faibles de l'adresse dans BLTxPTH et BLTxPTL. Les modulos A et D : BLTAMOD et BLTDMOD sont mis à jour. Les masques de début et fin BLTAFWM sont remplis à $FFFFFFFF (merci la zone de 16 pixels libre à droite du BOB !). Le BLTCON0 vaut $9F00 pour une copie A vers D et BLTCON1 est mis à jour par BLTCON0 en utilisant un MOVE.L au lieu de deux MOVE.W. Enfin, l'instruction qui démarre le Blitter : la mise à jour de la taille des données dans BLTSIZE.

Et bien sûr : la procédure copie_masque(xb,yb,lb,hb,xe,ye). Cette procédure est l'équivalente de la procédure Intuition BltMaskBitMapRastPort. Elle copie un BOB avec le Blitter en utilisant un masque (les fameuses ombres du fichier bob_1.msk). Les paramètres "xb" et "yb" sont en coordonnées pixels de la zone rectangulaire (coin supérieur haut) englobant le BOB, mais "xb" sera arrondi à 8 pixels près. "lb" et "hb" sont la largeur et la hauteur du BOB. "lb" est exprimée en octet. "xe" et "ye" sont les coordonnées à l'écran où va apparaître le BOB. C'est un peu comme la procédure efface zone, mais on utilise les trois sources ABC du Blitter et la destination D. D pointe vers l'écran altéré qui sera affiché, A pointe vers l'image des BOB : bob_1.raw, B pointe vers le masque bob1.msk, C pointe vers les données de l'écran intact level_1.raw. BLTCON0 Vaut $FCA et BLTCON1 VAUT 0 pour que la destination soit alimentée par la source A ou la source C en fonction du masque dans B.

La procédure swap_screen(x=0,y=0) permet d'échanger l'écran de travail avec l'écran visible. Elle fait comme la procédure init_stuff, mais pointant soit vers plans_ecran1 ou plan_ecran2. L'écran visible est mis dans la Copper-list, et la variable plan_ecran pointe vers l'écran de travail. Par la même occasion les décalages écran du Copper sont mis à jour pour pouvoir effectuer le défilement. L'écran est affiché à partir de x,y.

La fonction joystick() lit et renvoie l'état de la manette. 1 pour haut, 2 pour bas, 4 pour gauche, 8 pour droite et 16 pour feu correspondant aux constantes CS_JOYGAUCHE, CS_JOYDROITE etc. du SET défini en haut de l'unité.

Dans la Copper-list, j'ai modifié les premières lignes pour éliminer les effets de décalages lors du défilement (expérimental, les positions en FF ne semblent pas réagir, les valeurs après $008E et après $0090, 92 et 94 modifient la position de l'écran et de la fenêtre). J'ai mis une étiquette "dececr:" au niveau du décalage écran pour pouvoir l'utiliser.

Bon, ceci étant dit, passons au jeu : Lego.e.

La procédure main() en bas est assez simple :

init_stuff()
debut_jeu()
clean_up()

Elle initialise, lance le jeu et libère la mémoire et les écrans.

La procédure debut_jeu() utilise les variables "zeamix" et "zeamiy" pour stocker les coordonnées du personnage Zeami. Les suffixes "_int" et "_prec" sont les coordonnées de Zeami à l'instant précédant pour "prec" et intermédiaire pour "int". Donc "x" et "y" à l'instant t, "x_in"t et "y_int" à l'instant t-1, "x_prec" et "y_prec" à l'instant t-2. La procédure fait trois efface_zone, échange d'écran et recommence à effacer. De cette manière, on met le décor dans les écrans visibles et cachés. On ne peut pas effacer tout en une fois car la taille du Blitter (la variable BLTSIZE) tient sur un mot (2 octets). Elle est limitée de ce fait. Ensuite, tout est initialisé à 0 sauf les coordonnées de Zeami qui sont mis à leur bonne place, à 16 et 32 et la position de l'image de Zeami dans la planche de BOB bob_1.raw en pixel est sauvée dans "posbob" et initialisée à 64 (image de Zeami vers le bas : il nous fait face).

Le début de la boucle REPEAT UNTIL Mouse()<>0 commence : c'est la boucle du jeu. On efface la zone où Zeami se trouvait précédemment avec l'appel à efface_zone(...). On décale les coordonnées actuelles vers les précédentes en passant par les intermédiaires avec la série d'affectation. Après un appel à copie_masque(...). La position de l'écran est calculée en pour suivre la position de Zeami. Un appel à swap_screen(...) précédé bien sûr d'un wait_vbl() met en place l'écran sur lequel on vient d'effacer et afficher Zeami à la place de l'écran actuel, après avoir attendu la fin du VBL (balayage de l'écran). Ceci assure un déplacement fluide et synchronisé à 50 images par seconde si le programme tourne assez vite. Pour le reste, c'est simple : on lit la manette dans la variable "joy" et une batterie de tests met à jour les coordonnées "x" et "y" de Zeami (zeamix et zeamiy) et son image à afficher (posbob).

Attention, à ce niveau, vous pouvez sortir Zeami de l'écran et donc planter l'Amiga. Si Zeami sort et que l'Amiga ne plante pas (surtout vers le haut ou vers le bas), l'Amiga risque d'être altéré et d'avoir ses données en mémoire brouillées. Plus rien n'est sûr et un redémarrage s'impose.

C'est formidable, vous domptez maintenant la partie essentielle de l'Amiga : faire bouger un personnage sur un écran !

Télécharger le code source à l'issue de l'étape 4.

Étape 5 : gérer plusieurs BOB en créant une classe

Au précédent épisode, Zeami pouvait passer à travers les briques, comme un fantôme, la pauvre ! Le but est maintenant de matérialiser les briques. Tout d'abord, il faut les dessiner et dans bob_1.iff, à l'occasion, vous avez sans doute remarqué que Zeami a fait un régime : elle était trop grosse et ralentissait un peu l'animation. J'ai supprimé les parties vides et elle tient dans une grille de 32x48 avec la bande de 16 pixels vides à droite. Les briques elles, font 48x48 avec la bande de 16. Les fichiers "bob.raw" et "bob.msk" sont mis à jour avec GFXMaster. Le fichier "Level_1.iff" avait un bogue : sa taille était de 348 au lieu de 384. En effet, le jeu tient dans une "grille" de 10x8 cases, avec 48x48 pixels par case + une bande de 16 pixels noirs à gauche et à droite de l'écran, soit 512x384. Ce qui en découle a été mis à jour aussi.

Observons le fichier interface.e.

Il n'a pas évolué beaucoup. J'ai créé une procédure : efface_masque(). Elle efface un BOB selon son masque. En effet, quand deux BOB sont proches, si on en efface un, une partie de l'autre peut s'effacer aussi. En effaçant avec un masque, on n'efface que le BOB. Cette procédure ressemble beaucoup à copie_masque().

Maintenant, passons à lego.e. Nous allons créer une classe "t_bob" qui va pouvoir gérer l'affichage et les collisions. Et un tableau pour stocker les six BOB du jeu. "DEF tab_bob[6] : ARRAY OF LONG" est précisément ce tableau. Il va stocker des pointeurs vers des objets t_bob donc vers des "LONG".

Ensuite, on déclare notre objet "t_bob". Ses coordonnées "x,y", les précédentes et intermédiaires, son image et l'image précédente et intermédiaire.

La méthode init() de la classe "t_bob" initialise toutes les variables à 0.

La méthode teste_collision() de la classe "t_bob" va, pour le moment, se contenter de tester les collisions avec le bord de l'écran. Les coordonnées "x" et "y" sont remises à jour si le BOB déborde. La valeur renvoyée est un booléen qui vaut "vrai" s'il y a collision.

Note de style : il faut toujours renvoyer les valeurs des fonctions en utilisant la syntaxe "ENDPROC nom_de_la_variable". L'utilisation de la commande "RETURN" est à bannir puisqu'elle casse l'exécution : cela nuit à la lisibilité. De même, il est conseillé d'avoir un niveau d'indentation de 7 au maximum (7 boucles imbriquées ou IF) et une page par procédure.

La méthode point_dedant(x,y) teste si le pixel précisé en paramètre appartient au BOB ou pas, ou plutôt s'il est situé dans son rectangle englobant. Le BOB est sensé faire 48x48.

La méthode bob_pousse(bob) de la classe "t_bob" va permettre à Zeami de pousser un bloc. Le contexte d'appel de cette méthode est la suivante :

1. Zeami est un t_bob (DEF zeami PTR TO t_bob ; NEW zeami).
2. brique est un t_bob.
3. zeami.pousse(brique).

Donc ici "self" c'est Zeami et "bob" c'est la brique. Ok ?

Dans la première partie, on calcule la variable "grd_cote" qui est le côté de Zeami le plus près de la brique. Ensuite les coordonnées de la brique sont réajustées aux plus près de Zeami. A la fin, une boucle "FOR" teste si la brique qui vient de bouger touche une autre brique. Si c'est le cas, Zeami et sa brique repartent d'où ils viennent, donc on affecte à leur coordonnées "x" et "y" leurs coordonnées intermédiaires.

La procédure bob_collision(bob) teste si un bob touche un autre BOB. Il faut donc tester si chaque coin d'un BOB n'est pas dans l'autre grâce à quatre appels à point_dedant(...).

La méthode dessine() de la classe "t_bob" va effacer et dessiner le BOB dans l'écran caché. L'image à dessiner est identifiée grâce à son numéro : la variable de classe image. Elle sont numérotées dans l'ordre d'apparition dans la planche de dessin bob_1.raw de gauche à droite puis de bas en haut en comptant cinq images par lignes. Donc 0,1,2,3 = Zeami vers le haut, bas, gauche, droite et 5,6,7,8,9 = les briques.

La procédure efface_masque() est utilisée pour effacer le BOB. On utilise ses valeurs précédentes. On n'efface pas à la position actuelle, mais à celle qu'il avait deux images auparavant. Ensuite, un appel à copie_masque dessine le BOB. Au passage, on déplace les coordonnées actuelles dans les intermédiaires et les intermédiaires dans les précédentes.

Maintenant, tout est prêt pour le jeu, voici la procédure debut_jeu() : comme avant, l'écran est effacé. Puis on créé le BOB de Zeami grâce à la commande "NEW bob". Zeami est immédiatement sauvée dans "tab_bob[0]", ensuite ses coordonnées sont initialisées. Une petite boucle "FOR" va créer les BOB des cinq briques, initialiser leurs coordonnées et les sauver dans "tab_bob[]" de 1 à 5. Ensuite, viens le "REPEAT" de la boucle principale du jeu. Chaque BOB est dessiné dans la première boucle "FOR", puis Zeami bouge. A la fin de la boucle "REPEAT" on teste les collisions et Zeami (sauvée dans la variable BOB) pousse les briques du tableau "tab_bob[]" qu'elle touche dans sa boucle "FOR".

Et voilà, c'est plutôt simple de faire une classe en E !

Télécharger le code source à l'issue de l'étape 5.

Étape 6 : modéliser un plateau de jeu dans un tableau

Jusqu'à présent, Zeami pouvait marcher partout, maintenant ce n'est plus le cas. Zeami ne passe plus au travers des éléments du décor. Les briques non plus. Zeami va maintenant plus vite et elle est attirée par une grille magnétique qui réajuste sa position. J'ai mis en place un décor de fond pour remplacer le fond blanc. Donc forcément, le fichier "level_1.iff" a un peu changé (rajout du fond). J'ai intégré le schéma du niveau dans le fichier "bob_1.iff". Il apparaît en bas à gauche de la planche de dessin. Le fichier "level_1.msk" reste inchangé. Le schéma du niveau est un rectangle de 10x8 dans lequel un pixel de couleur 0 indique une case où Zeami peut marcher, un pixel de couleur 255 indique une case non accessible par Zeami. Tout ceci étant transformé en .raw avec GFXMaster, il n'y a plus qu'à traiter ceci en E.

Voyons comment le fichier interface.e à évolué :

Une fonction lit_pixel(x,y) est apparue. Elle permet de connaître la couleur d'un pixel x,y de l'image "bob_1.raw" (NB : c'est l'inverse du ChunckyToPlanar). Pour trouver ce résultat il faut regarder si le pixel de chaque plan de bits correspondant est allumé ou pas (variable "ptest"). La variable du résultat est reconstruite bit à bit. Il y a un léger bogue dans cette fonction : elle ne donne en fait que la couleur du premier plan de bits. Sauriez-vous la corriger ? (solution dans la version finale).

C'est tout.

Il ne reste plus qu'à regarder le fichier Lego.e :

Une variable globale tab_mur[80] a été introduite. C'est un tableau de 10x8 = 80 qui contient 0 pour une case vide et 255 pour une case pleine. Il représente le cadre de 10x8 en bas à gauche de l'image "bob_1.iff". Pour accéder à un élément en x,y on utilise la formule "tab_mur[x+10*y]".

La méthode teste_collision() de la classe "t_bob" a été modifiée. En plus de tester si le BOB est dans les limites de l'écran, elle reste si un des quatre coins du rectangle englobant le BOB est dans un endroit d'accès interdit. Ceci est réalisé par des consultations de "tab_mur". Dans ce cas la variable collision devient vraie, le BOB est renvoyé d'où il vient. Il y a un léger bogue : on peut passer à gauche de l'écran à travers le décor si on pousse la manette en biais. Sauriez-vous le dénicher ? (solution dans la version finale).

La méthode bob_pousse(...) a été modifiée en faisant un appel à "teste_collision" pour voir si la brique que l'on pousse ne sort pas de l'écran, ou ne touche pas un élément de décor.

Dans la procédure début_jeu(), après la boucle "FOR" qui crée les briques dans "tab_bob", il y a une nouvelle boucle "FOR" de 0 à 79 qui va remplir "tab_mur[i]" en utilisant notre nouvelle procédure "lit_pixel".

Télécharger le code source à l'issue de l'étape 6.

Étape 7 : premier niveau jouable !

Nous approchons du but : il ne reste plus qu'à coder la construction en elle-même. Le fichier "bob_1.iff" a été agrémenté d'images supplémentaires : celles des étapes de constructions. Toujours en respectant la grille de 48x48 avec la bande de 16 pixels vides sur la droite et converti en raw. Le fichier "interface.e" n'a pas évolué.

Le fichier lego.e a été modifié pour atteindre cet objectif. On peut remarquer une nouvelle variable globale : "fin_de_jeu". Elle vaut "0" pendant le jeu et s'incrémente quand Zeami est dans sa voiture, le temps de la laisser faire un petit tour.

Forcément, la méthode bob_pousse() a évolué. Au cas où l'on met deux briques qui s'emboîtent l'une contre l'autre, la construction se réalise. Le tout premier test "IF" permet de voir si Zeami touche l'image numéro 13 : c'est la voiture terminée. Dans ce cas, le jeu prend fin et "fin_de_jeu" commence son incrémentation par une affectation à 1. L'image du BOB devient 255 : c'est une convention qui sera testée dans la méthode "dessine()" du BOB pour ne plus être affichée, mais quand même effacée. La boucle "i" de 1 à 5, qui teste si la brique poussée en touche une autre, grâce à l'appel à "bob_collision", va maintenant mettre à jour les variables "ihit" et "iconsrt". A la sortie de cette boucle "ihit" est le numéro de la brique touchée, "ibob" est le numéro de la brique poussée et "iconstr" est le numéro de la première brique non nulle du tableau "tab_bob", c'est la position de l'élément qui peut recevoir la brique suivante. Ensuite, si "ihit" est différent de 0, alors on a bel et bien touché une brique avec une autre et on teste si la brique de construction rentre dans la suivante ou si la suivante rentre dans la brique de construction. Dans ce cas la brique de construction a son image mis à 255 et la brique suivante devient la brique de construction.

La méthode dessine() de la classe "t_bob", comme vu plus haut, est modifiée pour pouvoir dessiner les nouvelles images et aussi pour ne pas dessiner un BOB dont l'image vaut 255. Elle sera un peu mieux écrite dans la version finale.

La procédure debut_jeu(), juste au début de la boucle "REPEAT" libère et met à NIL un BOB dont "l'image_prec" vaut 255, car il est, à ce moment, complètement effacé des deux écrans visible et caché, il peut donc être supprimé. Pour le reste, si "fin_de_jeu" est différent de 0, Zeami va plus vite et son image est celle de Zeami dans la voiture, et "fin_de_jeu" est alors incrémenté jusqu'à atteindre 251 pour sortir de la boucle "REPEAT" et du jeu.

Du côté du son, nous avons pris le fichier eaudio.m de l'archive aminet.net/package/dev/e/eaudio11.lha qui permet de jouer des sons dans un programme en E.

Voilà, c'est fini, il ne vous reste plus qu'à regarder la version finale qui intègre un défilement de texte, deux images de présentation et cinq niveaux. Le cadre 10x8 est passé en haut à gauche de l'image "bob_xx.iff" les pixels de couleur <0 représentent la position de Zeami et des briques dans le niveau. En dessous de ce cadre, une ligne de couleur 0 contient un pixel de couleur 255, c'est pour définir le numéro d'image où la construction se termine. Le pixel du dessous indique si l'on construit la voiture ou la maison. Le jeu boucle jusqu'à ne plus trouver de fichier "image_xx.raw" suivant.

Télécharger le code source à l'issue de l'étape 7.

Étape 8 : version finale avec intro et changement de niveau

Voici la version finale (?) avec source.

Comment jouer :
Déplacer Zeami : manette.
Rejouer le niveau : bouton feu.
Fin du jeu : clic sur la souris.
Passer au niveau suivant : bouton droit de la souris.

Actuellement, on peut déjà créer et jouer 100 niveaux sans modifier le programme, en générant les fichiers "level_xx.col", "level_xx.raw", "bob_xx.raw" et "bob_xx.msk" avec GFXMaster, à partir des images IFF correspondantes que l'on peut créer ("xx" est le numéro de niveau).

Le programme s'arrête quand il ne trouve plus le fichier suivant. Le petit cadre en haut à gauche de 10x8 pixels dans le fichier "bob_xx.raw" représente le niveau : un pixel couleur 0 est un endroit où Zeami peut marcher, un pixel couleur 255 est un endroit où Zeami ne peut pas aller. Le pixel de couleur 1 représente le point de départ de Zeami, et les pixels de couleurs autres à partir de 2 sont les briques ou éléments déplaçables (araignée, rat...). Le premier à la couleur 2, le suivant à la couleur 3, etc.

La ligne deux pixels en dessous de ce cadre (de x=310,y=9 à x=319,y=9) est de couleur 0 et contient un pixel de couleur 255 et sa position en "x" indique le nombre d'étapes de construction (déplacer le pixel blanc vers la droite pour paramétrer une image de construction supplémentaire et vers la gauche pour en retirer une). Le pixel de couleur 0 ou 255 en dessous de cette ligne indique si les cinq dernières images de la planche sont des éléments de construction ou les sprites pour le véhicule qui va se déplacer.

Télécharger le code source à l'issue de l'étape 8.


[Retour en haut] / [Retour aux articles]