ABCene for Android-spillutvikling: Innspill fra brukerne

Dette innlegget markerer den endelige avbetalingen i mine ABCs for Android-spillutviklingsserie. For å gjenskape, i det første innlegget skapte vi et lerret og rammeverk for spillet vårt. I det andre innlegget introduserte vi sprites. I det tredje lærte vi hvordan vi skulle takle animasjon, og i fjerde innlegg behandlet vi kollisjoner.

Målet med Android-spillet er å justere den flytende tallerkenens fart for å unngå å bli smadret til biter av asteroiden. Dette enkle prosjektet inkluderer alle de viktigste aspektene ved et videospill: et lerret, sprites, animasjon, kollisjonsdeteksjon og brukerinput. Det er mange andre bjeller og fløyter som blir pepret inn i et videospill - lydeffekter, eksplosjoner og poengsum for å nevne noen - men jeg vurderer alle disse elementene som digitalt godteri. Den eneste viktige ingrediensen vi mangler i spillet vårt er å gi spilleren litt kontroll over romskipet.

Tilbake på 1980-tallet, da videospill styrte pizzastaller og skøytebaner, ble brukerinnspill først og fremst gjort via en styrespak. Det var noen få unntak ( Breakout, Marble Madness og Paperboy kommer til hjernen), men som tommelfingerregel var joysticken defacto-kontrolleren for de fleste spill.

Dagens generasjon smarttelefoner og nettbrett har blåst den gamle modellen av spillkontroller bort. Halvparten av moroa er det innovative innspillet som er tilgjengelig for brukere og utviklere. Berøringsskjermkontroller, tastaturer, styrekuler og vippebaserte spill som gjør bruk av det innebygde akselerometeret på de fleste telefoner er et utvalg av kontrollene som er tilgjengelige.

For vårt spill er ikke meningen å implementere kompliserte kontroller for å manøvrere den spillerstyrte spriten; vi ønsker bare å demonstrere hvordan innspill kan avledes fra en bruker og hvordan denne inngangen kan brukes til å påvirke handlingen på skjermen. Derfor vil vi svare bare på berøringshendelser, og spesielt ACTION_DOWN og ACTION_UP-hendelsene.

Brukerinndata

Denne opplæringen bygger på det vi opprettet i del fire. For å virkelig forstå hva som skjer, hjelper det å se koden i sammenheng med helheten. Derfor er kodelisten i denne opplæringen vår komplette og endelige kodebase. Den nye koden er kommentert inline for å hjelpe deg med å få øye på forskjellene mellom dette innlegget og forgjengeren. Du kan følge med trinnvise instruksjoner eller laste ned og importere hele prosjektet til Eclipse.

1. Lag et nytt Android-prosjekt i Eclipse. Målrette Android 2.1 eller nyere. Sørg for å gi nytt navn til oppstartsaktiviteten Main.java og den tilhørende utformingen til main.xml.

2. Selv om det ikke er noen endringer i manifestfilen eller oppsettet vårt siden del fire, inkluderte jeg begge nedenfor for fullstendighet.

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 = "portrait" 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 = "vertikal" > 
 android: layout_width = "wrap_content" 
 android: layout_height = "wrap_content" 
 android: layout_gravity = "topp | sentrum" 
 android: text = "ABC's of Android Game Dev" /> 
 android: id = "@ + id / the_button" 
 android: layout_width = "wrap_content" 
 android: layout_height = "wrap_content" 
 android: layout_gravity = "sentrum" 
 android: gravitasjon = "sentrum" 
 android: enabled = "falsk" 
 android: text = "Reset" /> 
 android: layout_width = "wrap_content" 
 android: layout_height = "wrap_content" 
 android: layout_gravity = "sentrum" 
 android: text = "Sprite Speed ​​(?, ?)" 
 android: id = "@ + id / the_label" /> 
 android: layout_width = "wrap_content" 
 android: layout_height = "wrap_content" 
 android: layout_gravity = "sentrum" 
 android: text = "Last Collision 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. Som i de foregående opplæringene, opprettet vi en / tegnbar mappe i / res-katalogen der vi plasserer våre to sprite-bilder: ufo.png og asteroid.png. Begge bildene er lagret på et lerret på 50 x 50 piksler med gjennomsiktig bakgrunn.

