Revenir au plan du site

Une caméra de suivi - Ou comment asservir le scrolling au 'personnage'

Jusqu'à présent, on avait le contrôle du scrolling avec le curseur. L'idée de ce tutorial va être de contrôler le véhicule et de laisser le scrolling rattraper la voiture.

On a déjà les coordonnées du scrolling, on a les coordonnées du véhicule, on doit pouvoir automatiser des ordres de scrolling en fonction des différences entre coordonnées.
Asservissement
ld hl,(scroll_corner_posx) : ld bc,256+32 : add hl,bc : ld bc,(tuture.positionx) : sbc hl,bc : jr z,.testeHautBas : jp m,.allerDroite
call ScrollGauche : jr z,.testeHautBas : ld hl,depx : dec (hl) : ld hl,(scroll_corner_posx) : ld bc,-4 : add hl,bc : ld (scroll_corner_posx),hl : jr .testeHautBas
.allerDroite call ScrollDroite : jr z,.testeHautBas : ld hl,depx : inc (hl) : ld hl,(scroll_corner_posx) : ld bc,4 : add hl,bc : ld (scroll_corner_posx),hl
.testeHautBas
ld hl,(scroll_corner_posy) : ld bc,128+16 : add hl,bc : ld bc,(tuture.positiony) : sbc hl,bc : jr z,.finAsservissement : jp p,.allerHaut
call ScrollBas : jr z,.finAsservissement : ld hl,depy : dec (hl) : ld hl,(scroll_corner_posy) : inc l : inc hl : ld (scroll_corner_posy),hl : jr .finAsservissement
.allerHaut call ScrollHaut : jr z,.finAsservissement : ld hl,depy : inc (hl) : ld hl,(scroll_corner_posy) : dec hl : dec l : ld (scroll_corner_posy),hl
.finAsservissement

On garde notre test de touche pour bouger le véhicule (en croix, comme un curseur pour le moment)
ld a,(OCTET_CURSEUR_BAS) : and BIT_CURSEUR_BAS : jr nz,.noBas
ld hl,(tuture.positiony) : ld bc,2 : add hl,bc : ld (tuture.positiony),hl : .noBas
ld a,(OCTET_CURSEUR_HAUT) : and BIT_CURSEUR_HAUT : jr nz,.noHaut
ld hl,(tuture.positiony) : ld bc,-2 : add hl,bc : ld (tuture.positiony),hl : .noHaut
ld a,(OCTET_CURSEUR_DROITE) : and BIT_CURSEUR_DROITE : jr nz,.noDroite
ld hl,(tuture.positionx) : ld bc,4 : add hl,bc : ld (tuture.positionx),hl : .noDroite
ld a,(OCTET_CURSEUR_GAUCHE) : and BIT_CURSEUR_GAUCHE : jr nz,.noGauche
ld hl,(tuture.positionx) : ld bc,-4 : add hl,bc : ld (tuture.positionx),hl : .noGauche

Avec ce premier jet, on peut voir le scrolling se figer sur le véhicule et le marquer au fer, sauf quand on approche des bords.


Maintenant, vu qu'on est parti sur un jeu de voiture, ce qui va être intéressant de faire, c'est de changer la cible du scrolling en fonction de l'orientation du véhicule. Il est peu intéressant niveau gameplay d'avoir un véhicule centré sur l'écran, et c'est en fait pareil pour un jeu d'arcade! Il est plus important de voir devant soi que derrière. Si on veut regarder derrière, bah on se retourne!

On va fusionner le scrolling avec les données de l'article [ Animation des sprites hard ]
En accord avec nos 32 orientations de voiture, on va créer un cercle (centré sur zéro) pour dire à la caméra de pointer devant la voiture. J'en profite pour ajouter le décalage correspondant au centre de l'écran rapport aux coordonnées du coin supérieur gauche, ainsi que le décalage à l'intérieur de notre assemblage de sprites (32/16). Ouf!
directionVisuelle
repeat 32,x
  defw cos(90-x*360/32)*80+256-32,sin(270-x*360/32)*40+128-16
rend

