Das ABC der Android-Spieleentwicklung: Benutzereingaben

Dieser Beitrag markiert die letzte Folge meiner ABC-Entwicklungsserie für Android-Spiele. Zusammenfassend haben wir im ersten Beitrag eine Leinwand und ein Framework für unser Spiel erstellt. Im zweiten Beitrag haben wir Sprites eingeführt. Im dritten haben wir gelernt, wie man mit Animation umgeht, und im vierten Beitrag haben wir uns mit Kollisionen befasst.

Das Ziel des Android-Spiels ist es, den Schwung der fliegenden Untertasse anzupassen, um zu vermeiden, dass der Asteroid ihn in Stücke zerschmettert. Dieses einfache Projekt beinhaltet alle wesentlichen Aspekte eines Videospiels: Leinwand, Sprites, Animation, Kollisionserkennung und Benutzereingaben. Es gibt viele andere Schnickschnack, die in ein Videospiel einfließen - Soundeffekte, Explosionen und Partitur, um nur einige zu nennen -, aber ich betrachte all diese Elemente als digitale Süßigkeiten. Die einzige wesentliche Zutat, die wir in unserem Spiel vermissen, ist, dem Spieler die Kontrolle über das Raumschiff zu geben.

In den 1980er Jahren, als Videospiele über Pizzerien und Eisbahnen herrschten, erfolgte die Benutzereingabe hauptsächlich über einen Joystick. Es gab einige Ausnahmen ( Breakout, Marble Madness und Paperboy kommen in den Sinn), aber als Faustregel war der Joystick der Defacto-Controller für die meisten Spiele.

Die heutige Generation von Smartphones und Tablets hat das alte Modell der Spielsteuerung umgehauen. Der halbe Spaß ist der innovative Input, der Benutzern und Entwicklern zur Verfügung steht. Touchscreen-Steuerelemente, Tastaturen, Trackballs und kippbasierte Spiele, die den integrierten Beschleunigungsmesser der meisten Telefone verwenden, sind nur einige Beispiele der verfügbaren Steuerelemente.

Für unser Spiel ist es nicht beabsichtigt, komplizierte Steuerelemente zum Manövrieren des vom Spieler gesteuerten Sprites zu implementieren. Wir möchten nur zeigen, wie Eingaben von einem Benutzer abgeleitet werden können und wie diese Eingaben verwendet werden können, um die Bildschirmaktion zu beeinflussen. Daher reagieren wir nur auf Berührungsereignisse, insbesondere auf die Ereignisse ACTION_DOWN und ACTION_UP.

Benutzereingabe

Dieses Tutorial baut auf dem auf, was wir in Teil 4 erstellt haben. Um wirklich zu verstehen, was los ist, ist es hilfreich, den Code im Kontext des Ganzen zu sehen. Daher ist die Codeliste in diesem Tutorial unsere vollständige und endgültige Codebasis. Der neue Code wird inline kommentiert, damit Sie die Unterschiede zwischen diesem Beitrag und seinem Vorgänger erkennen können. 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.xml umbenennen.

2. Obwohl seit dem vierten 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.gamedevtut05" 
 android: versionCode = "1" 
 android: versionName = "1.0" > 
 android: minSdkVersion = "8" 
 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. Wie in den vorherigen Tutorials haben wir einen Ordner / drawable im Verzeichnis / res erstellt, in dem wir unsere beiden Sprite-Images ablegen: ufo.png und asteroid.png. Beide Bilder wurden auf einer Leinwand mit 50 x 50 Pixel und transparentem Hintergrund gespeichert.

4. Im ersten Tutorial haben wir unser Spiel so eingerichtet, dass die GameBoard-Klasse die gesamte Zeichnung abwickelt, während die Hauptklasse für die Steuerung der Positionen unserer Spielobjekte verantwortlich ist. Nun kommen wir zur Kraft dieses Programmierparadigmas. Da die GameBoard-Klasse in sich geschlossen ist und nur für das Zeichnen der Aktion verantwortlich ist und nicht bestimmt, wohin die Sprites gehen, wie schnell sie sind usw., können wir unsere Spielsteuerungen (sowie einige andere Verhaltensweisen) hinzufügen, ohne unsere Zeichnung zu berühren Paket. Überzeugen Sie sich selbst - seit unserem letzten Tutorial gibt es keinen neuen Code in der GameBoard.java-Klasse.

