Das ABC der Android-Spieleentwicklung: Sprites animieren

Wenn Sie diese Serie über die Entwicklung von Android-Spielen verfolgt haben, wissen Sie, dass wir jetzt ein Spielset und unsere Sprites haben. Der nächste Schritt besteht darin, unsere Sprites auf dem Spielbrett zu animieren. (Das Ziel unseres Spiels ist es, den Schwung der Untertasse anzupassen, um zu vermeiden, dass der Asteroid sie in Stücke zerschmettert. Das Projekt ist zwar simpel, enthält jedoch alle wesentlichen Aspekte eines Videospiels: Leinwand, Sprites, Animation, Kollisionserkennung und Benutzereingabe.)

Animation ist einer der wichtigsten Aspekte eines Videospiels. Im Allgemeinen gibt es zwei Arten von Sprite-Animationen: Der erste Typ verschiebt das Bild von einem Ort zum nächsten, während der zweite Typ das nächste Bild in einer Folge von abgespielten Animationen ergibt.

Das Verschieben eines Sprites ist einfach zu konzipieren, da das Sprite physisch von einer Stelle auf dem Bildschirm zu einer anderen transportiert wird. Die zweite Art der Animation ist etwas weniger einfach. Haben Sie als Kind jemals eine Reihe von Kritzeleien an die Ecke jeder Seite eines Notizbuchs gezeichnet und dann mit dem Daumen durch die Seiten geblättert, damit das Kritzeleien so aussieht, als wäre es lebendig? Wenn ja, versuchen wir dies zu tun, wenn wir die Frames innerhalb eines Sprites animieren - fügen Sie unseren Spielobjekten etwas Leben hinzu.

Einige Sprites in Spielen verwenden nur eine Art von Animation, während andere möglicherweise beide verwenden. In unserem Spiel werden wir die erste Art von Animation für unser UFO und die erste und zweite Art von Animation für unseren Asteroiden verwenden. Der Effekt sollte so aussehen, als würde sich der Asteroid bewegen und drehen.

Da dies eine Demo ist und wir das Konzept so einfach wie möglich erklären möchten, werden wir keine Physik oder Schwerkraft in unser Spiel implementieren. Das Schiff bewegt sich (vorerst) mit einer konstanten Geschwindigkeit, während sich der Asteroid mit einer zufälligen Geschwindigkeit bewegt, die bei jedem Drücken der Reset-Taste initialisiert wird. Wenn einer unserer Sprites die Grenzen unserer Leinwand erreicht, kehren wir x und y um, sodass er im Wesentlichen wieder ins Spiel zurückspringt ( Abbildung A ). Abbildung A.

Illustration von zwei verschiedenen Arten von Sprite-Animationen.

Sprites animieren

Dieses Tutorial baut auf dem auf, was wir im zweiten Teil erstellt haben. Um jedoch wirklich zu verstehen, was vor sich geht, ist es hilfreich, den Code im Kontext des Ganzen zu sehen. Daher wird die Codeliste im Tutorial unsere vollständige Arbeitsbasis sein, wobei der neue Code inline kommentiert wird. Sie können den schrittweisen Anweisungen folgen oder das gesamte Projekt herunterladen und in Eclipse importieren.

1. Erstellen Sie ein neues Android-Projekt in Eclipse. Ziel Android 2.1 oder höher. Stellen Sie sicher, dass Sie die Startaktivität Main.java und das entsprechende Layout in main.java umbenennen.

2. Obwohl seit dem zweiten Teil keine Änderungen an unserer Manifestdatei oder unserem Layout vorgenommen wurden, habe ich der Vollständigkeit halber beide unten aufgeführt.

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 = "@ string / app_name" 
 android: theme = "@ style / AppTheme" > 
 android: name = ".Main" 
 android: label = "@ string / title_activity_main" 
 android: screenOrientation = "Porträt" 
 android: configChanges = "Ausrichtung | Tastatur versteckt " > 
 "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: orientierung = "vertikal" > 
 android: layout_width = "wrap_content" 
 android: layout_height = "wrap_content" 
 android: layout_gravity = "top | center" 
 android: text = "ABC's von Android Game Dev" /> 
 android: id = "@ + id / the_button" 
 android: layout_width = "wrap_content" 
 android: layout_height = "wrap_content" 
 android: layout_gravity = "center" 
 Android: Schwerkraft = "Zentrum" 
 android: enabled = "false" 
 android: text = "Zurücksetzen" /> 
 android: layout_width = "wrap_content" 
 android: layout_height = "wrap_content" 
 android: layout_gravity = "center" 
 android: text = "Sprite-Geschwindigkeit (?, ?)" 
 android: id = "@ + id / the_label" /> 
 android: layout_width = "wrap_content" 
 android: layout_height = "wrap_content" 
 android: layout_gravity = "center" 
 android: text = "Letzte 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. Verwenden Sie zwei Bilder in unserem Ordner / res / drawable. Diese sind beide unten gezeigt.

