Raycaster-pelimoottori 3: Seinien tekstuurit
Pelkät yksiväriset seinät ovat aika tylsiä, kuten jo tasohyppelypelin tapauksessa havaittiin; tuleehan iso osa pelin tunnelmasta siitä, miltä se näyttää. Yksi parannus on seinien piirtäminen bittikarttatekstuurien avulla, ikään kuin yksiväriselle pinnalle levitettäisiin tapetti. Tässä osassa lisätään siis teksturoitujen seinien piirtäminen.
Globals.h
Lisää globals.h-otsikkotiedostoon vakiot tekstuurin leveydelle (TEX_WIDTH) ja korkeudelle (TEX_HEIGHT), joiden arvoksi annetaan 64. Bittikarttatekstuurien leveys ja korkeus on siis 64 pikseliä.
Lisää myös osoitinmuuttujat kahdelle bittikartalle (*textureA ja *textureB), jotka viittaavat ladattaviin tekstuureihin.
Main.c
Lisää main.c-lähdekooditiedostoon tekstuuriosoitinmuuttujien alustukset nollaksi (NULL), mikä tarkoittaa, ettei tekstuureja ole vielä ladattu.
Setup.c
Lisää setup.c-lähdekooditiedoston SetupProgram-funktion määrittelyyn arvot muuttujille textureA ja textureB, eli ladataan kaksi tekstuuria tiedostoista walla.bmp ja wallb.bmp kutsumalla funktiota load_bitmap. int SetupProgram() { if (allegro_init() != 0) return 1; install_keyboard(); install_int_ex(TimerHandler, BPS_TO_TIMER(FRAMERATE)); LOCK_FUNCTION(TimerHandler); LOCK_VARIABLE(thCounter); if (set_gfx_mode(GFX_AUTODETECT, SCREEN_WIDTH, SCREEN_HEIGHT, 0, 0) != 0) { if (set_gfx_mode(GFX_SAFE, SCREEN_WIDTH, SCREEN_HEIGHT, 0, 0) != 0) { set_gfx_mode(GFX_TEXT, 0, 0, 0, 0); allegro_message("Unable to set any graphic mode\n%s\n", allegro_error); return 1; } } buffer = create_bitmap(SCREEN_WIDTH, SCREEN_HEIGHT); textureA = load_bitmap("walla.bmp", NULL); textureB = load_bitmap("wallb.bmp", NULL); return 0; } Miksi tekstuureja on kaksi, saatat miettiä. No, muistanet, että edellisessä osassa tiettyyn suuntaan "osoittavat" seinät olivat pimeämpiä kuin toiset. Sama tehdään tekstuureillakin, eli pohjois–eteläsuunnassa seinässä käytetään tekstuuria walla.bmp ja länsi–itäsuunnassa tekstuuria wallb.bmp, joka on pimeämpi versio walla.bmp:stä.
Kyseiset tekstuurit on muokattu Little Martianin Retro Texture Packin ilmaisen näytepaketin (https://little-martian.itch.io/retro-texture-pack)
tekstuurista LAB_2B.PNG. Muokkauksiin kuului mm. alkuperäisen PNG-tiedoston 24-bittisen värisyvyyden muuttaminen
8-bittiseksi. Samassa yhteydessä määritettiin kuvan paletti, ja koska emme ole vielä perehtyneet oman 8-bittisen paletin
määrittämiseen, paletiksi piti asettaa 8-bittisen VGA-kuvatilan
oletuspaletti. Lopputulos on aika mälsä, jopa ruma, mutta pääsemmepähän
varmistumaan siitä, että bittikartat piirretään pelissä oikein.
Ks. tämä postaus oppiaksi, mitä pitää ottaa huomioon, jotta tekstuuribittikartat piirretään pelissä oikein. Main.cLisää main.c-lähdekooditiedoston main-funktioon kutsut tekstuurien vapauttamiseksi muistista. Eli, kun painetaan ESCiä ja hypätään pelisilmukasta ulos, kutsutaan kullekin tekstuurille – textureA ja textureB funktiota destroy_bitmap ennen kuin suljetaan Allegro ja koko ohjelma. int main(void) { if (SetupProgram() != 0) return 1; SetupGame(); while(!key[KEY_ESC]) { while (thCounter > 0) { MovePlayer(); RotatePlayer(); DrawGame(); thCounter--; } } destroy_bitmap(textureA); destroy_bitmap(textureB); destroy_bitmap(buffer); remove_timer(); allegro_exit(); return 0; } END_OF_MAIN() Draw.hLisää draw.h-otsikkotiedostoon DrawWallsTextured-funktion esittely. Parametreja on aika hirvittävä määrä; koita pärjätä. #ifndef DRAW_H #define DRAW_H void HandleWalls(double zBuffer[]); void DrawWallsTextured(int x, int originalDrawStart, int originalDrawEnd, int side, double perpWallDist, double rayDirX, double rayDirY, int lineHeight, int drawStart); void DrawWallsAsFlatColors(int x, int mapX, int mapY, int side, int drawStart, int drawEnd); void DrawGame(); #endif Draw.cLisää draw.c-lähdekooditiedostoon DrawWallsTextured-funktion määrittely. Pikseli lienee jo sinulle tuttu termi; nehän ovat niitä pieniä erivärisiä neliöitä, joiden avulla kaikki graafinen sisältö tehdään näkyväksi tietokoneesi näytöllä. Mutta teksturoituja seiniä piirrettäessä vastaan tulee mahdollisesti uusi termi: tekseli eli tekstuuripikseli. Eli, kun 3D-mallin pinnalle projisoidaan bittikarttatekstuuri, ko. tekstuurin pikselit ovat nimeltään tekseleitä. Tekstuuriemme resoluutiohan on 64 × 64 pikseliä, ja kun ne piirretään seiniin, myös tekseleitä on (tietysti) saman verran. Tässä funktiossa siis:
void DrawWallsTextured(int x, int originalDrawStart, int originalDrawEnd, int side, double perpWallDist, double rayDirX, double rayDirY, int lineHeight, int drawStart) { // Tekstuurikoordinaattien siirto, jos seinä menee ruudun ulkopuolelle double texCoordShift = 0.0; // Jos seinän alku menee näytön yläreunan yli, lasketaan siirtymä if (originalDrawStart < 0) { texCoordShift = (double)(-originalDrawStart) / (originalDrawEnd - originalDrawStart); } // Jos seinän loppu menee näytön alareunan yli, lasketaan siirtymä if (originalDrawEnd > VIEW3D_HEIGHT) { texCoordShift = (double)(originalDrawEnd - VIEW3D_HEIGHT) / (originalDrawEnd - originalDrawStart); } // Lasketaan alkuperäinen tekstuurikohta pystysuunnassa double texPos = texCoordShift * TEX_HEIGHT; // Lasketaan tarkka kohta, johon säde osui seinään double wallX; if (side == 0) wallX = player.posY + perpWallDist * rayDirY; else wallX = player.posX + perpWallDist * rayDirX; // Otetaan vain desimaaliosa (seinän sisäinen offset) wallX -= floor((wallX)); // Lasketaan vastaava X-koordinaatti tekstuurista int texX = (int)(wallX * (double)TEX_WIDTH); // Varmistetaan, ettei mennä tekstuurin ulkopuolelle if (texX < 0) texX = 0; if (texX >= TEX_WIDTH) texX = TEX_WIDTH - 1; // Käännetään tekstuurikoordinaatti, jos katsotaan seinää "takaa" if ((side == 0 && rayDirX > 0) || (side == 1 && rayDirY < 0)) texX = TEX_WIDTH - texX - 1; // Alku- ja loppukohta tekstuurista pystysuunnassa int texYStart = (int)(texPos); int texYEnd = TEX_HEIGHT - 1; // Vastaavat kohdat ruudulla int screenStartY = drawStart; int screenEndY = originalDrawEnd; // Jos piirtäminen alkaa ruudun ulkopuolelta, säädetään tekstuurialkua if (screenStartY < 0) { texYStart += ((-screenStartY) * (texYEnd - texYStart + 1)) / (screenEndY - screenStartY + 1); screenStartY = 0; } // Jos piirtäminen päättyy ruudun ulkopuolelle, säädetään tekstuurin loppua if (screenEndY >= VIEW3D_HEIGHT) { texYEnd -= ((screenEndY - VIEW3D_HEIGHT + 1) * (texYEnd - texYStart + 1)) / (screenEndY - screenStartY + 1); screenEndY = VIEW3D_HEIGHT - 1; } // Varmistetaan, ettei tekstuurikoordinaatit mene rajojen yli if (texYStart < 0) texYStart = 0; if (texYEnd >= TEX_HEIGHT) texYEnd = TEX_HEIGHT - 1; // Valitaan oikea tekstuuri seinän suunnan mukaan BITMAP* texture = (side == 0) ? textureA : textureB; // Jos piirtoväli on järkevä, piirretään seinän pystysuora siivu if (screenStartY <= screenEndY) { stretch_blit(texture, buffer, texX, texYStart, 1, texYEnd - texYStart + 1, x, screenStartY, 1, screenEndY - screenStartY + 1); } } Muuta lopuksi draw.c-lähdekooditiedoston Handlewalls-funktiota niin, että lopussa kutsutaan DrawWallsTextured-funktiota aiemman DrawWallsAsFlatColors-funktion sijaan. void HandleWalls(double zBuffer[]) { int x; for (x = 0; x < SCREEN_W; x++) { double cameraX = 2 * x / (double)SCREEN_W - 1; double rayDirX = playerDirX + planeX * cameraX; double rayDirY = playerDirY + planeY * cameraX; int mapX = (int)(playerPosX); int mapY = (int)(playerPosY); double sideDistX, sideDistY; double deltaDistX = (rayDirX == 0) ? 1e30 : fabs(1 / rayDirX); double deltaDistY = (rayDirY == 0) ? 1e30 : fabs(1 / rayDirY); double perpWallDist; int stepX, stepY; int hit = 0; // Was there a wall hit? int side; // Was a NS or a EW wall hit? if (rayDirX < 0) { stepX = -1; sideDistX = (playerPosX - mapX) * deltaDistX; } else { stepX = 1; sideDistX = (mapX + 1.0 - playerPosX) * deltaDistX; } if (rayDirY < 0) { stepY = -1; sideDistY = (playerPosY - mapY) * deltaDistY; } else { stepY = 1; sideDistY = (mapY + 1.0 - playerPosY) * deltaDistY; } while (hit == 0) { if (sideDistX < sideDistY) { sideDistX += deltaDistX; mapX += stepX; side = 0; } else { sideDistY += deltaDistY; mapY += stepY; side = 1; } if (map[mapX][mapY] == 1) { hit = 1; } } if (side == 0) perpWallDist = (sideDistX - deltaDistX); else perpWallDist = (sideDistY - deltaDistY); zBuffer[x] = perpWallDist; int lineHeight = (int)(VIEW3D_HEIGHT / perpWallDist); int drawStart = -lineHeight / 2 + VIEW3D_HEIGHT / 2; int drawEnd = lineHeight / 2 + VIEW3D_HEIGHT / 2; int originalDrawStart = drawStart; int originalDrawEnd = drawEnd; if (drawStart < 0) drawStart = 0; if (drawEnd >= VIEW3D_HEIGHT) drawEnd = VIEW3D_HEIGHT - 1; DrawWallsTextured(x, originalDrawStart, originalDrawEnd, side, perpWallDist, rayDirX, rayDirY, lineHeight, drawStart); } } |
Kommentit
Lähetä kommentti