VGA, Przewijanie ekranu, dwie warstwy
LOSMARCELOS
Techniki przewijania "paralaktycznego"
Co to jest zjawisko paralaksy ? Doświadczemy tego jak jedziemy samochodem. Obiekty bliżej przesuwają się szybciej, dalsze... wolniej.
Można taki efekt zrobić przy pomocy C, assemblera i trybu 13 h
O to przykładowy kod w C
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <dos.h>
#include "paralx.h"
char *MemBuf, // wskaźnik na bufor
*BackGroundBmp, // wskaźnik na bitmape tła
*ForeGroundBmp, // wskaźnik na bitmapę pierwszego planu
*VideoRam; // wskaźnik na pamięć video VGA
PcxFile pcx; // struktura danych pliku typu PCX
int volatile KeyScan; // modyfikowane przez przerwanie klawiatury
int frames=0, // liczba wyświetlonych klatek
PrevMode; // przechowuje oryginalny tryb video
int background, // znacznik podziału tła bitmapy
foreground, // znacznik podziału bitmapy pierwszoplanowej
position; // całkowita odległość przewijania
void _interrupt (*OldInt9)(void); // // wskaźnik na procedure obsługi przerwania klawiatury systemu BIOS
//
// Ta funkcja ładuje plik PCX
//
int ReadPcxFile(char *filename,PcxFile *pcx)
{
long i;
int mode=NORMAL,nbytes;
char abyte,*p;
FILE *f;
f=fopen(filename,"rb");
if(f==NULL)
return PCX_NOFILE;
fread(&pcx->hdr,sizeof(PcxHeader),1,f);
pcx->width=1+pcx->hdr.xmax-pcx->hdr.xmin;
pcx->height=1+pcx->hdr.ymax-pcx->hdr.ymin;
pcx->imagebytes=(unsigned int)(pcx->width*pcx->height);
if(pcx->imagebytes > PCX_MAX_SIZE)
return PCX_TOOBIG;
pcx->bitmap=(char*)malloc(pcx->imagebytes);
if(pcx->bitmap == NULL)
return PCX_NOMEM;
p=pcx->bitmap;
for(i=0;i<pcx->imagebytes;i++)
{
if(mode == NORMAL)
{
abyte=fgetc(f);
if((unsigned char)abyte > 0xbf)
{
nbytes=abyte & 0x3f;
abyte=fgetc(f);
if(--nbytes > 0)
mode=RLE;
}
}
else if(--nbytes == 0)
mode=NORMAL;
*p++=abyte;
}
fseek(f,-768L,SEEK_END); // odczytanie palety kolorów
fread(pcx->pal,768,1,f);
p=pcx->pal;
for(i=0;i<768;i++) // przesunięcie bitów palety
*p++=*p >>2;
fclose(f);
return PCX_OK; // zwróć wynik funkcji
}
//
// To jest nowa procedura obsługi przerwania 9h.
// W przypadku niewyłączenia obsługi przerwania systemu BIOS przytrzymanie klawisza
// powoduje przepełnienie bufora klawiatury i bardzo denerwujące pipczenie.
//
// void _(podkreślnik)interrupt NewInt9(void) w niektórych kompilatorach C/C++
void _interrupt NewInt9(void)
{
register char x;
KeyScan=inp(0x60); // odczytanie kodu klawisza
x=inp(0x61); // poinformowanie klawiatury, że naciśnięto klawisz
outp(0x61,(x|0x80));
outp(0x61,x);
outp(0x20,0x20); // wysyłanie kodu End-Of-Interrupt
if(KeyScan == RIGHT_ARROW_REL || // sprawdzenie kodu klawisza
KeyScan == LEFT_ARROW_REL)
KeyScan=0;
}
//
// Ta funkcja odtwarza oryginalną obsługę przerwania klawiatury
//
void RestoreKeyboard(void)
{
_dos_setvect(KEYBOARD,OldInt9);
}
//
// Ta funkcja zapamiętuje oryginalną obsługę przerwania klawiatury i instaluje nową
//
void InitKeyboard(void)
{
OldInt9=_dos_getvect(KEYBOARD); // zapamiętanie oryginału
_dos_setvect(KEYBOARD,NewInt9); // zainstalowanie nowej obsługi
}
//
// Ta funkcja wywołuje system BIOS do ustawienia wszystkich rejestrów karty VGA
// na podstawie zawartości tablicy PAL
//
void SetAllRgbPalette(char *pal)
{
struct SREGS s;
union REGS r;
segread(&s); // odczytanie wartości aktualnego segmentu
s.es=FP_SEG((void far*)pal); // ustawienie rejestru na tablice pal
r.x.dx=FP_OFF((void far*)pal); // odczyt przesunięcia tablicy pal
r.x.ax=0x1012; // funkcja 10h, podfunkcja 12h systemu BIOS
r.x.bx=0; // początkowy rejestr
r.x.cx=256; // końcowy rejestr
int86x(0x10,&r,&r,&s); // wywołanie systemu BIOS
}
//
// Ta funkcja ustawia tryb 13h
//
void InitVideo()
{
union REGS r;
r.h.ah=0x0f; // funkcja systemu BIOS 0fh
int86(0x10,&r,&r); // wywołanie systemu BIOS
PrevMode=r.h.al; // zapamiętanie poprzedniego trybu
r.x.ax=0x13; // ustawienie trybu 13h : 320X200X256
int86(0x10,&r,&r); // wywołanie systemu BIOS
VideoRam=MK_FP(0xa000,0); // utworzenie wskaźnika na pamięci VIDEO
}
//
// Ta funkjca przywraca poprzedni tryb VIDEO
//
void RestoreVideo()
{
union REGS r;
r.x.ax=PrevMode; // odtworzenie poprzedniego trybu
int86(0x10,&r,&r); // wywołanie systemu BIOS
}
//
// Ta funkjca ładuje warstwy
//
int InitBitmaps()
{
int r;
background=foreground=1; // punkt początkowego podziału
r=ReadPcxFile("backgrnd.pcx",&pcx); // odczytanie bitmapy tła
if(r != PCX_OK) // sprawdź czy nie było błędów
return FALSE;
BackGroundBmp=pcx.bitmap; // zapamiętanie wskaźnika na bitmape
SetAllRgbPalette(pcx.pal); // ustaw palete VGA
r=ReadPcxFile("foregrnd.pcx",&pcx); // odczytanie bitmapy pierwszego planu
if(r != PCX_OK) // sprawdź czy nie było błędów
return FALSE;
ForeGroundBmp=pcx.bitmap; // zapisz wskaźnik bitmapy
MemBuf=malloc(MEMBLK); // utworzenie bufora
if(MemBuf == NULL) // sprawdź czy nie było błędów
return FALSE;
memset(MemBuf,0,MEMBLK); // wyczyść bufor
return TRUE; // Wszystko OK ! :-)
}
//
// Ta funkcja zwalnia całą pamięć przydzieloną przez program
//
void FreeMem()
{
free(MemBuf);
free(BackGroundBmp);
free(ForeGroundBmp);
}
//
// Ta funkcja rysuje warstwy.
// Porządek jest wyznaczany na podstawie wartości współrzędnej "z"
//
void DrawLayers()
{
OpaqueBlt(BackGroundBmp,0,100,background);
TransparentBlt(ForeGroundBmp,50,100,foreground);
}
//
// Ta funkcja obsługuje animację. Proszę zauważyć, że jest to najbardziej krytyczna część kodu.
// Aby ją zoptymalizować należało by przepisać ją w asemblerze
//
void AnimLoop()
{
while(KeyScan != ESC_PRESSED) // powtarzaj dopóki nie naciśnięto ESC
{
switch(KeyScan) //
{
case RIGHT_ARROW_PRESSED: // naciśnięto strzałkę w prawo
position--; //
if(position < 0) // jeśli doszliśmy do końca to przerwij przewijanie
{
position=0;
break;
}
background-=1; // przewijanie tła o 2 piksele
if(background < 1) // czy osiągnęliśmy koniec ?
background+=VIEW_WIDTH; // ...to "zawijamy"
foreground-=2; // przewijanie planu pierwszego o 4 piksele
if(foreground < 1) // czy osiągneliśmy koniec ?
foreground+=VIEW_WIDTH; // ... jeśli tak to "zawijamy"
break;
case LEFT_ARROW_PRESSED: // naciśnięto strzałkę w lewo
position++; //
if(position > TOTAL_SCROLL) // zatrzymaj przewijanie jeśli doszliśmy do końca
{
position=TOTAL_SCROLL;
break;
}
background+=1; // przewijanie tła o 2 piksele
if(background > VIEW_WIDTH-1) // czy osiągneliśmy krawedź ekranu ?
background-=VIEW_WIDTH; // ...to "zawijamy"
foreground+=2; // przewijanie planu pierwszego o 4 piksele
if(foreground > VIEW_WIDTH-1) // czy osiągneliśmy krawędź ekranu ?
foreground-=VIEW_WIDTH; // ... to "zawijamy"
break;
default: //
break;
}
DrawLayers(); // narysowanie warstw w buforze
memcpy(VideoRam,MemBuf,MEMBLK); // skopiowanie zawartości bufora do pamięci video
frames++; // liczba wyświetlonych klatek
}
}
//
// Ta funkjca dokonuje inicjalizacji
//
void Initialize()
{
position=0;
InitVideo(); // ustaw tryb 13h
InitKeyboard(); // zainstaluj naszą "obsługe klawiatury"
if(!InitBitmaps()) // odczytanie bitmap
{
CleanUp(); // zwolnienie pamięci
printf("\nError loading bitmaps\n");
exit(1);
}
}
//
// Wykonanie operacji "porządkowych"
//
void CleanUp()
{
RestoreVideo(); // odtworzenie trybu video
RestoreKeyboard(); // odtworzenie obsługi klawiatury
FreeMem(); // zwolnienie pamięci
}
//
// To jest początek głównego programu. Ta funkcja wywołuje funkcje inicjalizacyjną.
// Następnie odczytuje zegar i uruchamia pętle animacyjną i na koniec ponownie odczytuje zegar
// Wartości zegara są wykorzystywane do obliczenia prędkości.
//
int main()
{
clock_t begin,fini;
Initialize(); // ustawienie trybu video, ładowanie bitmap. itp.
begin=clock(); // odczytanie zegara
AnimLoop(); // wykonianie animacji
fini=clock(); // ponowne odczytanie zegara
CleanUp(); // zwolnienie pamięci, operacje "porządkowe"
printf("Frames: %d\nfps: %f\n",frames,(float)CLK_TCK*frames/(fini-begin));
return 0;
}
A to nagłowek :
//
// Ten nagłówek definiuje stałe i struktury używane w programie
//
#define KEYBOARD 0x09
//
// Kody naciśnięcia i zwolnienia klawiszy wykorzystywane przez procedure obsługi przerwania INT 9h
//
#define RIGHT_ARROW_PRESSED 77
#define RIGHT_ARROW_REL 205
#define LEFT_ARROW_PRESSED 75
#define LEFT_ARROW_REL 203
#define ESC_PRESSED 129
#define UP_ARROW_PRESSED 72
#define UP_ARROW_REL 200
#define DOWN_ARROW_PRESSED 80
#define DOWN_ARROW_REL 208
#define VIEW_WIDTH 320
#define VIEW_HEIGHT 150
#define MEMBLK VIEW_WIDTH*VIEW_HEIGHT
#define TRANSPARENT 0 // indeks koloru przezroczystych pikseli
#define TOTAL_SCROLL 320
enum {NORMAL,RLE};
enum {FALSE,TRUE};
typedef struct
{
char manufacturer; /* Zawsze ma wartość 0 */
char version; /* Zawsze 5, jeśli paleta kolorów wynosi 256 */
char encoding; /* Zawsze ma wartość 1 */
char bits_per_pixel; /* Powinno mieć 8 w przypadku plików 256 kolorowych */
int xmin,ymin; /* Wspołrzędne górnego lewego rogu */
int xmax,ymax; /* Szerokość i wysokość obrazu */
int hres; /* Pozioma rozdzielczość obrazu */
int vres; /* Pionowa rozdzielczość obrazu */
char palette16[48]; /* Paleta karty EGA; nie używana w przypadku obrazów 256 kolorów */
char reserved; /* Zarezerwowane w przypadku przyszłych zastosowań */
char color_planes; /* */
int bytes_per_line; /* Liczba bajtów w jednym wierzszu */
int palette_type; /* Ma wartość 2, w przypadku palety kolorowej */
char filler[58]; /* */
} PcxHeader;
typedef struct
{
PcxHeader hdr;
char *bitmap;
char pal[768];
unsigned imagebytes,width,height;
} PcxFile;
#define PCX_MAX_SIZE 64000L
enum {PCX_OK,PCX_NOMEM,PCX_TOOBIG,PCX_NOFILE};
#ifdef __cplusplus
extern "C" {
#endif
int ReadPcxFile(char *filename,PcxFile *pcx);
void _interrupt NewInt9(void);
void RestoreKeyboard(void);
void InitKeyboard(void);
void SetAllRgbPalette(char *pal);
void InitVideo(void);
void RestoreVideo(void);
int InitBitmaps(void);
void FreeMem(void);
void DrawLayers(void);
void AnimLoop(void);
void Initialize(void);
void CleanUp(void);
void OpaqueBlt(char *,int,int,int);
void TransparentBlt(char *,int,int,int);
#ifdef __cplusplus
}
#endif
Literatura :
Sztuczki i Tajemnice Programowania Gier, LaMothe, Ratcliff, Seminatore & Tyler, LT&P, Warszawa 1996
Podręczk użytkownika systemu MS-DOS 6.22, Wrocław 1996
asm i C, ok, to sie teraz przydaje tylko do programowania hardware'u, ale nie do zastosowań dla jakich kolwiek End-Userów. Biblioteki graficzne + asm dla MMX i nowszych instrukcji -to jest to! Diabelnie szybkie i uzywane (np. blending). Ale 13h to juz zdecydowanie przezytek, mile wspomniania i tyle :)
hmm pomysl dobry ale stary, wszystko wporzo ale ta implementacja nie bedzie zdatna w zyciu dlatego ze jest zawolna. Jezeli chcesz miec plynna animacje uzywaj tego samego pomyslu ale w oparciu o pamiec karty VGA w trybie modeX...
ja tam korzystam raczej z asm'a i C czystego :P
eh, kto w tym jeszcze pisze ;P
teraz się korzysta z OpenGL'a :]
aby dzialalo nalezy zmienic nazwe pliku z LAYERS.PCX na BACKGRND.PCX wtedy dziala ale tez chyba cos nie do konca (przynajmniej u mnie)