4. Nehmen Sie einige Änderungen an der GameBoard-Klasse vor, die im Paket com.authorwjf.drawing vorhanden ist. Die Änderungen werden unten inline kommentiert und bestehen aus einigen Schlüsselelementen. Zuerst habe ich neue private Variablen hinzugefügt; Auf diese Weise können wir Grenzen für unsere Bilder in der Main.java-Klasse festlegen. Neben dem Hinzufügen neuer Getter / Setter habe ich auch einige der vorhandenen Accessoren so geändert, dass sie mit Primitiven anstelle der Point-Klasse von Java arbeiten. Dies soll sicherstellen, dass unsere Sprites nicht versehentlich als Referenz übergeben und direkt in unserem Controller manipuliert werden. Dies könnte unsere Synchronisation umgehen. Überprüfen Sie abschließend die Überschreibung beim Ziehen. Wir verwenden jetzt eine Matrix, um eine Rotation des Asteroiden durchzuführen. Dadurch wird unsere Spinnanimation ganz einfach ausgeführt.

GameBoard.java

 Paket com.authorwjf.drawing; 
 import java.util.ArrayList; 
 import java.util.List; 
 import java.util.Random; 
 import com.authorwjf.gamedevtut03.R; 
 android.content.Context importieren ; 
 android.graphics.Bitmap importieren ; 
 android.graphics.BitmapFactory importieren ; 
 android.graphics.Canvas importieren ; 
 android.graphics.Color importieren ; 
 android.graphics.Matrix importieren ; 
 android.graphics.Paint importieren ; 
 android.graphics.Point importieren ; 
 android.graphics.Rect importieren ; 
 import android.util.AttributeSet; 
 android.view.View importieren ; 
 public class GameBoard erweitert View { 
 Privatfarbe p; 
 private Liste starField = null ; 
 private int starAlpha = 80; 
 private int starFade = 2; 
 // Fügen Sie private Variablen hinzu, um mit der Position und Größe des Sprites Schritt zu halten 
 private Rect sprite1Bounds = new Rect (0, 0, 0, 0); 
 private Rect sprite2Bounds = new Rect (0, 0, 0, 0); 
 privater Punkt sprite1; 
 privater Punkt sprite2; 
 // Bitmaps, die die tatsächlichen Sprite-Bilder enthalten 
 private Bitmap bm1 = null ; 
 private Matrix m = null ; 
 private Bitmap bm2 = null ; 
 private int sprite1Rotation = 0; 
 private static final int NUM_OF_STARS = 25; 
 // Erlaube unserem Controller, die Sprite-Positionen abzurufen und einzustellen 
 // Sprite 1 Setter 
 synchronisierte öffentliche Leere setSprite1 ( int x, int y) { 
 sprite1 = neuer Punkt (x, y); 
 }} 
 // Sprite 1 Getter 
 synchronized public int getSprite1X () { 
 return sprite1.x; 
 }} 
 synchronized public int getSprite1Y () { 
 return sprite1.y; 
 }} 
 // Sprite 2 Setter 
 synchronisierte öffentliche Leere setSprite2 ( int x, int y) { 
 sprite2 = neuer Punkt (x, y); 
 }} 
 // Sprite 2 Getter 
 synchronisierte öffentliche int getSprite2X () { 
 return sprite2.x; 
 }} 
 synchronized public int getSprite2Y () { 
 return sprite2.y; 
 }} 
 synchronisierte öffentliche Leere resetStarField () { 
 starField = null ; 
 }} 
 // Sprite-Grenzen dem Controller aussetzen 
 synchronisierte öffentliche int getSprite1Width () { 
 return sprite1Bounds.width (); 
 }} 
 synchronized public int getSprite1Height () { 
 return sprite1Bounds.height (); 
 }} 
 synchronisierte öffentliche int getSprite2Width () { 
 return sprite2Bounds.width (); 
 }} 
 synchronized public int getSprite2Height () { 
 return sprite2Bounds.height (); 
 }} 
 öffentliches GameBoard (Kontextkontext, AttributeSet aSet) { 
 super (Kontext, aSet); 
 p = neue Farbe (); 
 // lade unsere Bitmaps und setze die Grenzen für den Controller 
 sprite1 = neuer Punkt (-1, -1); 
 sprite2 = neuer Punkt (-1, -1); 
 // Definiere eine Matrix, damit wir den Asteroiden drehen können 
 m = neue Matrix (); 
 p = neue Farbe (); 
 bm1 = BitmapFactory. decodeResource (getResources (), R.drawable. asteroid ); 
 bm2 = BitmapFactory. decodeResource (getResources (), R.drawable. ufo ); 
 sprite1Bounds = new Rect (0, 0, bm1.getWidth (), bm1.getHeight ()); 
 sprite2Bounds = new Rect (0, 0, bm2.getWidth (), bm2.getHeight ()); 
 }} 
 private void initializeStars ( int maxX, int maxY) { 
 starField = new ArrayList (); 
 für ( int i = 0; i < NUM_OF_STARS ; i ++) { 
 Random r = new Random (); 
 int x = r.nextInt (maxX-5 + 1) +5; 
 int y = r.nextInt (maxY-5 + 1) +5; 
 starField.add ( neuer Punkt (x, y)); 
 }} 
 }} 
 @Override 
 synchronisierte öffentliche Leere onDraw (Canvas Canvas) { 
 p.setColor (Farbe. SCHWARZ ); 
 p.setAlpha (255); 
 p.setStrokeWidth (1); 
 canvas.drawRect (0, 0, getWidth (), getHeight (), p); 
 if (starField == null ) { 
 initializeStars (canvas.getWidth (), canvas.getHeight ()); 
 }} 
 p.setColor (Farbe. CYAN ); 
 p.setAlpha (starAlpha + = starFade); 
 if (starAlpha> = 252 || starAlpha <= 80) starFade = starFade * -1; 
 p.setStrokeWidth (5); 
 für ( int i = 0; i < NUM_OF_STARS ; i ++) { 
 canvas.drawPoint (starField.get (i) .x, starField.get (i) .y, p); 
 }} 
 // Jetzt zeichnen wir unsere Sprites. In dieser Funktion gezeichnete Elemente werden gestapelt. 
 // Die am oberen Rand der Schleife gezeichneten Elemente befinden sich am unteren Rand der Z-Reihenfolge. 
 // Deshalb zeichnen wir unser Set, dann unsere Schauspieler und schließlich jeden 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. Wenn sich unser Spielbrett selbst aktualisiert, können wir in die Datei /src/Main.java wechseln und alles in Bewegung setzen. Seit Teil zwei haben wir Code hinzugefügt, um unsere Sprite-Geschwindigkeit einzurichten und die neue Position bei jedem Rückruf zu berechnen, um die Zeichenfläche zu aktualisieren. Wenn sich herausstellt, dass die neue xy-Koordinate eines Sprites es vom Bildschirm entfernt, multiplizieren wir die Geschwindigkeit für diese Achse mit der negativen, wodurch das Objekt abprallt.

