ABC'erne til Android-spiludvikling: Animer sprites

Hvis du har fulgt denne serie om Android-spiludvikling, ved du, at vi nu har et spilsæt og vores sprites på plads. Det næste trin er at animere vores sprites omkring spilbrættet. (Målet med vores spil er at justere tallerkenens fart for at undgå at blive smadret til biter af asteroiden. Mens det er forenklet, indeholder projektet alle de væsentlige aspekter af et videospil: et lærred, sprites, animation, kollisionsdetektion og brugerinput.)

Animation er et af de vigtigste aspekter af et videospil. Generelt er der to slags sprite-animation: Den første type flytter billedet fra et sted til det næste, mens den anden type resulterer i den næste ramme i en række af animationer, der afspilles.

At flytte en sprite er let at konceptualisere, fordi sprite fysisk transporteres fra et sted på skærmen til et andet. Den anden type animation er lidt mindre ligetil. Da du var barn, tegnet du nogensinde en række doodles på hjørnet af hver side på en notesbog, og bla derefter gennem siderne med tommelfingeren for at få doodle til at se ud som om den levede? Hvis det er tilfældet, er det, hvad vi prøver at gøre, når vi animerer rammerne inden for en sprite - tilføj vores spilgenstande noget liv.

Nogle sprites i spil bruger kun en type animation, mens andre muligvis bruger begge. I vores spil vil vi bruge den første type animation til vores UFO, og den første og anden type animation til vores asteroide. Effekten skal se ud som om asteroiden bevæger sig og roterer.

Da dette er en demo, og vi ønsker at forklare konceptet på de mest grundlæggende mulige vilkår, implementerer vi ikke nogen fysik eller tyngdekraft i vores spil. Skibet bevæger sig med en konstant hastighed (for nu), mens asteroiden bevæger sig med en tilfældig hastighed, som initialiseres, hver gang der trykkes på nulstillingsknappen. Hvis en af ​​vores sprites rammer grænserne for vores lærred, vil vi vende x og y, hvilket i det væsentlige får det til at "hoppe" tilbage til spil ( figur A ). Figur A

Illustration af to forskellige typer sprite-animation.

Animerende sprites

Denne tutorial bygger på det, vi oprettede i del to. For virkelig at forstå, hvad der foregår, hjælper det med at se koden i sammenhæng med helheden. Derfor vil kodelisten i tutorial være vores komplette arbejdsgrundlag, med den nye kode kommenteret inline. Du kan følge med trinvis vejledning eller downloade og importere hele projektet til Eclipse.

1. Opret et nyt Android-projekt i Eclipse. Mål Android 2.1 eller nyere. Sørg for at omdøbe startaktiviteten Main.java og det tilsvarende layout til main.java.

2. Selvom der ikke er nogen ændringer i vores manifestfil eller layout siden del to, har jeg inkluderet begge nedenfor for at være komplette.

AndroidManifest.xml

 "Http://schemas.android.com/apk/res/android" 
 package = "com.authorwjf.gamedevtut03" 
 android: versionCode = "1" 
 android: versionName = "1.0" > 
 android: minSdkVersion = "7" 
 android: targetSdkVersion = "15" /> 
 android: icon = "@ drawable / ic_launcher" 
 android: label = "@ streng / app_navn" 
 android: theme = "@ style / AppTheme" > 
 android: name = ".Main" 
 android: label = "@ string / title_activity_main" 
 android: screenOrientation = "portræt" 
 android: configChanges = "orientering | keyboardHidden" > 
 "android.intent.action.MAIN" /> 
 "android.intent.category.LAUNCHER" /> 

