Applets
Spelprogrammering
Vad jag jobbar med just nu
Spel
Effektapplets
Diverse länkar
Knuffa bollen med muspekaren
Om man knuffar till bollen i appleten, så sätter den full fart i knuffens riktning, Om den skulle stöta i väggarna så studsar den tillbaka.För att skapa den här appleten, så finns det ett antal mattematiska problem att lösa. Det första problemet är förstås kollisionskontroll en mellan muspekaren och bollen.
Det andra problemet är att få bollen att sätta av i knuffens riktning.
I övrigt så används Graphics2D ganska flitigt. Gradientpaint har du redan stiftat bekantskap med, åtminstone om du har kikat på animation(1). Hur som helst, den används i det här fallet, för att få till ett klotformat utseende på bollen.
Projektet består av två klasser:
- Ball - beskriver en boll
- Runner - sätter upp appleten och skapar en instans av klassen Ball
Kollisionskontroll boll och muspekare
Ja, innan vi fördjupar oss i själva kollisionskontrollen, så bör vi kanske ordna så att appleten fångar mushändelsen mouseMove.I Javaspråket fungerar det på det viset, att man måste registrera aktuell komponent som intresserad av viss händelse. I det här fallet, så är appleten intresserad av att få veta, när någon har rört muspekaren över appletens visningsyta. I grund och botten, så är det operativsystemets uppgift att notera händelser typiska för en dator, mushändelser och tangentbordshändelser.
Visserligen kommer nog någonting att hända med datorn, om du slänger något tungt på skärmen, men eftersom det inte är någon händelse typisk för datorn, så kommer den inte att registreras av operativsystemet. Vore väl kul annars med en blåskärm med texten, "Ta det lugnt!", efter nämnda händelse, gärna ackompanjerat med ett förskräckt ," Aj", från högtalarna.Det sagda innebär att appleten måste tala om för operativsystemet, att den är intresserad av händelsen, musrörelse över appleten. Vidare så ska appleten tala om för operativsystemet vilken metod det ska anropa, när händelsen inträffar.
Oftast, så finns det metoder, som heter ungefär addMouseListener, addActionListener osv, för de flesta komponenter. Dessa metoder tar oftas ett gränssnitt som argument. I detta gränssnitt finns det metoder, som fungerar som callbacks (metoder som anropas av operativsystemet, när händelsen inträffar).
Ett gränssnitt eller interface, som det heter på javaspråket, definieras som en samlig abstrakta metoder och konstanter. De fungerar ungefär som abstrakta klasser. Man kan ärva interface och då måste man implementera de abstrakta metoderna, annars får man kopileringsfel.
En lösning hade varit att låta appleten implementera interfacet mouseMotionListener. Detta gränssnitt har två abstrakta metoderm mouseMoved och mouseDragged. Men nu är jag bara intresserad av en av dessa metoder, mouseMoved, så jag valde en annan taktik.
Jag skapade en innre klass, som ärver mouseMotionAdapter och skrev över metoden mouseMoved. Koden till den innre klassen kan du se nedan.
class MusLyssnare extends MouseMotionAdapter { public void mouseMoved(MouseEvent e) { System.out.println("Någon rörde musen!"); System.out.println("Musx = " + e.getX() +" Musy="+e.getY()); } }Ja, vad som händer är väl ganska självförklarande. Observera att objektet e av typen MouseEvent har information om muspekarens position. För att nu få det hela att fungera, så måste appleten anmäla sitt intresser och den gör den med följande metodanrop: addMouseMotionListener(new MusLyssnare());

hypotenusan = kvadratroten ur ((sida_a*sida_a) + (sida_b * sida_b)).
public boolean isHit(double mx,double my) { double radie = diameter/2; /* De två första variablerna håller värdena på triangelsidornas längd. */ double dx = (x+radie)-mx; double dy = (y+radie)-my; double distance = Math.sqrt((dx*dx) + (dy*dy)); return (distance <= radie); }Något som kanske förvånar är double dx = (x+radie)-mx; och double dy = (y+radie)-my;, men det finns en enkel förklaring. Bollens x och ykordinater för utritning ligger längst upp till vänster, så man måste öka dessa värden med cirkelns radie. Variablerna mx och my är förstås kordinaterna för musens position.
Sätta bollen i rörelse
Om du har gått igenom exemplet "racerbil", så fördes där ett resonemang om beräkning av ett objekts position på skärmen. Vidare sades att rörelse handlar om förflyttning mellan två punkter i kordinatsystemet mellan två tidpunkter (omritningar som vi valde att använda). Vi fick till en beräknings som såg ut så här:x+= velocity * Math.cos(Math.toRadians(vinkel)); y+= velocity * Math.sin(Math.toRadians(vinkel));Velocity står för hastighet, i det här fallet antalet pixlar mellan varje omritning. Om nu velocity har värdet noll, så kommer objektet att stå stilla. Då skulle man kunna ställa hastigheten (velocity) till t.ex. 16 vid en träff med muspekaren och sedan minska hastigheten med t.ex. 0.2 vid varje omritning. Det skulle se ut som objektet (bollen) får en knuff, först hög fart, sedan avstannande och slutligen avstannad.
Om bollen träffar någon av väggarna, så skulle man kunna vända på hastigheten, ja just det, en negativ hastighet, vilket skulle få bollen att vända.