L'asservissement doit utiliser l'orientation du véhicule pour modifier sa cible, la routine change un peu.
Asservissement
ld a,(position_angulaire) : add a : add a : ld hl,directionVisuelle : ld b,0 : ld c,a : add hl,bc
ld c,(hl) : inc l : ld b,(hl) : inc l : ld e,(hl) : inc l : ld d,(hl) : ld (targetModifier),de
ld hl,(scroll_corner_posx) : add hl,bc : ld bc,(tuture.positionx) : sbc hl,bc : jr z,.testeHautBas : jp m,.allerDroite
call ScrollGauche : jr z,.testeHautBas : ld hl,depx : dec (hl) : ld hl,(scroll_corner_posx) : ld bc,-4 : add hl,bc : ld (scroll_corner_posx),hl : jr .testeHautBas
.allerDroite call ScrollDroite : jr z,.testeHautBas : ld hl,depx : inc (hl) : ld hl,(scroll_corner_posx) : ld bc,4 : add hl,bc : ld (scroll_corner_posx),hl
.testeHautBas
ld hl,(scroll_corner_posy) : ld bc,#1234 : targetModifier=$-2 : add hl,bc : ld bc,(tuture.positiony) : sbc hl,bc : jr z,.finAsservissement : jp p,.allerHaut
call ScrollBas : jr z,.finAsservissement : ld hl,depy : dec (hl) : ld hl,(scroll_corner_posy) : inc l : inc hl : ld (scroll_corner_posy),hl : jr .finAsservissement
.allerHaut call ScrollHaut : jr z,.finAsservissement : ld hl,depy : inc (hl) : ld hl,(scroll_corner_posy) : dec hl : dec l : ld (scroll_corner_posy),hl
.finAsservissement

En action! (Oui, il y a un petit problème)


Ça tremble de partout alors que ça ne tremblait pas tout à l'heure!

C'est à dire que le modificateur d'orientation n'est pas arrondi comme les valeurs du scrolling. À moins d'avoir la chance de tomber juste (X multiple de 4 et Y multiple de 2), le scrolling va chercher à compenser en permanence, un coup trop en avant, un coup trop en arrière, sans jamais s'arrêter. On ne va pas arrondir les valeurs de notre cercle, ça serait contreproductif car nous allons avoir besoin de précision pour les déplacements futurs du véhicules. Cet effet de bord est un travers courant de beaucoup de jeux.

Pour faire simple, il faut ne rien faire si on est en dessous de nos marges (4 en X et 2 en Y).

Voici le source final tenant compte de ces marges

Il vous faudra les fichiers suivants pour le compiler :
[binaire des sprites] | [tutureAlpha.asm] | [tutureAlphaBack.asm] | [la tileMap] | [les tuiles]
BUILDSNA : BANKSET 0
SNASET CPC_TYPE,4 ; modèle 6128+ conseillé
ORG #100 : RUN #100
MODE_0 equ 0 : MODE_1 equ 1 : MODE_2 equ 2 : MODE_3 equ 3 : CLEAR_INT equ %10000
ROM_OFF equ %1100 : ROM_BOTH equ 0 : ROM_UP equ %100 : ROM_LOW equ %1000 : INTRESET equ %10000
macro RMR tags : ld a,{tags}+%10000000 : ld b,#7F : out (c),a : mend
ASICOFF equ 0 : ROM0000 equ 0 : ROM4000 equ %01000 : ROM8000 equ %10000 : ASICON equ %11000
ROM0 equ 0 : ROM1 equ 1 : ROM2 equ 2 : ROM3 equ 3 : ROM4 equ 4 : ROM5 equ 5 : ROM6 equ 6 : ROM7 equ 7
macro RMR2 tags : ld a,{tags}+%10100000 : ld b,#7F : out (c),a : mend

LARGEUR_MAP equ 128 : HAUTEUR_MAP equ 128 : LARGEUR_ECRAN_OCTET equ 64 : LARGEUR_ECRAN_MODE_2 equ 512 : HAUTEUR_ECRAN_LIGNE equ 256
OCTET_CURSEUR_HAUT   equ matriceClavier+0 : BIT_CURSEUR_HAUT   equ 1 : OCTET_CURSEUR_DROITE equ matriceClavier+0 : BIT_CURSEUR_DROITE equ 2
OCTET_CURSEUR_BAS    equ matriceClavier+0 : BIT_CURSEUR_BAS    equ 4 : OCTET_CURSEUR_GAUCHE equ matriceClavier+1 : BIT_CURSEUR_GAUCHE equ 1

struct sprite
  positionx defw : positiony defw : adresseDonnees defb : adresseConfiguration defb : sprlargeur defb : sprhauteur defb
endstruct

struct multi
  mapOffset defw : HSSR defb : VSSR defb : colonne defb : ligne defb : adresseDebut defw : crtc12 defb : crtcHL defw : nextBuffer defw
endstruct