main.xml

 "Http://schemas.android.com/apk/res/android" 
 android: layout_width = "fill_parent" 
 android: layout_height = "fill_parent" 
 android: orientering = "lodret" > 
 android: layout_width = "wrap_content" 
 android: layout_height = "wrap_content" 
 android: layout_gravity = "top | center" 
 android: text = "ABC'er fra Android Game Dev" /> 
 android: id = "@ + id / the_button" 
 android: layout_width = "wrap_content" 
 android: layout_height = "wrap_content" 
 android: layout_gravity = "center" 
 android: tyngdekraft = "center" 
 android: aktiveret = "falsk" 
 android: text = "Nulstil" /> 
 android: layout_width = "wrap_content" 
 android: layout_height = "wrap_content" 
 android: layout_gravity = "center" 
 android: text = "Sprite Speed ​​(?, ?)" 
 android: id = "@ + id / the_label" /> 
 android: layout_width = "wrap_content" 
 android: layout_height = "wrap_content" 
 android: layout_gravity = "center" 
 android: text = "Sidste kollision XY (?, ?)" 
 android: id = "@ + id / the_other_label" /> 
 android: layout_width = "fill_parent" 
 android: layout_height = "fill_parent" 
 android: layout_margin = "20dip" 
 android: id = "@ + id / the_canvas" /> 

3. Brug to billeder i vores / res / drawable mappe. Disse er begge vist nedenfor.

4. Foretag et par ændringer i GameBoard-klassen, der findes i pakken com.authorwjf.drawing. Ændringerne kommenteres inline nedenfor og består af et par nøgleelementer. Først har jeg tilføjet nye private variabler; dette er, så vi kan indstille grænser for vores billeder i klassen Main.java. Udover at tilføje nye getter / seters, ændrede jeg også nogle af de eksisterende accessorer til at arbejde med primitiver i stedet for Java's Point-klasse; Dette er for at sikre, at vores sprites ikke ved en fejltagelse sendes ved henvisning og manipuleres direkte i vores controller. Dette kan omgå vores synkronisering. Endelig tjek on-draw-tilsidesættelsen. Vi bruger nu en matrix til at udføre en rotation på asteroiden; dette udfører vores spinnende animation ganske let.

