Research and Development 1/2011-12/projecten/TeamRocket/Logboek

Uit Werkplaats
Ga naar: navigatie, zoeken

Week 25

Spelbeschrijving.

Nu het spel bijna af is is er een officiele spelbeschrijving/handleiding: Bestand:Spelbeschrijving.pdf


Week 22

Allocatie probleem.

In het huidige ontwerp van ons spel zou er elke keer als er geschoten, een vijand gedood of als er een vijand bijkomt een nieuw spel object gealloceerd worden. Dit zorgt op de android al snel voor een snelheids probleem. De oplossing voor dit probleem vereist wel een andere aanpak voor het beheren van deze spel objecten dan in ons ontwerp document te lezen is. Voor de kogels lossen we dit probleem als volgt op.

Er is 1 klasse van elk type wapen. Deze klasse heeft een lijst met een bepaald aantal kogels welke tijdens het opstarten van het spel worden gealloceerd. De wapen klassen hebben een methode schiet(x, y, richting), welke een kogel die niet in gebruik is selecteerd en deze vanaf de gegeven positie afvuurd. Als een kogel van het scherm verdwijnd of een ander spel object raakt zal hij niet verwijderd worden maar onzichtbaar. Als er geen kogels beschikbaar zijn terwijl schiet() aangeroepen word zal er een log melding worden gemaakt, zodat later in de testfase het aantal kogels afgesteld kan worden.

Dit vereist maar een kleine aanpassing van het ontwerp en is momenteel functioneel.

Voor de vijanden gebruiken we een soortgelijke oplossing.

Er is een vijand_ai object voor elk type vijand. Er is een lijst met een vast aantal vijand objecten welke een struct vijand_info en een pointer naar de huidige vijand_ai bevatten. Als een vijand geactiveerd word zal hij aan vijand_ai zijn texture, afmeting en standaard waarde voor vijand_info opvragen. Een geactiveerde vijand zal in zijn ververs methode vijand_ai->ververs(this, info) aanroepen welke het object dan daadwerkelijk ververst. Als een vijand sterft zal hij de vijand_ai->sterf(this, info) methode aanroepen en daarna nonactief worden.

vijand_info is een struct dat alle informatie die nodig is om elk type vijand te representeren bevat.

Wederom zal er als er een tekort aan vijanden is een bericht gelogd worden zodat dit nummer tijdens de testfase aangepast kan worden.

Omdat kisten, rakketen en het moederschip maar af en toe voorkomen zullen we hier in eerste instantie niet van het huidige ontwerp afwijken. Mocht dit uiteindelijk toch voor snelheids problemen zorgen zouden we deze objecten ook kunnen omschakelen naar een soortgelijk ontwerp.

Week 22-2

Bij nader inzien maakt het kisten systeem gebruik van onnodig veel klassen. We zien hier twee mogelijkheden om het ontwerp aan te passen.

  1. 1 kist klasse met een constructor kist(afbeelding, leven_toename, salvo_toename, wapen_ptr), waar de onderdelen die niet van toepassing zijn 0 of NULL worden.
  2. 1 kist superklasse die het oppakken, bewegen, geef_afbeelding... beheerd en een virtual methode opgepakt(speler) die door verschillende kisten word geimplementeerd.

We kiezen voor aanpak nummer 2.

Week 20

Snelheid en richting zonder double/float.

Niet elke android telefoon kan optimaal omgaang met komma getallen, ook vermenigvuldigen en delen word op sommige telefoons in software gedaan. Objecten in onze spelwereld kunnen een richting en een snelheid hebben waarmee de spelmotor automatisch de positie van het object ververst. We hebben dit op de volgende manier voor elkaar gekregen.

  • Alle snelheden worden opgegeven in beeld eenheden per seconde (bij de standaard instellingen betekend dit pixels per seconde).
  • Er word een stappen teller bijgehouden (als een object een snelheid heeft) die teld van 0 tot 30. stappen_teller = (stappen_teller+1)%30;
  • Er worden 4 ints gebruikt om de beweging te beschrijven
int stap_x = hoeveel de x positie na 1 stap verhoogd word.
int stap_y = hoeveel de y positie na 1 stap verhoogd word.
int mod_x = als (stappen_teller%mod_x) == 0 , dan stap_x++;
int mod_y = als (stappen_teller%mod_y) == 0 , dan stap_y++;
bool inv_x = true als we eigenlijk een negatieve x snelheid hadden
bool inv_y = true als we eigenlijk een negatieve y snelheid hadden
  • Met behulp van 2 doubles word het aantal beeld eenheden per seconden voor de x en y as bepaald (x_comp en y_comp), met cos en sin. Dit gebeurd alleen als richting of snelheid veranderen.
voor elke 30 in x_comp en y_comp word stap_x, stap_y met 1 verhoogd.
Als er de rest in x_comp en y_comp != 0, dan word mod_x = rest_x_comp/30 , mod_y = rest_y_comp/30

Nu hebben we de mogelijkheid objecten een snelheid en richting te geven zonder elke frame floating point berekeningen te hoeven doen.

Week 19

Transparantie en bit diepte.

