Kod w C, Grafika w Blenderze. Jak napisałem intro na C64

W styczniu pokazywałem na kanale, jak przy użyciu kompilatora Oscar64 okiełznać język C i pisać programy na Commodore 64? Wtedy rzuciłem kilka luźnych fragmentów kodu, nie zdradzając do końca, nad czym tak naprawdę pracuję. Dziś opisuję więcej.

Wystartowałem w tradycyjnym konkursie demoscenowym na intro 4 KB oraz 16 KB. Uznałem, że nie będę szalał z czterema kilobajtami i spróbuję zmieścić się w tej drugiej, nieco bezpieczniejszej kategorii. Tak powstało oficjalne intro dla grupy Censor Design. Co ciekawe, oprócz kodu w C, do stworzenia grafiki i animacji zaprzęgłem m.in… Blendera 3D.

Jak to wszystko spiąłem w całość i zmieściłem w 16 KB? Zapraszam do tekstu! Cały kod źródłowy wraz z asetami znajdziecie oczywiście na moim GitHubie, więc możecie go pobrać i analizować.

Screen z intra Hires Bubbles by Censor Design

O wszystkim tym a także z większą ilością szczegółów mówię i pokazuję w filmie na moim kanale.

Charpad. Grafika w trybie ECM.

Prace nad intrem zacząłem od grafiki, a konkretnie od tej pikselowanej w trybie tekstowym. Ponownie sięgnąłem po świetny program Charpad Pro. Pozwala on na edycję grafiki w trybie ECM (Extended Background Color Mode), który uchodzi za dość trudny i upierdliwy.

Dlaczego go wybrałem? Kluczem była oszczędność pamięci. W trybie ECM mamy do dyspozycji tylko 64 znaki (takie klocki 8×8 pikseli), ale za to możemy zdefiniować aż cztery warianty koloru tła pod każdym z nich.

  • Wczytałem do programu świetne, standardowe logo naszej grupy autorstwa Mirage’a (jako zwykły plik PNG)
  • Trochę je poprzesuwałem i zoptymalizowałem tak, aby pewne fragmenty znaków się powtarzały.
  • Dzięki temu zmieściłem całe logo w zaledwie 64 znakach.

Szybka matematyka: 64 znaki po 8 bajtów każdy (jedna linia znaku to 1 bajt) daje nam dokładnie 512 bajtów na całą grafikę logo. Czysta oszczędność!

// ustawienie kolorów oraz włączenie trybu ECM w C (oscar64)

	vic.color_back = 0;
	vic.color_back1 = COLOR_1;
	vic.color_back2 = COLOR_2;
	vic.color_back3 = COLOR_3;

	vic_setmode(VICM_TEXT_ECM, SCREEN0, CHARS0);

Sprity w SpritePad

Kolejny krok to animowane sprity. Przygotowałem je w programie SpritePad. Jeśli mam być szczery, narysowałem je trochę „na odczep” na samym początku i… wyszły tak dobrze od strzału, że potem ich nie poprawiałem. Całość to zapętlona animacja składająca się z 8 spritów w wysokiej rozdzielczości (high-res), która leci w kółko.

Zajęło to równo 1 KB pamięci (8 spritów po 64 bajty oraz druga animacja z bańką). Mało? Mało, a w kodzie odpowiednio te sprity rozmnożyłem.

W intrze pojawiają się też scrollowane bańki, przy których użyłem fajnego triku, znanego z mojej wcześniejszej gry Robot Jet. One w rzeczywistości wcale się nie scrollują sprzętowo! Efekt płynnego ruchu polega na tym, że w każdym kolejnym znaczku bańka jest po prostu narysowana i przesunięta o piksele w prawo/lewo. Kiedy cały ekran przesuwa się sprzętowo o jeden piksel w lewo, a bańki w generatorze znaków „skaczą” o dwa piksele w prawo, uzyskujemy świetne złudzenie optyczne i taki jakby efekt paralaksy.

Animacja w Blenderze

Skoro w pamięci zostało mi jeszcze mnóstwo miejsca, stwierdziłem: „Zróbmy coś ekstra, sprawdźmy animację 3D!”. Do przygotowania trójwymiarowych kul wykorzystałem mój ulubiony opensource’owy program – Blender 3D.

Użyłem do tego celu obiektów typu Metaballs (świetnie nadają się do symulowania organicznych kształtów i kropel) oraz… starej wersji Blendera 2.7. Dlaczego starej? Ponieważ miała ona genialną funkcję renderowania dokładnie tego, co było widać w oknie podglądu (viewport), a nie przez kamerę. Używając odpowiedniego shadera (matcap), wygenerowałem 25 klatek animacji (od 0 do 24) jako czyste pliki PNG z uproszczonym materiałem.

AI jako pomocnik (Vibe Coding w praktyce)

Jak zamienić 25 plików PNG na format strawny dla Commodore 64? Zamiast szukać gotowców na CSDB albo pisać konwerter od zera, postanowiłem wykorzystać LLM (Sztuczną Inteligencję) jako narzędzie.

W godzinę, bez pośpiechu, wspólnie z AI „wykodowaliśmy” prosty skrypt w Pythonie. Skrypt brał nasze klatki z Blendera, ignorował czarne piksele, a wszystko inne zamieniał na bity „1” w strukturze sprita, wypluwając gotowe pliki .bin. AI pomogło mi też wygenerować żmudną tablicę wskaźników w C, żeby oszczędzić mi bezmyślnego kopiowania nazw plików. Ogromna oszczędność czasu!

Wszystkie te klatki wrzuciłem do kodu za pomocą oskarowej dyrektywy #embed z kompresją LZO. Ponieważ dekompresja w trakcie przerwania rastra skończyłaby się totalną kaszaną na ekranie, klatki rozpakowuję w tak zwanym „czasie wolnym” procesora, poza przerwaniami.

//przykład dodania pliku binarnego (klatka animacji) z pakowaniem przez kompilator Oscar64

__export char s_0000[] = {
    #embed lzo "data/anim/0000.bin"
};

__export char s_0001[] = {
    #embed lzo "data/anim/0001.bin"
};

// table of pointers to each of 25 frames
__export char* anim_frames[] = {
    s_0000,
    s_0001,
...
// rozpakowywanie klatki animacji za pomocą wbudowanej w oscar64 funkcji

oscar_expand_lzo( ANIM, anim_frames[animFrame]); 

Podsumowanie

Czy pisanie dem w C na Commodore 64 jest trudne? Moim zdaniem nie – jest przede wszystkim logiczne i daje niesamowitą frajdę. Samych bibliotek i zachowań kompilatora uczyłem się w locie, podglądając, jaki kod w assemblerze generuje pod maską.

Ostateczny plik wynikowy zamknął się przed adresem $4000, co oznacza, że w limicie 16 KB zmieściłem się z bezpiecznym zapasem niemal całego kilobajta!

Dajcie znać w komentarzach pod filmem na YouTube, co myślicie o takim podejściu do programowania retro i jakie tematy chcielibyście, żebym poruszył w kolejnych materiałach! 

Zobacz także