ld sp,#100 ; pile par défaut ailleurs qu'en #C000 car nous avons un buffer ici
ld bc,#7F00+%10001100+%00 : out (c),c ; MODE 0
call UnlockAsic : RMR2 ASICON
ld hl,palette_fond : ld de,#6400 : ld bc,32 : ldir : ld hl,#000 : ld (#6420),hl ; +border noir
ld hl,palette_sprite : ld de,#6422 : ld bc,30 : ldir
ld hl,superCarInit : ld de,#4000 : ld bc,1024 : ldir ; copier notre meta sprite
RMR2 ASICOFF
ld bc,#BC00+1 : out (c),c : ld bc,#BD00+32 : out (c),c : ld bc,#BC00+2 : out (c),c : ld bc,#BD00+42 : out (c),c
ld bc,#BC00+6 : out (c),c : ld bc,#BD00+32 : out (c),c : ld bc,#BC00+7 : out (c),c : ld bc,#BD00+34 : out (c),c
ld bc,#BC00+12 : out (c),c : ld bc,#BD30 : out (c),c : ld bc,#BC00+13 : out (c),c : ld bc,#BD00 : out (c),c

ld ix,tuture
ld hl,410*4 : ld (ix+sprite.positionx),hl
ld hl,405 : ld (ix+sprite.positiony),hl
ld a,64 : ld (ix+sprite.sprLargeur),a
ld a,32 : ld (ix+sprite.sprHauteur),a
ld a,#40 : ld (ix+sprite.adresseDonnees),a
ld a,0 : ld (ix+sprite.adresseConfiguration),a

ld ix,ecran1 : ld a,#80 : ld de,ecran2 : call InitEcran
ld ix,ecran2 : ld a,#C0 : ld de,ecran1 : call InitEcran
ld ix,ecran1 ; notre structure écran par défaut pour aller scroller
LaBoucle
call lectureMatriceClavier
call UpdateHardware
call AfficherTableauSprite

ld de,(ix+multi.nextBuffer) : ld ix,de ; permuter la structure de définition du buffer
; on compense rapport à l'écran précédent
.Xnegatif ld hl,depx : ld a,(hl) : and #80 : jr z,.XpasNegatif : inc (hl) : call ScrollGauche : jr .Xnegatif
.XpasNegatif
.Xpositif ld hl,depx : ld a,(hl) : and #7F : jr z,.XpasPositif : dec (hl) : call ScrollDroite : jr .Xpositif
.XpasPositif
.Ynegatif ld hl,depy : ld a,(hl) : and #80 : jr z,.YpasNegatif : inc (hl) : call ScrollBas : jr .Ynegatif
.YpasNegatif
.Ypositif ld hl,depy : ld a,(hl) : and #7F : jr z,.YpasPositif : dec (hl) : call ScrollHaut : jr .Ypositif
.YpasPositif

