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 >>
|