ABCene for Android-spillutvikling: Oppdage kollisjoner

Målet med Android-spillet vi utvikler i denne serien er å justere den flyvende tallerkenens fart for å unngå å bli knust til biter av asteroiden. Mens det er forenklet, inneholder prosjektet alle de viktige aspektene ved et videospill: et lerret, sprites, animasjon, kollisjonsdeteksjon og brukerinput.

I vår siste tutorial klarte vi å få spirene våre til å bevege seg rundt på skjermen. (Hvis du er ukjent med denne serien, kan du fange opp henholdsvis del én og to: Forbered lerretet og Last inn og vis spriter.) Nå må vi være i stand til å oppdage når asteroiden og skipet kolliderer. Det er i utgangspunktet to teknikker for kollisjonsdeteksjon: grensealgoritmer og bildemaskering. Begrensning er enklere og tar opp færre CPU-sykluser enn bildemaskering; Avgrensning er imidlertid normalt ikke nøyaktig nok for arkadetype spill. Hva er forskjellen? Tenk på våre to bilder. Jeg har begge deler på en 50x50 pikslers bakgrunn, men innholdet i disse spritene fyller ikke hele 50x50 pikselnettet. Ta en titt på illustrasjonene i figur A og figur B. Figur A

UFO-spriten vår inkluderer transparente områder.
Figur B

Asteroiden spriter med sine gjennomsiktige områder.
Hvis vi skulle bruke grenser for å oppdage kollisjoner, ville vi bare sjekke hver gang gjennom løkka vår for å se om 50x50 rektangler overlapper hverandre; hvis de overlapper hverandre, anser vi det som en kollisjon. Ved å bruke bildene over kan du se at denne teknikken kan fuskes for å fungere for kollisjoner på X-aksen. Kollisjoner på Y-aksen er ikke så klippete og tørre. ( Figur C ) Figur C

Grunnleggende avgrensning av kollisjon og mangel på nøyaktighet.

Som jeg sa før sammenligning av nærhet til de to sprittene på en piksel-for-pikselbasis (noen ganger kalt bitmaskering og noen ganger referert til som pixel perfekt kollisjonsdeteksjon) resulterer i en mye mer nøyaktig kollisjonsdeteksjonsmekanisme. Dette kan være tidkrevende, fordi det i utgangspunktet krever at du skanner de enkelte pikslene i begge bildene og ser etter overlapp. I de fleste spill blir en kombinasjon av de to brukt, og det er den tilnærmingen vi også vil bruke.

Så lenge de ytre rektanglene ikke overlapper hverandre, vil vi merke kollisjonsflagget som usant. Når rektanglene overlapper hverandre, vil vi bare skanne de pikslene i den overlappende delen. Hver gang vi har en piksel i den overlappende delen som ikke er gjennomsiktig på begge bildene, har spritene våre krasjet inn i hverandre.

Oppdage kollisjoner

Denne opplæringen bygger på det vi opprettet i del tre. For å virkelig forstå hva som skjer, hjelper det å se koden i sammenheng med helheten; Derfor vil kodelisten i opplæringen være vårt komplette arbeidsgrunnlag, med den nye koden kommentert på linje. 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 tre, har jeg inkludert begge nedenfor for fullstendighet.

AndroidManifest.xml

 "Http://schemas.android.com/apk/res/android" 
 package = "com.authorwjf.gamedevtut04" 
 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 = "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. Akkurat som i de foregående opplæringene, har vi laget en / tegnbar mappe i / res-katalogen der vi plasserer våre to sprite-bilder: ufo.png og asteroid.png. Nå som vi gjør påvisning av kollisjon, blir størrelsen viktigere, så hvis du bruker dine egne bilder, anbefaler jeg størrelsen på begge til 50x50 piksler. Du må også sørge for at bildene er lagret med en gjennomsiktig bakgrunn.

4. Det meste av magien denne gangen skjer i com.authorwjf.drawable.GameBoard.java-filen; det er her vi skal sjekke for en kollisjon hver gang gjennom trekningssløyfen. Hvis det oppstår en kollisjon, vil vi oppdatere flagget og koordinatene og tegne en rød x som indikerer hvor kollisjonen skjedde.

Hvis sjekken for kollisjonsfunksjon fremdeles virker forvirrende for deg på dette tidspunktet, er det greit - du kan bruke den i prosjektene dine i stor grad via klipp og lim, og etter hvert vil du bli mer komfortabel med teknikken.

