• Nasza pierwsza gra - kurs programowania AmigaOS i C - część 2

26.12.2012 10:40, autor artykułu: Asman
odsłon: 5223, powiększ obrazki, wersja do wydruku,

Poprzedni odcinek zakończyliśmy przykładem pokazującym okienko naszej gry. Podobnie jak poprzednio ustawiamy poprzeczkę wyżej i tym razem napiszemy namiastkę gry i ujrzymy ruch statka. Do tego brakuje nam jeszcze wielu rzeczy, więc do dzieła! Najpierw zajmiemy się timer.device, za pomocą którego będziemy mogli bez problemu pokazać ruch statku na naszym okienku, bez obawy, że przy innym odświeżaniu ekranu szykośc będzie inna od zamierzonej. A ponieważ programujemy pod OS, to nie będziemy ingerowali bezpośrednio w hardware amigi. Dostęp do hardware umożliwiają device'y. Nie jest łatwo czym jest device, to nie jest biblioteka trzymająca pewne funkcje, ja myśle o device jako o silniku, który sprawuję pieczę nad danym urządzeniem. On pozwala nam na skorzystanie z urządzenia poprzez próbę otwarcia danego device'a. Poprzez mechanizm zapytań (requestów) możliwe jest współpraca z urządzeniem a możemy to robić w sposób synchroniczny i tu do dyspozycji mamy funkcję DoIO z biblioteki exec. Funkcja ta czeka aż nasze zapytanie się spełni i dopiero wtedy powraca, co jak łatwo się domyśleć, może powodować problemy. Przykładem będzie długie czekanie aż inny program zakończy swoje zapytanie z urządzeniem. Bo jak wiemu multitaskig obowiązuje wszystkich i zagarnianie urządzeń może być bolesne przy współpracy z innymi programami. Mamy także możliwość wysyłania requestów asynchronicznie poprzez funkcję SendIO z exec.library. Ta funkcja powraca natychmiast ale nie róży bez kolców. Musimy poczekać na swoją kolej, sprawdzając czy nasz request został wykonany, używając do tego celu sygnałów. Z punktu widzenia pisania gry z device'ów na pewno warto wymienić Audio.device, odpowiedzialny za obsługę dźwięku czy też Gameport.device pozwalający dobrać się do joysticka od strony systemowej, no i wspomniany wyżej timer.device, który zajmuje się mierzeniem czasu.

Zaczniemy od porządków. Nie będziemy pisali wszystkiego w jednym pliku, bo to powoduje większy chaos i każda zmiana w jednym pliku powoduje wydłużenie czasu kompilacji całego projektu. Przy małych projektach to może mieć małe znaczenie, ale myślę, że to poza dobrymi nawykami programowania także zwiększy czytelność kodu i poprawi hermetyzację danych. Przede wszystkim przeniesiemy funckję ( i niektóre zmienne też ) związane z oknem do odzielnych plików: window.c i window.h. Utworzmy plik types.h zawierający nasze własne często używane typy. Dodamy także dwa pliki timer.h i timer.c, zawierające funkcję związane z timer.device. I stworzymy pliki input.c, input.h, które będą związane z obsługą joysticka. Wszystkie te pliki umieścimy w tym samym miejscu, gdzie znajduje się boxo.c. Aby kompliator wiedział, że musi skompilować wszystkie te pliki to musimy zmienić odrobine nasz plik makefile, w którym przechowujemy reguły kompilacji naszego projektu. Do zmiennych OBJ i LINKOBJ dodajmy "window.o timer.o input.o".

OBJ = boxo.o window.o timer.o input.o
LINKOBJ = boxo.o window.o timer.o input.o

i po regule box.o: napiszmy jak kompilator ma stworzyć obiekty window.o i timer.o.

window.o: window.c
	$(CC) $(CFLAGS) window.c -o window.o
timer.o: timer.c
	$(CC) $(CFLAGS) timer.c -o timer.o
input.o: input.c
	$(CC) $(CFLAGS) input.c -o input.o

