Applets
Spelprogrammering
Vad jag jobbar med just nu
Spel
Effektapplets
Diverse länkar
Spelprogrammering
På Itgymnasiet undervisar jag bland annat i spelprogrammering. Jag lägger därför upp några enkla exempel i form av javapplets, för att belysa vissa principer och huvudpunkter inom spelprogrammeringen.Bäst att vara tydlig redan början och understryka, att det inte kommer att handla om några avancerade spel, utan endast exempel, som är tänkta att förklara en och annan aspekt. Däremot kan det längre fram tänkas, att jag lägger ut något färdigt spel.
Inledning
I spelprogrammering handlar det väldigt ofta om att få någonting att röra sig på skärmen, antingen av sig självt eller användarstyrt. I det här fallet är skärmen lika med en applets rityta.Principen är enkel, rita ut någonting, bestäm en ny position och rita om igen. Det är när man kommer till, hur någonting ska röra sig någorlunda korrekt, som bekymren dyker upp. I det här första exemplet, så hade jag tänkt att låta användaren styra en bil med hjälp av tangentbordet.
Låt oss börja med några enkla principer och förutsättningar.
Kordinatsystemet

I en javaapplet, så ligger origo längst upp till vänster. Det gäller för övrigt de flesta spelprogrammeringsmiljöer med något/några undantag, t.ex. Open GL, som har origo i mitten.
Den observante kan se ytterligare en underlighet, y-axeln är omvänd, d.v.s ju längre ner man kommer på y-axeln, dessto större värden får man,
Om du tittar noga på bilden, så kan du se en liten ring. Den har positionen 2,2. Det innebär att man kan bestämma ett objekts position i en applet med hjälp av kordinatsystemet.
En annan fråga är förstås, hur ringen har hamnat där, vilken position hade den innan den t.ex. blåste dit.
Rörelse
För att en kropp ska komma i rörelse, så måste den utsättas för någon kraft. Det kan t.ex. handla om en förbränningsmotor, vind, gravitation osv. Rörelsen yttrar sig i en förflyttning mellan två punkter med viss hastighet. Hastigheten är lika med förflyttningen uttryckt i något längdmått, t.ex. meter , per tidsenhet t.ex. sekund.När man programmerar, så är det ganska lätt att tänka sig tidsenheten såsom omritningen, sk. frames. Men det finns ingenting som hindrar att man beräknar tiden mot systemklockan.
För att beräkna hur långt ett objekt har rört sig till nästa omritning (tidsenhet), så kan man använda vektormatte. I mattematiken, så är en vektor ett uttryck för riktning och kraft (styrka, hastighet osv). Vektorer kan man räkna med. Låt oss anta att en bil rör sig med viss hastighet och riktning , då kan man beräkna nästa position på följande sätt:
bilx = hastigheten * cos(riktning)
bily = hastigheten * sin(riktning)
Skulle det vara så att bilen är utsatt för stark vind från något håll, så kan man plussa ihop bilens vektor med vindens vektor.
vindx = vindhastigheten * cos(vindriktning)
vindy = vindhastigheten * sin(vindriktning)
bilx+ =vindx
bily+=vindy
cosinus och sinus hör förstås trigonometrin till. Det handlar om beräkningar av vinklar och det är någonting som väldigt ofta kommer till användning inom just spelprogrammering. I de flesta programmeringsmiljöer finns det rikligt utformade mattebibliotek, med bland annat funktioner för cos och cosinus. Java är inget undantag.
Struktur - strategi
Jag valde att skapa en klass, som beskriver en bil. Jag bjuder på källkoden här.car,java
import java.applet.Applet; import java.awt.Graphics2D; import java.awt.Image; import java.awt.geom.AffineTransform; import java.awt.image.ImageObserver; public class Car { private Image bild; private double dx,dy,vridmoment,velocity; private AffineTransform transform; private ImageObserver observer; public Car(Image bild, double dx, double dy,ImageObserver observer) { this.observer=observer; this.bild = bild; this.dx = dx; this.dy = dy; vridmoment = 45; velocity = 0; } /*I bilens paintmetod arbetar jag med ett objekt av typen AffineTransform och ett grafiskt objekt av typen Graphics2D. AffineTransform har möjligheten att flytta på kordinatsystemet - istället för att förflytta x och y, så kan man faktiskt flytta på hela kordinatsystemet och uppnå precis samma effekt, något som ofta används inom 3d-programmering. I det här fallet handlar det om en temporär förflyttning - sker bara för att på ett smidigt sätt kunna rotera på bildbilen. Notera att metoden rotate anger att bilden ska rotera utifrån 12.5 och 25, vilket är ungerfär mitten på bilden. setToIdentity() betyder att transformationen återställs. */ public void paintBil(Graphics2D g) { transform = new AffineTransform(); transform.setToTranslation(dx,dy); transform.rotate(Math.toRadians(vridmoment),12.5,25); g.drawImage(bild,transform,observer); transform.setToIdentity(); } public void vridLeft() { vridmoment-=10; } public void vridRight() { vridmoment+=10; } public void move() { if(isFree()) { /*Här kan du ser hur bilens nästa position bestäms. Beräkningen följer den modell, som jag angav i texten ovan. Math.cos tar radianer som argument, vilket för övrigt inte är helt ovanligt i mattebiblioteken. Velocity är förstås bilens aktuella hastighet. Du kan också observera att jag minskar vridmomentet med 90, dvs. ett kvarts varv. Det handlar om kompensation för den inverterade y-axeln. */ dx += velocity * Math.cos(Math.toRadians(vridmoment-90)); dy += velocity * Math.sin(Math.toRadians(vridmoment-90)); } } /* isFree är en ganska primitiv kollisionskontroll. Den ska helt enkelt se till att bilen inte lämnar appleten. (Vart tog vägen vägen? Jag vet inte, men vi åker på en åker) */ private boolean isFree() { double tempx=dx; double tempy=dy; tempx += velocity * Math.cos(Math.toRadians(vridmoment-90)); tempy += velocity * Math.sin(Math.toRadians(vridmoment-90)); if((tempx) <= 10 || tempx+25 >= Runner.BREDD-10 || (tempy) <=10 || tempy + 50 >= Runner.BREDD-10) { velocity = 0; return false; } return true; } public void gasa() { if(velocity < 2) velocity++; } public void bromsa() { velocity=0; } }
En klass ja, men sen då?
Jo, sedan gjorde jag en applet, som implementerar Runnable (finns beskrivet i Animation(1)). I denna applet, som jag kallade för Runner, så läste jag in en bild av en bil, som jag ritat själv, och skapade ett objekt av klassen Car. Bjuder på källkoden här.(Runner.java)
import java.applet.Applet; import java.awt.BasicStroke; import java.awt.Color; import java.awt.Font; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.Image; import java.awt.MediaTracker; import java.awt.RenderingHints; import java.awt.event.KeyAdapter; import java.awt.event.KeyEvent; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.awt.geom.Rectangle2D; import java.awt.image.BufferedImage; import java.awt.image.ImageObserver; public class Runner extends Applet implements Runnable { private Car bil; private Thread thread = null; private BufferedImage bakgrund; private Graphics2D g; private Rectangle2D rect; public static int BREDD = 400; private boolean aktiv = false; public void init() { bakgrund = new BufferedImage(getWidth(), getHeight(), BufferedImage.TYPE_INT_RGB); g = bakgrund.createGraphics(); g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); g.setFont(new Font("Sans-serif", Font.BOLD, 14)); rect = new Rectangle2D.Double(0, 0, this.getWidth(), this.getHeight()); Image bild = null; try { bild = getImage(getCodeBase(), "car_smaller.gif"); /* MediaTracker är en smart javaklass, som man kan använda för försäkra sig om att en hel bild är inläst till appleten innan man börjar utföra några operationer på den. */ MediaTracker mt = new MediaTracker(this); mt.addImage(bild, 0); mt.waitForAll(); } catch (Exception error) { } bil = new Car(bild, 100.0, 100.0, (ImageObserver) this); addKeyListener(new TangentLyssnare()); addMouseListener(new MusLyssnare()); } public void paint(java.awt.Graphics page) { g.setPaint(new Color(0, 80, 0)); g.fill(rect); if (!aktiv) { g.setPaint(new Color(255, 255, 0)); g.drawString("Klicka med musen på appleten för att aktivera den", 15, 40); } g.setStroke(new BasicStroke(20)); g.setPaint(new Color(0, 40, 0)); g.draw(rect); bil.paintBil(g); page.drawImage(bakgrund, 0, 0, this); } public void update(Graphics page) { paint(page); } public void run() { while (thread != null) { bil.move(); repaint(); try { Thread.sleep(5); } catch (InterruptedException error) { } } } public void start() { if (thread == null) { thread = new Thread(this); thread.start(); } } public void stop() { thread = null; } class MusLyssnare extends MouseAdapter { public void mouseClicked(MouseEvent e) { aktiv = true; } } class TangentLyssnare extends KeyAdapter { public void keyPressed(KeyEvent e) { action(e); } public void keyTyped(KeyEvent e) { action(e); } public void action(KeyEvent e) { // System.out.println(e.getKeyCode()); switch (e.getKeyCode()) { case 32: bil.gasa(); break; case 38: bil.bromsa(); break; case 39: bil.vridRight(); break; case 37: bil.vridLeft(); break; } } } }Ja, sist men inte minst en webbsida.
<html> <body> <applet code=Runner.class width="400" height="400"> </applet> </body> </html>
Hur blev det då?
Jo, jag tyckte nog att jag fick en bil, som jag kunde styra med tangentbordet.Man måste klicka på appleten, för att aktivera den. En gammal appletsjukdom, som innebär att den inte kan ta tangentbordhändelser, om den inte är i fokus. Man gasar med space-tangenten. Man styr med vänster och högerpilarna. Man stannar med uppåtpil. Skulle bilen köra in i, eller för nära någon vägg, så stannar den. Då vrider man lite på den och gasar igen.
Det finns säker en hel ytterligare att förklara i den här appleten och det kommer jag att göra inom kort.
Tills dess så kanske du skulle vilja testa att få igång appleten och experimentera lite på egen hand.
Högerklicka på bilden och spara bild som.
