Kurs C na Atari cz.2 by Ilmenit 2010-08-25 15:28:50

Jakub "Ilmenit" Dębski napisał:



Piszemy grę w C, część 2

W poprzedniej części kursu nauczyliśmy się podstaw używania CC65. Wspomniałem o niezgodności CC65 ze standardem języka oraz standardem wytyczonym przez popularne kompilatory. Główne niezgodności są trzy.

Najważniejsza z nich dotyczy typów ze znakiem i bez znaku (signed, unsigned). Przy pominięciu w deklaracji zmiennej znakowości, CC65 ustala, że jest on unsigned, w przeciwieństwie do większości kompilatorów C. Tak więc "char a;" znaczy "unsigned char a;", a nie "signed char a;". Dla określenia zmiennej ze znakiem musimy użyć "signed". Jest to główna rzecz, na którą należy zwrócić uwagę kompilując w CC65 kody tworzone dla innych kompilatorów. Druga różnica dotyczy ścieżek w poleceniu preprocesora #include "". W CC65 wszystkie takie ścieżki dołączane są względem nie aktualnego pliku, ale katalogu, w którym został uruchomiony kompilator. Trzecia niezgodność dotyczy typów zmiennoprzecinkowych – w kompilatorze CC65 ich brak, choć można znaleźć w necie kilka ich implementacji (nie potrzebowałem, więc nie testowałem).



Obsługa duszków

Duszki (zwane Player-Missile Graphics, PMG) w małym Atari są bardzo ograniczone w stosunku do innych platform. W prosty sposób można przesuwać je tylko w poziomie, zaś do przesunięcia w pionie konieczne jest kopiowanie pamięci, w której się znajdują. W naszej grze duszki wykorzystamy do płynnego przesuwania paletki oraz piłki, ponieważ w użytym wcześniej trybie znakowym przesuwają się one skokowo. PMG dokładniej opisane jest w DeRe Atari (po angielsku w AtariArchive i po polsku w Bibliotece Atarowca serwisu AtariOnline.pl).

Pamięć duszków jest liniowa i zajmuje 1024 lub 2048 bajtów zależnie od tego, czy używamy duszków w podwójnej czy pojedynczej rozdzielczości. UWAGA: pamięć ta MUSI być wyrównana do 1024, co znaczy, że musi zaczynać się od adresu będącego wielokrotnością 1024 (400h). Język C nie umożliwia deklarowania tablic pod określonymi adresami, ale w CC65 można to uzyskać na kilka sposobów. Można zadeklarować większą tablicę statyczną lub przydzielić ją dynamicznie, po czym użyć wyrównanego adresu, zaś niewykorzystany obszar pamięci dodać do sterty za pomocą funkcji _heapadd() (poniższy kod jest autorstwa Shawna Jeffersona):

void __fastcall__ *aligned_malloc(size_t size, unsigned int bound)
{
char *d, *b;
unsigned int lo_free, hi_free, bnd;

bnd = bound - 1;

//*** Allocate an aligned block of memory ***//
d = (char *) malloc(size + bnd); // allocate mem
if (!d) return(NULL); // mem not avail

b = (char *) ((((unsigned int) d) + bnd) & ~bnd); // aligned pointer

lo_free = b - d; // memory not used
hi_free = bnd - lo_free;
_heapadd(d, lo_free); // free low
_heapadd(b + bound, hi_free); // free high

return(b);
}


Wadą jest to, że przy użyciu dynamicznej pamięci kompilator nie przeprowadza dobrej optymalizacji, ponieważ adres nie jest znany w czasie kompilacji. Drugą wadą jest fragmentacja pamięci - obszary zwolnione można ponownie zaalokować, ale nie są one w ciągłym miejscu pamięci, więc nie można zalokować dużych obszarów. Ogólnie odradzam używanie malloc() i free() w CC65 i polecam wykorzystać w ich miejsce tablice globalne – Atari ma zbyt mało pamięci na dynamiczne nią zarządzanie.

