From 573300ae498ac30e74a20e8082812cfc10440380 Mon Sep 17 00:00:00 2001 From: Jan Philipp Timme Date: Sat, 29 Nov 2014 16:33:27 +0100 Subject: [PATCH] Add fading transition for changing Screens. --- src/de/teamteamteam/spacescooter/Main.java | 2 +- .../spacescooter/screen/Screen.java | 211 ++++++++++++++++-- 2 files changed, 195 insertions(+), 18 deletions(-) diff --git a/src/de/teamteamteam/spacescooter/Main.java b/src/de/teamteamteam/spacescooter/Main.java index 053af57..0a48ed3 100644 --- a/src/de/teamteamteam/spacescooter/Main.java +++ b/src/de/teamteamteam/spacescooter/Main.java @@ -33,7 +33,7 @@ public class Main { // Set up the LoadingScreen final LoadingScreen loadingScreen = new LoadingScreen(superScreen); - superScreen.setOverlay(loadingScreen); + superScreen.setOverlay(loadingScreen, false); // Initialize the GameFrame properly within the AWT EventQueue try { diff --git a/src/de/teamteamteam/spacescooter/screen/Screen.java b/src/de/teamteamteam/spacescooter/screen/Screen.java index 7e0762c..8cf54ad 100644 --- a/src/de/teamteamteam/spacescooter/screen/Screen.java +++ b/src/de/teamteamteam/spacescooter/screen/Screen.java @@ -1,7 +1,9 @@ package de.teamteamteam.spacescooter.screen; +import java.awt.Color; import java.awt.Graphics2D; +import de.teamteamteam.spacescooter.brain.GameConfig; import de.teamteamteam.spacescooter.datastructure.ConcurrentIterator; import de.teamteamteam.spacescooter.datastructure.ConcurrentLinkedList; import de.teamteamteam.spacescooter.entity.Entity; @@ -29,6 +31,18 @@ public abstract class Screen { * Parent Screen of the current Screen */ protected Screen parent; + + /** + * Temporary holder of the new Overlay, used by setOverlay() to + * implement the transitions before passing focus to the new overlay. + */ + private Screen newOverlay; + + /** + * Marker telling whether this Screen is currently processing a + * setOverlay call. + */ + private boolean processSetOverlayCall; /** * List of entities this screen is taking care of @@ -51,10 +65,29 @@ public abstract class Screen { private ConcurrentIterator collisionIteratorOne; private ConcurrentIterator collisionIteratorTwo; + /** + * Transition state, value in [0,1]. Used to implement + * the fade-to-black, fade-to-screen effects. + * Value reflects the "visibility" of the Screen, from + * 0 (not visible) to 1 (visible). + */ + private float transitionState; + + /** + * Transition setting, telling whether a transition is currently happening + * and in which direction. + * Values: -1 (fade to black), 0 (nothing happening), 1 (fade to screen) + */ + private int transitionSetting; + /** * Initialize parent, overlay and the Entity list */ public Screen(Screen parent) { + this.transitionState = 1.0F; + this.transitionSetting = 0; + this.newOverlay = null; + this.processSetOverlayCall = false; this.setOverlay(null); this.parent = parent; this.entities = new ConcurrentLinkedList(); @@ -105,6 +138,8 @@ public abstract class Screen { this.paint(g); if(this.overlay != null) { this.overlay.doPaint(g); + } else if(this.transitionState != 1.0) { + this.paintTransition(g); } } @@ -116,46 +151,133 @@ public abstract class Screen { public final void doUpdate() { if(this.overlay != null) { this.overlay.doUpdate(); + } else if (this.transitionSetting != 0) { + this.updateTransition(); } else { this.update(); } } /** - * Sets the overlay Screen for the current Screen. In case an overlay is - * being replaced, the old overlays cleanup-method is called. Also, this - * takes care of the static currentScreen value, which is accessible for - * everybody. + * Sets the overlay Screen for the current Screen. + * Triggers a fade out transition. + * After that transition, fadeOutDone() will bubble back up. + * The transition will only trigger if the new overlay is not null + * OR an existing overlay is being removed. */ public final void setOverlay(Screen screen) { - if(this.overlay != null) this.overlay.cleanup(); - if(screen == null) { + if(screen != null || (this.overlay != null && screen == null)) { + //Trigger the effect if we're fading to the new Screen. + this.processSetOverlayCall = true; + this.newOverlay = screen; + this.fadeOut(); + } else if(this.overlay == null) { + this.overlay = null; Screen.currentScreen = this; - } else { - Screen.currentScreen = screen; } - this.overlay = screen; } /** - * When a Screens life ends, because it is removed, this method will take - * care of existing overlays and remove all references to existing entities. + * There are very few occasions where the transition effect is not neccessary. + * This overloaded method is used for this purpose. */ - private final void cleanup() { - if(this.overlay != null) this.overlay.cleanup(); - //tell all entities to cleanup themselves. + public final void setOverlay(Screen screen, boolean useEffect) { + if(useEffect == true) { + this.setOverlay(screen); + } else { + if(this.overlay != null) this.overlay.cleanup(); + this.overlay = screen; + if(screen != null) { + Screen.currentScreen = screen; + } else { + Screen.currentScreen = this; + } + } + } + + /** + * A screen will pass the fadeIn() effect up the most upper + * overlay, so the transition will be visible. + * After fadeIn, a call will be bubbled up to the parent, telling + * it that the transition has finished. + */ + private void fadeIn() { + if(this.overlay != null) { + this.overlay.fadeIn(); + } else { + this.initializeTransition(1); + } + } + + /** + * A screen will pass the fadeOut() effect up the most upper + * overlay, so the transition will be visible. + * After fadeOut, a call will be bubbled up to the parent, telling + * it that the transition has finished. + */ + private void fadeOut() { + if(this.overlay != null) { + this.overlay.fadeOut(); + } else { + this.initializeTransition(-1); + } + } + + /** + * Notification bubbling upwards telling that the fade in transition is done. + */ + private void fadeInDone() { + if(this.parent != null) { + this.parent.fadeInDone(); + } + } + + /** + * Notification bubbling upwards telling that the fade out transition is done. + * In case this Screen is processing a setOverlay call, the old overlay will now receive + * the cleanup() call, and the new screen will be put active with a fade in transition. + */ + private void fadeOutDone() { + if(this.processSetOverlayCall) { + if(this.overlay != null) { + this.overlay.cleanup(); + } + if(this.newOverlay == null) { + Screen.currentScreen = this; + } else { + Screen.currentScreen = this.newOverlay; + } + this.overlay = this.newOverlay; + this.newOverlay = null; + this.fadeIn(); + this.processSetOverlayCall = false; + } else { + if(this.parent != null) { + this.parent.fadeOutDone(); + } + } + } + + /** + * When a Screens life ends, this method will take care of existing overlays + * and remove all references to existing entities. + */ + private void cleanup() { + if(this.overlay != null) { + this.overlay.cleanup(); + } this.entityUpdateIterator.reset(); while(this.entityUpdateIterator.hasNext()) { Entity e = this.entityUpdateIterator.next(); e.remove(); } } - + /** * Returns second collision iterator. * These are used for collision detection _only_! */ - public ConcurrentIterator getCollisionIteratorOne() { + public final ConcurrentIterator getCollisionIteratorOne() { return this.collisionIteratorOne; } @@ -163,7 +285,62 @@ public abstract class Screen { * Returns second collision iterator. * These are used for collision detection _only_! */ - public ConcurrentIterator getCollisionIteratorTwo() { + public final ConcurrentIterator getCollisionIteratorTwo() { return this.collisionIteratorTwo; } + + /** + * Triggered by doUpdate(). + * Updates the transitionState based on the current transitionSetting. + */ + private void updateTransition() { + this.transitionState += (0.03F * this.transitionSetting); + switch(this.transitionSetting) { + case 1: + if(this.transitionState > 1.0F) { + this.transitionState = 1.0F; + this.transitionSetting = 0; + this.fadeInDone(); + } + break; + case -1: + if(this.transitionState < 0.0F) { + this.transitionState = 0.0F; + this.transitionSetting = 0; + this.fadeOutDone(); + } + break; + default: + break; + } + } + + /** + * Kick off a transition. + * Setting tells from where to where the transition will happen. + */ + private void initializeTransition(int setting) { + if(this.transitionSetting != 0 || setting == 0) return; + switch(setting) { + case 1: + this.transitionState = 0.0F; + break; + case -1: + this.transitionState = 1.0F; + break; + default: + break; + } + this.transitionSetting = setting; + } + + /** + * Paint the transition effect. + */ + private void paintTransition(Graphics2D g) { + float alpha = (float) (1.0F - this.transitionState); + g.setColor(new Color(0.0F, 0.0F, 0.0F, alpha)); + g.fillRect(0, 0, GameConfig.windowWidth, GameConfig.windowHeight); + } + }