Gruzińskie Noce 05, czyli Nerdy Nights po polsku
« dnia: Maj 25, 2016, 13:13:53 »
Tej nocy: Pojedyncze sprite'y są dla leszczy, dorzućmy więcej! No i poruszajmy tym trochę używając pada.

Wiele Sprite'ów

Ostatnio był tylko jeden sprite, więc używaliśmy zaledwie kilku par LDA/STA żeby załadować jego dane. Tym razem będą 4 sprite'y na ekranie. Wykonywanie tylu wczyań/zapisów zajmuje sporo czasu i miejsca, więc zamiast tego użyjemy pętli podobnej do tej, która ustawiała wcześniej palety. Najpierw ustawiamy bajty używając dyrektywy .db:

sprite'y:

       ;pion tile    atr   poziom

  .db $80, $32, $00, $80   ;sprite 0

  .db $80, $33, $00, $88   ;sprite 1

  .db $88, $34, $00, $80   ;sprite 2

  .db $88, $35, $00, $88   ;sprite 3

Mamy 4 bajty na sprite w jednym wierszu. Bajty są we właściwej kolejności i łatwo je zmieniać. Są to tylko dane początkowe, kiedy program jest uruchomiony kopia w pamięci RAM może być zmieniana, żeby poruszać sprite'm.

Dalej potrzebujesz pętli, żeby skopiować dane do RAM'u. Ta pętla działa tak samo jak ta od ładowania palet z rejestrem X jako licznikiem pętli.

LoadSprites:
  LDX #$00                  ; zaczynamy od 0

LoadSpritesLoop:
  LDA sprites, x             ; załaduj dane z adresu (sprites + x)
  STA $0200, x             ; zapisz pod adresem RAM ($0200 + x)
  INX                          ; X = X + 1
  CPX #$10                  ; Porównaj X z hex'em $10, dziesiętnie 16
  BNE LoadSpritesLoop   ; Odgałąź do LoadSpritesLoop jeśli porównanie Nie było równe 0
                                 ; Jeśli porównanie było równe 16, kontynuuj kod dalej (nie wracaj do pętli)


Jeśli chcesz dodać więcej sprite'ów, możesz dostawić linijkę do sekcji srpite .db i zwiększyć wartość porównywania CPX. Odtworzy to pętlę więcej razy, przekopiuje więcej bajtów.

Kiedy sprite został załadowany do RAM'u możesz zmieniać tam jego dane.


Porty kontrolerów

Kontrolery są dostępne pod adresami pamięci $4016 i $4017. Najpierw musisz wpisać wartość $01 a potem $00 do portu $416. To rozkaże kontrolerowi, by załapał aktualne pozycje przycisków. Potem je wczytujesz spod $4016 dla I pada, $4017 dla II. Przyciski są wysyłane na raz w bicie 0. Jeśli bit zerowy ma wartość 0 - przycisk nie jest wciśnięty; jeśli bit zerowy ma stan 1 - przycisk jest wciśnięty.

Stan przycisków dla każdego z padów jest wczytywany w tej kolejności: A, B, Select, Start, Góra, Dół, Lewa, Prawa.

  LDA #$01
  STA $4016
  LDA #$00

  STA $4016     ; rozkaż obu kontrolerom by załapały stany przycisków
  LDA $4016     ; player 1 - A
  LDA $4016     ; player 1 - B
  LDA $4016     ; player 1 - Select
  LDA $4016     ; player 1 - Start
  LDA $4016     ; player 1 - Góra
  LDA $4016     ; player 1 - Dół
  LDA $4016     ; player 1 - Lewo
  LDA $4016     ; player 1 - Prawo

  LDA $4017     ; player 2 - A
  LDA $4017     ; player 2 - B
  LDA $4017     ; player 2 - Select
  LDA $4017     ; player 2 - Start
  LDA $4017     ; player 2 - Góra
  LDA $4017     ; player 2 - Dół
  LDA $4017     ; player 2 - Lewo
  LDA $4017     ; player 2 - Prawo


Instrukcja AND

Informacja o stanie przycisku jest wysyłana tylko w bicie zerowym, więc wygodnie by było wykasować pozostałe, zbędne bity. Każdy z ośmiu bitów jest ANDowany z bitem spod następnej wartości. Jeśli oba mają wartość 1, wynikiem instrukcji AND jest też jedynka. W każdym innym przypadku wynikiem jest 0.

0 AND 0 = 0

0 AND 1 = 0

1 AND 0 = 0

1 AND 1 = 1

