Paso 8: Dar contenido al juego: II
Ahora es el momento para crear el fondo móvil que vemos en la pantalla de bienvenida. Esto requiere añadir el método gameScreen TopClass, así como varios métodos setter en PlayGameScreen.
Comenzamos con un análisis de la adición del método gameScreen en TopClass - esto es donde reside el reloj de juego. En primer lugar, crear dos instancias de BottomPipe y TopPipe. Tan pronto como un conjunto de tubos sale de la pantalla, se reposicionó así que vienen detrás en la pantalla. Puede fijar el tubo ancho y altura variables que usted desee, pero basado en el tamaño de la pantalla para optimizar el juego para diferentes tamaños de pantalla.
El xLoc1, xLoc2, yLoc1 y yLoc2 variables de referencia las coordenadas de los dos objetos BottomPipe; ubicaciones de los objetos TopPipe estará en relación con los lugares de BottomPipe. He creado un método auxiliar llamado bottomPipeLoc() que genera un entero aleatorio que se utilizará para la coordenada de y de BottomPipe. Este número debe permitir objetos la TopPipe y BottomPipe a permanecer en la pantalla.
En la siguiente línea de código cree una variable de tipo largo para mantener la hora actual del sistema - esto nos da un valor para el tiempo el reloj de juego se inicia. Esta variable se actualiza al final de cada iteración del reloj del juego en la instrucción IF para almacenar horas de inicio posterior de cada iteración del reloj de juego. El principio del reloj del juego es seguir iterando a través el tiempo bucle hasta que la diferencia entre la hora actual del sistema y la iteración tiempo de inicio es mayor que un valor predeterminado (en milisegundos). El reloj de juego se mantenga iterando mientras loopVar es verdad; Esto sólo se cambiará en false cuando se detecta algún tipo de colisión.
El código del reloj incluye comentarios para explicar lo que está sucediendo allí. El orden es: actualizar la ubicación del elemento, y luego analizar aquellos elementos actualizados a la clase que dibuja y finalmente realmente actualizar el panel para ver los cambios.
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; } }
A continuación, podrás ver la clase de PlayGameScreen actualizada que refleje la incorporación de los métodos setter por encima. Vamos a añadir un poco de código para el método paintComponent, así la carne. Primero nos secuencialmente establecer el color de gráficos y crear el rectángulo para el cielo y la tierra, luego dibujar la linea negra entre ellos. A continuación dibujamos los elementos BottomPipe y TopPipe. Estos elementos no deben ser null (es decir, se debe haber creado) con el fin de atraerlos.
Después de esto, queremos llamar "pájaro Flappy" en letras grandes en la parte superior de la pantalla. En primer lugar establecemos una instrucción try-catch para tratar de definir el tipo. La primera fuente puede no existir en los equipos, y si no es así, que progresamos a la declaración de capturas donde se establece la fuente a una fuente más universal. También queremos que el texto esté centrado en pantalla, así que conseguir la anchura del mensaje que vamos a dibujar utilizando la línea de FontMetrics. Finalmente dibujamos el mensaje en la pantalla tras el bloque try-catch.
El siguiente cambio fue la adición de unos métodos setter simple. Estos deben ser autoexplicativos. El cambio final es la incorporación del método sendText. Haremos uso de esto cuando el juego termina para enviar "juego" que se adopte. Que texto reemplazará el texto de la variable de mensaje existentes.
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; } }