De scherm uitvoer van de android gebruikt 16bits kleuren (RGB565). Omdat OpenGL geen directe ondersteuning bied voor color keys (1 kleur in een afbeelding transparant maken) moeten we een kleur diepte kiezen die een alpha kannaal bevat. Als we hier voor 32bits kleuren zouden kiezen zou dat betekenen dat alle afbeeldingen twee keer zo groot zouden worden zonder enige toename in de kwaliteit van de afbeeldingen. Daarom maken wij gebruik van 16 bits RGBA5551 kleuren. Omdat het nogal een gedoe was om de android zo ver te krijgen onze afbeeldingen niet te mishandelen alvorens ze in de .apk te stoppen hebben we een eigen afbeeldings formaat gemaakt die we als raw resources aan onze app toevoegen. Het formaat is niet meer dan een lijst met 16bits unsigned getallen. De eerste twee getallen symboliseren de breedte en hoogte van de afbeelding de rest de kleuren.

Het shel script om afbeeldingen mee te converteren.

#!/bin/bash
#
#dit script zal een door gimp ondersteund formaat naar RGBA5551 converteren.
#

#verkrijg de hoofdmap
HOOFDMAP=${BASH_SOURCE[0]}
HOOFDMAP=$(dirname $HOOFDMAP)
OPTIES=$HOOFDMAP/makefile.opties

#genoeg paramters?
if [ "$1" == "" -o "$2" == "" ]; then
    echo "fout: ongeldige invoer!"
    echo "gebruik: tekening [resource naam] [xcf bestand]"
    exit 1
fi

#lees opties
if [[ ! -f $OPTIES ]]; then
    echo "fout: kan makefile.opties niet vinden!"
    exit 1
fi
source $OPTIES

#maak uitvoer map?
UITMAP=$HOOFDMAP/$MAP_BOUWPUT/res/raw
mkdir -p $UITMAP
UITBESTAND=$UITMAP/$1.afbeelding

#converteer!
gimp -n -i --batch-interpreter python-fu-eval -b - <<EOF
from array import *
import gimpfu

print("tekening: afbeelding laden... [$2]")
afbeelding = pdb.gimp_file_load("$2", "$2")
laag       = pdb.gimp_image_merge_visible_layers(afbeelding, 1)

breedte = pdb.gimp_drawable_width(laag)
hoogte  = pdb.gimp_drawable_height(laag)
bitmap  = array('H', [breedte, hoogte])

print("tekening: afbeelding converteren... ["+str(breedte)+"x"+str(hoogte)+"]")
for y in range(0, hoogte):
    for x in range(0, breedte):
        nc, pixel = pdb.gimp_drawable_get_pixel(laag, x, y)
        if nc < 3:
            print("tekening: fout: ongeldige pixels!")
            pdb.gimp_quit(1)

        if ((pixel[0] == 255) and (pixel[1] == 0) and (pixel[2] == 255)):
            bitmap.append(0)
        else:
            r = int(pixel[0]/8)
            g = int(pixel[1]/8)
            b = int(pixel[2]/8)
            bitmap.append((r<<11) | (g<<6) | (b<<1) | 1)

print("tekening: afbeelding schrijven... [$UITBESTAND]")
bestand = file('$UITBESTAND', 'wb')
bitmap.tofile(bestand)
bestand.close()

print("tekening: afbeelding geconverteerd!")
pdb.gimp_quit(1)
EOF

if [ "$?" != "0" ]; then
    echo "fout: kan bestand niet converteren!"
    exit 1
fi

#succes
exit 0

Week 18 (vakantie)

Na wat research hebben wij 4 verschillende render methode onderzocht. Twee maken gebruik van de een Canvas de overige twee maken gebruik van OpenGL ES.

  1. SurfaceView met onDraw() - asynchroon met gamethread, makkelijk te programmeren, langzaam
  2. SurfaceView met lockCanvas()/unlockCanvasAndPost() - synchroon met gamethread, veel lelijke Java try/synchronize code, langzaam
  3. OpenGL ES met GLSurfaceView - asynchroon met gamethread, hardware acceleratie (indien aanwezig), makkelijk toegankelijk vanuit C++, makkelijk te programmeren
  4. OpenGL ES met eigen View en EGL - synchroon met gamethread, hardware acceleratie (indien aanwezig), makkelijk toegankelijk vanuit C++, ingewikkeltst om te programmeren

Methode 1 en 3 vielen als snel af, omdat wij een hoop hoofdpijn voorzagen in het synchronizeren van het verversen van het model en het tekenen van het model. We hebben voor methode 4 gekozen omdat deze de meeste mogelijkheden bied. Hiervoor was wel wat extra java code nodig aangezien EGL pas vanaf api level 9 in C++ word ondersteund. Onze gameloop word nu in de volgende volgorde doorlopen.

  • Teken de spel wereld
  • Behandel invoer berichten
  • Ververs het model
  • Wissel back en front buffer
  • Wacht totdat er 33ms zijn verstreken sinds het tekenen is begonnen (30 beelden per seconde)

De rede waarom we eerst het model tekenen en daarna verversen heeft te maken met het feit dat opengl zijn render calls doorstuurt naar de GPU of software renderer. Deze gaat daarna aan de slag met deze opdrachten als de back en front buffer gewisseld worden wacht deze methode eerst totdat alle render calls zijn afgehandeld. Door het model te verversen terwijl de GPU bezig is met het tekenen van het spel zijn we zo min mogelijk aan het wachten op de GPU.

Wij hebben voor een vast aantal beelden per seconde gekozen zodat het spel geen rekening hoeft te houden met de verstreken tijd sinds de laatste update.