GameBoard.java

 pakke com.authorwjf.drawing; 
 import java.util.ArrayList; 
 import java.util.List; 
 import java.util.Random; 
 import com.authorwjf.gamedevtut03.R; 
 import android.content.Context; 
 import android.graphics.Bitmap; 
 import android.graphics.BitmapFactory; 
 import android.graphics.Canvas; 
 import android.graphics.Color; 
 import android.graphics.Matrix; 
 import android.graphics.Paint; 
 import android.graphics.Point; 
 import android.graphics.Rect; 
 import android.util.AttributeSet; 
 import android.view.View; 
 offentlig klasse GameBoard udvider Vis { 
 privat maling; 
 privat liste starField = null ; 
 privat int starAlpha = 80; 
 privat int starFade = 2; 
 // Tilføj private variabler for at holde trit med sprite position og størrelse 
 privat Rect sprite1Bounds = ny Rect (0, 0, 0, 0); 
 privat Rect sprite2Bounds = ny Rect (0, 0, 0, 0); 
 privat punkt sprite1; 
 privat punkt sprite2; 
 // Bitmaps, der indeholder de faktiske sprite-billeder 
 privat Bitmap bm1 = null ; 
 privat matrix m = null ; 
 privat Bitmap bm2 = null ; 
 privat int sprite1Rotation = 0; 
 privat statisk endelig int NUM_OF_STARS = 25; 
 // Lad vores controller få og indstille sprite-positioner 
 // sprite 1 setter 
 synkroniseret offentligt annulleret sætSprite1 ( int x, int y) { 
 sprite1 = nyt punkt (x, y); 
 } 
 // sprite 1 getter 
 synkroniseret offentlig int getSprite1X () { 
 retur sprite1.x; 
 } 
 synkroniseret offentlig int getSprite1Y () { 
 returnere sprite1.y; 
 } 
 // sprite 2 setter 
 synkroniseret offentligt tomrum setSprite2 ( int x, int y) { 
 sprite2 = nyt punkt (x, y); 
 } 
 // sprite 2 getter 
 synkroniseret offentlig int getSprite2X () { 
 retur sprite2.x; 
 } 
 synkroniseret offentlig int getSprite2Y () { 
 returnere sprite2.y; 
 } 
 synkroniseret offentligt tomrom resetStarField () { 
 starField = null ; 
 } 
 // udsæt sprite-grænser for controller 
 synkroniseret offentlig int getSprite1Width () { 
 returnere sprite1Bounds.width (); 
 } 
 synkroniseret offentlig int getSprite1Height () { 
 retur sprite1Bounds.height (); 
 } 
 synkroniseret offentlig int getSprite2Width () { 
 returnere sprite2Bounds.width (); 
 } 
 synkroniseret offentlig int getSprite2Height () { 
 returnere sprite2Bounds.height (); 
 } 
 public GameBoard (Kontekstkontekst, AttributeSet aSet) { 
 super (kontekst, aSet); 
 p = ny maling (); 
 // indlæse vores bitmaps og indstil grænserne for controlleren 
 sprite1 = nyt punkt (-1, -1); 
 sprite2 = nyt punkt (-1, -1); 
 // Definer en matrix, så vi kan rotere asteroiden 
 m = ny matrix (); 
 p = ny maling (); 
 bm1 = BitmapFactory. dekodeResource (getResources (), R. udtrækkelig. asteroide ); 
 bm2 = BitmapFactory. decodeResource (getResources (), R.drawable. ufo ); 
 sprite1Bounds = ny Rect (0, 0, bm1.getWidth (), bm1.getHeight ()); 
 sprite2Bounds = ny Rect (0, 0, bm2.getWidth (), bm2.getHeight ()); 
 } 
 private void initializeStars ( int maxX, int maxY) { 
 starField = ny ArrayList (); 
 for ( int i = 0; i < NUM_OF_STARS ; i ++) { 
 Tilfældig r = ny Tilfældig (); 
 int x = r.nextInt (maxX-5 + 1) +5; 
 int y = r.nextInt (maxY-5 + 1) +5; 
 starField.add ( nyt punkt (x, y)); 
 } 
 } 
 @Override 
 synkroniseret offentligt tomrum onDraw (lærred lærred) { 
 p.setColor (farve. SORT ); 
 p.setAlpha (255); 
 p.setStrokeWidth (1); 
 canvas.drawRect (0, 0, getWidth (), getHeight (), p); 
 if (starField == null ) { 
 initialisereStars (canvas.getWidth (), canvas.getHeight ()); 
 } 
 p.setColor (farve. CYAN ); 
 p.setAlpha (starAlpha + = starFade); 
 if (starAlpha> = 252 || starAlpha <= 80) starFade = starFade * -1; 
 p.setStrokeWidth (5); 
 for ( int i = 0; i < NUM_OF_STARS ; i ++) { 
 canvas.drawPoint (starField.get (i) .x, starField.get (i) .y, p); 
 } 
 // Nu tegner vi vores sprites. Elementer, der tegnes i denne funktion, stables. 
 // Elementerne, der er trukket øverst på løkken, er i bunden af ​​z-ordren. 
 // Derfor tegner vi vores sæt, så vores skuespillere og til sidst enhver fx. 
 if (sprite1.x> = 0) { 
 m.reset (); 
 m.postTranslate (( float ) (sprite1.x), ( float ) (sprite1.y)); 
 m.postRotate (sprite1Rotation, 
 ( float ) (sprite1.x + sprite1Bounds.width () / 2.0), 
 ( float ) (sprite1.y + sprite1Bounds.width () / 2.0)); 
 canvas.drawBitmap (bm1, m, null ); 
 sprite1Rotation + = 5; 
 if (sprite1Rotation> = 360) sprite1Rotation = 0; 
 } 
 if (sprite2.x> = 0) { 
 canvas.drawBitmap (bm2, sprite2.x, sprite2.y, null ); 
 } 
 } 
 } 