ld a,(OCTET_CURSEUR_DROITE) : and BIT_CURSEUR_DROITE : jr nz,.pasDroite ; sens horaire
ld a,(position_angulaire) : ld b,0 : ld c,a : add a : add c : ld c,a ; BC = position x3
ld hl,rotation_horaire : add hl,bc : ld c,(hl) : ld b,#DF : out (c),c ; connexion ROM
inc hl : ld a,(hl) : inc hl : ld h,(hl) : ld l,a : ld (AdresseRoutine),hl
ld a,(position_angulaire) : inc a : and 31 : ld (position_angulaire),a
jp AppelCodGenSprite
.pasDroite
ld a,(OCTET_CURSEUR_GAUCHE) : and BIT_CURSEUR_GAUCHE : jr nz,AppelCodGenSprite.suite
ld a,(position_angulaire) : ld b,0 : ld c,a : add a : add c : ld c,a ; BC = position x3
ld hl,rotation_antihoraire : add hl,bc : ld c,(hl) : ld b,#DF : out (c),c ; connexion ROM
inc hl : ld a,(hl) : inc hl : ld h,(hl) : ld l,a : ld (AdresseRoutine),hl
ld a,(position_angulaire) : dec a : and 31 : ld (position_angulaire),a
AppelCodGenSprite
RMR2 ASICON
RMR ROM_UP|MODE_0
ld h,#40
call #1234 : AdresseRoutine=$-2
RMR ROM_OFF|MODE_0
RMR2 ASICOFF
.suite
Asservissement
ld a,(position_angulaire) : add a : add a : ld hl,directionVisuelle : ld b,0 : ld c,a : add hl,bc
ld c,(hl) : inc l : ld b,(hl) : inc l : ld e,(hl) : inc l : ld d,(hl) : ld (targetModifier),de
ld hl,(scroll_corner_posx) : add hl,bc : ld bc,(tuture.positionx)
ld a,l : and #FC : ld l,a : ld a,c : and #FC : ld c,a : sbc hl,bc : jr z,.testeHautBas : jp m,.allerDroite
call ScrollGauche : jr z,.testeHautBas : ld hl,depx : dec (hl) : ld hl,(scroll_corner_posx) : ld bc,-4 : add hl,bc : ld (scroll_corner_posx),hl : jr .testeHautBas
.allerDroite call ScrollDroite : jr z,.testeHautBas : ld hl,depx : inc (hl) : ld hl,(scroll_corner_posx) : ld bc,4 : add hl,bc : ld (scroll_corner_posx),hl
.testeHautBas
ld hl,(scroll_corner_posy) : ld bc,#1234 : targetModifier=$-2 : add hl,bc : ld bc,(tuture.positiony)
ld a,l : and #FE : ld l,a : ld a,c : and #FE : ld c,a : sbc hl,bc : jr z,.finAsservissement : jp p,.allerHaut
call ScrollBas : jr z,.finAsservissement : ld hl,depy : dec (hl) : ld hl,(scroll_corner_posy) : inc l : inc hl : ld (scroll_corner_posy),hl : jr .finAsservissement
.allerHaut call ScrollHaut : jr z,.finAsservissement : ld hl,depy : inc (hl) : ld hl,(scroll_corner_posy) : dec hl : dec l : ld (scroll_corner_posy),hl
.finAsservissement
jp LaBoucle
position_angulaire defb 0 ; étape de départ ZERO
;---------------------------------------------------------------------
AfficherTableauSprite
RMR2 ASICON
ld iy,tableau_sprite
.loop
ld hl,(iy+sprite.positionx) : or a : ld de,(scroll_corner_posx) : sbc hl,de : ld (posx_compensee),hl
bit 7,h : jr nz,.testGauche ; si c'est négatif on fait le test de gauche
; on teste le débordement à droite (512 pixels mode 2 en largeur)
ld de,LARGEUR_ECRAN_MODE_2 : or a : sbc hl,de
jp m,.hautBas
jp .horsEcran
.testGauche
ld a,(iy+sprite.sprlargeur) : add l : ld l,a : ld a,h : adc 0
jp m,.horsEcran ; négatif hors écran, reste à tester le zéro
ld a,l : or a : jp z,.horsEcran ; on fait un raccourci de calcul car on partait d'une position négative
; quasiment le même code de tester le débordement en vertical
.hautBas
ld hl,(iy+sprite.positiony) : or a : ld de,(scroll_corner_posy) : sbc hl,de : ld (posy_compensee),hl
bit 7,h : jr nz,.testHaut ; si c'est négatif on fait le test du haut
; on teste le débordement en bas de l'écran de 256 lignes
ld de,HAUTEUR_ECRAN_LIGNE : or a : sbc hl,de
jp m,.affiche
jp .horsEcran
.testHaut
ld a,(iy+sprite.sprhauteur) : add l : ld l,a : ld a,h : adc 0
jp m,.horsEcran ; négatif hors écran, reste à tester le zéro
ld a,l : or a : jp z,.horsEcran ; on fait un raccourci de calcul car on partait d'une position négative
.affiche
; écriture peu optimisée des 4 sprites (X / Y / zoom) à cause de l'ordre des sprites, on verra plus tard ;)
ld bc,#1234 : posx_compensee=$-2
ld de,#1234 : posy_compensee=$-2
ld h,#60 : ld l,(iy+sprite.adresseConfiguration)
ld (hl),c : inc l : ld (hl),b : inc l : ld (hl),e : inc l : ld (hl),d : inc l : ld (hl),%1001 : ld a,l : add 4 : ld l,a ; sprite 1
push bc : ld a,c : add 32 : ld c,a : ld a,b : adc 0 : ld b,a ; X+32
ld (hl),c : inc l : ld (hl),b : inc l : ld (hl),e : inc l : ld (hl),d : inc l : ld (hl),%1001 : ld a,l : add 4 : ld l,a ; sprite 2
pop bc : ld a,e : add 16 : ld e,a : ld a,d : adc 0 : ld d,a ; Y+16
ld (hl),c : inc l : ld (hl),b : inc l : ld (hl),e : inc l : ld (hl),d : inc l : ld (hl),%1001 : ld a,l : add 4 : ld l,a ; sprite 3
ld a,c : add 32 : ld c,a : ld a,b : adc 0 : ld b,a ; X+32
ld (hl),c : inc l : ld (hl),b : inc l : ld (hl),e : inc l : ld (hl),d : inc l : ld (hl),%1001 ; sprite 4
jr .auSuivant
.horsEcran
ld h,#60 : ld l,4 : add (iy+sprite.adresseConfiguration) : ld bc,8 : xor a ; invisibiliser le sprite
ld (hl),a : add hl,bc : ld (hl),a : add hl,bc : ld (hl),a : add hl,bc : ld (hl),a
;
.auSuivant