4. I den første opplæringen satte vi opp spillet vårt slik at GameBoard-klassen håndterte all tegning, mens Main-klassen hadde ansvar for å kontrollere posisjonene til våre spillobjekter. Nå, nå får vi kraften i det programmeringsparadigmet. Fordi GameBoard-klassen er selvforsynt og bare ansvarlig for å tegne handlingen, ikke bestemme hvor spritene går, hastigheten deres osv., Kan vi legge til spillkontrollene våre (så vel som litt annen oppførsel) uten å berøre tegningen vår pakke. Ta en titt selv - det er ingen ny kode i GameBoard.java-klassen siden vår siste opplæring.

GameBoard.java

 pakke com.authorwjf.drawing; 
 importer java.util.ArrayList; 
 import java.util.List; 
 importer java.util.Random; 
 import com.authorwjf.gamedevtut05.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; 
 public class GameBoard utvider View { 
 privat maling; 
 privat liste starField = null ; 
 privat int starAlpha = 80; 
 privat int starFade = 2; 
 private Rect sprite1Bounds = new Rect (0, 0, 0, 0); 
 private Rect sprite2Bounds = new Rect (0, 0, 0, 0); 
 private Point sprite1; 
 private Point sprite2; 
 privat Bitmap bm1 = null ; 
 privat Matrix m = null ; 
 privat Bitmap bm2 = null ; 
 privat boolsk kollisjon Detektert = usant ; 
 private Point lastCollision = nytt punkt (-1, -1); 
 privat int sprite1Rotasjon = 0; 
 privat statisk endelig int NUM_OF_STARS = 25; 
 synkronisert offentlig tomrom setSprite1 ( int x, int y) { 
 sprite1 = nytt punkt (x, y); 
 } 
 synkronisert offentlig int getSprite1X () { 
 retur sprite1.x; 
 } 
 synkronisert offentlig int getSprite1Y () { 
 retur sprite1.y; 
 } 
 synkronisert offentlig tomrom setSprite2 ( int x, int y) { 
 sprite2 = nytt punkt (x, y); 
 } 
 synkronisert offentlig int getSprite2X () { 
 retur sprite2.x; 
 } 
 synkronisert offentlig int getSprite2Y () { 
 retur sprite2.y; 
 } 
 synkronisert offentlig tomrom resetStarField () { 
 starField = null ; 
 } 
 synkronisert offentlig int getSprite1Width () { 
 return sprite1Bounds.width (); 
 } 
 synkronisert offentlig int getSprite1Height () { 
 return sprite1Bounds.height (); 
 } 
 synkronisert offentlig int getSprite2Width () { 
 returnere sprite2Bounds.width (); 
 } 
 synkronisert offentlig int getSprite2Height () { 
 retur sprite2Bounds.height (); 
 } 
 synkronisert offentlig Point getLastCollision () { 
 return lastCollision; 
 } 
 synkronisert offentlig boolesk varCollisionDetected () { 
 retur kollisjon Detektert; 
 } 
 public GameBoard (Context context, AttributeSet aSet) { 
 super (kontekst, aSet); 
 p = ny maling (); 
 sprite1 = nytt punkt (-1, -1); 
 sprite2 = nytt punkt (-1, -1); 
 m = ny matrise (); 
 p = ny maling (); 
 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 ()); 
 } 
 synkronisert privat tomrom initialisereStjerner ( int maxX, int maxY) { 
 starField = new ArrayList (); 
 for ( int i = 0; i < NUM_OF_STARS ; i ++) { 
 Tilfeldig r = ny Tilfeldig (); 
 int x = r.nextInt (maxX-5 + 1) +5; 
 int y = r.nextInt (maxY-5 + 1) +5; 
 starField.add ( nytt punkt (x, y)); 
 } 
 kollisjon Detektert = usant ; 
 } 
 privat boolesk sjekkForCollision () { 
 hvis (sprite1.x <0 && sprite2.x <0 && sprite1.y <0 && sprite2.y <0) returner 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 ()); 
 Rect r3 = new Rect (r1); 
 if (r1.intersect (r2)) { 
 for ( int i = r1.left; i 
 for ( int j = r1.top; j 
 if (bm1.getPixel (i-r3.left, j-r3.top)! = Farge. TRANSPARENT ) { 
 if (bm2.getPixel (i-r2.left, j-r2.top)! = Farge. TRANSPARENT ) { 
 lastCollision = nytt punkt (sprite2.x + i-r2.left, sprite2.y + j-r2.top); 
 return true ; 
 } 
 } 
 } 
 } 
 } 
 lastCollision = nytt punkt (-1, -1); 
 return falsk ; 
 } 
 @Overstyring 
 synkronisert offentlig tomrom onDraw (Canvas canvas) { 
 p.setColor (farge. SVART ); 
 p.setAlpha (255); 
 p.setStrokeWidth (1); 
 canvas.drawRect (0, 0, getWidth (), getHeight (), p); 
 if (starField == null ) { 
 initialisereStars (canvas.getWidth (), canvas.getHeight ()); 
 } 
 p.setColor (farge. 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); 
 } 
 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)); 
 lerret.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 (farge. RØD ); 
 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. Dette er det første trinnet der vi skriver ny kode. I filen /src/Main.java skal vi legge til et nytt flagg som indikerer om spilleren prøver å akselerere. Vi bestemmer dette basert på om spillerens finger løftes fra telefonens skjerm. Med andre ord, jo lenger spilleren berører en finger til skjermen, desto raskere akselererer UFO. Når spilleren løfter fingeren, begynner retardasjonen.