4. Med vores spillebræt, der opdaterer sig selv, kan vi flytte til filen /src/Main.java og sætte alt i gang. Siden del to har vi tilføjet kode til at indstille vores spritehastighed og til at beregne den nye position med hvert opkald tilbage for at opdatere lærredet. Hvis det viser sig, at den nye xy-koordinat for en sprite vil placere den fra skærmen, multiplicerer vi hastigheden for den akse med negativ en, hvilket får objektet til at hoppe.

Main.java

 pakke com.authorwjf.gamedevtut03; 
 import java.util.Random; 
 import com.authorwjf.drawing.GameBoard; 
 import android.os.Bundle; 
 import android.os.Handler; 
 import android.view.View; 
 import android.view.View.OnClickListener; 
 import android.widget.Button; 
 import android.app.Aktivitet; 
 import android.graphics.Point; 
 public class Main udvider Aktivitetsimplementeringer OnClickListener { 
 privat håndtereramme = ny håndterer (); 
 // Hastighed inkluderer hastigheden og retningen for vores sprite bevægelse 
 private Point sprite1Velocity; 
 private Point sprite2Velocity; 
 privat int sprite1MaxX; 
 privat int sprite1MaxY; 
 privat int sprite2MaxX; 
 privat int sprite2MaxY; 
 // Del rammen med 1000 for at beregne, hvor mange gange i sekundet skærmen opdateres. 
 privat statisk endelig int FRAME_RATE = 20; // 50 billeder i sekundet 
 @Override 
 offentligt tomrum onCreate (Bundle gemtInstanceState) { 
 super .onCreate (gemtInstanceState); 
 setContentView (R.layout. main ); 
 Håndterer h = ny Håndterer (); 
 ((Knap) findViewById (R.id. The_button )). SetOnClickListener ( dette ); 
 // Vi kan ikke initialisere grafikken med det samme, fordi layoutadministratoren 
 // har brug for at køre først, så ring tilbage på et sekund. 
 h.postDelayed ( nyt Runnable () { 
 @Override 
 offentlig annullering () { 
 initGfx (); 
 } 
 }, 1000); 
 } 
 private Point getRandomVelocity () { 
 Tilfældig r = ny Tilfældig (); 
 int min = 1; 
 int max = 5; 
 int x = r.nextInt (max-min + 1) + min; 
 int y = r.nextInt (max-min + 1) + min; 
 returnere nyt punkt (x, y); 
 } 
 private Point getRandomPoint () { 
 Tilfældig r = ny Tilfældig (); 
 int minX = 0; 
 int maxX = findViewById (R.id. the_canvas ) .getWidth () - 
 ((GameBoard) findViewById (R.id. The_canvas )). GetSprite1Width (); 
 int x = 0; 
 int minY = 0; 
 int maxY = findViewById (R.id. the_canvas ) .getHeight () - 
 ((GameBoard) findViewById (R.id. The_canvas )). GetSprite1Height (); 
 int y = 0; 
 x = r.nextInt (maxX-minX + 1) + minX; 
 y = r.nextInt (maxY-minY + 1) + minY; 
 returnere nyt punkt (x, y); 
 } 
 synkroniseret offentligt tomrum initGfx () { 
 ((GameBoard) findViewById (R.id. The_canvas )). ResetStarField (); 
 // Vælg to tilfældige punkter til vores indledende sprite-placering. 
 // Løkken er bare for at sikre, at vi ikke ved et uheld vælger 
 // to punkter, der overlapper hinanden. 
 Punkt p1, p2; 
 gør { 
 p1 = getRandomPoint (); 
 p2 = getRandomPoint (); 
 } while (Math. abs (p1.x - p2.x) < 
 ((GameBoard) findViewById (R.id. The_canvas )). GetSprite1Width ()); 
 ((GameBoard) findViewById (R.id. The_canvas )). SetSprite1 (p1.x, p1.y); 
 ((GameBoard) findViewById (R.id. The_canvas )). SetSprite2 (p2.x, p2.y); 
 // Giv asteroiden en tilfældig hastighed 
 sprite1Velocity = getRandomVelocity (); 
 // Fastgør skibets hastighed med en konstant hastighed for nu 
 sprite2Velocity = nyt punkt (1, 1); 
 // Angiv vores grænser for sprites 
 sprite1MaxX = findViewById (R.id. the_canvas ) .getWidth () - 
 ((GameBoard) findViewById (R.id. The_canvas )). GetSprite1Width (); 
 sprite1MaxY = findViewById (R.id. the_canvas ) .getHeight () - 
 ((GameBoard) findViewById (R.id. The_canvas )). GetSprite1Height (); 
 sprite2MaxX = findViewById (R.id. the_canvas ) .getWidth () - 
 ((GameBoard) findViewById (R.id. The_canvas )). GetSprite2Width (); 
 sprite2MaxY = findViewById (R.id. the_canvas ) .getHeight () - 
 ((GameBoard) findViewById (R.id. The_canvas )). GetSprite2Height (); 
 ((Knap) findViewById (R.id. The_button )). SetEnabled ( sand ); 
 frame.removeCallbacks (frameUpdate); 
 frame.postDelayed (frameUpdate, FRAME_RATE ); 
 } 
 @Override 
 synkroniseret offentligt tomrum onClick (Vis v) { 
 initGfx (); 
 } 
 private Runnable frameUpdate = new Runnable () { 
 @Override 
 synkroniseret offentligt tomrumsløb () { 
 frame.removeCallbacks (frameUpdate); 
 // Få først de nuværende positioner for begge sprites 
 Punkt sprite1 = nyt punkt 
 (((GameBoard) findViewById (R.id. The_canvas )). GetSprite1X (), 
 ((GameBoard) findViewById (R.id. The_canvas )). GetSprite1Y ()); 
 Punkt sprite2 = nyt punkt 
 (((GameBoard) findViewById (R.id. The_canvas )). GetSprite2X (), 
 ((GameBoard) findViewById (R.id. The_canvas )). GetSprite2Y ()); 
 // Beregn nu de nye positioner. 
 // Bemærk, hvis vi overskrider en grænse, bliver hastighedsretningen vendt. 
 sprite1.x = sprite1.x + sprite1Velocity.x; 
 if (sprite1.x> sprite1MaxX || sprite1.x <5) { 
 sprite1Velocity.x * = -1; 
 } 
 sprite1.y = sprite1.y + sprite1Velocity.y; 
 if (sprite1.y> sprite1MaxY || sprite1.y <5) { 
 sprite1Velocity.y * = -1; 
 } 
 sprite2.x = sprite2.x + sprite2Velocity.x; 
 if (sprite2.x> sprite2MaxX || sprite2.x <5) { 
 sprite2Velocity.x * = -1; 
 } 
 sprite2.y = sprite2.y + sprite2Velocity.y; 
 if (sprite2.y> sprite2MaxY || sprite2.y <5) { 
 sprite2Velocity.y * = -1; 
 } 
 ((GameBoard) findViewById (R.id. The_canvas )). SetSprite1 (sprite1.x, 
 sprite1.y); 
 ((GameBoard) findViewById (R.id. The_canvas )). SetSprite2 (sprite2.x, sprite2.y); 
 ((GameBoard) findViewById (R.id. The_canvas )). Ugyldig (); 
 frame.postDelayed (frameUpdate, FRAME_RATE ); 
 } 
 }; 
 } 

Endnu en gang har vi vores spil på et stabilt punkt. Bedre endnu, på dette tidspunkt, når det køres på en enhed eller en emulator, begynder vores kode faktisk at ligne et videospil. Gå foran og se selv!

Bemærk, hvordan asteroiden passerer lige gennem skibet? Det er et problem. For ikke at nævne det ville være rart, hvis du havde en vis kontrol over tallerkenen og den lille fremmede i førersædet. Vi vil håndtere begge i de sidste to dele af denne spiludviklingsserie, så sørg for at tjekke det ud.

© Copyright 2021 | pepebotifarra.com