Hem E-böcker Specialeffekter och spelutveckling i Java Ditt första spel

Specialeffekter och spelutveckling i Java(TM) - Ditt första spel 

av Anibal Wainstein

5.1 Ditt första spel, Rymdpiraterna (SpacePirates)

Nu har vi tillräckligt med kunskaper för att ge oss in på att göra spel. Trots att vi ännu inte vet hur man lägger in ljudeffekter, fonter, färgtoningar, tangentbordshändelser och bildbehandling så kan man ändå komma ganska långt med bara animationer och musmeddelanden.

Året är 2334 och det svenska luftutrummet har invaderats av rymdpirater. Som rymdpolis har du tillgång till din kamuflerade blågula stridsskepp FT101, svenska rymdpolisens stolthet, för att bemöta attackerna (FT står för Flygande Tunnan). Hårda strider kommer rasa över de svenska skogarna och många modiga pirater kommer att störta till sin död när du släpper lös din destruktiva kraft. Spelet är en s.k. shoot'em up spel där du ser skeppen utifrån fågelperspektiv. Storleken på appleten kommer att vara 400x300 pixels.

5.1.1 Första steget: polisskeppets animation och rörelser

Polisskeppet är riktad mot höger och det förflyttar sig upp eller ner beroende hur spelaren flyttar på muspekaren. Observera att skeppet kommer inte att åka mot höger utan den står still, medan bakgrunden rör sig mot vänster. Detta kommer att ge intrycket att skeppet åker genom skogarna. När skeppet rör sig upp eller ner så kommer det också att vinklas för att ge en mer realistisk effekt. Detta betyder att det måste animeras.

Det svenska polisskeppet FT101s animation.

Vi börjar med att deklarera de nödvändiga variablerna för att animera skeppet och lägger in koden för att ladda in animationen i init() metoden:

//Vi slipper inte ifrån trådhantering den här
//gången.
Thread programthread = null;

//Tidsfördröjningen på bilderna i spelet
//får vara 60 millisekunder.
int sleeptime=60;

//Arrayen "policeship" innehåller animationen
//för polisskeppet.
Image policeship[];

//Vi måste förstås använda dubbelbuffring.
Image bufferimage;
Graphics bufferg;

public void init()
{
    Dimension d=size();
    bufferimage=createImage(d.width,d.height);
    bufferg=bufferimage.getGraphics();

    //Vi använder oss som vanligt av en
    //for-slinga för att ladda in
    //animationen. Polisskeppets animation
    //är på 5 bilder.
    policeship=new Image[5];
    MediaTracker tracker=new MediaTracker(this);
    for (int i=0; i<5; i++)
    {
        policeship[i]=getImage(getDocumentBase(),"policeship"+i+".gif");
        tracker.addImage(policeship[i],i);
    }
    try {tracker.waitForAll();}
    catch(InterruptedException e)
    {
        System.out.println("Någonting stoppade inladdningen...");
    }
}

Observera att nu är vi tvugna att använda trådar. Glöm inte att lägga in start() och stop() metoderna som beskrevs i kapitel3 och att skriva "implements Runnable" appletdeklarationen. För att kunna förstå hur skeppet kommer att bete sig är det viktigt att du vet hur varje bild kommer att placeras i "policeship" arrayen:

policeship[0] policeship[1] policeship[2] policeship[3] policeship[4]

Som du kan se så ligger det stillastående skeppet i position 2 och inte i 0 som man skulle kunna tro. Detta förenklar arbetet när vi ska skriva rörelserna för skeppet. Tanken med detta är att skeppet skall "släpa efter" muspekarens position. Det betyder att om du t.ex. snabbt flyttar upp muspekaren och skeppet är längst ner, så kommer det att svänga och förflytta sig sakta uppåt, tills det hamnar i samma position. Nu kanske du undrar varför man inte helt enkelt kan låta muspekarens position vara densamma som skeppets? Vi måste ju vara lite realistiska här, skeppet kan ju inte röra sig med ljusets hastighet dit spelaren pekar, det är inte fysiskt möjligt. Förresten ger det skeppet skönare rörelser. Observera att det är inte i paint() metoden som allt ritande kommer att ske utan i run() metoden. Det enda vi gör i paint() metoden är att rita ut bufferbilden. Observera att paint() måste vara synkroniserad nu när vi använder trådar:

public synchronized void paint(Graphics g)
{
    //Det enda man gör i paint() är att
    //rita ut bufferbilden. Allt annat
    //ritande görs i run() metoden.
    if (bufferimage!=null) g.drawImage(bufferimage,0,0,this);
}

public void update(Graphics g)
{
    paint(g);
}

Två variabler kommer att användas för polisskeppets skull, hastigheten "v" och den vertikala positionen "py". Vi använder oss av variabeln "y0" för att hålla reda på muspekarens vertikala position. Du kanske undrar varför vi inte lägger in en variabel för den horizontella positionen också, men eftersom skeppet bara kommer att röra sig upp eller ner vid vänstra kanten av appleten så behövs den inte.