Tangent(vinkel) = sida_a / sida_b . Det betyder att vinkelns (det lilla rundade strecket i triangeln på bilden) tangens är lika med motstående sida delat med närstående sida. Den intresserade kan läsa mer här.
Nu är vi inte intresserade av vinkelns tangens utan av vinkeln, den s.k inverterade tangensen. Vilken tur att det finns en funktion, som heter atan2 i Java. Den returnerar faktiskt vinkeln i triangeln uttryckt i radianer. Som argument tar den längden på triangelns sidor. Med sidor avses de två sidor eller katetrar, som de ockå brukar kallas i mer formella mattematiska sammanhang, som inte är hypotenusan. Eftersom jag valde att lägga både kollisionskontrollen och vinkelberäkningen i samma metod, så presenterar jag ånyo denna metod och har fetmarkerat vinkelberäkningen.
public boolean isHit(double mx,double my) { double radie = diameter/2; /* De två första variablerna håller värdena på triangelsidornas längd. De används både vid kollisionskontrollen och vinkelberäkningen. */ double dx = (x+radie)-mx; double dy = (y+radie)-my; double distance = Math.sqrt((dx*dx) + (dy*dy)); if(distance <= radie) { velocity=14; clr=new Color(0,0,180); vinkel = Math.toDegrees(Math.atan2(dy,dx));// vinkeln uttryckt i grader return true; } clr=new Color(180,0,0); return false; }Okey, innan jag presenterar källkoden i sin helhet, så vill jag göra en kommentar. Metoden isHit returnerar en boolean. Men jag har i det här fallet valt att anropa den från klassen Runner som vilken metod som helst. Jag använder således inte returvärdet. Tanken är att jag ska göra det vid ett senare tillfälle. Det kan se lite knepigt ut, men nu förstår du varför.
Ball.java
import java.awt.Color; import java.awt.GradientPaint; import java.awt.Graphics2D; public class Ball { double x, y, diameter, velocity, vinkel; Color clr; public Ball(double x, double y) { this.x = x; this.y = y; velocity = 0; diameter = 50; clr = new Color(180, 0, 0); vinkel = 0; } public void paintBall(Graphics2D g) { if (velocity > 0) velocity -= 0.2; else if (velocity < 0) velocity += 0.2; if (Math.abs(velocity) < 0.2) velocity = 0; x += velocity * Math.cos(Math.toRadians(vinkel)); y += velocity * Math.sin(Math.toRadians(vinkel)); kollaVäggar(); g.setPaint(new Color(0, 0, 0)); g.fillOval((int) x + 10, (int) y + 10, (int) diameter, (int) diameter); GradientPaint gr = new GradientPaint((float) x, (float) y, new Color( 255, 0, 0), (float) x + (float) diameter, (float) y + (float) diameter, new Color(0, 0, 255), false); g.setPaint(gr); g.fillOval((int) x, (int) y, (int) diameter, (int) diameter); } private void kollaVäggar() { double tempx = x; double tempy = y; tempx += velocity * Math.cos(Math.toRadians(vinkel)); tempy += velocity * Math.sin(Math.toRadians(vinkel)); if (tempx < 0 || tempx + diameter > Runner.BREDD || tempy + diameter > Runner.BREDD || tempy < 0) { velocity *= -1; } } public boolean isHit(double mx, double my) { double radie = diameter / 2; double dx = (x + radie) - mx; double dy = (y + radie) - my; double distance = Math.sqrt((dx * dx) + (dy * dy)); if (distance <= radie) { velocity = 14; clr = new Color(0, 0, 180); vinkel = Math.toDegrees(Math.atan2(dy, dx)); return true; } clr = new Color(180, 0, 0); return false; } }Sedan klassen Runner, som sätter upp appleten, skapar och använder en instans av klassen Ball.
Runner.java
import java.applet.Applet; import java.awt.Color; import java.awt.Font; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.Image; import java.awt.RenderingHints; import java.awt.event.MouseEvent; import java.awt.event.MouseMotionAdapter; import java.awt.geom.Rectangle2D; import java.awt.image.BufferedImage; public class Runner extends Applet implements Runnable { private Ball ball; private Thread thread = null; private BufferedImage bakgrund; private BufferedImage plattor; private Graphics2D g; private Rectangle2D rect; public static int BREDD = 400; 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, 24)); rect = new Rectangle2D.Double(0, 0, this.getWidth(), this.getHeight()); Image bild = getImage(getCodeBase(), "car_smaller.gif"); ball = new Ball(100, 100); plattor = getBakgrund(); addMouseMotionListener(new MusLyssnare()); } private BufferedImage getBakgrund() { BufferedImage retval = new BufferedImage(getWidth(), getHeight(), BufferedImage.TYPE_INT_RGB); Graphics2D g2d = retval.createGraphics(); int bredd = getWidth() / 10; int x = 0; int y = 0; g2d.setPaint(new Color(180, 180, 180)); for (int i = 0; i < 10; i++) { x = 0; for (int j = 0; j < 10; j++) { g2d.fill3DRect(x, y, bredd, bredd, true); x += bredd; } y += bredd; } return retval; } public void paint(java.awt.Graphics page) { g.drawImage(plattor, 0, 0, this); g.setPaint(new Color(0, 0, 0)); g.drawString("Knuffa bollen med muspekaren", 10, 30); g.setPaint(new Color(0, 200, 0)); g.drawString("Knuffa bollen med muspekaren", 8, 28); ball.paintBall(g); page.drawImage(bakgrund, 0, 0, this); } public void update(Graphics page) { paint(page); } public void run() { while (thread != null) { 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 MouseMotionAdapter { public void mouseMoved(MouseEvent e) { ball.isHit(e.getX(), e.getY()); } } }Ja, sist men inte minst en webbsida.
<html> <body> <applet code=Runner.class width="400" height="400"> </applet> </body> </html>