ld de,{sizeof}sprite : add iy,de
ld a,(iy+sprite.sprlargeur) : or a ; tester la largeur du prochain sprite
jp nz,.loop ; on reboucle si le prochaine sprite n'a pas une largeur nulle
RMR2 ASICOFF
ret
;---------------------------------------------------------------------
lectureMatriceClavier
ld hl,matriceClavier : ld bc,#f782 : out (c),c : ld bc,#f40e : ld e,b : out (c),c : ld bc,#f6c0
ld d,b : out (c),c : out (c),0 : ld bc,#f792 : out (c),c : ld a,#40 : ld c,d
.loop ld b,d : out (c),a : ld b,e : ini : inc a : inc c : jr nz,.loop
ld bc,#f782 : out (c),c : ret : matriceClavier defs 10,#FF
;---------------------------------------------------------------------
MACRO calculeAdresseTuile increment
exx : ld a,(hl) ; index de la tile dans A
if {increment}==0 : elseif {increment}==1 : inc hl : else : add hl,bc : endif
exx ; index de la tile dans A / HL'=tileMap
ld hl,tuiles : ld b,a : ld c,0 : srl bc : srl bc : add hl,bc ; HL=adresse du début de la tuile
MEND
MACRO calculeAdresseTuileLigne increment
calculeAdresseTuile {increment}
ld a,(ix+multi.ligne) : add l : ld l,a ; HL=adresse du début de la tuile à la bonne ligne
MEND
MACRO calculeAdresseTuileColonne increment
calculeAdresseTuile {increment}
ld a,(ix+multi.colonne) : add l : ld l,a ; HL=adresse du début de la tuile à la bonne colonne
MEND
MACRO calculeAdresseTuileLigneColonne increment
calculeAdresseTuile {increment}
ld a,(ix+multi.ligne) : add (ix+multi.colonne) : add l : ld l,a ; HL=adresse du début de la tuile à la bonne ligne ET bonne colonne
MEND
MACRO expandNextLineDE
ld a,d : add 8 : ld d,a : and #38 : jr nz,@termine
ld a,LARGEUR_ECRAN_OCTET : add e : ld e,a : ld a,#C0 : adc d : ld d,a : res 3,d
@termine
MEND
;---------------------------------------------------------------------
ScrollGauche
;---------------------------------------------------------------------
ld a,(ix+multi.colonne) : or a : jr nz,.goForScroll
ld a,(ix+multi.mapOffset) : and LARGEUR_MAP-1 : ret z ; si on est sur la colonne zéro de la tile la plus à gauche... Bye!
.goForScroll
ld a,(ix+multi.HSSR) : bit 2,a : jr nz,.willDraw : add 4 : ld (ix+multi.HSSR),a : /* always NZ */ ret ; on ne change que le décalage et bye!
.willDraw
add 4 : and 15 : jr nz,.stillSSR
ld hl,(ix+multi.crtcHL) : dec hl : ld (ix+multi.crtcHL),hl ; each word
.stillSSR ld (ix+multi.HSSR),a
ld hl,(ix+multi.adresseDebut) :
ld a,h : and 7 : or l : jr nz,.skip : ld a,h : add 8 : ld h,a : .skip dec hl ; gérer le rebouclage à la décrémentation
ld (ix+multi.adresseDebut),hl
ld a,(ix+multi.colonne) : dec a : and 3 : ld (ix+multi.colonne),a : cp 3 : jr nz,.stillPosx : dec (ix+multi.mapOffset) : .stillPosx
ld hl,(ix+multi.mapOffset) : ld bc,LARGEUR_MAP : exx ; HL'=adresse de la tile BC'=saut de ligne dans la tileMap
ld de,(ix+multi.adresseDebut) ; DE=haut de la colonne à rafraichir
; accès direct à l'affichage d'une colonne
;------------------
afficheColonne
;------------------
ld a,(ix+multi.ligne) : or a : jp nz,.clipped
ld yh,16
.loopTiles
calculeAdresseTuileColonne LARGEUR_MAP
ld bc,#0804
repeat 7 : ld a,(hl) : ld (de),a : ld a,l : add c : ld l,a : ld a,d : add b : ld d,a : rend
ld a,(hl) : ld (de),a : ld a,l : add c : ld l,a
ld a,LARGEUR_ECRAN_OCTET : add e : ld e,a : ld a,#C8 : adc d : ld d,a : res 3,d
repeat 7 : ld a,(hl) : ld (de),a : ld a,l : add c : ld l,a : ld a,d : add b : ld d,a : rend
ld a,(hl) : ld (de),a
ld a,LARGEUR_ECRAN_OCTET : add e : ld e,a : ld a,#C8 : adc d : ld d,a : res 3,d
dec yh : jp nz,.loopTiles
inc b ; reset Z
ret
;----------
.clipped
calculeAdresseTuileLigneColonne LARGEUR_MAP
ld a,64 : sub (ix+multi.ligne) : rrca : rrca : and 15 : ld b,a : ld c,4
.loopColumnC1 ld a,(hl) : ld (de),a : expandNextLineDE (void) : ld a,l : add c : ld l,a : djnz .loopColumnC1
ld yh,15
call .loopTiles
; dernière tuile clippée
calculeAdresseTuileColonne 0
ld a,(ix+multi.ligne) : rrca : rrca : and 15 : ld b,a : ld c,4
.loopColumnC3 ld a,(hl) : ld (de),a : expandNextLineDE (void) : ld a,l : add c : ld l,a : djnz .loopColumnC3
inc b ; reset Z
ret
;---------------------------------------------------------------------
ScrollDroite
;---------------------------------------------------------------------
ld a,(ix+multi.mapOffset) : and LARGEUR_MAP-1 : cp LARGEUR_MAP-16 : ret z ; gérer le bord droit
ld a,(ix+multi.HSSR) : bit 2,a : jr z,.willDraw : sub 4 : ld (ix+multi.HSSR),a : inc a /* reset Z */: ret
.willDraw
sub 4 : jr nc,.stillHSSR : jr z,.stillHSSR
ld hl,(ix+multi.crtcHL) : inc hl : ld (ix+multi.crtcHL),hl
ld a,12
.stillHSSR ld (ix+multi.HSSR),a

