Stap 9: De Fade werking
Op dit punt in de tijd, we hebben veel van wat nodig is om het spel ingepakt te krijgen: de leidingen zijn verplaatsen op het scherm, de speler en belemmering klassen zijn afgewerkt, de klasse PlayGameScreen is bijna klaar en we zijn bijna klaar met de TopClass.
Deze stap is een kwestie van het beter maken van de game flow. Als wij dit niet gebruiken, wij hadden een plotselinge sprong van een splash-scherm naar een spelscherm en het zou erg onaangenaam om te spelen. De fadeOperation methode in TopClass zal uitvoeren van een 'eenvoudige' fade-naar-zwart en fade-uit-zwart om te beginnen met spelen van het spel.
De fade-bewerking worden geactiveerd met de methode actionPerformed. Wanneer de bron van de date de startGame-knop is, wijzigen we loopVar op false om te vertellen van de spel lus te stoppen. Hierna noemen we fadeOperation().
Het eerste deel van schrijven fadeOperation() begint een nieuwe thread om de veranderingen te komen. Binnen de methode van de nieuwe thread run(), we eerst de knop verwijderen uit de primaire inhoud deelvensters (topPanel) en de afbeeldingen en vervolgens vernieuwen van topPanel. Dan maken we een tijdelijke JPanel (genaamd temp) die wij op de top van topPanel toevoegen zullen; Dit is het paneel we vervagen (onthoud topPanel de achtergrond is zwart).
We creëren een nieuwe variabele om te houden van de alpha, dan temp de achtergrond ingesteld op een transparante zwarte JPanel. We temp aan topPanel toevoegt en het PaintGameScreen aanleg (pgs) terug op de top van dat, gevolgd door een vernieuwen.
Op dit punt moeten we een tijdje lus voor het uitvoeren van de werkelijke fade naar zwart. Zoals in de game lus, maken we een variabele om te houden van de iteratie-tijd, maar dit keer, de tijdjelijn einde voorwaarde is wanneer de temp van het panel alpha-waarde 255 (geheel doorzichtig). Een IF statement instellen om ons te vertellen hoe vaak invloed kunnen zijn op veranderingen. Ik gebruikte dan een eenvoudige If-Else-instructie om te dicteren hoe de fade plaatsvindt (meestal lineair hier). U kunt doen wat je wilt hier. Na dit, je temp de achtergrond instellen en vernieuwen van topPanel.
Zodra de fade naar zwart voltooid is, we alles uit de topPanel verwijderen, opnieuw de temp paneel toevoegen, maken van een nieuw exemplaar van de PlayGameScreen (dwingende pgs), verwijder de titeltekst, en voeg pgs terug naar topPanel. Als u wilt vervagen van zwart, voeren wij in wezen de omgekeerde logica van de fade-naar-zwart-operatie.
Eenmaal voltooid, moeten we bij het spel dat het is toegestaan om te beginnen. Om dit te doen, we een globale object met de naam buildComplete gemaakt. Aan het einde van de fadeOperation activeren we handmatig een date op buildComplete.
De laatste wijziging die we op deze stap maken is aan de actionPerformed-methode. We creëren een nieuwe voorwaardelijke (anders als) voor buildComplete. Hier maken we een nieuwe draad, en in de methode run(), wijzigen we de loopVar terug op true (het toestaan van het spel klok opnieuw uit te voeren) en de gameScreen methode aanroepen, ditmaal passeren in valse - we zullen zien waarom op de volgende stap.
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; } }