W pliku types.h zdefiniujemy nowy typ i będzie to wskaźnik na funkcję, która nie przyjmuję argumentów i nie zwraca wartości. Ot i cały plik types.h:

#ifndef BOXO_TYPES_H
#define BOXO_TYPES_H
//============================================================================
#include <exec/types.h>
//--------------------------------------------------------
typedef void(*PVF)(void);
//============================================================================
#endif // BOXO_TYPES_H

Dla znawców C konstrukcja #ifndef, #define #endif nie powinna sprawiać problemów, jest to strażnik kompilacji, dzięki niemu kompilator nie zgłosi błędu powtórnej definicji a konstrukcja typedef pozwoli nam zadeklarować wskaźnik na funkcję w uproszczony sposób o czym przekonacie się gdy będzięmy usprawniać boxo.c.

Czas na window.h i window.c. W pliku nagłówkowym umieścimy funkcje i zmienne, które będą widoczne za zewnątrz.

#ifndef BOXO_WINDOW_H
#define BOXO_WINDOW_H
//============================================================================
#include "types.h"

#define PENS_AMOUNT 2
//--------------------------------------------------------
extern struct RastPort* g_pRpMain;
extern ULONG g_nWindowSignal;
extern ULONG g_tabPens[PENS_AMOUNT];
//--------------------------------------------------------
extern BOOL signalsWindow(void);
extern int initWindow(void);
extern void killWindow(void);
//============================================================================
#endif // BOXO_WINDOW_H

Funkcja signalsWindow to pod zmienioną nazwą nasza funkcja z poprzedniego odcinka, która zajmowała się odbieraniem sygnałów z naszego okna, odrobinę ją rozszerzymy. Za rzeczy związane z otwarciem okna i inicjalizacją zmiennych będzie odpowiadać initWindow. Ostatnia funkcja będzie zamykać okno.

#include "window.h"
#include "input.h"

