/FTN-LPRS2-Emulator

Making a 3D game in a 2D emulator for Uni project using raycasting

Primary LanguageCMIT LicenseMIT

LPRS2 Emulator

Grupni projekat iz predmeta LPRS2.

Autori: Marko Đorđević, Radomir Zlatković, Aleksa Heler

Gameplay video

Sadržaj

Pokretanje

sudo ./waf prerequisites - install waf prerequisites

sudo ./waf configure - configure project

sudo ./waf build run --app=project - run the app

Uvod

U folderu dist se nalazi zip koji treba otpakovati i zatim pokrenuti komande iznad u tom folderu.

Koncept

Ideja je bila napraviti igricu za emulator koja je slična Wolfenstein 3D. Kako bi mogli napraviti 3D igru u 2D emulatoru, koristimo Raycasting algoritam (ne raytracing). Kako bi pojednostavili sebi problem, i kako nije neophodno imati vertikalnost u igri (spratovi, stepenice, skokovi), možemo mapu predstaviti 2D matricom celih brojeva, gde broj označava tip zida, i ako je 0 znači prazno polje po kojem se igrač može kretati.

Kako bismo dobili 3D efekat, potrebno je zidove nacrtati tako da im je visina obrnuto proporcionalna udaljenosti od kamere. To radimo tako što crtamo jednu po jednu vertikalnu liniju. Za svaku vertikalnu liniju bacimo zrak u tom smeru i gledamo kad ce udariti u prvi zid i time dobijemo udaljenost, i za to koristimo relativno jednostavan "Digital Differential Analysis" (DDA) algoritam.

DDA je relativno brz i koristi se za pretragu koje kvadrate zrak (ray) pogađa. Mi ga koristimo da nađemo koji kvadrat naše mape je zrak pogodio i zaustavimo algoritam u tom trenutku i nacrtamo vertikalnu liniju čija visina odgovara udaljenosti zida tj. dužini zraka. Ideju za ovaj algoritam nam je dao Lode Vandevenne, i deo implementacije je odrađen po njegovom tutorijalu.

Timeline

  • Proof of concept
    • Render bez tekstura sa dve boje (1bit color indexing)
    • Render bez tekstura sa više boja (4bit color indexing)
    • Render sa teksturama (učitavanje iz fajla)
    • Render sa podom i plafonom
    • Render sa sprajtovima (bure, stub, lampa)
  • Engine
    • Renderer
      • Pomeriti sve u svoje funkcije i fajlove i sredjivanje koda (Aleksa)
      • Sort funkcija za sprite-ove kako bi bili jedni ispred drugih kad se renderuju (Radomir)
      • Particle system: samo renderovati sitne pravougaonike u world space-u. Hard code: stvori mi particle ovde ove boje i da bude samo jedan efekat kao eksplozija (kada pogodi neprijatelja npr) (Marko)
      • Animacija za sprajtove: apstrakcija da iz game code-a kazemo taj sprajt promeni u taj state (tako mozemo pucati i neprijatelj imati animacije)
      • Sprite rendering: directly to world space 1:1 (Aleksa)
      • Double buffering: da se ne racuna u toku vSync-a, vec u slobodno vreme i upisuje u drugi bafer i kad dodje vSync samo se kopira u pravi bafer za ekran (Aleksa)
      • Napraviti da player init vraca pokazivac na player strukturu i da se to prosledjuje dalje funkcijama, a ne da imamo samo globalnu kameru ili nesto tako - pomoci ce kasnije sa apstrakcijama (Aleksa)
      • /// Osvetljenje i dithering (bayer ordered dithering?) ///
      • /// Preci na packed mod indeksiranja ///
      • /// Renderer funkcija se poziva maksimalno 60 puta u sekundi (moze i manje ali ne i vise) da se izbegne prevelik framerate i ubrzanje igrice ///
      • /// smooth ubrzanje za igraca ///
    • Apstrakcija engine-a (Marko)
    • fmath.h
      • preci na fixed point (Marko)
      • floor, sin, cos - CORDIC algoritam (Aleksa)
      • abs, round (Radomir)
  • Game
    • UI
      • Main menu: biranje nivoa i tezine (player/enemy damage) (Aleksa)
    • Graphics design
      • Naci dobru paletu (Endesga 16 Palette) - 16 boja: 8 pravih boja ali svaka ima svoj taminiji duplikat
      • Naci dobre sprajtove za igru: teksture, sprajtovi, UI
    • Game mechanics
      • Imamo dva dugmeta: A i B. Na jedno se menja selekcija, na drugo se upotrebljava selektovano
      • oruzje (za pocetak samo jedno)
      • municija, smrt, taking damage
      • hp / lives
      • Consumable items / grenades
      • Igrac ima 'debljinu' da ne moze prici zidu beskonacno blizu
      • Player UI (nisan, municija, health)
      • (optional) Postoji kljuc na mapi koji treba da se pokupi i otkljuca vrata da se ode na naredni nivo/pobedi
      • Vezati brzinu kretanja za vreme, a ne FPS
    • Enemy - AI
      • Za pocetak samo jedan tip
      • Kako se krece (npr odrzava neku idealnu razdaljinu)
      • Puca ka igracu kad je u nekom dometu
      • Kako umire (animacija/nestaje/drop)
    • Apstrakcija nivoa
    • Development timelapse video

