Applets
Spelprogrammering
Vad jag jobbar med just nu
Spel
Effektapplets
Diverse länkar
- En första animation
- Skapa en bild och hämta ett Graphics2Dobjekt
- Att rita med Graphics2D
- Det färdiga resultatet
En första animation
Nu ska vi sätta den första appleten (en enkel applet) i rörelse. Så vi fortsätter helt enkelt att arbeta med den.Jag hade tänkt att vi inte skulle göra den gamla vanliga skrollen, utan hitta på något annat. Vi ska låta ett ljussken spela över texten. För att lyckas uppnå en sedan effekt, så ska vi använda oss av en klass, som heter Graphics2D. En klass, som utgör en utökning av klassen Graphics, dvs. den ärver denna klass. Graphics2D har några år på nacken nu, men är betydligt nyare än Graphics. Den innehåller väldigt många av de verktyg, som man kan förvänta sig av ett modernt grafiskt bibliotek. För att nämna några, så kan man måla med texturer, man kan använda antialiasing för mjukare kanter i utritningarna och det finns stöd för skalning, skevning och transformationer av olika slag.
Principen för en animation är ganska enkel. Man flyttar på något, genom att tilldela någon eller några variabler nya värden, innan man ritar om. I java finns det riktligt med möjligheter att skapa processer, eller trådar om man så vill. Det finns en klass för processhantering, som heter Thread och som man kan ärva. Nu passar inte det oss riktigt, eftersom vår applet redan ärver klassen Applet och i Java tillåts endast enkla arv.
Lösningen är att låta vår applet implementera gränssnittet Runnable och skriva över metoden Run.
import java.applet.Applet; import java.awt.Color; import java.awt.Font; import java.awt.FontMetrics; import java.awt.Graphics; /* Här låter vi klass MyApplet implementera Runnable */ public class MyApplet extends Applet implements Runnable { private Color text_color; private Color bakgrund; private String str; private Font f; private String familj; private int storlek; private int stil; /* Nytt objekt av typen Thread */ private Thread thread; public void init() { text_color = null; try { text_color = new Color(Integer.parseInt(getParameter("textfärg"),16)); } catch(Exception fel) { text_color = new Color(230,0,40); //Oj då något fel har uppstått! } bakgrund = null; try { bakgrund = new Color(Integer.parseInt(getParameter("bakgrundsfärg"),16)); } catch(Exception fel) { bakgrund = new Color(0,0,0); } str=null; try { str = getParameter("message"); } catch(Exception error) { str="AlesnaWebbSystem.se"; } if(str.length()==0 || str== null) str = str="AlesnaWebbSystem.se"; familj = null; try { familj = getParameter("teckensnitt"); } catch(Exception error) { familj = "Sans-Serif"; } if(familj.length()==0 || familj== null) familj ="Sans-Serif"; storlek = 0; try { storlek = Integer.parseInt(getParameter("fontstorlek")); } catch(Exception error) { storlek = 22; } if(storlek < 0) storlek = 22; String font_stil=null; try { font_stil = getParameter("font_stil").toUpperCase(); } catch(Exception error) { font_stil = "PLAIN"; } if(font_stil.length()==0 || font_stil== null) font_stil="PLAIN"; if(font_stil.equals("BOLD")) stil = Font.BOLD; if(font_stil.equals("PLAIN")) stil = Font.PLAIN; if(font_stil.equals("ITALIC")) stil = Font.ITALIC; f = new Font(familj,stil,storlek); } public void paint(Graphics page) { page.setFont(f); FontMetrics fm = page.getFontMetrics(); int x = (getWidth()/2)- (int) fm.stringWidth(str)/2; int y = (getHeight()/2)+ (fm.getHeight()/2); page.setColor(bakgrund); page.fillRect(0,0,getWidth(),getHeight()); page.setColor(text_color); page.drawString(str,x,y); } /* * (non-Javadoc) * @see java.applet.Applet#start() * Här nedan skriver vi över ytterligare en metod, start. * Denna metod körs så fort appleten laddas in på en webbsida. * I denna, så undersöks om tråden är lika med null, vilket * den ska vara enligt deklarationen ovan på klassnivå. * Eftersom den är null, så skapas tråden genom ett anrop * till konstureraren i klassen Thread, Sedan anropas trådens * startmetod. Via arv, så kommer en metod, som heter Run att anropas. * Det är vår uppgift att skriva över metoden run och göra något * lämpligt i denna. */ public void start() { if(thread==null) { thread = new Thread(this); thread.start(); } } /* * (non-Javadoc) * @see java.applet.Applet#stop() * Vi skriver också över appletens stopmetod. * I den dödar vi tråden, genom att nulla den. */ public void stop() { thread = null; } /* * Metoden run anropas, när man startar tråden. * Jag har valt att lägga en whileloop i denna. * Whileloopen går runt, så länge som tråden inte * är lika med null, * I whileloopen sker en omritning av appleten via * anropet repaint. Sedan körs ett försök att vila * exekveringen med en statisk metod i klassen Thread, * som heter sleep(millisekunder). Javakompilatorn fordrar * att man felhanterar sovandet. */ public void run() { while(thread!=null) { repaint(); try { Thread.sleep(50); } catch(InterruptedException fel) { }; } } }Om du skulle pröva att exekvera appleten nu, så finns risken att du bli besviken, eftersom det inte händer mycket. Möjligen kan du se att det flimrar lite i appleten. Det är ett tecken på att du har gjort rätt, eftersom den ritar om.

Skapa en bild och hämta ett Graphics2Dobjekt
Strategin vi ska använda är följande. Vi ska skapa en bild, en instans av BufferedImage närmare bestämt. Sedan ska vi koppla ett grafiskt objekt av typen Graphics2D till denna.Orsaken till detta kallas för dubbelbuffring. Om man ritar på en bild, som så att säga ligger i minnet, och ritar ut bilden, när den är färdig, så förhindrar man omritningar innan pågående ritoperation är slutförd.
Eftersom vi ska börja varje ritoperation med att rita över appleten med en rektangel, så skapar vi också en instans av klass Rectangle2D, som är lika stor som bilden och appletens rityta.
import java.applet.Applet; import java.awt.Color; import java.awt.Font; import java.awt.FontMetrics; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.RenderingHints; import java.awt.geom.Rectangle2D; import java.awt.image.BufferedImage; /* Här låter vi klass MyApplet implementera Runnable */ public class MyApplet extends Applet implements Runnable { private Color text_color; private Color bakgrund; private String str; private Font f; private String familj; private int storlek; private int stil; private Thread thread; /* Nya objekt av klas BufferedImage * , Graphics2d och Rectangle2D. */ private BufferedImage bild; private Graphics2D g; Rectangle2D rect; public void init() { text_color = null; try { text_color = new Color(Integer.parseInt(getParameter("textfärg"),16)); } catch(Exception fel) { text_color = new Color(230,0,40); //Oj då något fel har uppstått! } bakgrund = null; try { bakgrund = new Color(Integer.parseInt(getParameter("bakgrundsfärg"),16)); } catch(Exception fel) { bakgrund = new Color(0,0,0); } str=null; try { str = getParameter("message"); } catch(Exception error) { str="AlesnaWebbSystem.se"; } if(str.length()==0 || str== null) str = str="AlesnaWebbSystem.se"; familj = null; try { familj = getParameter("teckensnitt"); } catch(Exception error) { familj = "Sans-Serif"; } if(familj.length()==0 || familj== null) familj ="Sans-Serif"; storlek = 0; try { storlek = Integer.parseInt(getParameter("fontstorlek")); } catch(Exception error) { storlek = 22; } if(storlek < 0) storlek = 22; String font_stil=null; try { font_stil = getParameter("font_stil").toUpperCase(); } catch(Exception error) { font_stil = "PLAIN"; } if(font_stil.length()==0 || font_stil== null) font_stil="PLAIN"; if(font_stil.equals("BOLD")) stil = Font.BOLD; if(font_stil.equals("PLAIN")) stil = Font.PLAIN; if(font_stil.equals("ITALIC")) stil = Font.ITALIC; f = new Font(familj,stil,storlek); /* Konstrueraren som tar bredd och höjd samt bildtyp i * BufferedImage anropas. Observera att bilden blir lika * stor som appleten. */ bild = new BufferedImage(getWidth(),getHeight(), BufferedImage.TYPE_INT_RGB); /* * Vi hämtar bildens grafiska objekt. Innebär * att vi kan rita på bilden. */ g = bild.createGraphics(); /* * Ställer in att bildens grafiska objekt * ska använda antialiasing, när den ritar. * Det ger mjukar övergångar och kanter. */ g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); /* * Vi skar rektangeln och ger den också samma mått som appleten */ rect=new Rectangle2D.Double(0,0,(double)getWidth(), (double)getHeight()); }Det händer fortfarande ingenting, om du skulle pröva att exekvera appleten. Men vi har gort nödvändiga förberedelse, för att kunna göra snygga ritningar i paintmetoden, vilket är nästa steg.
Nyheterna ligger i paintmetoden, variabellistan längst upp i klassen, den nyskriva updatemetoden samt i importerna,

Att rita med Graphics2D
I klassen Graphics2D finns en metod, som heter setPaint(Paint). Paint är ett interface, vilket är nästan samma sak som en abstrakt klass. Det innebär att man inte direkt kan skapa en instans av Paint, utan måste gå via en ärvande klass, en ganska vanlig javastrategi. Det finns en hel uppsättning av klasser, som ärver Paint. För att nämna två Color och GradientPaint.
GradientPaint innebär att man börjar med en färg och går gradvis över till en annan färg. Om man kunde förflytta övergångspunkten mellan omritningarna, så kommer man att uppnå effekten, svepande ljus, om man i övrigt använder färger, som inte motverkan effekten. Det är exakt denna strategi som jag använder nedan.
För att få till steg två i dubbelbuffringen, så skriver jag också över metoden update. Den ursprungliga updatemetoden rensar appletens rityta, innan den ritar om. Detta skapar oönskat flimmer.
Nyheterna ligger i paintmetoden, variabellistan längst upp i klassen, den nyskriva updatemetoden samt i importerna,
import java.applet.Applet; import java.awt.Color; import java.awt.Font; import java.awt.FontMetrics; import java.awt.GradientPaint; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.RenderingHints; import java.awt.geom.Rectangle2D; import java.awt.image.BufferedImage; public class MyApplet extends Applet implements Runnable { private Color text_color; private Color bakgrund; private String str; private Font f; private String familj; private int storlek; private int stil; private Thread thread=null; /************Nyheter******************/ private BufferedImage bild; private Graphics2D g; Rectangle2D rect; /* Här nedan två nya variabler, vars syfte * är att förflytta skärningspunkten för * färgövergången i GradientPaint */ float punkt=50; float faktor=4.5f; public void init() { text_color = null; try { text_color = new Color(Integer.parseInt(getParameter("textfärg"),16)); } catch(Exception fel) { text_color = new Color(230,0,40); //Oj då något fel har uppstått! } bakgrund = null; try { bakgrund = new Color(Integer.parseInt(getParameter("bakgrundsfärg"),16)); } catch(Exception fel) { bakgrund = new Color(0,0,0); } str=null; try { str = getParameter("message"); } catch(Exception error) { str="AlesnaWebbSystem.se"; } if(str.length()==0 || str== null) str = str="AlesnaWebbSystem.se"; familj = null; try { familj = getParameter("teckensnitt"); } catch(Exception error) { familj = "Sans-Serif"; } if(familj.length()==0 || familj== null) familj ="Sans-Serif"; storlek = 0; try { storlek = Integer.parseInt(getParameter("fontstorlek")); } catch(Exception error) { storlek = 22; } if(storlek < 0) storlek = 22; /* * Nästa steg är att läsa in önskad stil på * fonten. */ String font_stil=null; try { font_stil = getParameter("font_stil").toUpperCase(); } catch(Exception error) { font_stil = "PLAIN"; } if(font_stil.length()==0 || font_stil== null) font_stil="PLAIN"; if(font_stil.equals("BOLD")) stil = Font.BOLD; if(font_stil.equals("PLAIN")) stil = Font.PLAIN; if(font_stil.equals("ITALIC")) stil = Font.ITALIC; f = new Font(familj,stil,storlek); bild = new BufferedImage(getWidth(),getHeight(), BufferedImage.TYPE_INT_RGB); g = bild.createGraphics(); g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); rect=new Rectangle2D.Double(0,0,(double)getWidth(),(double)getHeight()); } /* * (non-Javadoc) * @see java.awt.Container#paint(java.awt.Graphics) * I princip kan man säga att innehållet i paintmetoden är * helt nytt, så skriv om allting för säkerhets skulle */ public void paint(Graphics page) { /* * Som du kan se, så är det g, som utför * ritoperationerna och inte page som förut. * Börjar med att sätta färgen för bakgrunden samt * fonten. Sedan målas bakgrunden. */ g.setPaint(bakgrund); g.setFont(f); FontMetrics fm = g.getFontMetrics(); int x = (getWidth()/2)- (int) fm.stringWidth(str)/2; int y = (getHeight()/2)+ (fm.getHeight()/2); g.fill(rect); /*GradientPaints konstruktor finns i ett flertal varianter. * Man kan se av argumenten, att den börjar med en färg och * övergår i en annan. Variabeln punkt används för att flytta * övergångspunkten mellan omritningarna */ GradientPaint grp = new GradientPaint(punkt-40,0,text_color,punkt,getHeight()/2, new Color(0,0,0),false); g.setPaint(grp); g.drawString(str,x,y); /* * Sist men inte minst , så ritar page ut den * färdiga bilden. */ page.drawImage(bild,0,0,this); /* * Punkts värde räknas upp med faktor. Om punkts * värde blir mindre eller lika med 40 eller * om punkt blir större eller lika med appletens * bredd+ 20% av appletens bredd, så vänds värdet * på faktor till positivt respektive negativt. */ punkt+=faktor; if(punkt >= getWidth()* 1.20 || punkt <=40 ) faktor*=-1; } /* * (non-Javadoc) * @see java.awt.Container#update(java.awt.Graphics) * Vi skriver över appletens updatemetod. Den ursprungliga * rensar appletens visningsyta innan den ritar om, vilket * skapar flicker i animationen. Detta är steg två i * dubbelbuffringen. */ public void update(Graphics g) { paint(g); } public void start() { if(thread==null) { thread = new Thread(this); thread.start(); } } public void stop() { thread=null; } public void run() { while(thread!=null) { repaint(); try { Thread.sleep(50); } catch(InterruptedException fel) { }; } } }Vi ska väl också presentera html-koden.
<html> <body> <applet code="MyApplet" width="300" height="100"> <param name="teckensnitt" value="sans-serif"><!-- ny parameter--!> <param name="fontstorlek" value="22"><!-- ny parameter--!> <param name="font_stil" value="italic"><!-- ny parameter--!> <param name="message" value="Morgan is cool!"> <parame name="textfärg" value="0000AC"> <param name="bakgrundsfärg" value="002121"> </applet> </body> </html>

Det färdiga resultatet
Jag hoppas att genomgången ska ha givit ökad förståelse för javaprogrammering i allmänhet, applets och animeringar i synnerhet. Naturligvis ska du ge dig på att försöka ändra saker och ting i appleten, vilket dels är kul, dels är lärorikt.När det gäller själva processhanteringen och dubbelbuffringen, så kan den lösningen fungera i de flesta animeringar, varför den koden kan vara bra att ha längre fram.