Paso 9: La operación de fundido
En este momento, tenemos mucho de lo que se necesita para obtener el juego envuelto: las tuberías se mueven en la pantalla, se acaban las clases jugador y obstáculo, la clase PlayGameScreen está casi terminada, y estamos casi terminados con el TopClass.
Este paso trata de hacer mejor el flujo de juego. Si no usamos esto, tendríamos un salto abrupto de un splash screen a una pantalla de juego y sería muy desagradable para jugar. El método fadeOperation TopClass se presentará una 'simple' fundido a negro y fundido de negro para comenzar el juego.
La operación se descolora se activará mediante el método actionPerformed. Cuando la fuente de la ActionEvent es el botón startGame, cambiamos loopVar a false para contar el bucle de juego para detener la ejecución. A continuación llamamos fadeOperation().
La primera parte de la escritura fadeOperation() es a partir de un nuevo hilo para gestionar los cambios a venir. En el método run() de nuevo hilo nos quite el botón desde el panel de contenido primario (topPanel) y el panel de gráficos primero actualizar topPanel. Luego creamos un JPanel temporal (llamado temp) que vamos a añadir en la parte superior topPanel; Este es el panel que se descolora (recordar antecedentes de topPanel es negro).
Creamos una nueva variable para mantener el alfa, luego establecer fondo de temp en un JPanel negro transparente. Añadir temperatura a topPanel y añadir la instancia PaintGameScreen (pgs) detrás de eso, seguida por una actualización.
En este punto, necesitamos un tiempo bucle para realizar la transición real al negro. Como en el circuito del juego, creamos una variable que contenga el tiempo de iteración, pero esta vez, en el tiempo bucle final condición es cuando el valor alfa del grupo temp es 255 (completamente transparente). Configurar una instrucción IF para decirnos cuánto para afectar cambios. Luego usé una simple declaración If-Else para dictar cómo ocurre la fusión (en su mayoría linealmente aquí). Usted puede hacer lo que quieras aquí. Después de esto, puede establece fondo de temp y actualizar topPanel.
Una vez que el fundido a negro, nos todo lo quite de topPanel, volver a agregar el panel de temperatura, crear una nueva instancia de PlayGameScreen (reemplazar pgs), eliminar el texto de título y añadir pgs a topPanel. Para descolorarse del negro, realizamos esencialmente la lógica opuesta de la operación de fundido a negro.
Una vez completa, tenemos que informar el juego que ha permitido comenzar. Para ello, hemos creado un objeto global, llamado buildComplete. Al final de fadeOperation, activar manualmente un ActionEvent en buildComplete.
El cambio final que hacemos en este paso es el método actionPerformed. Creamos un nuevo condicional (else if) buildComplete. Aquí, creamos un nuevo hilo, y en su método run(), cambiar el loopVar a true (permitir que el reloj del juego funcionar otra vez) y llamar al método gameScreen, esta vez en falso - veremos por qué en el siguiente paso.
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 private Object buildComplete = new Object(); //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) { //stop the splash screen loopVar = false; fadeOperation(); } else if(e.getSource() == buildComplete) { Thread t = new Thread() { public void run() { loopVar = true; tc.gameScreen(false); } }; t.start(); } } /** * Perform the fade operation that takes place before the start of rounds */ private void fadeOperation() { Thread t = new Thread() { public void run() { topPanel.remove(startGame); topPanel.remove(pgs); topPanel.revalidate(); topPanel.repaint(); //panel to fade JPanel temp = new JPanel(); int alpha = 0; //alpha channel variable temp.setBackground(new Color(0, 0, 0, alpha)); //transparent, black JPanel topPanel.add(temp); topPanel.add(pgs); topPanel.revalidate(); topPanel.repaint(); long currentTime = System.currentTimeMillis(); long startTime = currentTime; while((System.currentTimeMillis() - startTime) > FADE_TIME_MILLIS || temp.getBackground().getAlpha() != 255) { if((System.currentTimeMillis() - startTime) > UPDATE_DIFFERENCE/2) { if(alpha < 255 - 10) { alpha += 10; } else { alpha = 255; } temp.setBackground(new Color(0, 0, 0, alpha)); topPanel.revalidate(); topPanel.repaint(); startTime = System.currentTimeMillis(); } } topPanel.removeAll(); topPanel.add(temp); pgs = new PlayGameScreen(SCREEN_WIDTH, SCREEN_HEIGHT, false); pgs.sendText(""); //remove title text topPanel.add(pgs); while((System.currentTimeMillis() - startTime) > FADE_TIME_MILLIS || temp.getBackground().getAlpha() != 0) { if((System.currentTimeMillis() - startTime) > UPDATE_DIFFERENCE/2) { if(alpha > 10) { alpha -= 10; } else { alpha = 0; } temp.setBackground(new Color(0, 0, 0, alpha)); topPanel.revalidate(); topPanel.repaint(); startTime = System.currentTimeMillis(); } } actionPerformed(new ActionEvent(buildComplete, -1, "Build Finished")); } }; t.start(); } /** * 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; } }