Javaspel programmering handledning - Flappy fågeln Redux (11 / 12 steg)
Steg 11: Upptäcka kollisioner
Detta är det sista steget. Spelet har för närvarande full funktionalitet med pipe rörelse, fågelrörelse, etc. Nu kommer vi skriva kod som avgör när spelaren har förlorat inom TopClass. Vi kommer att åstadkomma detta med en kodrad inom spelet slingan och två metoder (collisionDetection och collisionHelper). Nedan ser du en video av några kollision tester.
Hand skapa skelettet för collisionDetection (så eclipse inte blir arg på dig), Lägg sedan "collisionDetection (bp1 bp2, tp1, tp2, fågel)," precis innan metoden updateScore anropas i spelet slingan.
Om du tänker på det, finns det fem eventuella kollisioner som kan hända. Fågeln kunde kollidera med en av de fyra rören eller marken. Detta innebär att du kan skapa en helper metod som hanterar identiska logiken för de fyra möjliga pipe kollisionerna. Därför börjar vi med metoden collisionHelper.
Logiskt, det är hur vi kommer att upptäcka en kollision (något jag utvecklat, så jag inte vet hur effektivt det är jämfört med andra kollisionsidentifiering metoder):
* Vi behöver klasserna spelare och hinder att ha metoder som returnerar ett Rectangle-objekt och BufferedImage objekt
* Med hjälp av rektanglar, kontrollerar vi om fågelns rektangel skär ett särskilt rör rektangel
* Om det finns en skärningspunkt, få spänna av koordinater som bunden skärningspunkten (första x, slutlig x, första y, final y)
* Med hjälp av fågel och pipe BufferedImages, testa varje pixel i området kollision för varje BufferedImage; om pixel inte är transparent (kom ihåg grafiken sammanflätning) för både fågel och röret, sedan en kollision har inträffat
Att veta detta, börjar vi arbeta med collisionHelper. Rektanglar är trevligt eftersom du kan testa om korsningar har inträffat, samt hålla reda på skärmposition i området skärningspunkten.
Vi skapar en ny rektangel som är skärningspunkten mellan fågeln och pipe. Variabeln firstI är den första X pixel att iterera från; Det är skillnaden mellan långt till vänster i korsningen rektangeln och fågelns (r1) långt kvar X-koordinaten. Variabeln firstJ är den första Y pixel att iterera från; Det är skillnaden mellan ovansidan av korsningen rektangeln och Bird's top Y-koordinaten. Dessa två används för att referera till objektet fågel.
Vi behöver också helper variabler att använda när du refererar till objektet kollision. Variablerna bp1XHelper och bp1YHelper använder liknande logik som firstI och firstJ, förutom de hänvisar till fågel och kollision objekt.
För våra iterativ analys, skapar vi en for-loop nästlad i en annan for-loop, iteration från firstI/J bredd/höjd i objektet fågel (r.getWidth() + firstI är samma som r1.getWidth()). Inbäddad i den inre loopen, har vi en villkorlig programsats som testar pixel insynen. B1.getRGB (i, j) & 0xFF000000 helt enkelt griper pixeln alfa värde och om den inte har värdet av 0x00, det finns en icke-transparent pixel närvarande. Vi testar för b1 och b2, och om båda är icke-transparent, det har varit en kollision.
Förutsatt en kollision uppstår, skicka "Game Over" till PlayGameScreen för att måla över skärmen vi, avsluta spelet slingan genom att ändra loopVar till false, ange spelet har slut genom att ändra gamePlay till false och bryta från slingan.
------
Nu är vidareutveckla collisionDetection ganska enkel. Starta genom att ringa collisionHelper för fågeln och varje rör objekt, passerar i deras relativa rektangel och BufferedImage objekt. Nästa, testa om det har skett en kollision med "marken" genom att testa om botten av fågeln har överskridit positionen för marken (SCREEN_HEIGHT * 7/8). Om så är fallet, skicka "Game Over," sluta spelet slingan och ange spelet har slut.
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Image;
import java.awt.Color;
import java.awt.LayoutManager;
import java.awt.Rectangle;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.image.BufferedImage;
import javax.swing.*;
allmän klass TopClass implementerar ActionListener, KeyListener {
globala konstant variabler
privata statisk sista int skärmbredd = (int) Toolkit.getDefaultToolkit().getScreenSize().getWidth();
privata statisk sista int SCREEN_HEIGHT = (int) Toolkit.getDefaultToolkit().getScreenSize().getHeight();
privata statisk sista int PIPE_GAP = SCREEN_HEIGHT/5; avståndet i bildpunkter mellan rören
privata statisk sista int PIPE_WIDTH = skärmbredd/8, PIPE_HEIGHT = 4 * PIPE_WIDTH;
privata statisk sista int BIRD_WIDTH = 120, BIRD_HEIGHT = 75.
privata statisk sista int UPDATE_DIFFERENCE = 25; tid i ms mellan uppdateringar
privata statisk sista int X_MOVEMENT_DIFFERENCE = 5; distansera det rör flytten varje uppdatering
privata statisk sista int SCREEN_DELAY = 300; behövs på grund av långa laddningstider tvingar rör dyka upp halva skärmen
privata statisk sista int BIRD_X_LOCATION = skärmbredd/7;
privata statisk sista int BIRD_JUMP_DIFF = 10, BIRD_FALL_DIFF = BIRD_JUMP_DIFF/2, BIRD_JUMP_HEIGHT = PIPE_GAP - BIRD_HEIGHT - BIRD_JUMP_DIFF * 2;
globala variabler
privata boolean loopVar = sant; falskt -> Kör inte slinga; -True > springa slinga för rör
privata boolean gamePlay = false; falskt -> spelet inte spelas
privata boolean birdThrust = false; falskt -> nyckel inte har tryckt att flytta fågeln vertikalt
privata boolean birdFired = false; sant -> knappen intryckt innan hoppet har slutförts
privata boolean släppt = sant; mellanslagstangenten för släppt; startar som sant så först tryck registren
privata int birdYTracker = SCREEN_HEIGHT/2 - BIRD_HEIGHT;
privata objekt buildComplete = ny objekt();
globala swing objekt
privat JFrame f = nya JFrame ("Flappy fågeln Redux");
privat JButton startGame;
privat JPanel topPanel; förklarat globalt att rymma måla verksamheten och möjliggöra removeAll(), etc.
andra globala objekt
privata statisk TopClass tc = nya TopClass();
privata statisk PlayGameScreen pgs; panel som har rörliga bakgrunden i början av spelet
/**
* Standardkonstruktör
*/
offentliga TopClass() {
}
/**
* Huvudsakliga körbara metoden anropas när du kör hittar.jar-filen
* args
*/
offentliga statisk void main (String [] args) {
bygga GUI på en ny tråd
javax.swing.SwingUtilities.invokeLater (nya Runnable() {
public void run() {
tc.buildFrame();
skapa en ny tråd för att hålla det grafiska Gränssnittet lyhörd medan spelet körs
Gänga t = nya Thread() {
public void run() {
tc.gameScreen(true);
}
};
t.start();
}
});
}
/**
-Metoden att bygga JFrame och lägga till programmet innehåll
*/
privata void buildFrame() {
Bildikon = Toolkit.getDefaultToolkit().getImage(this.getClass().getResource("resources/blue_bird.png"));
f.setContentPane(createContentPane());
f.setResizable(true);
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.setAlwaysOnTop(false);
f.setVisible(true);
f.setMinimumSize (ny Dimension (skärmbredd * 1/4, SCREEN_HEIGHT * 1/4));
f.setExtendedState(JFrame.MAXIMIZED_BOTH);
f.setIconImage(icon);
f.addKeyListener(this);
}
privat JPanel createContentPane() {
topPanel = nya JPanel(); översta JPanel i layout hierarki
topPanel.setBackground(Color.BLACK);
Tillåt oss till lagret panelerna
LayoutManager overlay = nya OverlayLayout(topPanel);
topPanel.setLayout(overlay);
Starta spelet JButton
startGame = ny JButton ("börja spela!");
startGame.setBackground(Color.BLUE);
startGame.setForeground(Color.WHITE);
startGame.setFocusable(false); snarare än bara setFocusabled(false)
startGame.setFont (nya typsnitt ("kaliber", Font.BOLD, 42));
startGame.setAlignmentX(0.5f); Centrera vågrätt på skärmen
startGame.setAlignmentY(0.5f); Centrera lodrätt på skärmen
startGame.addActionListener(this);
topPanel.add(startGame);
måste lägga till sist för att säkerställa knappens synlighet
PGS = nya PlayGameScreen (skärmbredd, SCREEN_HEIGHT, true); sant -> vi vill pgs vara startbilden
topPanel.add(pgs);
returnera topPanel;
}
/**
* Genomförandet för åtgärder händelser
*/
public void actionPerformed (ActionEvent e) {
IF(e.GetSource() == startGame) {
stoppa startbilden
loopVar = false;
fadeOperation();
}
annat if(e.getSource() == buildComplete) {
Gänga t = nya Thread() {
public void run() {
loopVar = sant;
gamePlay = sant;
tc.gameScreen(false);
}
};
t.start();
}
}
public void keyPressed (KeyEvent e) {
IF(e.getKeyCode() == KeyEvent.VK_SPACE & & gamePlay == true & & släppta == true) {
uppdatera ett booleskt värde som testas i spelet slinga att flytta fågeln
IF(birdThrust) {//need detta att registrera tryck på knapp och återställa birdYTracker före hoppet är klart
birdFired = sant;
}
birdThrust = sant;
släppt = false;
}
annat if(e.getKeyCode() == KeyEvent.VK_B & & gamePlay == false) {
birdYTracker = SCREEN_HEIGHT/2 - BIRD_HEIGHT; måste återställa fågelns start höjd
birdThrust = false; om användaren trycker utrymme innan kollisionen och en kollision inträffar innan den når max höjd, få du kvarvarande hoppa, så detta är förebyggande
actionPerformed (nya ActionEvent (startGame, -1, ""));
}
IF(e.getKeyCode() == KeyEvent.VK_ESCAPE) {
System.Exit(0);
}
}
public void keyReleased (KeyEvent e) {
IF(e.getKeyCode() == KeyEvent.VK_SPACE) {
släppt = sant;
}
}
public void keyTyped (KeyEvent e) {
}
/**
* Utföra fade operation som sker innan rundor
*/
privata void fadeOperation() {
Gänga t = nya Thread() {
public void run() {
topPanel.remove(startGame);
topPanel.remove(pgs);
topPanel.revalidate();
topPanel.repaint();
panelen att blekna
JPanel temp = nya JPanel();
int alpha = 0; alfakanal variabel
temp.setBackground (ny färg (0, 0, 0, alfa)); transparent, svart JPanel
topPanel.add(temp);
topPanel.add(pgs);
topPanel.revalidate();
topPanel.repaint();
långa currentTime = System.currentTimeMillis();
While(temp.getBackground().getAlpha()! = 255) {
IF((system.currentTimeMillis() - currentTime) > UPDATE_DIFFERENCE/2) {
om (alpha < 255-10) {
alpha + = 10.
}
annat {
alpha = 255;
}
temp.setBackground (ny färg (0, 0, 0, alfa));
topPanel.revalidate();
topPanel.repaint();
currentTime = System.currentTimeMillis();
}
}
topPanel.removeAll();
topPanel.add(temp);
PGS = nya PlayGameScreen (skärmbredd, SCREEN_HEIGHT, false);
pgs.sendText(""); ta bort rubriktext
topPanel.add(pgs);
While(temp.getBackground().getAlpha()! = 0) {
IF((system.currentTimeMillis() - currentTime) > UPDATE_DIFFERENCE/2) {
om (alfa > 10) {
alpha-= 10.
}
annat {
alpha = 0;
}
temp.setBackground (ny färg (0, 0, 0, alfa));
topPanel.revalidate();
topPanel.repaint();
currentTime = System.currentTimeMillis();
}
}
actionPerformed (nya ActionEvent (buildComplete, -1, "Bygga avslutad"));
}
};
t.start();
}
/**
* Metod som utför plaska skärm grafik rörelser
*/
privata void gameScreen (boolean isSplash) {
BottomPipe bp1 = ny BottomPipe (PIPE_WIDTH, PIPE_HEIGHT);
BottomPipe bp2 = ny BottomPipe (PIPE_WIDTH, PIPE_HEIGHT);
TopPipe tp1 = ny TopPipe (PIPE_WIDTH, PIPE_HEIGHT);
TopPipe tp2 = ny TopPipe (PIPE_WIDTH, PIPE_HEIGHT);
Fågel fågel = ny fågel (BIRD_WIDTH, BIRD_HEIGHT);
variabler att spåra x och y bild platser för nedre röret
int xLoc1 = skärmbredd + SCREEN_DELAY, xLoc2 = (int) ((dubbel) 3.0/2.0*SCREEN_WIDTH+PIPE_WIDTH/2.0)+SCREEN_DELAY;
int yLoc1 = bottomPipeLoc(), yLoc2 = bottomPipeLoc();
int birdX = BIRD_X_LOCATION, birdY = birdYTracker;
variabel att hålla slingan starttid
långa startTime = System.currentTimeMillis();
While(loopVar) {
IF((system.currentTimeMillis() - startTime) > UPDATE_DIFFERENCE) {
Kontrollera om en uppsättning rör har skärmen
om så är fallet, Återställ rörets X läge och tilldela en ny Y plats
om (xLoc1 < (0-PIPE_WIDTH)) {
xLoc1 = skärmbredd;
yLoc1 = bottomPipeLoc();
}
annars om (xLoc2 < (0-PIPE_WIDTH)) {
xLoc2 = skärmbredd;
yLoc2 = bottomPipeLoc();
}
minska på röret platser med förutbestämda belopp
xLoc1-= X_MOVEMENT_DIFFERENCE;
xLoc2-= X_MOVEMENT_DIFFERENCE;
om (birdFired & &! isSplash) {
birdYTracker = birdY;
birdFired = false;
}
om (birdThrust & &! isSplash) {
gå fågel vertikalt
om (birdYTracker - birdY - BIRD_JUMP_DIFF < BIRD_JUMP_HEIGHT) {
om (birdY - BIRD_JUMP_DIFF > 0) {
birdY-= BIRD_JUMP_DIFF; koordinater olika
}
annat {
birdY = 0;
birdYTracker = birdY;
birdThrust = false;
}
}
annat {
birdYTracker = birdY;
birdThrust = false;
}
}
annat if(!isSplash) {
birdY += BIRD_FALL_DIFF;
birdYTracker = birdY;
}
uppdatera BottomPipe och TopPipe platserna
BP1.setX(xLoc1);
BP1.setY(yLoc1);
bp2.setX(xLoc2);
bp2.setY(yLoc2);
TP1.setX(xLoc1);
TP1.setY(yLoc1-PIPE_GAP-PIPE_HEIGHT); säkerställa tp1 på rätt plats
TP2.setX(xLoc2);
TP2.setY(yLoc2-PIPE_GAP-PIPE_HEIGHT); säkerställa tp2 på rätt plats
IF(!isSplash) {
bird.setX(birdX);
bird.setY(birdY);
pgs.setBird(bird);
}
som de BottomPipe och TopPipe lokala variablerna i PlayGameScreen genom att analysera de lokala variablerna
pgs.setBottomPipe (bp1, bp2);
pgs.setTopPipe (tp1, tp2);
om (! isSplash & & bird.getWidth()! = -1) {//need den andra delen eftersom om fågeln inte på skärmen, inte kan få bildens bredd och har CSS fel i kollision
collisionDetection (bp1 bp2, tp1, tp2, fågel);
updateScore (bp1 bp2, fågel);
}
uppdatera pgs's JPanel
topPanel.revalidate();
topPanel.repaint();
uppdatera variabeln tid-spårning efter alla som har slutförts
startTime = System.currentTimeMillis();
}
}
}
/**
* Beräknar ett slumpmässigt heltal för botten rörets placering
* int
*/
privata int bottomPipeLoc() {
int temp = 0;
iterera tills temp är ett värde som gör att båda rören vara på skärmen
medan (temp < = PIPE_GAP + 50 || temp > = SCREEN_HEIGHT-PIPE_GAP) {
Temp = (int) ((dubbel) Math.random()*((double)SCREEN_HEIGHT));
}
returnera härda;
}
/**
* Metod som kontrollerar om poängen behöver uppdateras
* bp1 första BottomPipe objekt
* bp2 andra BottomPipe objektet
* fågel fågel objekt
*/
privata void updateScore (BottomPipe bp1, BottomPipe bp2, fågel fågel) {
IF(BP1.getX() + PIPE_WIDTH < bird.getX() & & bp1.getX() + PIPE_WIDTH > bird.getX() - X_MOVEMENT_DIFFERENCE) {
pgs.incrementJump();
}
annat if(bp2.getX() + PIPE_WIDTH < bird.getX() & & bp2.getX() + PIPE_WIDTH > bird.getX() - X_MOVEMENT_DIFFERENCE) {
pgs.incrementJump();
}
}
/**
-Metoden att testa om en kollision har inträffat
* bp1 första BottomPipe objekt
* bp2 andra BottomPipe objektet
* tp1 första TopPipe objekt
* tp2 andra TopPipe objektet
* fågel fågel objekt
*/
privata void collisionDetection (BottomPipe bp1 bp2 BottomPipe, TopPipe tp1, TopPipe tp2, fågel fågel) {
collisionHelper(bird.getRectangle(), bp1.getRectangle(), bird.getBI(), bp1.getBI());
collisionHelper(bird.getRectangle(), bp2.getRectangle(), bird.getBI(), bp2.getBI());
collisionHelper(bird.getRectangle(), tp1.getRectangle(), bird.getBI(), tp1.getBI());
collisionHelper(bird.getRectangle(), tp2.getRectangle(), bird.getBI(), tp2.getBI());
IF(Bird.getY() + BIRD_HEIGHT > SCREEN_HEIGHT * 7/8) {//ground upptäckt
pgs.sendText ("Game Over");
loopVar = false;
gamePlay = false; spelet har avslutats
}
}
/**
* Helper metod att testa fågel objektets potentiell kollision med en pipe-objektet.
* r1 fågelns rektangel komponent
* r2 kollision komponent rektangel
* b1 fågelns BufferedImage komponent
* b2 kollision komponent BufferedImage
*/
privata void collisionHelper (rektangel r1, rektangel r2, BufferedImage b1, BufferedImage b2) {
IF(R1.intersects(R2)) {
Rektangel r = r1.intersection(r2);
int firstI (int) = (r.getMinX() - r1.getMinX()); firstI är den första x-pixel att iterera från
int firstJ (int) = (r.getMinY() - r1.getMinY()); firstJ är den första y-pixel att iterera från
int bp1XHelper (int) = (r1.getMinX() - r2.getMinX()); helper variabler att använda när du refererar till kollision objekt
int bp1YHelper (int) = (r1.getMinY() - r2.getMinY());
för (int jag = firstI; jag < r.getWidth() + firstI; i ++) {/ /
för (int j = firstJ; j < r.getHeight() + firstJ; j ++) {
om ((b1.getRGB (i, j) & 0xFF000000)! = 0x00 & & (b2.getRGB (i + bp1XHelper, j + bp1YHelper) & 0xFF000000)! = 0x00) {
pgs.sendText ("Game Over");
loopVar = false; stoppa spelet slingan
gamePlay = false; spelet har avslutats
bryta;
}
}
}
}
}
}