ld hl,(ix+multi.mapOffset) : ld bc,16 : add hl,bc : ld bc,LARGEUR_MAP : exx ; HL'=adresse de la tile BC'=saut de ligne dans la tileMap
ld hl,(ix+multi.adresseDebut) : ld bc,64 : ld a,h : and %11111000 : ld d,a : add hl,bc : ld a,h : and 7 : or d ; gère le rebouclage de bloc!
ex hl,de ; DE=haut de la colonne à rafraichir
call afficheColonne
ld a,(ix+multi.colonne) : inc a : and 3 : ld (ix+multi.colonne),a : jr nz,.stillPosx : inc (ix+multi.mapOffset) : .stillPosx
ld hl,(ix+multi.adresseDebut) : inc hl : ld a,h : and 7 : or l : jr nz,.skip : ld a,h : sub 8 : ld h,a : .skip : ld (ix+multi.adresseDebut),hl
inc b ; reset Z
ret
;---------------------------------------------------------------------
ScrollBas
;---------------------------------------------------------------------
ld hl,(ix+multi.mapOffset) : ld a,hi(tileMap+(HAUTEUR_MAP-16)*LARGEUR_MAP) : cp h : ret z ; trop bas, on ne fait rien!
; la tuile en bas sera 16 lignes de tuiles plus bas, soit 2048 octets de tileMap de largeur 128
ld bc,16*LARGEUR_MAP : add hl,bc : exx ; ajouter 16x128 à l'offset de tuile car on va afficher en bas de l'écran + sauvegarde dans HL'
ld de,(ix+multi.adresseDebut) ; écran carré, calculs simples, le bas revient en haut et vice versa :)
call afficheTuilesHorizontalesDeuxLignes
; mettre à jour les informations courantes
ld a,(ix+multi.ligne) : add 8 : cp 64 : jr nz,.pasDeChangementDeTuile ; ligne contient l'offset de début de la ligne suivante
ld hl,(ix+multi.mapOffset) : ld bc,LARGEUR_MAP : add hl,bc : ld (ix+multi.mapOffset),hl : xor a ; passer à la tuile dessous
.pasDeChangementDeTuile
ld (ix+multi.ligne),a ; valider la ligne de tuile
ld hl,(ix+multi.adresseDebut) : call NextLineHL : call NextLineHL : ld (ix+multi.adresseDebut),hl
ld a,(ix+multi.VSSR) : add #20 : and #60 : ld (ix+multi.VSSR),a : jr nz,.pasDeChangementDeBloc
ld hl,(ix+multi.crtcHL) : ld bc,32 : add hl,bc : ld (ix+multi.crtcHL),hl ; adresse CRTC augmente de 32 mots si le bloc change
.pasDeChangementDeBloc
or 1 ; reset Z
ret
;---------------------------------------------------------------------
ScrollHaut
;---------------------------------------------------------------------
ld hl,(ix+multi.mapOffset) : ld a,hi(tileMap) : cp h : jr nz,.okPourLeScroll ; si on est près du début on doit contrôler le "Y"
ld a,l : cp LARGEUR_MAP-1 : jr nc,.okPourLeScroll ; si on est sur la première ligne, on doit regarder la ligne de tuile!
ld a,(ix+multi.ligne) : or a : ret z ; tout en haut, aurevoir!
.okPourLeScroll ; on peut scroller!