GameBoard.java

 Paket com.authorwjf.drawing; 
 import java.util.ArrayList; 
 import java.util.List; 
 import java.util.Random; 
 import com.authorwjf.gamedevtut05.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; 
 private Rect sprite1Bounds = new Rect (0, 0, 0, 0); 
 private Rect sprite2Bounds = new Rect (0, 0, 0, 0); 
 privater Punkt sprite1; 
 privater Punkt sprite2; 
 private Bitmap bm1 = null ; 
 private Matrix m = null ; 
 private Bitmap bm2 = null ; 
 private boolean collisionDetected = false ; 
 privater Punkt lastCollision = neuer Punkt (-1, -1); 
 private int sprite1Rotation = 0; 
 private static final int NUM_OF_STARS = 25; 
 synchronisierte öffentliche Leere setSprite1 ( int x, int y) { 
 sprite1 = neuer Punkt (x, y); 
 }} 
 synchronized public int getSprite1X () { 
 return sprite1.x; 
 }} 
 synchronized public int getSprite1Y () { 
 return sprite1.y; 
 }} 
 synchronisierte öffentliche Leere setSprite2 ( int x, int y) { 
 sprite2 = neuer Punkt (x, y); 
 }} 
 synchronisierte öffentliche int getSprite2X () { 
 return sprite2.x; 
 }} 
 synchronized public int getSprite2Y () { 
 return sprite2.y; 
 }} 
 synchronisierte öffentliche Leere resetStarField () { 
 starField = null ; 
 }} 
 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 (); 
 }} 
 synchronisierter öffentlicher Punkt getLastCollision () { 
 return lastCollision; 
 }} 
 synchronisierter öffentlicher Boolescher Wert wasCollisionDetected () { 
 return collisionDetected; 
 }} 
 öffentliches GameBoard (Kontextkontext, AttributeSet aSet) { 
 super (Kontext, aSet); 
 p = neue Farbe (); 
 sprite1 = neuer Punkt (-1, -1); 
 sprite2 = neuer Punkt (-1, -1); 
 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 ()); 
 }} 
 synchronisierte 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)); 
 }} 
 collisionDetected = false ; 
 }} 
 private boolean checkForCollision () { 
 if (sprite1.x <0 && sprite2.x <0 && sprite1.y <0 && sprite2.y <0) return false ; 
 Rect r1 = new Rect (sprite1.x, sprite1.y, sprite1.x + sprite1Bounds.width (), sprite1.y + sprite1Bounds.height ()); 
 Rect r2 = new Rect (sprite2.x, sprite2.y, sprite2.x + sprite2Bounds.width (), sprite2.y + sprite2Bounds.height ()); 
 Rechteck r3 = neues Rechteck (r1); 
 if (r1.intersect (r2)) { 
 für ( int i = r1.left; i 
 für ( int j = r1.top; j 
 if (bm1.getPixel (i-r3.left, j-r3.top)! = Farbe. TRANSPARENT ) { 
 if (bm2.getPixel (i-r2.left, j-r2.top)! = Farbe. TRANSPARENT ) { 
 lastCollision = neuer Punkt (sprite2.x + i-r2.left, sprite2.y + j-r2.top); 
 return true ; 
 }} 
 }} 
 }} 
 }} 
 }} 
 lastCollision = neuer Punkt (-1, -1); 
 return false ; 
 }} 
 @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); 
 }} 
 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 ); 
 }} 
 collisionDetected = checkForCollision (); 
 if (collisionDetected) { 
 p.setColor (Farbe. ROT ); 
 p.setAlpha (255); 
 p.setStrokeWidth (5); 
 canvas.drawLine (lastCollision.x - 5, lastCollision.y - 5, lastCollision.x + 5, lastCollision.y + 5, p); 
 canvas.drawLine (lastCollision.x + 5, lastCollision.y - 5, lastCollision.x - 5, lastCollision.y + 5, p); 
 }} 
 }} 
 }} 

5. Dies ist der erste Schritt, in dem wir neuen Code schreiben werden. In der Datei /src/Main.java fügen wir ein neues Flag hinzu, das angibt, ob der Player versucht zu beschleunigen. Wir bestimmen dies basierend darauf, ob der Finger des Spielers vom Display des Telefons abgehoben wird. Mit anderen Worten, je länger der Spieler mit einem Finger den Bildschirm berührt, desto schneller beschleunigt das UFO. Wenn der Spieler seinen Finger hebt, beginnt die Verzögerung.

Die neue Methode updateVelocity () ist verantwortlich für das Addieren oder Subtrahieren der aktuellen Geschwindigkeit des Sprites unter Berücksichtigung der von uns festgelegten oberen und unteren Grenzen. Diese Methode wird jedes Mal aufgerufen, wenn unser Frame-Update-Rückruf erfolgt. Dann aktualisieren wir unser Label mit der neuen Geschwindigkeit. Der Rest geschieht automatisch.