#include <proto/exec.h>
#include <proto/intuition.h>
#include <proto/graphics.h>
#include <intuition/intuition.h>
//============================================================================
#define KEY_ESC 0x45
#define KEY_A 0x20
#define KEY_CURSOR_UP 0x4c
#define KEY_CURSOR_DOWN 0x4d
#define KEY_CURSOR_RIGHT 0x4e
#define KEY_CURSOR_LEFT 0x4f
//--------------------------------------------------------
ULONG g_tabPens[PENS_AMOUNT];
ULONG g_nWindowSignal;
struct RastPort* g_pRpMain = NULL;
static struct Window* m_pWin = NULL;
static BOOL m_bPensObtained = FALSE;
//--------------------------------------------------------
static void getPens(void);
static void freePens(void);
//============================================================================
int initWindow(void)
{
	m_pWin = (struct Window*)OpenWindowTags(NULL,
		WA_Left, 0, WA_Top, 0,
		WA_Width, 320, WA_Height, 256,
		WA_CloseGadget, TRUE,
		WA_Title, (ULONG)"boxo",
		WA_Activate, TRUE,
		WA_DragBar, TRUE,
		WA_GimmeZeroZero, TRUE,
		WA_IDCMP, IDCMP_CLOSEWINDOW | IDCMP_RAWKEY,
		TAG_END);

	if (NULL == m_pWin)
	{
		return -1;
	}

	getPens();

	g_nWindowSignal = 1L << m_pWin->UserPort->mp_SigBit;
	g_pRpMain = m_pWin->RPort;

	return 0;	//OK
}
//============================================================================
void killWindow(void)
{
	freePens();

	if (m_pWin)
	{
		CloseWindow(m_pWin);
	}
}
//============================================================================
BOOL signalsWindow(void)
{
	BOOL bEnd = FALSE;

	while (TRUE)
	{
		struct IntuiMessage* pMsg = (struct IntuiMessage*)
		GetMsg(m_pWin->UserPort);

		if (NULL == pMsg)
		{
			break;
		}

		ULONG msg_class = pMsg->Class;
		UWORD msg_code = pMsg->Code;

		ReplyMsg((struct Message*)pMsg);

		if (IDCMP_CLOSEWINDOW == msg_class)
		{
			bEnd = TRUE;
		}
		else if (IDCMP_RAWKEY == msg_class)
		{
			switch (msg_code)
			{
				case KEY_ESC: bEnd = TRUE; break;
				case KEY_A: g_bFire = TRUE; break;
				case KEY_CURSOR_UP: g_bUp = TRUE; break;
				case KEY_CURSOR_DOWN: g_bDown = TRUE; break;
				case KEY_CURSOR_RIGHT: g_bRight = TRUE; break;
				case KEY_CURSOR_LEFT: g_bLeft = TRUE; break;
				default: break;
			}
		}
	}

	return bEnd;
}
//============================================================================
static void getPens(void)
{
	typedef struct
	{
		ULONG red;
		ULONG green;
		ULONG blue;
	} Color;

	Color colors[PENS_AMOUNT] =
	{
		{0x00000000, 0x00000000, 0x00000000},	//BLACK
		{0x7fffffff, 0x7fffffff, 0x7fffffff},	//GREY
	};

	int i;
	struct ColorMap* cm = m_pWin->WScreen->ViewPort.ColorMap;

	for (i = 0; i < PENS_AMOUNT; i++)
	{
		ULONG red = colors[i].red;
		ULONG green = colors[i].green;
		ULONG blue = colors[i].blue;
		ULONG pen = ObtainPen(cm, 0xffffffff, red, green, blue, PEN_EXCLUSIVE);

		if( -1 == pen )
		{
			pen = ObtainBestPenA(cm, red, green, blue, NULL);
		}
		g_tabPens[i] = pen;
	}

	m_bPensObtained = TRUE;
}
//============================================================================
static void freePens(void)
{
	if (m_bPensObtained)
	{
		int i;
		for (i = 0; i < PENS_AMOUNT; i++)
		{
			ReleasePen(m_pWin->WScreen->ViewPort.ColorMap, g_tabPens[i]);
		}
	}
}
//============================================================================

Na samym początku warto zwrócić uwagę na fakt dołączenia pliku window.h zaraz w pierwszej linii, warto dołączać w ten sposób, oczywiście pierwsze linie mogą być komentarzami. Zapobiega to dziwnym błędom, które mogą dopaść początkujących, objawiajace się kompilowaniem tylko niektórych modułów (przez moduł rozumiem tu parę plików o tym typu window.c i window.h). Zmienną m_pWin uczyniłem widoczną tylko w tym pliku, pisząc static przed struct Window, co uniemożliwi obcym modułom ingerecję w nasze okno. Pisząc moduł mam na myśli parę plików o tej samej nazwie. Do funkcji signalsWindow dodaliśmy obsługę klawiatury dodając stosowną flagę IDCMP_RAWKEY przy otwieraniu okna, dzięki temu możemy uzyskać informacje o kursorach, klawiszu A i ESC, co przyda nam się przy poruszaniu naszym statkiem. Tutaj także umieściłem pobieranie kolorów do naszej gry.

Zmierzmy się teraz z timer.device. Przedstawię plik nagłówkowy i w paru słowach skomentuje.

#ifndef BOXO_TIMER_H
#define BOXO_TIMER_H
//============================================================================
#include "types.h"
//--------------------------------------------------------
extern ULONG g_nTimerSignal;
//--------------------------------------------------------
extern int initTimer(void);
extern void killTimer(void);
extern void signalsTimer(void);
//============================================================================
#endif // BOXO_TIMER_H

Jak widać mamy tu parę funkcji typu init/kill, które jak łatwo się domyśleć będą odpowiadały za otwarcie/zamknięcie timer.device a signalsTimer zajmie się sygnałami pochodzącymi z naszego portu ( w dalszej części zostanie wyjaśnione skąd się wziął ten "nasz" port). Zewnętrzna zmienna g_nTimeSignal przechowuje numer sygnału. Kod pliku timer.c:

#include "timer.h"

#include <proto/exec.h>
#include <devices/timer.h>
//============================================================================
ULONG g_nTimerSignal;

static BOOL m_bTimerWasSent = FALSE;
static struct MsgPort* m_pMsgTimerPort = NULL;
static struct timerequest* m_pTimerIO = NULL;
static struct timeval m_tv;
//--------------------------------------------------------
static void sendTimerReq(void);
//============================================================================
int initTimer(void)
{
	m_pMsgTimerPort = CreateMsgPort();

	if (0 == m_pMsgTimerPort)
	{
		return -1;
	}

	m_pTimerIO = (struct timerequest*) 
		CreateIORequest(m_pMsgTimerPort, sizeof(struct timerequest));

	if (0 == m_pTimerIO)
	{
		return -1;
	}

	LONG error = OpenDevice(TIMERNAME, UNIT_VBLANK,
		(struct IORequest*)m_pTimerIO, 0);

	if (0 != error)
	{
		return -1;
	}

	g_nTimerSignal = 1L << m_pMsgTimerPort->mp_SigBit;

	m_pTimerIO->tr_node.io_Command = TR_ADDREQUEST;
	m_pTimerIO->tr_time = m_tv;

	sendTimerReq();
	m_bTimerWasSent = TRUE;

	return 0;	//ok
}
//============================================================================
void killTimer(void)
{
	if (m_pTimerIO)
	{
		if (m_bTimerWasSent)
		{
			AbortIO((struct IORequest*)m_pTimerIO);
			WaitIO((struct IORequest*)m_pTimerIO);
		}
		CloseDevice((struct IORequest*)m_pTimerIO);
		DeleteIORequest(m_pTimerIO);
	}

	if (m_pMsgTimerPort)
	{
		DeleteMsgPort((struct MsgPort*)m_pMsgTimerPort);
	}
}
//============================================================================
void signalsTimer(void)
{
	while (TRUE)
	{
		struct IntuiMessage* pMsg =
			(struct IntuiMessage*)GetMsg(m_pMsgTimerPort);

		if (NULL == pMsg)
		{
			break;
		}
	}

	sendTimerReq();
}
//============================================================================
static void sendTimerReq(void)
{
	m_tv.tv_secs = 0;
	m_tv.tv_micro = 20000;

	SendIO((struct IORequest*)m_pTimerIO);
}
//============================================================================

Skupmy się na funkcji initTimer. Aby zacząc pracę z urządzeniem to musimy najpierw spróbować otworzyć je. Do teg ocelu muszę mieć request, który przeważnie jest rozszerzeniem zwykłego IORequest, nie inaczej jest w timer.device. Najpierw tworzymy port do komunikacji a następnie robimy IORequest dla timer.device. Kolejnym krokiem jest otwarcie urządzenia. Jeśli wszystko się powiodło to inicjujemy zmienne, które bedą używane, wypełniamy nasz timerrequest i wysyłamy go za pomocą funkcji sendTimerReq, która to ustawia co ile mikrosekund ma przyjść powiadomienie. W tym przypadku jest to 1/50 sekundy ( można by też napisać 1000000/5 ). Warto odnotować fakt, że robimy asynchroniczne zapytanie do timer.device, bo nie chcemy blokować naszym programem systemu i dlatego użyliśmy SendIO. W funkcji signalsTimer odbieramy sygnały pochodzące z naszego portu i od razu wysyłamy ten sam request do urządzenia, by uzyskać cykliczność 1/50 sekundy. A funkcja killTimer zajmuje się jak nietrudno się domyśleć, zamykaniem tego co otworzyliśmy w initTimer. Bardziej doświadczonych programistów może zaskoczyć fakt że ustawiamy zmienną m_bTimerWasSent na wartość TRUE po pierwszym wysłaniu requesta. Jest to potrzebne, gdyż może się zdarzyć, że użytkownik zaraz po ukazaniu się okna kliknie gadżet zamykania i wtedy AbortIO może powiesić system. Jasne jest, że trzeba mieć niebywałe szczęście aby to uczynić ale wystarczy, że w naszym requescie będziemy czekali parę sekund a to już wystarczy by ktoś chciał opuścić nasz program, zanim odbierzemy request. Po wywołaniu AbortIO wywołujemy WaitIO by system mógł dokończyć nasz request.