; mettre à jour les informations courantes
ld a,(ix+multi.ligne) : sub 8 : jr nc,.pasDeChangementDeTuile ; ligne contient l'offset de début de la ligne suivante
ld hl,(ix+multi.mapOffset) : ld bc,-LARGEUR_MAP : add hl,bc : ld (ix+multi.mapOffset),hl : ld a,64-8 ; passer à la dernière ligne de la tuile dessous
.pasDeChangementDeTuile
ld (ix+multi.ligne),a ; valider la ligne de tuile
ld hl,(ix+multi.adresseDebut) : call PreviousLineHL : call PreviousLineHL : ld (ix+multi.adresseDebut),hl
ld a,(ix+multi.VSSR) : sub #20 : and #60 : ld (ix+multi.VSSR),a : cp #60 : jr nz,.pasDeChangementDeBloc
ld hl,(ix+multi.crtcHL) : ld bc,-32 : add hl,bc : ld (ix+multi.crtcHL),hl ; adresse CRTC baisse de 32 mots si le bloc change
.pasDeChangementDeBloc
; on affiche après en remontant
ld hl,(ix+multi.mapOffset) : exx
ld de,(ix+multi.adresseDebut)
; on continue directement dans l'affichage des lignes
;-------------------------------
afficheTuilesHorizontalesDeuxLignes
;-------------------------------
push de
.Paires
ld a,(ix+multi.colonne) : or a : jp nz,.routineClippee
calculeAdresseTuileLigne 1 : ldi 4 : res 3,d : ld (suiteImpaire.imp0+1),hl ; adresse de la ligne suivante de la tuile à la bonne ligne
.loop
repeat 15,x
calculeAdresseTuileLigne 1 : ldi 4 : res 3,d : ld (suiteImpaire.imp{x}+1),hl ; adresse de la ligne suivante de la tuile à la bonne ligne
rend
jp suiteImpaire.NonClippee

;---------------
.routineClippee
calculeAdresseTuileLigneColonne 1
ld a,4 : sub (ix+multi.colonne) : ld c,a : ld (suiteImpaire.lenFirst+1),a
ld b,0 : ldir : res 3,d
ld a,(ix+multi.colonne) : add l : ld l,a : ld (suiteImpaire.impFirst+1),hl ; adresse de la ligne suivante de la tuile à la bonne colonne
repeat 15,x
calculeAdresseTuileLigne 1 : ldi 4 : res 3,d : ld (suiteImpaire.imp{x}+1),hl ; adresse de la ligne suivante de la tuile à la bonne ligne
rend
calculeAdresseTuileLigne 0
ld c,(ix+multi.colonne)
ld b,0 : ldir : res 3,d
ld a,4 : sub (ix+multi.colonne) : add l : ld l,a : ld (suiteImpaire.impLast+1),hl ; adresse de la ligne suivante de la tuile à la bonne colonne

suiteImpaire
.clippee
pop de : ld a,d : add 8 : ld d,a
.impFirst ld hl,#1234
.lenFirst ld c,#12
ld b,0 : ldir : dec de : res 3,d : inc de : set 3,d
call .loop
.impLast ld hl,#1234
ld c,(ix+multi.colonne)
ld b,0 : ldir
inc b ; reset Z
ret
.NonClippee
pop de : ld a,d : add 8 : ld d,a
.imp0 ld hl,#1234 : ldi 3 : ld a,(hl) : ld (de),a : res 3,d : inc de : set 3,d
.loop
repeat 15,x
.imp{x} ld hl,#1234 : ldi 3 : ld a,(hl) : ld (de),a : res 3,d : inc de : set 3,d
rend
or 1 ; reset Z
ret
;---------------------------------------------------------------------
InitEcran
; IX = structure buffer
; A = poids fort de la page vidéo
ld (ix+multi.nextBuffer),de
ld hl,depx : ld (hl),0
ld hl,depy : ld (hl),0
ld hl,tileMap
ld (ix+multi.mapOffset),hl
ld hl,0
ld (scroll_corner_posx),hl
ld (scroll_corner_posy),hl
ld (ix+multi.HSSR),h
ld (ix+multi.VSSR),h
ld (ix+multi.colonne),h
ld (ix+multi.ligne),h
ld (ix+multi.crtcHL),hl
ld h,a : ld (ix+multi.adresseDebut),hl
rrca : rrca : ld (ix+multi.crtc12),a