Main.java

 Paket com.authorwjf.gamedevtut05; 
 import java.util.Random; 
 import com.authorwjf.drawing.GameBoard; 
 import android.os.Bundle; 
 import android.os.Handler; 
 android.view.MotionEvent importieren ; 
 android.view.View importieren ; 
 android.view.View.OnClickListener importieren ; 
 android.widget.Button importieren ; 
 android.widget.TextView importieren ; 
 android.app.Activity importieren ; 
 android.graphics.Point importieren ; 
 public class Main erweitert Activity implementiert OnClickListener { 
 privater Handler-Frame = neuer Handler (); 
 private Point sprite1Velocity; 
 privater Punkt sprite2Velocity; 
 private int sprite1MaxX; 
 private int sprite1MaxY; 
 private int sprite2MaxX; 
 private int sprite2MaxY; 
 // Beschleunigungsflag 
 privater Boolescher Wert isAccelerating = false ; 
 private static final int FRAME_RATE = 20; // 50 Bilder pro Sekunde 
 // Methode zum Abrufen des Berührungsstatus - erfordert Android 2.1 oder höher 
 @Override 
 synchronisierter öffentlicher boolescher Wert onTouchEvent (MotionEvent ev) { 
 final int action = ev.getAction (); 
 switch (action & MotionEvent. ACTION_MASK ) { 
 Fall MotionEvent. ACTION_DOWN : 
 Fall MotionEvent. ACTION_POINTER_DOWN : 
 isAccelerating = true ; 
 Pause ; 
 Fall MotionEvent. ACTION_UP : 
 Fall MotionEvent. ACTION_POINTER_UP : 
 isAccelerating = false ; 
 Pause ; 
 }} 
 return true ; 
 }} 
 // Erhöhen Sie die Geschwindigkeit in Richtung fünf oder verringern Sie sie 
 // je nach Zustand zurück zu eins 
 private void updateVelocity () { 
 int xDir = (sprite2Velocity.x> 0)? 1: -1; 
 int yDir = (sprite2Velocity.y> 0)? 1: -1; 
 int speed = 0; 
 if (isAccelerating) { 
 Geschwindigkeit = Math. abs (sprite2Velocity.x) +1; 
 } else { 
 Geschwindigkeit = Math. abs (sprite2Velocity.x) -1; 
 }} 
 wenn (Geschwindigkeit> 5) Geschwindigkeit = 5; 
 wenn (Geschwindigkeit <1) Geschwindigkeit = 1; 
 sprite2Velocity.x = Geschwindigkeit * xDir; 
 sprite2Velocity.y = speed * yDir; 
 }} 
 @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 ); 
 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 (); 
 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); 
 sprite1Velocity = getRandomVelocity (); 
 sprite2Velocity = neuer Punkt (1, 1); 
 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); 
 ((GameBoard) findViewById (R.id. the_canvas )). Invalidate (); 
 frame.postDelayed (frameUpdate, FRAME_RATE ); 
 }} 
 @Override 
 synchronisierte öffentliche Leere onClick (View v) { 
 initGfx (); 
 }} 
 private Runnable frameUpdate = new Runnable () { 
 @Override 
 synchronisierter öffentlicher void run () { 
 if (((GameBoard) findViewById (R.id. the_canvas )). wasCollisionDetected ()) { 
 Punkt collisionPoint = ((GameBoard) findViewById (R.id. the_canvas )). GetLastCollision (); 
 if (collisionPoint.x> = 0) { 
 ((TextView) findViewById (R.id. the_other_label )). SetText ("Last 

Kollision XY

("+ Integer. ToString (collisionPoint.x) +", "+ Integer. ToString (collisionPoint.y) +") ");
 }} 
 zurück ; 
 }} 
 frame.removeCallbacks (frameUpdate); 
 // Fügen Sie unseren Aufruf hinzu, um die Geschwindigkeit zu erhöhen oder zu verringern 
 updateVelocity (); 
 // UFO-Geschwindigkeit anzeigen 
 ((TextView) findViewById (R.id. the_label )). SetText ("Sprite-Beschleunigung (" + Integer. ToString (sprite2Velocity.x) + ", " + Integer. ToString (sprite2Velocity.y) + ")"); 
 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 ()); 
 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 ); 
 }} 
 }; 
 }} 

Herzliche Glückwünsche! Sie haben gerade Ihr erstes Spiel für ein Android-Gerät geschrieben. Glaubst du mir nicht? Laden Sie den resultierenden Code auf Ihr Gerät oder Ihren Emulator und probieren Sie ihn aus. Pass auf diesen Asteroiden auf! Wenn es sich schließt, halten Sie einfach Ihren Finger auf das Telefon, um Ihrem Schiff einen Schub zu geben.

Zu diesem Zeitpunkt haben Sie alle Elemente eines echten Videospiels. Ich überlasse es dem Leser als Übung, ausgefeiltere Steuerelemente, Soundeffekte und Partituren hinzuzufügen - das funktioniert!

Für diejenigen Leser, die von Anfang an mit dieser Reihe Schritt gehalten haben, möchte ich mich bei Ihnen bedanken und Sie einladen, unten Kommentare und Fragen zu posten. Diese Serie war eine tolle Zeit für mich zu schreiben. Ich schätze es, dass die Redakteure von TechRepublic eine Chance nutzen, und ich hoffe, dass die Leser es informativ finden und viel Spaß machen.

© Copyright 2021 | pepebotifarra.com