Main.java

 Paket com.authorwjf.gamedevtut03; 
 import java.util.Random; 
 import com.authorwjf.drawing.GameBoard; 
 import android.os.Bundle; 
 import android.os.Handler; 
 android.view.View importieren ; 
 android.view.View.OnClickListener importieren ; 
 android.widget.Button importieren ; 
 android.app.Activity importieren ; 
 android.graphics.Point importieren ; 
 public class Main erweitert Activity implementiert OnClickListener { 
 privater Handler-Frame = neuer Handler (); 
 // Geschwindigkeit beinhaltet die Geschwindigkeit und die Richtung unserer Sprite-Bewegung 
 private Point sprite1Velocity; 
 privater Punkt sprite2Velocity; 
 private int sprite1MaxX; 
 private int sprite1MaxY; 
 private int sprite2MaxX; 
 private int sprite2MaxY; 
 // Teilen Sie den Frame durch 1000, um zu berechnen, wie oft pro Sekunde der Bildschirm aktualisiert wird. 
 private static final int FRAME_RATE = 20; // 50 Bilder pro Sekunde 
 @Override 
 public void onCreate (Bundle savedInstanceState) { 
 super .onCreate (savedInstanceState); 
 setContentView (R.layout. main ); 
 Handler h = neuer Handler (); 
 ((Button) findViewById (R.id. the_button )). SetOnClickListener ( this ); 
 // Wir können die Grafiken nicht sofort initialisieren, da der Layout-Manager 
 // muss zuerst ausgeführt werden, also rufe in einer Sekunde zurück. 
 h.postDelayed ( new Runnable () { 
 @Override 
 public void run () { 
 initGfx (); 
 }} 
 }, 1000); 
 }} 
 privater Punkt getRandomVelocity () { 
 Random r = new Random (); 
 int min = 1; 
 int max = 5; 
 int x = r.nextInt (max-min + 1) + min; 
 int y = r.nextInt (max-min + 1) + min; 
 neuen Punkt zurückgeben (x, y); 
 }} 
 privater Punkt getRandomPoint () { 
 Random r = new Random (); 
 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; 
 neuen Punkt zurückgeben (x, y); 
 }} 
 synchronisierte öffentliche Leere initGfx () { 
 ((GameBoard) findViewById (R.id. the_canvas )). ResetStarField (); 
 // Wähle zwei zufällige Punkte für unsere anfängliche Sprite-Platzierung aus. 
 // Die Schleife soll nur sicherstellen, dass wir nicht versehentlich auswählen 
 // zwei Punkte, die sich überlappen. 
 Punkt p1, p2; 
 do { 
 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); 
 // Gib dem Asteroiden eine zufällige Geschwindigkeit 
 sprite1Velocity = getRandomVelocity (); 
 // Fixiere die Schiffsgeschwindigkeit vorerst auf eine konstante Geschwindigkeit 
 sprite2Velocity = neuer Punkt (1, 1); 
 // Setze unsere Grenzen für die 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 (); 
 ((Button) findViewById (R.id. the_button )). SetEnabled ( true ); 
 frame.removeCallbacks (frameUpdate); 
 frame.postDelayed (frameUpdate, FRAME_RATE ); 
 }} 
 @Override 
 synchronisierte öffentliche Leere onClick (View v) { 
 initGfx (); 
 }} 
 private Runnable frameUpdate = new Runnable () { 
 @Override 
 synchronisierter öffentlicher void run () { 
 frame.removeCallbacks (frameUpdate); 
 // Zuerst die aktuellen Positionen beider Sprites abrufen 
 Punkt sprite1 = neuer Punkt 
 (((GameBoard) findViewById (R.id. the_canvas )). GetSprite1X (), 
 ((GameBoard) findViewById (R.id. the_canvas )). GetSprite1Y ()); 
 Punkt sprite2 = neuer Punkt 
 (((GameBoard) findViewById (R.id. the_canvas )). GetSprite2X (), 
 ((GameBoard) findViewById (R.id. the_canvas )). GetSprite2Y ()); 
 // Berechne nun die neuen Positionen. 
 // Beachten Sie, wenn wir eine Grenze überschreiten, wird die Richtung der Geschwindigkeit umgekehrt. 
 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 )). Invalidate (); 
 frame.postDelayed (frameUpdate, FRAME_RATE ); 
 }} 
 }; 
 }} 

Wir haben unser Spiel wieder an einem stabilen Punkt. Besser noch, zu diesem Zeitpunkt sieht unser Code auf einem Gerät oder Emulator tatsächlich wie ein Videospiel aus. Überzeugen Sie sich selbst!

Beachten Sie, wie der Asteroid direkt durch das Schiff geht? Das ist ein Problem. Ganz zu schweigen davon, dass es schön wäre, wenn Sie die Untertasse und den kleinen Außerirdischen auf dem Fahrersitz unter Kontrolle hätten. Wir werden beide in den letzten beiden Teilen dieser Spieleentwicklungsserie behandeln. Schauen Sie sich das also unbedingt an.

© Copyright 2021 | pepebotifarra.com