Dla jakichś dwóch przypadkowych bajtów wygląda to tak:

       0 1 0 1 1 0 1 1
AND  1 0 1 0 1 1 0 1
---------------------
        0 0 0 0 1 0 0 1


Interesuje nas wyłącznie bit zerowy, więc czyścimy wszystkie pozostałe:

        01011011    dane z kontrolera
AND   00000001    ANDujemy z tym
----------------
        00000001    wynik (tylko pierwszy z prawej, bit 0 o wartości 1 ) pozostałe usunięta


Więc, aby usunąć zbędne bity podczas wczytywania kontrolerów trzeba dodać funkcję AND po każdym wczytaniu spod $4016 albo $4017:

  LDA $4016       ; player 1 - A
  AND #%00000001

  LDA $4016       ; player 1 - B
  AND #%00000001

  LDA $4016       ; player 1 - Select
  AND #%00000001


instrukcja BEQ

Instrukcja BNE (Branch if Not Equal) używana wcześniej  w pętlach do Odgałęzienia, jeśli Nie Równe ustawionej wartości. Tutaj w BEQ (Branch if Equal) wykonuje się odgałęzienie, jeśli wartość wynosi zero.

ReadA:

  LDA $4016            ; player 1 - A
  AND #%00000001  ; wykasuj wszystko prócz bitu 0
  BEQ ReadADone     ; odgałąź do ReadADone jeśli przycisk NIE jest wciśnięty (0)
                            ; tu dodajemy instrukcje jakie mają się wykonywać, kiedy przycisk JEST wciśnięty (1)
ReadADone:            ; Zakończona obsługa przycisku.


instrukcje CLC/ADC

Do naszego dema użyjemy kontrolera I gracza, by poruszyć sprite'm Mario. Żeby to zrobić musimy umieć dodawać wartości. ADC to dodawanie z przenoszeniem. Przed dodawaniem warto się upewnić, że przeniesienie jest wyczyszczone używając CLC. W przykładzie załadujemy pozycję sprite'a do A, wyczyścimy przeniesienie, dodamy jeden do wartości i wpiszemy wartość z powrotem jako pozycjię sprite'a":

  LDA $0203   ; wczytaj poziomą pozycję sprite'a (X)
  CLC         ; czyścimy flagę przeniesienia
  ADC #$01    ; A = A + 1
  STA $0203   ; zapisz poziomą pozycję sprite'a (X)


instrukcje SEC/SBC

By poruszyć sprite w przeciwnym kierunku potrzebne jest odejmowanie. SCB to odejmowanie z przeniesieniem. Tu dla odmiany ustawiamy flagę przeniesienia (SEC).

  LDA $0203   ; wczytaj pozycję sprite'a
  SEC         ; ustawiamy flagę przeniesienia
  SBC #$01    ; A = A - 1
  STA $0203   ; zapisz pozycję sprite'a


Podsumowując

http://www.nespowerpak.com/nesasm/controller.zip

Pobierz i rozpakuj controller.zip.  Powyższy kod znajuje się w pliku controller.asm.  Upewnij się, że pliki mario.chr oraz controller.bat są w tym samym folderze NESASM, potem dwuklik w controller.bat.  Włączy to NESASM i powinno utworzyć plik controller.nes.  Otwórz go przez FCEUXD SP żeby zobaczyć małego Mario.  Wciśnij przyciski A i B, żeby poruszyć sprite'm. Ruch będzie wykonywany raz na ramkę, lub 60 pikseli na sekundę w trybie NTSC.  Jeśli Mario się nie rusza, upewnij się, że kontrolery są normalnie ustawione w menu Config pod Input...  Jeśli wciśniesz oba przyciski na raz nic się nie powinno ruszać..

Spróbuj zedytować wartości ADC i SBC, żeby hydraulik ruszał się żwawiej.  Ekran ma tylko 256 pikseli szerokości, więc jeśli przesadzisz będzie skakał losowo! poza tym spróbuj edytować kod tak, żeby poruszać czterema sprite'ami na raz.

Na koniec spróbuj użyć d-pada zamiast przycisków. Lewo/prawo niech porusza sprite'ami pozioma, a góra/dół pionowo.


Literatura uzupełniająca:
Oryginał [ENG]:
http://www.nintendoage.com/forum/messageview.cfm?catid=22&threadid=7974

Podręcznik ASM 6502 dla Atari [PL]:
http://atarionline.pl/biblioteka/materialy_ksiazkowe/Asembler%206502%20(v2).pdf