Tomasz "TDC" Cieślewicz napisał:
1. Kulisy powstania
Gra powstała na potrzeby pisma "Atari Magazyn" w 1994 roku. Jako
osoba zajmująca się w redakcji językiem "Action!", przygotowałem
szereg artykułów o tym języku, niestety większość z nich
ostatecznie nie została opublikowana. Ale co się odwlecze to nie
uciecze...
"Atari Magazyn" był pismem Wydawnictwa Bajtek, więc zgodnie z
kanonem starych, dobrych czasów - przygotowałem także źródło
programu do wklepania. Efektem listingu miała być dość dynamiczna
gra, odwołująca się do takich tytułów jak "Zybex", "Uridium",
"R-type" czy "Warhawk". Oczywiście gra była znacznie prostsza i z
powodów objętościowych pisma oparta o grafikę symboliczną.
Celem artykułu było popularyzowanie "Action!" jako języka świetnie
nadającego się do programowania gier. Kod został napisany dość
czytelnie, z wieloma uproszczeniami, aby czytelnik mógł łatwo
poznać sposób tworzenia gier. Redaktor naczelny pisma nie był
jednak zadowolony z tego, że dużą część pisma miałby zająć listing
programu, którym mają się "katować" czytelnicy. Zaproponowałem, aby
program był dostępny na redakcyjnym BBS-ie (powszechnego internetu
wtedy jeszcze nie było) albo na dyskietce do pisma, którą już
wcześniej sugerowali na przykład redaktorzy odpowiedzialni za duże
Atari. Jednak wszystko wskazywało na to, że kierownictwo
Wydawnictwa Bajtka nie zgodzi się na taki drogi dodatek. I
rzeczywiście, pismu "AM" nigdy nie towarzyszyły dyskietki.
2. Opis źródła gry
W starym źródle, jakie znalazłem (wersja 0,8), widać wyraźnie, że
zależało mi na przejrzystości i czytelności kodu, wiele rzeczy było
realizowanych w prosty sposób, bez optymalizacji wydajności, aby
nie zaciemniać kodu dla czytelników. Przykładem uproszczeń jest
brak obsługi 8 kierunków ruchów joystickiem czy niezmazywanie
przeciwników z ekranu. Ze względu na dość dużą liczbę
skomentowanych instrukcji w źródle (czyli chwilowo wyłączonych)
zakładam, że wersja 0,8 nie jest ostatnią, jaka powstała. Jest to
jednak jedyna wersja, jaką znalazłem. Postanowiłem uzupełnić
brakujące elementy, takie jak kolizje z przeciwnikami, straty
życia, strzelających przeciwników i niniejszym zaprezentować jako
przykład gry w "Action!", dla osób które śledzą na AtariOnline.pl
artykuły dotyczące nauki tego języka. Poniżej wersja 1.4.
Uważni obserwatorzy zorientują się, że gra jest podobna do niedawno
publikowanego "Tomcata". I będą mieli rację, to właśnie na bazie
niniejszego kodu powstała gra na Grzybsoniadę. Można więc elementy
kodu skonfrontować ze źródłami nowej gry i przekonać się, że po
niewielkiej optymalizacji wszystko faktycznie działa szybciej.
"Space Travel" wykorzystuje prawie całą wydajność małego Atari, a
przecież "Tomcat" jest znacznie bardziej zaawansowany.
Opis zmiennych i tablic globalnych:
Podstawowe zmienne dla gracza oraz dla przeciwników:
BYTE A,B,C,C1,D,E,E1,F,EE,OPE
A i B to współrzędne gracza x i y.
C1, C i D to zmienne wykorzystywane w strzałach gracza (procedura
LUDZSTR() )
E, E1, F i EE – zmienne związane z kolejnymi przeciwnikami
E i F to współrzędne przeciwników x i y
E1 to stan aktualnego przeciwnika (istnieje, nie istnieje, wybucha
itp.)
EE to zmienna wskazująca indeks w tablicy, czyli aktualnego
przeciwnika (np. procedura PRZECMEN() )
OPE – to licznik ramki, w której animowany jest konkretny
przeciwnik
,BX,BY,B1,B2,BC,BD,BC1,BB,WYB,WY2
BX, BY... to zmienne związane z bossem na końcu etapu (np. x i y
itp.)
BC, BD, BC1 – to zmienne sterujące dużym promieniem jakim strzelają
bossowie
BB zmienna sterująca wyświetlaniem promienia
WYB – zmienna sterująca dźwiękami wybuchów po trafieniu w
przeciwnika
WY2 – zmienna pomocnicza do dźwięków wybuchów
,Q1,Q2,Q3,Q4,Q5
Zmienne pomocnicze wykorzystywane w różnych częściach programu
(np. sterowanie pętlami, danymi itp.)
,ZYC,LEVEL
Aktualna ilość żyć oraz poziom gry.
,P1,P2,P3,P4
Punkty gracza, każda zmienna odpowiada za kolejną potęgę 10. Takie
rozwiązanie jest bardzo wydajne bo nie wykorzystuje arytmetyki 16
bitowej oraz bibliotecznej procedury PRINT
,ILP,ILZP,BAZA,POW,ILP1
ILP, ILZP, ILP1 - maksymalna ilość przeciwników w grze, ilość
przeciwników zliczonych w danej planszy, ilość zestrzelonych
przeciwników. Porównanie ostatnich wartości pozwala stwierdzić
sprawność gracza, co oczywiście wiąże się z dodatkowym bonusem.
BAZA – czy rozgrywka jest w trybie walki z bossem czy nie
POW – ilość energii bossa (wzrasta wraz z każdym zakończonym
poziomem gry)
,K=764,KON=53279,JO=$278,FIRE
Zmienne przypisane rejestrom:
K – obsługa klawiatury. Przechowuje ostatni naciśnięty klawisz oraz
ew. wymusza rodzaj klawisza.
KON – klawisze funkcyjne
JO – obsługa joysticka w pierwszym porcie. Oczywiście zamiast tego
rozwiązania można użyć zwykłej bibliotecznej funkcji STICK(n) -
zrezygnowałem z niej po sygnałach od Sikora, że funkcja ta potrafi
nieprawidłowo działać (z runtimem) na real Atari.
FIRE – zmienna sterująca strzelaniem gracza
,RND=$D20A
RND – sprzętowy generator liczb losowych
CARD CZAS,CZMAX
Odliczają czas rozgrywki, gdy czas się skończy (CZMAX), kończy się
etap gry i rozpoczyna się walka z bossem.
Tablice:
1. parametry kolejnych przeciwników (współrzędne x, y i stan):
BYTE ARRAY PRZECE
BYTE ARRAY PRZECF
BYTE ARRAY PRZECE1
2. parametry kolejnych strzałów gracza:
BYTE ARRAY STRX
BYTE ARRAY STRY
BYTE ARRAY STR1
3. parametry kolejnych strzałów przeciwników:
BYTE ARRAY PSX
BYTE ARRAY PSY
BYTE ARRAY PS1
Nowe zmienne dodane do kodu w wersji 0,9:
BYTE PSM,PSI
PSM – maksymalna ilość pocisków przeciwników jakie może obsłużyć
gra
PSI – zmienna pomocnicza wskazująca pierwszy wolny (teoretycznie)
indeks w tablicy pocisków przeciwników.
BYTE TRU
Zmienna, która wskazuje aktualny poziom trudności w grze. Wzrasta
stopniowo wraz ze zmienną LEVEL. Jednak ta ostatnia zmienna
odpowiada za wyświetlanie odpowiedniej wartości na ekranie,
natomiast TRU jest wykorzystywane bezpośrednio w algorytmach
dotyczących poziomu trudności rozgrywki. Od tej zmiennej zależy
przykładowo ilość wystrzeliwanych (i czy w ogóle) pocisków przez
przeciwników.
Procedury w grze:
PROC TX(BYTE X,Y BYTE ARRAY T)
Dość szybka procedura wyświetlająca na ekranie ciąg znaków. W grze
wykorzystywana jest intensywnie do odrysowywania na ekranie postaci
gracza oraz przeciwników. Napisana jest w sposób czytelny, ale nie
maksymalnie wydajny (jednak jak na kluczową procedurę graficzną -
jest wystarczająco wydajna). Wadą tej procedury jest zmienianie
znaków pomiędzy kodami ekranowymi oraz znakami ATASCI, pokrywają
się np. małe litery i dla czytelności kodu, najczęściej napisy w
grze są pisane małymi literami.
PROC PO(BYTE X,Y BYTE ARRAY T)
Procedura pomocnicza łącząca dwie procedury biblioteczne Action!
Nie są potrzebne bo wszystko można zrobić za pomocą TX, jednak tu
zostały użyte dla większej czytelności kodu źródłowego.
PROC RYSPRZ(BYTE X,Y)
Rysowanie przeciwnika w podanych współrzędnych x i y.
PROC RYSWYB(BYTE X,Y)
Rysowanie wybuchu w podanych współrzędnych x i y.
PROC LUDZ(BYTE X,Y)
Rysowanie pojazdu gracza w podanych współrzędnych x i y.
PROC RYSBAZ(BYTE X,Y)
Rysowanie trzech różnych faz bossa (Bazy) w podanych współrzędnych
x i y.
PROC SOUWY()
Generowanie różnych rodzajów odgłosów trafienia przeciwników
PROC REKLAMOWKA()
Czołówka gry SPACE TRAVEL, wyświetlenie napisów oraz obsługa
klawiatury.
PROC RYNCHR()
Wykorzystywana w różnych miejscach kodu procedura w asemblerze
synchronizująca wykonywanie kodu gry z początkiem generowania
obrazu przez procesor graficzny.
PROC RUCHYJO()
Obsługa ruchów joystickiem oraz rysowanie pojazdu gracza.
PROC LUDZSTR()
Animowanie kolejnych pocisków gracza.
Procedura ponownie definiuje zmienną pomocniczą Q1, jednak tym
razem jest to zmienna lokalna. Jest to niezbędny zabieg, gdyż
zmienna globalna Q1 jest wykorzystywana w innych miejscach kodu,
który wywołuje procedurę LUDZSTR(). Dzięki temu ta sama zmienna nie
jest modyfikowana w różnych miejscach kodu i program działa
prawidłowo.
PROC RYSZYC()
Odrysowanie na ekranie aktualnej ilości żyć.
PROC RYSPUN()
Zoptymalizowana procedura zliczająca punkty (specyficzna arytmetyka
na 4 bajtach) i wyświetlająca je na ekranie. Odpowiada także za
bonusowe życia.
PROC OPGR()
Odrysowanie elementów ekranu gry.
PROC PRZECSTR()
Poruszanie i animowanie pocisków przeciwników
Procedura definiuje lokalną zmienną pomocniczą Q1, podobnie jak ma
to miejsce w procedurze LUDZSTR().
PROC PRZECWYB()
Wyświetlenie wszystkich wybuchających przeciwników (w kolejnych
fazach animacji).
PROC PRZECMEN()
Generowanie, animowanie przeciwników oraz losowanie czy dany
przeciwnik ma strzelić. Wyjaśnienia wymaga zapis „IF RND > 200 &
RND < TRU THEN (...)”, dzięki temu, że generator losowy jest na
Atari zrealizowany sprzętowo (i to na dobrym poziomie w stosunku do
innych komputerów od ZX Spectrum zaczynając a na pecetach kończąc),
jest to bardzo prosty zapis dwóch losowań w oparciu o 8 bitową
wartość. Jedno losowanie nie jest wystarczające, dlatego po
spełnieniu pierwszego warunku, losowanie odbywa się drugi raz (z
uwzględnieniem aktualnego poziomu trudności – zmienna TRU) i
dopiero po spełnieniu obu warunków może zostać wygenerowany
strzał.
Jest to prosty zapis realizujący tą istotną dla rozgrywki funkcję.
Jest też bardzo szybki, bo wykorzystuje możliwości sprzętowe Atari
oraz nie wykonuje skoku do bibliotecznej procedury RAND(), która do
najszybszych nie należy (jest ona użyta w innym miejscu programu
dla zobrazowania funkcjonalności obu rozwiązań).
PROC CZARY(BYTE X,Y)
Generuje animację opartą o wartości losowe, ma to obrazować efekt
przejścia pomiędzy wymiarami dokonywanego przez bossa.
Dodatkowo kod wykorzystuje w innym celu zmienną POW, a mianowicie
jako zmienną pomocniczą. Związane jest to z tym, że ta procedura
wykorzystuje wszystkie dostępne zmienne pomocnicze od Q1 do Q5.
Zgodnie z chętnie stosowaną w programowaniu zasadą
brzytwy Ockhama,
została tu zminimalizowana ilość zmiennych wykorzystywanych w
programie.
PROC ZERTAB()
Zerowanie kluczowych tablic gry.
PROC NEXTLEV()
Przejście do kolejnego etapu gry. Wyświetlenie stosownych napisów,
ustawienie parametrów rozgrywki (np. zwiększenie poziomu trudności
itp.).
PROC STRATAZYC(BYTE X)
Procedura realizująca wszystkie niezbędne operacje związane ze
stratą życia. Parametr x, umożliwia przekazanie przez algorytmy
miejsca, w którym został trafiony gracz. Następnie w tym miejscu
rysowany jest wybuch i zatrzymywana jest akacja gry. Początkujący
programiści gier powinni prześledzić w jaki sposób jest
zrealizowane to, że procedura odpowiedzialna za rysowanie tylko
wybuchających przeciwników zostaje tutaj zaadoptowana do
wyświetlenia dodatkowego wybuchu, który tutaj nie jest (wyjątkowo)
związany z przeciwnikami.
PROC DANEPOCZ()
Ustawia wartości początkowe wszystkich istotnych zmiennych dla
rozgrywki – jest to swoiste zresetowanie gry.
PROC GRA()
Główna pętla gry: poruszanie się gracza, generowanie efektów
dźwiękowych, strzelanie gracza, algorytmy obu typów przeciwników,
kolizje, odliczanie czasu gry, obsługa klawiatury, zakończenie gry
gdy ilość żyć równa jest zero oraz odrysowanie związanego z tym
ekranu końcowego gry.
Zawiera również początkowe odrysowanie ekranu w momencie
rozpoczęcia gry.
PROC MAIN()
Pierwsza uruchamiana procedura w programie, która rozpoczyna się od
ustalenia stałych parametrów dla pocisków przeciwników (są to
parametry programu a nie gry, więc nie zmieniają się one w dalszej
części programu – ich zmiana może doprowadzić do zawieszenia się
programu). Następnie w pętli uruchamiane są naprzemiennie procedury
REKLAMOWKA() oraz GRA(), zapewniając ciągłość tych dwóch
niezbędnych w każdej grze elementów.
3. Listing programu
Plik ACT wspomnianej wersji dostępny
tutaj. Właściwie każdy algorytm funkcjonujący w tej grze da się
znacznie przyspieszyć, nawet w samym "Action!", czyli bez
najmniejszych wstawek asemblera. Zachęcam do prób osoby, które chcą
nabrać biegłości w optymalizacji :).
Każdego kto zechce samodzielnie przygotować wersję samouruchamialną
(plik XEX), przestrzegam, że to konkretne źródło jest niestabilne
po wykorzystaniu "Action! Library". Wcześniej nigdy nie miałem z
tym podobnych problemów, nie wnikam dlaczego tak się dzieje, ale
jestem mocno zdziwiony, bo kod jest niezwykle prosty i nie
wykorzystuje nic szczególnego.
4. Gotowa gra
Nie ma jednak rzeczy niemożliwych, udało się przygotować
skompilowaną wersję :). Z katalogu gier AOL można ściągnąć
plik XEX z gotową wersją "Space Travel". Gra nie wymaga opisu
poza informacją, że klawiszem Esc można zakończyć grę, a w
"Action!" klawiszem SELECT. Miłego giercowania i zmagań z
"Action!".