Większym zmianom ulegnie też Boxo.c:

#include "window.h"
#include "timer.h"
#include "input.h"

#include <proto/exec.h>
#include <proto/graphics.h>
#include <dos/dos.h>
//============================================================================
struct IntuitionBase* IntuitionBase;
struct GfxBase* GfxBase;
PVF g_pFnc = NULL;
//---------------------------------------------------------
static int init(void);
static void loop(void);
static void close(void);
static void putShip(void);
//---------------------------------------------------------
static LONG m_nPosX = 0;
static int m_nVelocityX = 0;
static int m_nVelocityY = 0;
static LONG m_nPosY = 0;
static BOOL m_bShipStand;
//============================================================================
int main(void)
{
	if (0 == init())
	{
		m_nPosX = 200;
		m_nPosY = 120;
		m_nVelocityX = 0;
		m_nVelocityY = 0;
		g_pFnc = &putShip;
		m_bShipStand = TRUE;
		SetRast(g_pRpMain,g_tabPens[0]);
		SetAPen(g_pRpMain,g_tabPens[0]);
		SetBPen(g_pRpMain,g_tabPens[1]);
		SetOutlinePen(g_pRpMain, g_tabPens[1]);
		RectFill(g_pRpMain,16,16,288,224);
		SetOutlinePen(g_pRpMain, g_tabPens[0]);

		loop();
	}

	close();

	return 0;	//ok
}
//============================================================================
static int init(void)
{
	IntuitionBase = (struct IntuitionBase*)OpenLibrary("intuition.library", 34);
	if (NULL == IntuitionBase)
	{
		return -1;
	}
	GfxBase = (struct GfxBase*)OpenLibrary("graphics.library", 34);
	if (NULL == GfxBase)
	{
		return -1;
	}
	//-----------------------------------------------------
	if ( 0 != initWindow())
	{
		return -1;
	}
	if ( 0 != initTimer())
	{
		return -1;
	}
	//-----------------------------------------------------
	return 0;	//ok
}
//============================================================================
static void close(void)
{
	killTimer();
	killWindow();
	//-----------------------------------------------------
	if (IntuitionBase)
	{
		CloseLibrary((struct Library*)IntuitionBase);
	}
	if (GfxBase)
	{
		CloseLibrary((struct Library*)GfxBase);
	}
}
//============================================================================
static void loop(void)
{
	BOOL bEnd = FALSE;

	while (!bEnd)
	{
		ULONG signals = Wait(g_nWindowSignal | g_nTimerSignal | SIGBREAKF_CTRL_C);

		if (signals & SIGBREAKF_CTRL_C)
		{
			bEnd = TRUE;
		}
		if (signals & g_nWindowSignal)
		{
			bEnd = signalsWindow();
		}
		if (signals & g_nTimerSignal)
		{
			signalsTimer();
			(*g_pFnc)();
		}
	}
}
//============================================================================
static void putShip(void)
{
	SetAPen(g_pRpMain, g_tabPens[0]);
	RectFill(g_pRpMain, m_nPosX, m_nPosY, m_nPosX + 8, m_nPosY + 8);

	if (m_bShipStand)
	{
		if (g_bLeft)
		{
			m_bShipStand = FALSE;
			g_bLeft = FALSE;
			m_nVelocityX = -2;
			m_nVelocityY = 0;
		}
		else if (g_bRight)
		{
			m_bShipStand = FALSE;
			g_bRight = FALSE;
			m_nVelocityX = 2;
			m_nVelocityY = 0;
		}

		if (g_bUp)
		{
			m_bShipStand = FALSE;
			g_bUp = FALSE;
			m_nVelocityX = 0;
			m_nVelocityY = -2;

		}
		else if (g_bDown)
		{
			m_bShipStand = FALSE;
			g_bDown = FALSE;
			m_nVelocityX = 0;
			m_nVelocityY = 2;
		}
	}

	ULONG nX = 4+m_nPosX + 3*m_nVelocityX;
	ULONG nY = 4+m_nPosY + 3*m_nVelocityY;

	ULONG nColor = ReadPixel(g_pRpMain, nX, nY);

	if (g_tabPens[1] == nColor)
	{
		m_nVelocityX = 0;
		m_nVelocityY = 0;
		m_bShipStand = TRUE;
	}
	else
	{
		m_nPosX += m_nVelocityX;
		m_nPosY += m_nVelocityY;
	}

	SetAPen(g_pRpMain, g_tabPens[1]);
	RectFill(g_pRpMain, m_nPosX, m_nPosY, m_nPosX + 8, m_nPosY + 8);
}
//============================================================================

