Stap 8: Invulling van het spel: II
Nu is het tijd om te maken van de bewegende achtergrond die we op het scherm van de plons zien. Dit vereist de gameScreen-methode toe te voegen aan de TopClass, evenals verschillende setter methoden in PlayGameScreen.
We beginnen met een bespreking van de toevoeging van de gameScreen-methode in TopClass - dit is waar het spel klok verblijft. Maak eerst twee exemplaren van de BottomPipe en TopPipe. Zodra één set van buizen het scherm verlaat, zal ze worden verplaatst zodat ze terug op het scherm komen. U kunt de pijp breedte en hoogte variabelen ingesteld op wat je wilt, maar ik hen gebaseerd op de grootte van het scherm voor het optimaliseren van het spel voor verschillende scherm groottes.
De xLoc1, xLoc2, yLoc1 en yLoc2 variabelen verwijst naar de coördinaten van de twee objecten van de BottomPipe; de TopPipe objecten locaties zullen ten opzichte van de BottomPipe-locaties. Ik heb een helper methode genaamd bottomPipeLoc() dat genereert een willekeurig geheel getal dat wordt gebruikt voor de y-coördinaat van de BottomPipe. Dit nummer mag zowel de TopPipe als de BottomPipe objecten blijven op het scherm.
Op de volgende regel code maken we een variabele van het type lang om te houden van de huidige systeemtijd - dit geeft ons een waarde voor de tijd die het spel klok begint. Deze variabele wordt bijgewerkt aan het einde van iedere iteratie van het spel klok in de IF-instructie voor het opslaan van latere begintijden van elke iteratie spel klok. Het principe van het spel klok is te houden doorlopen via de while lus totdat het verschil tussen de huidige systeemtijd en de iteratie begintijd groter is dan een vooraf bepaalde waarde (in milliseconden). Het spel klok zal blijven doorlopen, zolang loopVar geldt; Dit zal alleen worden gewijzigd op false wanneer enige vorm van botsing wordt gedetecteerd.
De code van het spel klok bevat opmerkingen om uit te leggen wat er gaande is er. De volgorde is: update element locaties, dan ontleden die bijgewerkte elementen aan de klasse die hen trekt, en eindelijk daadwerkelijk het deelvenster om de wijzigingen te zien.
import java.awt.Dimension; import java.awt.Font; import java.awt.Image; import java.awt.Color; import java.awt.LayoutManager; import java.awt.Toolkit; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import javax.swing.*; public class TopClass implements ActionListener { //global constant variables private static final int SCREEN_WIDTH = (int) Toolkit.getDefaultToolkit().getScreenSize().getWidth(); private static final int SCREEN_HEIGHT = (int) Toolkit.getDefaultToolkit().getScreenSize().getHeight(); private static final int PIPE_GAP = SCREEN_HEIGHT/5; //distance in pixels between pipes private static final int PIPE_WIDTH = SCREEN_WIDTH/8, PIPE_HEIGHT = 4*PIPE_WIDTH; private static final int UPDATE_DIFFERENCE = 25; //time in ms between updates private static final int X_MOVEMENT_DIFFERENCE = 5; //distance the pipes move every update private static final int SCREEN_DELAY = 300; //needed because of long load times forcing pipes to pop up mid-screen //global variables private boolean loopVar = true; //false -> don't run loop; true -> run loop for pipes //global swing objects private JFrame f = new JFrame("Flappy Bird Redux"); private JButton startGame; private JPanel topPanel; //declared globally to accommodate the repaint operation and allow for removeAll(), etc. //other global objects private static TopClass tc = new TopClass(); private static PlayGameScreen pgs; //panel that has the moving background at the start of the game /** * Default constructor */ public TopClass() { } /** * Main executable method invoked when running .jar file * args */ public static void main(String[] args) { //build the GUI on a new thread javax.swing.SwingUtilities.invokeLater(new Runnable() { public void run() { tc.buildFrame(); //create a new thread to keep the GUI responsive while the game runs Thread t = new Thread() { public void run() { tc.gameScreen(true); } }; t.start(); } }); } /** * Method to construct the JFrame and add the program content */ private void buildFrame() { Image icon = 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(new Dimension(SCREEN_WIDTH*1/4, SCREEN_HEIGHT*1/4)); f.setExtendedState(JFrame.MAXIMIZED_BOTH); f.setIconImage(icon); f.addKeyListener(this); } private JPanel createContentPane() { topPanel = new JPanel(); //top-most JPanel in layout hierarchy topPanel.setBackground(Color.BLACK); //allow us to layer the panels LayoutManager overlay = new OverlayLayout(topPanel); topPanel.setLayout(overlay); //Start Game JButton startGame = new JButton("Start Playing!"); startGame.setBackground(Color.BLUE); startGame.setForeground(Color.WHITE); startGame.setFocusable(false); //rather than just setFocusabled(false) startGame.setFont(new Font("Calibri", Font.BOLD, 42)); startGame.setAlignmentX(0.5f); //center horizontally on-screen startGame.setAlignmentY(0.5f); //center vertically on-screen startGame.addActionListener(this); topPanel.add(startGame); //must add last to ensure button's visibility pgs = new PlayGameScreen(SCREEN_WIDTH, SCREEN_HEIGHT, true); //true --> we want pgs to be the splash screen topPanel.add(pgs); return topPanel; } /** * Implementation for action events */ public void actionPerformed(ActionEvent e) { if(e.getSource() == startGame) { //do something } } /** * Method that performs the splash screen graphics movements */ private void gameScreen(boolean isSplash) { BottomPipe bp1 = new BottomPipe(PIPE_WIDTH, PIPE_HEIGHT); BottomPipe bp2 = new BottomPipe(PIPE_WIDTH, PIPE_HEIGHT); TopPipe tp1 = new TopPipe(PIPE_WIDTH, PIPE_HEIGHT); TopPipe tp2 = new TopPipe(PIPE_WIDTH, PIPE_HEIGHT); //variables to track x and y image locations for the bottom pipe int xLoc1 = SCREEN_WIDTH+SCREEN_DELAY, xLoc2 = (int) ((double) 3.0/2.0*SCREEN_WIDTH+PIPE_WIDTH/2.0)+SCREEN_DELAY; int yLoc1 = bottomPipeLoc(), yLoc2 = bottomPipeLoc(); //variable to hold the loop start time long startTime = System.currentTimeMillis(); while(loopVar) { if((System.currentTimeMillis() - startTime) > UPDATE_DIFFERENCE) { //check if a set of pipes has left the screen //if so, reset the pipe's X location and assign a new Y location if(xLoc1 < (0-PIPE_WIDTH)) { xLoc1 = SCREEN_WIDTH; yLoc1 = bottomPipeLoc(); } else if(xLoc2 < (0-PIPE_WIDTH)) { xLoc2 = SCREEN_WIDTH; yLoc2 = bottomPipeLoc(); } //decrement the pipe locations by the predetermined amount xLoc1 -= X_MOVEMENT_DIFFERENCE; xLoc2 -= X_MOVEMENT_DIFFERENCE; //update the BottomPipe and TopPipe locations bp1.setX(xLoc1); bp1.setY(yLoc1); bp2.setX(xLoc2); bp2.setY(yLoc2); tp1.setX(xLoc1); tp1.setY(yLoc1-PIPE_GAP-PIPE_HEIGHT); //ensure tp1 placed in proper location tp2.setX(xLoc2); tp2.setY(yLoc2-PIPE_GAP-PIPE_HEIGHT); //ensure tp2 placed in proper location //set the BottomPipe and TopPipe local variables in PlayGameScreen by parsing the local variables pgs.setBottomPipe(bp1, bp2); pgs.setTopPipe(tp1, tp2); //update pgs's JPanel topPanel.revalidate(); topPanel.repaint(); //update the time-tracking variable after all operations completed startTime = System.currentTimeMillis(); } } } /** * Calculates a random int for the bottom pipe's placement * int */ private int bottomPipeLoc() { int temp = 0; //iterate until temp is a value that allows both pipes to be onscreen while(temp <= PIPE_GAP+50 || temp >= SCREEN_HEIGHT-PIPE_GAP) { temp = (int) ((double) Math.random()*((double)SCREEN_HEIGHT)); } return temp; } }
Hieronder ziet u de bijgewerkte PlayGameScreen klasse die overeenkomt met de toevoeging van de setter en methodes hierboven. We zullen het toevoegen van een beetje van code aan het uitwerken van de Paint-methode ook. We opeenvolgend de grafische kleur instellen en maakt eerst de rechthoek van de lucht en de grond en trekken de zwarte lijn tussen hen. Daarna tekenen we de items BottomPipe en TopPipe. Deze elementen moeten niet null (dat wil zeggen zij moeten zijn gemaakt) om hen te vestigen.
Na dit, wij willen "Flappy Bird" in grote letters in de buurt van de bovenkant van het scherm. Eerst instellen wij een instructie try-catch te willen het lettertype instellen. Het eerste lettertype mogelijk niet aanwezig op computers, en als dit niet gebeurt, we gaan naar de instructie catch waar het lettertype wordt ingesteld op een meer universele lettertype. Wij willen ook om te controleren of dat de berichttekst is gecentreerd op het scherm, zodat we de breedte van het bericht dat we gonna tekenen met behulp van de FontMetrics-lijn krijgen. Wij trekken ten slotte het bericht op het scherm na het poging-vangst blok.
De volgende wijziging aangebracht was de toevoeging van een paar eenvoudige setter methoden. Dit moeten worden spreekt voor zich. De laatste wijziging is de toevoeging van de sendText-methode. We zullen dit gebruiken wanneer het spel beëindigd te sturen "Game Over" moeten worden getrokken. Dat zal de bestaande bericht van variabele tekst vervangen door tekst.
import javax.swing.*; import java.awt.Font; import java.awt.FontMetrics; import java.awt.Graphics; import java.awt.Color; public class PlayGameScreen extends JPanel { //default reference ID private static final long serialVersionUID = 1L; //global variables private int screenWidth, screenHeight; private boolean isSplash = true; private String message = "Flappy Bird"; private Font primaryFont = new Font("Goudy Stout", Font.BOLD, 56), failFont = new Font("Calibri", Font.BOLD, 56); private int messageWidth = 0; private BottomPipe bp1, bp2; private TopPipe tp1, tp2; /** * Default constructor for the PlayGameScreen class */ public PlayGameScreen(int screenWidth, int screenHeight, boolean isSplash) { this.screenWidth = screenWidth; this.screenHeight = screenHeight; this.isSplash = isSplash; } /** * Manually control what's drawn on this JPanel by calling the paintComponent method * with a graphics object and painting using that object */ public void paintComponent(Graphics g) { super.paintComponent(g); g.setColor(new Color(89, 81, 247)); //color for the blue sky g.fillRect(0, 0, screenWidth, screenHeight*7/8); //create the sky rectangle g.setColor(new Color(147, 136, 9)); //brown color for ground g.fillRect(0, screenHeight*7/8, screenWidth, screenHeight/8); //create the ground rectangle g.setColor(Color.BLACK); //dividing line color g.drawLine(0, screenHeight*7/8, screenWidth, screenHeight*7/8); //draw the dividing line //objects must be instantiated before they're drawn! if(bp1 != null && bp2 != null && tp1 != null && tp2 != null) { g.drawImage(bp1.getPipe(), bp1.getX(), bp1.getY(), null); g.drawImage(bp2.getPipe(), bp2.getX(), bp2.getY(), null); g.drawImage(tp1.getPipe(), tp1.getX(), tp1.getY(), null); g.drawImage(tp2.getPipe(), tp2.getX(), tp2.getY(), null); } //needed in case the primary font does not exist try { g.setFont(primaryFont); FontMetrics metric = g.getFontMetrics(primaryFont); messageWidth = metric.stringWidth(message); } catch(Exception e) { g.setFont(failFont); FontMetrics metric = g.getFontMetrics(failFont); messageWidth = metric.stringWidth(message); } g.drawString(message, screenWidth/2-messageWidth/2, screenHeight/4); } /** * Parsing method for PlayGameScreen's global BottomPipe variables * bp1 The first BottomPipe * bp2 The second BottomPipe */ public void setBottomPipe(BottomPipe bp1, BottomPipe bp2) { this.bp1 = bp1; this.bp2 = bp2; } /** * Parsing method for PlayGameScreen's global TopPipe variables * tp1 The first TopPipe * tp2 The second TopPipe */ public void setTopPipe(TopPipe tp1, TopPipe tp2) { this.tp1 = tp1; this.tp2 = tp2; } /** * Method called to parse a message onto the screen * message The message to parse */ public void sendText(String message) { this.message = message; } }