Den nye metoden updateVelocity () er ansvarlig for å legge til eller trekke fra sprite sin nåværende hastighet, respektere øvre og nedre grenser vi har satt. Denne metoden blir oppringt hver gang tilbakering av rammeoppdatering skjer. Så oppdaterer vi etiketten vår med den nye hastigheten. Resten skjer auto-magisk.

Main.java

 pakke com.authorwjf.gamedevtut05; 
 importer java.util.Random; 
 import com.authorwjf.drawing.GameBoard; 
 import android.os.Bundle; 
 import android.os.Handler; 
 import android.view.MotionEvent; 
 import android.view.View; 
 import android.view.View.OnClickListener; 
 import android.widget.Button; 
 import android.widget.TextView; 
 import android.app.Aktivitet; 
 import android.graphics.Point; 
 public class Main utvider aktivitetsredskaper OnClickListener { 
 private Handler frame = new Handler (); 
 private Point sprite1Velocity; 
 private Point sprite2Velocity; 
 privat int sprite1MaxX; 
 privat int sprite1MaxY; 
 privat int sprite2MaxX; 
 privat int sprite2MaxY; 
 // akselerasjonsflagg 
 privat boolsk isAccelerating = falsk ; 
 privat statisk endelig int FRAME_RATE = 20; // 50 bilder per sekund 
 // Metode for å få berøringsstatus - krever Android 2.1 eller nyere 
 @Overstyring 
 synkronisert offentlig boolesk onTouchEvent (MotionEvent ev) { 
 endelig int- handling = ev.getAction (); 
 bytte (action & MotionEvent. ACTION_MASK ) { 
 sak MotionEvent. ACTION_DOWN : 
 sak MotionEvent. ACTION_POINTER_DOWN : 
 isAccelerating = true ; 
 pause ; 
 sak MotionEvent. ACTION_UP : 
 sak MotionEvent. ACTION_POINTER_UP : 
 isAccelerating = falsk ; 
 pause ; 
 } 
 return true ; 
 } 
 // Øk hastigheten mot fem eller senk 
 // tilbake til en avhengig av tilstand 
 private void updateVelocity () { 
 int xDir = (sprite2Velocity.x> 0)? 1: -1; 
 int yDir = (sprite2Velocity.y> 0)? 1: -1; 
 int hastighet = 0; 
 if (isAccelerating) { 
 hastighet = matematikk. abs (sprite2Velocity.x) +1; 
 } annet { 
 hastighet = matematikk. abs (sprite2Velocity.x) -1; 
 } 
 if (hastighet> 5) hastighet = 5; 
 if (hastighet <1) hastighet = 1; 
 sprite2Velocity.x = hastighet * xDir; 
 sprite2Velocity.y = hastighet * yDir; 
 } 
 @Overstyring 
 public void onCreate (Bundle savedInstanceState) { 
 super .onCreate (savedInstanceState); 
 setContentView (R.layout. main ); 
 Handler h = new Handler (); 
 ((Knapp) findViewById (R.id. The_button )). SetOnClickListener ( dette ); 
 h.postDelayed ( new Runnable () { 
 @Overstyring 
 public void run () { 
 initGfx (); 
 } 
 }, 1000); 
 } 
 private Point getRandomVelocity () { 
 Tilfeldig r = ny Tilfeldig (); 
 int min = 1; 
 int maks = 5; 
 int x = r.nextInt (maks-min + 1) + min; 
 int y = r.nextInt (maks-min + 1) + min; 
 returnere nytt punkt (x, y); 
 } 
 private Point getRandomPoint () { 
 Tilfeldig r = ny Tilfeldig (); 
 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 nytt punkt (x, y); 
 } 
 synkronisert offentlig tomrom initGfx () { 
 ((GameBoard) findViewById (R.id. The_canvas )). ResetStarField (); 
 Punkt p1, p2; 
 gjør { 
 p1 = getRandomPoint (); 
 p2 = getRandomPoint (); 
 } mens (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 = nytt 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 (); 
 ((Knapp) findViewById (R.id. The_button )). SetEnabled ( true ); 
 frame.removeCallbacks (frameUpdate); 
 ((GameBoard) findViewById (R.id. The_canvas )). Ugyldig (); 
 frame.postDelayed (frameUpdate, FRAME_RATE ); 
 } 
 @Overstyring 
 synkronisert offentlig tomrom onClick (View v) { 
 initGfx (); 
 } 
 private Runnable frameUpdate = new Runnable () { 
 @Overstyring 
 synkronisert offentlig tomgangskjøring () { 
 hvis (((GameBoard) findViewById (R.id. the_canvas )). wasCollisionDetected ()) { 
 Point collisionPoint = ((GameBoard) findViewById (R.id. The_canvas )). GetLastCollision (); 
 if (collisionPoint.x> = 0) { 
 ((TextView) findViewById (R.id. The_other_label)). SetText ("Siste 

Kollisjon XY

("+ Heltall. ToString (collisionPoint.x) +", "+ Heltall. ToString (collisionPoint.y) +") ");
 } 
 tilbake ; 
 } 
 frame.removeCallbacks (frameUpdate); 
 // Legg til samtalen for å øke eller redusere hastigheten 
 updateVelocity (); 
 // Vis UFO-hastighet 
 ((TextView) findViewById (R.id. The_label )). SetText ("Sprite Acceleration (" + Heltall. ToString (sprite2Velocity.x) + ", " + Heltall tilString (sprite2Velocity.y) + ")"); 
 Point sprite1 = new Point (((GameBoard) findViewById (R.id. The_canvas )). GetSprite1X (), 
 ((GameBoard) findViewById (R.id. The_canvas )). GetSprite1Y ()); 
 Point sprite2 = new Point (((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 )). Ugyldig (); 
 frame.postDelayed (frameUpdate, FRAME_RATE ); 
 } 
 }; 
 } 

Gratulerer! Du har nettopp skrevet det første spillet ditt for en Android-drevet enhet. Tro meg ikke? Legg den resulterende koden på enheten eller emulatoren, og ta den for en snurr. Se opp for den asteroiden! Hvis det blir tett, holder du bare fingeren nede på telefonen for å gi skipet ditt et løft.

På dette tidspunktet har du alle elementene i et ekte videospill. Jeg overlater det som en øvelse for leseren å legge til mer sofistikerte kontroller, lydeffekter, scoreholdning - fungerer!

For de leserne som har holdt følge med denne serien fra begynnelsen, takker jeg en takk og inviterer deg til å legge inn kommentarer og spørsmål nedenfor. Denne serien var en eksplosjon for meg å skrive. Jeg setter pris på at redaktørene på TechRepublic tar sjansen på det, og jeg håper leserne synes det er informativt og mye moro.

© Copyright 2021 | pepebotifarra.com