Jak widać przenieśliśmy funkcje i zmienne dotyczące okna. Zmieniliśmy init dodając otwarcie biblioteki graphics i dodając inicjację okna i timer.device. funkcja main urosła o ustawienie początkowe zmiennych które poza ustaleniem rzeczy związanych ze statkiem rysuje też ramkę ograniczającą nasz ruch. W pętli głównej oczekujemy sygnałów z okna jak i z naszego requesta z timer.device. Sercem tej funkcji jest wywołanie funkcji za pomocą wskaźnika.

Zamiast wywoływania funkcji putShip za pomocą wskaźnika na funkcję, byłoby łatwiej od razu umieścić wywołanie putShip i sterować przebiegiem programu za pomocą sprawdzania co aktualnie powinno być wykonywane. Ale w ten sposób stracimy dwie rzeczy, elastyczność głównej pętli i niepowtarzalność kodu, która jest bardzo ważnym elementem w programowaniu, umożliwiającym sprawne wprowadzanie zmian i zmniejszające czas na konserwację kodu. Poza tym dzięki temu że wywołujemy kod za pomocą wskaźnika na funkcję, możemy w dowolny sposób zmieniać tenże wskaźnik i tym samym decydować jaka funkcja ma być aktualnie wykonywana. My wywołujemy funkcję putShip, która odpowiada za ruch, rysowanie statku i sprawdzenie czy dotyka ściany. To dużo odpowiedzialności jak na taką funkcję, prawdę powiedziawszy powinniśmy ją rozdzielić na cztery funkcję: undrawShip, moveShip, drawShip, collisionShip. To pozostawiam jako proste ćwiczenie. Podpowiem, że undrawShip powinno usunąć statek z planszy, moveShip odpowiada za ruch statku, który jest możliwy tylko wtedy gdy statek stoi nieruchomy. Zadaniem funkcji collisionShip ma być sprawdzenie czy dalszy ruch statku jest możliwy. Detekcja statku ze ścianą jest zrealizowana za pomocą sprawdzania koloru. Najpierw wyliczamy pozycję statku w nowym położeniu i odczytujemy w nim kolor. jeśli natrafiliśmy na siwy kolor to unieruchamiamy nasz statek, w przeciwnym razie dalszy ruch jest możliwy do wykonania i tylko wtedy zmieniamy pozycję naszego bohatera, jakim jest statek.

Przejdźmy teraz do modułu input. Będzie on odpowiedzialny za joystick, w jednym z kolejnych odcinku uzupełnimy go odpowiednimi funkcjami a na potrzeby tego odcinka stworzymy niezbędne zmienne. Plik nagłówkowy:

#ifndef BOXO_INPUT_H
#define BOXO_INPUT_H
//============================================================================
#include "types.h"
//--------------------------------------------------------
extern BOOL g_bRight;
extern BOOL g_bLeft;
extern BOOL g_bUp;
extern BOOL g_bDown;
extern BOOL g_bFire;
//============================================================================
#endif // BOXO_INPUT_H