; Afficher les tuiles sur tout l'écran

ld de,(ix+multi.adresseDebut)
ld ix,tileMap ; on n'utilise plus la structure à partir de là
ld yh,16 ; 16 tuiles en hauteur
afficheLigne
ld yl,16 ; nombre de tuiles en largeur
push de ; sauvegarder l'adresse de début de ligne de l'écran
afficheTuile
push de ; sauvegarder l'adresse du début de la tuile à l'écran
ld h,(ix+0) : inc ix : ld l,0 : srl hl : srl hl ; x 256 / 4 c'est la taille de nos tuiles (64 octets)
ld bc,tuiles : add hl,bc ; on a l'adresse de la tile
ld a,16
afficheLigneTuile
push de : ldi 4 : pop de
exa : call NextLineDE : exa
dec a : jr nz,afficheLigneTuile
pop hl : ld bc,4 : add hl,bc : ex hl,de ; se placer juste à côté de la tuile précédente
dec yl : jr nz,afficheTuile
ld bc,LARGEUR_MAP-16 : add ix,bc ; revenir à la ligne de tuile suivante
pop hl : ld bc,128 : add hl,bc : ex hl,de ; se placer sous les tuiles à gauche
dec yh : jr nz,afficheLigne
ret
;---------------------------------------------------------------------
NextLineHL ld a,h : add 8 : ld h,a : and #38 : ret nz ; tester le changement de bloc sur n'importe quelle page
ld a,64 : add l : ld l,a : ld a,#C0 : adc h : ld h,a : res 3,h : ret
PreviousLineHL ld a,h : sub 8 : ld h,a : and #38 : cp #38 : ret nz ; test du chgt de bloc (bis)
ld a,l : add #C0 : ld l,a : ld a,#3F : adc h : ld h,a : set 3,h : ret
NextLineDE ld a,d : add 8 : ld d,a : and #38 : ret nz ; tester le changement de bloc sur n'importe quelle page
ld a,64 : add e : ld e,a : ld a,#C0 : adc d : ld d,a : res 3,d : ret
;---------------------------------------------------------------------
UpdateHardware
call WaitVBL
RMR2 ASICON
ld a,(ix+multi.HSSR) : or (ix+multi.VSSR) : or #80 : ld (#6804),a
ld hl,(ix+multi.crtcHL) : ld a,h : and 3 : or (ix+multi.crtc12) : ld bc,#BC00+12 : out (c),c : inc b : out (c),a
inc c : dec b : out (c),c : inc b : out (c),l
RMR2 ASICOFF
ret
;---------------------------------------------------------------------
UnlockAsic
ld bc,#BCFF
out (c),c
out (c),0
ld hl,%1001000011101010
.loop out (c),c : ld a,h : rlca : ld h,l : ld l,a
srl c : res 3,c : and #88 : or c : ld c,a : cp #4D : jr nz,.loop
ld a,#CD : out (c),a : out (c),a : ret
;---------------------------------------------------------------------
WaitVBL
ld b,#F5 : noVBL in a,(c) : rra : jr c,noVBL
VBL in a,(c) : rra : jr nc,VBL : ret

palette_fond: defw #000,#600,#060,#006,#666,#0F0,#00F,#F06,#6F0,#FF0,#FFF
align 256 : tuiles incbin 'carlosMapRatio.bin'
palette_sprite defw #000,#002,#300,#040,#440,#550,#090,#4B0,#9B0,#0F0
superCarInit incbin 'tutureAlpha.bin',0,1024 ; seulement 1024 octets soit 4 sprites ou une étape
; et on se déclare deux structures pour un double buffer
struct multi ecran1
struct multi ecran2
depx defb 0
depy defb 0
scroll_corner_posx defw 0
scroll_corner_posy defw 0
tableau_sprite
struct sprite tuture
struct sprite terminator
startingindex 0
align 4
rotation_horaire
repeat 32,x
defb {bank}supercar{x} : defw supercar{x}
rend
align 4
rotation_antihoraire
repeat 32,x
defb {bank}supercar{x+32} : defw supercar{x+32}
rend
align 4
directionVisuelle
repeat 32,x
  defw cos(90-x*360/32)*80+256-32,sin(270-x*360/32)*40+128-16
rend

org #4000 : tileMap include 'carlosMapRatio.tilemap'

ROMBANK 8 ; En ROM!
org #C000
include 'tutureAlpha.asm'
include 'tutureAlphaBack.asm'