GameBoard.java

 pakke com.authorwjf.drawing; 
 importer java.util.ArrayList; 
 import java.util.List; 
 importer java.util.Random; 
 import com.authorwjf.gamedevtut04.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 ; 
 // Kollisjonsflagg og punkt 
 privat boolsk kollisjon Detektert = usant ; 
 private Point lastCollision = nytt punkt (-1, -1); 
 privat int sprite1Rotasjon = 0; 
 privat statisk endelig int NUM_OF_STARS = 25; 
 // La kontrolleren vår få og stille inn sprite-stillingene 
 // sprite 1 setter 
 synkronisert offentlig tomrom setSprite1 ( int x, int y) { 
 sprite1 = nytt punkt (x, y); 
 } 
 // sprite 1 getter 
 synkronisert offentlig int getSprite1X () { 
 retur sprite1.x; 
 } 
 synkronisert offentlig int getSprite1Y () { 
 retur sprite1.y; 
 } 
 // sprite 2 setter 
 synkronisert offentlig tomrom setSprite2 ( int x, int y) { 
 sprite2 = nytt punkt (x, y); 
 } 
 // sprite 2 getter 
 synkronisert offentlig int getSprite2X () { 
 retur sprite2.x; 
 } 
 synkronisert offentlig int getSprite2Y () { 
 retur sprite2.y; 
 } 
 synkronisert offentlig tomrom resetStarField () { 
 starField = null ; 
 } 
 // utsett sprite grenser for kontrolleren 
 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 (); 
 } 
 // returnere poenget med den siste kollisjonen 
 synkronisert offentlig Point getLastCollision () { 
 return lastCollision; 
 } 
 // returnere kollisjonsflagget 
 synkronisert offentlig boolesk varCollisionDetected () { 
 retur kollisjon Detektert; 
 } 
 public GameBoard (Context context, AttributeSet aSet) { 
 super (kontekst, aSet); 
 p = ny maling (); 
 // last inn bitmappene våre og sett grensene for kontrolleren 
 sprite1 = nytt punkt (-1, -1); 
 sprite2 = nytt punkt (-1, -1); 
 // Definer en matrise slik at vi kan rotere asteroiden 
 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 ); 
 } 
 // Den siste bestillingen av virksomheten er å sjekke for kollisjon 
 collisionDetected = checkForCollision (); 
 if (collisionDetected) { 
 // hvis det er en, kan vi tegne et rødt X 
 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. Vi har fortsatt noen få endringer å gjøre i /src/Main.java-filen. Først ønsker vi å legge til en sjekk av kollisjonsflagget i rammeoppdateringen. Hvis det oppstod en kollisjon, oppdaterer vi xy-etiketten og avbryter deretter. For det andre vil vi sørge for at Reset-knappen ugyldiggjør skjermen. Dette sikrer at du trykker Reset for å starte spillet på nytt.

Main.java

 pakke com.authorwjf.gamedevtut04; 
 importer 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.widget.TextView; 
 import android.app.Aktivitet; 
 import android.graphics.Point; 
 public class Main utvider aktivitetsredskaper OnClickListener { 
 private Handler frame = new Handler (); 
 // Velocity inkluderer hastigheten og retningen på vår bevegelse 
 private Point sprite1Velocity; 
 private Point sprite2Velocity; 
 privat int sprite1MaxX; 
 privat int sprite1MaxY; 
 privat int sprite2MaxX; 
 privat int sprite2MaxY; 
 // Del rammen med 1000 for å beregne hvor mange ganger i sekundet skjermen vil oppdatere. 
 privat statisk endelig int FRAME_RATE = 20; // 50 bilder per sekund 
 @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 ); 
 // Vi kan ikke initialisere grafikken umiddelbart fordi layout manager 
 // trenger å løpe først, og ring tilbake på et sekund. 
 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); 
 // Gi asteroiden en tilfeldig hastighet 
 sprite1Velocity = getRandomVelocity (); 
 // Fix skipets hastighet med konstant hastighet for nå 
 sprite2Velocity = nytt punkt (1, 1); 
 // Sett våre grenser 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 (); 
 ((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 () { 
 // Før vi gjør noe annet, sjekk for kollisjon 
 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) +") ");
 } 
 // slå av animasjonen til du trykker på nullstillingen 
 tilbake ; 
 } 
 frame.removeCallbacks (frameUpdate); 
 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 ); 
 } 
 }; 
 } 

Last opplæringen på en enhet eller en emulator og vent til skipet og asteroiden skal kollidere. Hvis det tar for lang tid, treffer Reset, og både UFO og asteroiden vil få nye baner.

På dette tidspunktet har du et spill, om enn verdens mest frustrerende. Mens begge spritene spretter rundt og kolliderer, har du ingen kontroll over heller. Det neste innlegget i denne serien vil dekke grunnleggende brukerinnspill og gi oss akkurat nok kontroll over det fremmede romfartøyet til å scoot ut av veien for den innkommende asteroiden.

© Copyright 2020 | mobilegn.com