i implementacja input.c:

#include "input.h"
//============================================================================
BOOL g_bRight;
BOOL g_bLeft;
BOOL g_bUp;
BOOL g_bDown;
BOOL g_bFire;
//============================================================================

Z pewnością warto będzie w tym module umieścić rzeczy związane z obsługą joysticka, więc na pewno trzeba będzie się zmierzyć z input.device bądź z gameport.device, ale to wszystko przed nami.

Jako ćwiczenie proponuję zmodyfikować nasz projekt i uczynić go jeszcze bardziej czytelnym. Na początek warto przyjrzeć się wywołaniu SetAPen, mamy tam numer kolory który otrzymujemy zaglądając do pierwszego elementu tablicy ( g_tabPens[0] ). O wiele jaśniej by było gdybyśmy wiedzieli o jaki kolor nam chodzi bez uciekania się do zaglądania do funkcji którya pobiera pióra (getPens). Można to zrealizować na wiele sposobów, korzstając z #define bądź używając enumeracji - wybór zostawiam czytelnikowi. Przyjrzyjmy się funkcji main a skupmy się na konczącym go return 0;. Bardziej klarowne by było zwrócić jakąś stałą ( na przykład RETURN_OK, która by mówiła wprost, że kończymy program z sukcesem. Te kończące zero nie bez kozery jest na końcu funkcji main. Za pomocą zwracanej wartości można powiadomić OS o rezultacie naszego programu - co jest wykorzystywane w skryptach. Podobnie jak main należe zmienić funkcję init, modyfikując możliwe powroty z tejże funkcji. Pozwoli to wyłapywać potencjalne błędy w przyszłości. Dlaczego w przyszłości, bo dla uproszczenia naszego projektu, nie informujemy użytkownika o ewentualnych błędach. po zamianie magicznych liczb typu 0, -1 w funkcji init, można pokusić się o napisanie prostej funckji, która na podstawie zadanego błędu zwraca informację przeznaczoną dla zwyczajnego użytkownika, bo chyba nikt nie lubi, gdy gra się nie uruchamia informując, że nastąpił błąd numer -1, bądź też nie informując wcale.

W kolejnym kroku zajmiemy się „kafelkami” (można spotkać się z nazwami: tile, blok, klocek). Są to bloki grafiki o ustalonych rozmiarach. Przykładowo kafelek 8x8 oznacza blok szeroki na 8 pikseli i wysoki na 8 pikseli. Dzięki nim oszczędzamy pamięć kosztem pewnej powtarzalności na ekranie gry. Wyobraźmy sobie ekran o rozdzielczości 320 pikseli szerokości i 256 pikselach wysokości. Daje nam to 1280 różnych kafli 8x8, więc jeśli użyjemy tylko 256 kafli, to sporo zaoszczędzimy. Oczywiście musimy gdzieś przechować informacje, który kafel gdzie ma się znajdować, czyli musimy posiadać mapę, która odwzorowuje planszę na ekranie. Dla naszego przykładowego ekranu taka mapa będzie miała 40 elementów szerokości i 32 elementów wysokości. Zagadnieniem map kafelkowych (tile maps) zajmiemy się w kolejnym odcinku.

Artykuł oryginalnie pojawił się w siódmym numerze Polskiego Pisma Amigowego.

    tagi: C, AmigaOS, programowanie
dodaj komentarz
Na stronie www.PPA.pl, podobnie jak na wielu innych stronach internetowych, wykorzystywane są tzw. cookies (ciasteczka). Służą ona m.in. do tego, aby zalogować się na swoje konto, czy brać udział w ankietach. Ze względu na nowe regulacje prawne jesteśmy zobowiązani do poinformowania Cię o tym w wyraźniejszy niż dotychczas sposób. Dalsze korzystanie z naszej strony bez zmiany ustawień przeglądarki internetowej będzie oznaczać, że zgadzasz się na ich wykorzystywanie.
OK, rozumiem