Najwygodniej byłoby ustalić stałe miejsce w pamięci, na przykład adres 4000h i wykorzystać kolejne 1024 lub 2048 bajtów. Musimy znaleźć (lub zrobić) miejsce w pamięci, które nie jest wykorzystywane przez nasz program. Takim miejscem jest na przykład pamięć pod ROM, do której możemy się dostać za pomocą rejestru PORTB ($D301).

Możemy również zmodyfikować plik konfiguracyjny, który modyfikowaliśmy w poprzedniej części kursu przy zmianie trybu graficznego. Plik ten zawiera informacje dla kompilatora, co i gdzie ma zostać umieszczone w pamięci. Potrzebujemy zatem zrobić "dziurę" w pamięci na nasze dane. Możemy albo zmodyfikować istniejące segmenty, albo dodać kolejny, ale najprostszym sposobem jest przeniesienie w górę miejsca, od którego zaczyna się program.

Domyślnym „dolnym” adresem programu jest STARTADDRESS: default = $2E00; Wartość ta została wybrana tak, aby możliwe było załadowanie różnych sterowników DOS-a (źródło). Aby mieć więcej pamięci dla naszego programu możemy bezpiecznie obniżyć ją do adresu $2000. My jednak potrzebujemy zrobić coś przeciwnego, czyli zabrać programowi pamięć. W tym celu ustalamy adres początku programu na $2800, dzięki czemu mamy 2KB ($800 bajtów) pamięci od adresu $2000 do $2800.

Wiemy już gdzie będzie znajdować się pamięć duszków, trzeba je teraz zainicjować. Ponieważ deklarowanie za każdym razem adresów specyficznych dla Atari jest irytujące, wykorzystajmy plik nagłówkowy przygotowany przez Jakuba Husaka, który rozszerzyłem o kilka przydatnych definicji związanych z obsługą duszków.

Za pomocą rejestru SDMCTL ustalamy tryb pracy ANTICa. Pamięć duszków wskazujemy za pomocą rejestru PMBASE. Do włączenia PMG służy rejestr GRACTL. Priorytet wyświetlania duszków określa GPRIOR. Wszystko oczywiście opisane w mapie pamięci. Kolory obiektów ustalamy za pomocą PCOLRx. Jeżeli nie wiesz, jaka wartość odpowiada kolorowi, możesz użyć jednego z załączonych programów (rgb.zip).

Każdy obiekt PMG zajmuje 128 bajtów pamięci w rozdzielczości dwuliniowej i 256 bajtów w rozdzielczości jednoliniowej. Pamięć poszczególnych obiektów dostępna jest pod adresami:

Rozdzielczość dwuliniowa Początek
Pociski PMBASE+0x180
Gracz 0 PMBASE+0x200
Gracz 1 PMBASE+0x280
Gracz 2 PMBASE+0x300
Gracz 3 PMBASE+0x380
Rozdzielczość jednoliniowa Początek
Pociski PMBASE+0x300
Gracz 0 PMBASE+0x400
Gracz 1 PMBASE+0x500
Gracz 2 PMBASE+0x600
Gracz 3 PMBASE+0x700


Pozycja pozioma duszków ustawiana jest za pomocą rejestrów HPOSPx, zaś pionowa według pozycji w pamięci obiektu. Widoczność obiektów opisana jest tutaj. Zatem nasza procedura inicjalizująca grafikę PMG wygląda następująco:

#define pmg_memory 0x2000
#define pmg_memory_ptr ((unsigned char *) 0x2000)

char paddle_gfx[]={ 0x3C, 0x7E, 0xFF, 0xFF };
char ball_gfx[]={ 0, 0x2, 0x2, 0x5, 0x5, 0x2, 0x2, 0 };

void place_ball()
{
// +0x500 for the player 1,
memcpy ( pmg_memory_ptr+0x500+ball_y , ball_gfx,sizeof(ball_gfx));
POKE(HPOSP1,ball_x); // horizontal position
}

