package org.erikaredmark.monkeyshines.menu; import java.awt.Dimension; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.GraphicsConfiguration; import java.awt.GraphicsDevice; import java.awt.GraphicsEnvironment; import java.awt.image.VolatileImage; import javax.swing.JPanel; import org.erikaredmark.monkeyshines.Bonzo; import org.erikaredmark.monkeyshines.GameConstants; import org.erikaredmark.monkeyshines.GameWorldLogic; import org.erikaredmark.monkeyshines.KeyBindings; import org.erikaredmark.monkeyshines.KeyboardInput; import org.erikaredmark.monkeyshines.World; import org.erikaredmark.monkeyshines.animation.GracePeriodAnimation; import org.erikaredmark.monkeyshines.global.SpecialSettings; import org.erikaredmark.monkeyshines.screendraw.StandardSurface; import org.erikaredmark.monkeyshines.util.GameEndCallback; import com.google.common.base.Function; /** * * Initialises a JPanel that holds the game window. This window is where all the action and * sprites will be drawn to. * <p/> * Only used in non-fullscreen mode * * @author Erika Redmark * */ @SuppressWarnings("serial") public final class GamePanel extends JPanel { private GamePanel(final KeyboardInput keys, final KeyBindings keyBindings, final GameEndCallback endGame, final World world) { super(); this.addKeyListener(keys); // Wrap the endGame callback in another callback. We intercept for drawing the end screens // if needed. final GameEndCallback endGameWrapped = new GameEndCallback() { @Override public void gameOverWin(World w) { Graphics2D g2d = (Graphics2D) getGraphics(); EndGameBonusAnimation.runOn( g2d, universe.getWorld(), repaintCallback); endGame.gameOverWin(w); } @Override public void gameOverFail(World w) { endGame.gameOverFail(w); } @Override public void gameOverEscape(World w) { endGame.gameOverEscape(w); } }; this.universe = new GameWorldLogic( keys, keyBindings, world, endGameWrapped, repaintCallback, activateGraceAnimation, SpecialSettings.isThunderbird() ); this.surface = new StandardSurface(universe); setDoubleBuffered(true); // Accommodate the UI and the game. UI is a banner and width is equal to screen width setMinimumSize(new Dimension(GameConstants.SCREEN_WIDTH, GameConstants.SCREEN_HEIGHT + GameConstants.UI_HEIGHT) ); setPreferredSize(new Dimension(GameConstants.SCREEN_WIDTH, GameConstants.SCREEN_HEIGHT + GameConstants.UI_HEIGHT) ); // Both UI and game will share the same panel setLayout(null); JPanel gameplayPanel = pickBestPanel(); gameplayPanel.setSize(GameConstants.SCREEN_WIDTH, GameConstants.SCREEN_HEIGHT + GameConstants.UI_HEIGHT); gameplayPanel.setBounds(0, 0, GameConstants.SCREEN_WIDTH, GameConstants.SCREEN_HEIGHT + GameConstants.UI_HEIGHT); add(gameplayPanel); } /** * * Constructs a GamePanel listening to the keyboard. When the game is over, the callback is called. * <p/> * Note that it takes a few seconds after this method returns for the game to start proper, as it will * start by going through the splash screen. * * @param keys * keyboard input device to register * * @param keyBindings * a binding object that determines which keys on the keyboard map to which * actions bonzo can take. * * @param endGame * callback for when the game is over * * @param world * world to start a game for * */ public static GamePanel newGamePanel(final KeyboardInput keys, final KeyBindings keyBindings, final GameEndCallback endGame, final World world) { final GamePanel panel = new GamePanel(keys, keyBindings, endGame, world); panel.setVisible(true); panel.universe.start(true); return panel; } /** * * internal method that examines the graphical hardware available for the user's configuration, * and if it supports hardware acceleration, constructs the appropriate graphics configuration * and returns a panel that will draw the main game screen in the best possible way. * * @return * best pick game panel for the current system to use * */ private JPanel pickBestPanel() { GraphicsEnvironment env = GraphicsEnvironment.getLocalGraphicsEnvironment(); GraphicsDevice mainScreen = env.getDefaultScreenDevice(); // TODO actually determine which one is better? GraphicsConfiguration primary = mainScreen.getDefaultConfiguration(); return new VolatileUIPanel(primary, universe); } // UI Panel for displaying buffered images. May not be as fast as volatile // but used when required hardware is unavailable. // TODO Currently unused. Need to test if VolatileImage works on all majour systems // private final class BufferedUIPanel extends JPanel { // private static final long serialVersionUID = 1L; // // @Override public void paint(Graphics g) { // final BufferedImage page = surface.renderBuffered(); // g.drawImage(page, 0, 0, null); // } // } // UI panel for displaying volatile images. Preferred method if hardware is // available. private final class VolatileUIPanel extends JPanel { private static final long serialVersionUID = 1L; private final GraphicsConfiguration gc; private final GameWorldLogic universe; // The panel will start the game after it is done rendering the splash screen. public VolatileUIPanel(final GraphicsConfiguration gc, final GameWorldLogic universe) { this.gc = gc; this.universe = universe; } @Override public void paint(Graphics g) { VolatileImage page = null; do { page = surface.renderVolatile(gc, !(universe.showingSplash() ) ); Graphics2D pageG2d = page.createGraphics(); try { if (graceAnimation != null) graceAnimation.paint((Graphics2D)pageG2d); } finally { pageG2d.dispose(); } } while (page.contentsLost() ); g.drawImage(page, 0, 0, null); if ( graceAnimation != null && !(graceAnimation.update() ) ) { universe.unfreeze(); graceAnimation = null; } } } /** * * Disposes of graphics and sound resources for the running game. Intended to be called during a primary * game state transition when leaving the game. * */ public void dispose() { universe.dispose(); } private final Runnable repaintCallback = new Runnable() { @Override public void run() { repaint(); } }; /** * Creates a new grace period animation object and freezes the game (not the music). Renderer will resume gameplay when * the animation indicates it is finished. */ private final Function<Bonzo, Void> activateGraceAnimation = new Function<Bonzo, Void>() { @Override public Void apply(Bonzo bonzo) { // repainting will NOT actual run the world, just paint it to allow the animation to run. universe.freeze(false, repaintCallback); graceAnimation = new GracePeriodAnimation(universe.getBonzo(), (int)((double)(GameConstants.FRAMES_PER_SECOND * 1.5)), 0, 80); return null; } }; // The actual surface that will provide drawing to this component private final GameWorldLogic universe; private final StandardSurface surface; // Initially and may be null. If non-null, will be played alongside basic game rendering. private GracePeriodAnimation graceAnimation; }