//Variablerna "y0" indikerar muspekarens
//vertikala position. Vi har ingen nytta
//av den horizontella positionen.
int y0;

//Variabeln "v" indikerar polisskeppets
//vertikala hastighet och "py" indikerar
//skeppets vertikala position.
int v=0, py=0;

Vi passar också på att initiera "py" till 150 i init() metoden så att skeppets visas i mitten av skärmen när appleten startas. Metoden mouseMove() behöver bara se till att uppdatera "y0" med muspekarens senaste position.

public boolean mouseMove(Event e, int x, int y)
{
    y0=y;
    return true;
}

Nu kommer vi till det roliga. Som jag sade tidigare så skedde det mesta av arbetet i run() metoden:







public void run()
{
    while (true)
    {
        //Om muspekaren pekar ovanför skeppet
        //så accelerera nedåt.
        if (y0-py>0) v++;

        //Om muspekaren pekar nedanför skeppet
        //så accelerera uppåt.
        if (y0-py<0) v--;

        //Låt skeppet stå still när muspekaren
        //är i samma position som skeppet.
        if (y0-py==0) v=0;

        //Hastigheten får högst vara 4 pixlar
        //uppåt eller nedåt.
        if (v<-4) v=-4;
        if (v>4) v=4;

        //Lägg till hastigheten.
        py+=v;

        //Rensa skärmen.
        bufferg.setColor(Color.black);
        bufferg.fillRect(0,0,400,300);

        //När polisskeppet skall ritas ut så kan vi
        //använda oss av hastigheten. policeship[2]
        //är ju bilden på skeppet när det står still.
        //Dela hastigheten med 2 och så får du värden mellan
        //-2 och 2. Lägg till 2 och du får värden mellan
        //0 och 4 vilket är animationen av skeppet när det
        //svänger.
        //Slutligen måste vi också se till att
        //bilden på skeppet justeras så att den vertikala
        //medelpunkten i skeppet pekar där muspekaren pekar.
        bufferg.drawImage(policeship[2+v/2],0,-55+py,this);
        update(getGraphics());
        try {Thread.sleep(sleeptime);}
        catch(InterruptedException e) {}
    }
}

Skillnaden mellan muspekarens position och skeppets position kan utnyttjas för att ta reda på hur skeppet skall röra sig. Beroende på om den här skillnaden är negativ (<0), positiv (>0), eller lika med noll, så accelereras, deccelereras respektive stoppas skeppets hastighet. Samtidigt så ser vi till så att skeppet inte rör sig snabbare än 4 pixlar per bild genom att begränsa hastigheten "v" till högst 4 och minst -4. Den här beräknade hastigheten lägger vi till skeppets position. När det blir dags att rita ut skeppet så ser vi till att den först centreras kring "py"s position. Detta för att den skall peka där muspekaren pekar när den står stilla. Som jag sa tidigare så kommer hastigheten "v" att pendla mellan värderna -4 och 4. När hastigheten är -4 så svänger skeppet som mest uppåt och vice versa. Om vi nu heltalsdividerar hastigheten med 2, får vi värden mellan -2 och 2. Lägger vi till 2, får vi värden mellan 0 och 4, vilket är ett perfekt index för skeppets rörelser. Detta betyder att när hastigheten är 0 svänger inte skeppet. Titta på tabell nedan för att se hur hastigheten kommer att påverka animationen på skeppet:

v=-4 v=-3,v=-2 v=-1,v=0,v=1 v=2,v=3, v=4

När skeppet är utritat i bufferten så anropas update() så att paint() uppdaterar skärmen. Nu klickar du här för att se FT101 i action.

Det var ju snyggt. Men har varför rör sig skeppet så konstigt kring muspekarens position? Jo, eftersom skeppet rör sig med en hög hastighet dit muspekaren pekar så kommer det att åka över den punkten och därför försöka åka tillbaka. Till slut sitter skeppet och svänger fram och tillbaka över muspositionen. Lösningen för detta är att man bromsar skeppet när det börjar närma sig destinationen. Ändra de två första if-satserna i run() metoden till följande:

//Om muspekaren pekar ovanför skeppet
//så accelerera nedåt, bromsa när skeppet
//kommer nära muspekarens position.
if (y0-py>0)
{
    v++;
    if (y0-py<10) v=3;
    if (y0-py<5) v=1;
}

//Om muspekaren pekar nedanför skeppet
//så accelerera uppåt, bromsa när skeppet
//kommer nära muspekarens position.
if (y0-py<0)
{
    v--;
    if (y0-py>-10) v=-3;
    if (y0-py>-5) v=-1;
}

 

Vi har ovan lagt in två "bromsar" på skeppet. Den ena aktiveras när skeppet är inom 10 pixlar från muspekarens position och sätter hastigheten till 3 pixlar. Den andra aktiveras när skeppet är inom 5 pixlar från muspekarens position. Denna sätter hastigheten till 1 och skriver över den första bromsen. Det är detta de extra if-satserna gör. Klicka här för att titta på den korrigerade versionen.

 

 

 

Nästa sida >>