void init_pmg()
{
POKE(SDMCTL,DMACTL_ENABLE_PLAYER_DMA | DMACTL_ENABLE_MISSLE_DMA | DMACTL_NORMAL_PLAYFIELD | DMACTL_SINGLE_LINE_RESOLUTION | DMACTL_DMA_FETCH_INSTRUCTION);
// PMBASE points to our PMG memory page (page has 256 bytes)
POKE(PMBASE,pmg_memory/256);

// Zero pmg memory
memset(pmg_memory_ptr,0,2048);

// turn on PMG for players and missiles
POKE(GRACTL,PMG_PLAYERS | PMG_MISSILES);

// set PMG priority
POKE(GPRIOR,1);

// init paddle
POKE(PCOLR0,0xFF);
POKE(SIZEP0,SIZEP_DOUBLE);
POKE(HPOSP0,paddle_pos); // paddle position

// +0x400 for the player 0, +192 for vertical position on the screen
memcpy(pmg_memory_ptr+0x400+192 , paddle_gfx , sizeof(paddle_gfx));

// init ball
POKE(PCOLR1,0x5F);
POKE(SIZEP1,SIZEP_SINGLE);
place_ball();
}


Aby przykład był bardziej przejrzysty, w przykładzie PiszemyGre6.zip została uproszczona obsługa piłki:



Nie jest mi znany działający pod Windows edytor grafiki duszków, choć są takie programy działające na małym Atari (nie używałem, więc nie polecę konkretnego).



Zmiana zestawu znaków

Do przygotowania własnego zestawu znaków można użyć programu "Atari Font Maker" lub dla bardziej doświadczonych mającego znacznie większe możliwości "Graph2Font". Można również korzystać z bogatej kolekcji fontów dostępnych w serwisie AtariOnline.pl. W tym przykładzie wykorzystamy dostępny w powyższej kolekcji font XFTOOL.FNT, który lekko zmodyfikujemy dodając 4 rodzaje cegiełek do niszczenia, począwszy od znaku 64, dwa znaki na cegiełkę:



Do ustawienia własnego zestawu znaków wykorzystujemy rejestr CHBAS. Wskazuje on na stronę pamięci, która zawiera zestaw znaków (1024 bajty = 128 znaków * 8 bajtów na znak). UWAGA: podobnie jak przy duszkach początek pamięci zestawu znaków musi być wyrównany do 1024 ($400). W przypadku duszków mogliśmy w prosty sposób korzystać z pamięci pod adresem $2000, ponieważ były tam niezainicjowane dane. Sytuacja nie jest tak prosta, gdy chcemy wykorzystać wyrównaną pamięć z danymi. W CC65 powinien do tego służyć plik konfiguracji linkera, ale w przypadku platformy Atari nie działa on wystarczająco elastycznie (źródło). W małych projektach można w prosty sposób zabrać wyrównany fragment pamięci RAM dodając własny segment w pliku konfiguracyjnym:

SEGMENTS {
...
FONT: load = RAM, type = rw, align=$400, define=yes;
...
}


a następnie w pliku asemblerowym dołączyć (.incbin) plik binarny do tego segmentu:

.export _font_base
.segment "FONT"
_font_base:
.incbin "XFTOOL.FNT"


Jeżeli nie chcemy używać do tego asemblera, musimy użyć programu "BIN2C" i zmienić plik z fontem na tablicę języka C, a następnie dołączyć taką tablicę w kodzie za pomocą:

#pragma dataseg (push,"FONT")
#include "font.h"
#pragma dataseg (pop)


Teraz w celu zmiany zestawu znaków pozostanie jedynie ustawić CHBAS:

POKE(CHBAS,((unsigned int) &font_base)/256);

Plik PiszemyGre7.zip


Niestety, ten sposób dołączania plików pod wyrównane adresy ma tę wadę, że zabiera dużo cennej pamięci, co ma znaczenie przy większych projektach. Wystarczy spojrzeć do środka powstałego pliku xex, żeby zobaczyć dużą dziurę w jego środku odpowiadającą za umieszczenie danych w wyrównanym miejscu. Sposób jest jednak prosty, więc w małych programach można go stosować. Lepszy sposób umieszczenia zewnętrznych danych pod wybranymi adresami pokażę przy odtwarzaniu muzyki i dźwięków.