Proof of concept

Click to expand

proof_of_concept1.c

Koristi 1bit indeksiranje boja, dakle postoje dve boje (u našem slučaju plava 0 i crvena 1). Implementira jednostavno kretanje igrača (napred nazad, okretanje levo desno). Kod iscrtavanja na ekran, nakon što sačeka vSync signal, sve piksele postavi na plavo (pozadina = 0), i zatim prolazi kroz širinu ekrana, od levo ka desno i iscrtava linije odgovarajuće visine koristeći DDA algoritam i svaki zid oboji istom bojom. Mapa je zabeležena u kodu (hard coded) kao dvodimenzionalni niz tipa int.

proof of concept 1

proof_of_concept2.c

Slično kao i prvi p.o.c. samo koristi 4bit indeksiranje za boje, i paletu definišemo na početku koda. Onda se kod iscrtavanja linije gleda koji je broj (tip) kvadrata na mapi pogođen i u zavisnosti od toga oboji liniju. Ostatak je u suštini isti. Može se primetitit da je u drugom p.o.c. rezolucija manja. To je zato što za indeksiranje koristimo više bitova, i sa istim ograničenjem memorije može da stane manje piksela, u ovom slučaju 4x manje, što je rezolucija 320x240 umesto 640x480.

proof of concept 2

proof_of_concept3.c

Nastavak na drugi proof of concept, gde je dodato da se umesto jednobojne vertikalne linije crta linija koja se ucitava iz teksture.

proof of concept 3

Proof of concept 4

Nije prilozen kod za ovo, ali je dodatak na raycasting algoritam gde se na pocetku frejma iscrtaju plafon i pod.

proof of concept 4

Proof of concept 5

Dodati sprajtovi (bure, stub i lampa). Crtanje sprajtova se odvija nakon zidova i poda po sledećim koracima:

  1. Dok se zidovi raycast-uju, cuvamo udaljenosti svake vertikalne linije u 1D baferu (ZBuffer)
  2. Izracunamo udaljenost svakog sprajta od igrača
  3. Iskoristimo tu udaljenost od igrača da sortiramo sprajtove, od najdaljeg do najbližeg kameri
  4. Projektujemo sprajtove na ravan kamere (u 2D): oduzmemo poziciju igrača od pozicije sprajta i pomnožimo rezultat inverznom 2x2 matricom kamere
  5. Izračunamo veličinu sprajta na ekranu (u x i y smeru) koristeći udaljenost svake vertikalne linije iz tačke 1.
  6. Nacrtamo sprajtove jednu po jednu vertikalnu liniju, i ne crtamo linije gde je sprajt uddaljeniji od 1D ZBuffer-a koji govori da je zid između
  7. Nacrtamo vertikalnu liniju piksel po piksel, i obratimo pažnju da postoje 'nevidljive' boje (u našem slučaju 0xffffff) kako svi sprajtovi ne bi bili kockasti Nije potrebno apdejtovati ZBuffer dok crtamo linije, kako su već sortirani sprajtovi, oni koji su bliži biće nacrtani poslednji.

proof of concept 5

Proof of concept 6

Ovde se postavlja pitanje da li vredi preći na RGB333 color space koji nudi vise boja i lakse upravljanje bojama, po cenu smanjenje rezolucije, ili zadržati IDX4 color space. Na slikama ispod se vidi primer razlike (levo RGB333, desno IDX4): proof of concept 6 a proof of concept 6 b

Struktura projekta

project.c - glavni fajl, main funkcija, entry point

device.h - podaci o platformi: rezolucija ekrana, define-ovi, ucitavanje "system.h" fajla sa emulatorom

engine.h - strukture kamere i sprajta

fmath.h - nasa matematicka biblioteka: sin, cos, abs, floor, shift divide

game_data.h - mapa, skladistenje tekstura i sprajtova

sprites_data.h - pikseli sprajtova i tekstura

player.h - funkcije igraca: initialize i update

raycast_renderer.h - funkcije renderera: init, render, wait_for_vsync, cls, floor/wall/sprite_raycaster, transfer_buffer, dda

sprites_renderer.h - funkcije sprajtova: draw_sprite, sort_sprites (quick_sort, partition, swap)

Igrica

Gameplay video

Gameplay video