Wszystkie pliki do powyższego odcinka kursu znajdują się tutaj oraz w tym wątku na forum.
XaVeR 2010-08-25 15:35:55

Czy tylko u mnie ta nowinka dziwnie wygląda?
Wychodzi z środkowej kolumny i tekst z grafiką wchodzi na prawą kolumne (i dalej).

ir 2010-08-25 15:36:52

Rozjeżdża się jak chu chu

Kaz 2010-08-25 15:43:53

W Operze jest poprawnie, musicie zmienic przegladarke ;)

ir 2010-08-25 15:48:00

firefox-a?

Kaz 2010-08-25 15:50:27

Dziwna sprawa. W Operze jest okey, IE rozciaga calosc, Firefox nie rozciaga, ale tez tekstu nie zwija. Poprzedni odcinek kursu C jest poprawny wszedzie.

Wolfen 2010-08-25 15:55:59

W Firefoxie tez mi rozciaga... nie da sie czytac po ludzku artykulu ;/

larek 2010-08-25 15:58:37

W Safari też nie jest dobrze - tekst wystaje z prawej strony wraz z obrazkami.

insert 2010-08-25 15:58:42

oooo nareszcie coś się dzieje ;)

Kaz 2010-08-25 16:00:43

Bo to nowinka dywersyjna, dziala poprawnie tylko w Operze :D

George 2010-08-25 16:11:52

W Chromej przeglądarce również środkowa kolumna nie zważa na prawą :)

jhusak 2010-08-25 16:35:02

Czyli strona jest zoptymalizowana pod jedynie słuszną operę :)

jhusak 2010-08-25 16:41:01

@ilmenit, świetna sprawa z tym kursem. Tak po prostu łopatologicznie - dla wszystkich zrozumiale i nie trzeba rozgryzać, dlaczego coś nie działa.

Kaz 2010-08-25 16:46:55

Kuba - chcialbym, zeby tak bylo :). Prawda jest brutalniejsza: cos jest w nowince (zapewne w kodzie C dla Atari), co jest nieprawidlowo interpretowane przez CuteNews i powoduje takie niezamierzone efekty (poza Opera). Wyslalem mail do admina, moze bedzie mial czas rzucic okiem, znalezc i zaradzic problemowi.

jhusak 2010-08-25 16:47:16

Edytor duszków to po prostu edycja bitmapy o szerokości 8 pikseli. Można to w Gimpie wyrysować i zapisać jako źródło w C(!). Podobnie z duszkami 3-kolorowymi, ale tutaj trzeba już sobie paletkę ustawić 3kolorową, z czego 2 kolory po zsumowaniu (or) mają dać trzeci, jaśniejszy. Następnie prosty skrypcik i mamy duszki 3kolorowe.

jhusak 2010-08-25 16:50:26

@Kaz, złam tę długaśną linijkę w drugim źródle w funkcji init_pmg() i podziel na 3 równe części. Program się dalej skompiluje, a szerokość szpalty będzie ok.

jhusak 2010-08-25 16:54:18

no, jeszcze w dwóch innych miejscach nieco zachodzi. Też trzeba złamać i już będzie ok.

Wolfen 2010-08-25 17:20:25

W domu na innej wersji FF tez zle ;)

Ilmenit 2010-08-25 17:39:41

Poprawiłem kody i powinny być w każdej przeglądarce łamane podobnie.

George 2010-08-25 21:32:36

Na Chromie już jest OK!

Wolfen 2010-08-25 22:25:04

Teraz juz OK na wszystkim faktycznie :)

@Ilmenit - dzie-ku-je-my! :)

xeen 2010-08-26 07:30:06

jeszcze raz dzięki, jeszcze jeden dwa odcineczki i będzie bardzo przyzwoity tutorial.

tdc 2010-08-29 22:54:13

Ja kiedyś napisałem prosty edytor duszków w Action! (oparty na źródłach helpme), kiedyś go opublikuję.


Brawa dla autora za cykl, który jest dobrze przyjęty, jak będę miał chwilkę to z chęcią przeczytam;)