package de.onyxbits.bureauengine.screen; import com.badlogic.gdx.Screen; import com.badlogic.gdx.Game; import com.badlogic.gdx.Gdx; import com.badlogic.gdx.graphics.Pixmap; import com.badlogic.gdx.graphics.Pixmap.Format; import com.badlogic.gdx.graphics.Color; import com.badlogic.gdx.audio.Music; import com.badlogic.gdx.graphics.Texture; import de.onyxbits.bureauengine.BureauGame; /** * Implements a fading over effect between two screens (fade to black, fade from black). Note: * music is faded as well, meaning, this class will manipulate the <code>Music</code> object * on the target screen, potentially setting it to null or even replacing it. */ public class FadeOverScreen implements Screen { private Screen fromScreen; private BureauScreen toScreen; private BureauGame game; private int state; private int screenWidth,screenHeight; /** * timespan for fading in/out */ private float fadeDuration; /** * Accumulator for the fading */ private float fadeTime; /** * Progress status of the fading process */ private float fadePercent; /** * Fading state */ private static final int FADEOUT=1; /** * Fading state */ private static final int MIDWAY=2; /** * Fading state */ private static final int SKIP=3; /** * Fading state */ private static final int FADEIN=4; /** * Overlay for producing the fade effect */ private Texture blankTexture; /** * Fade over to another screen * @param toScreen target screen. * @param time time in seconds for how long the fading in/out takes each. */ public void fadeTo(BureauScreen toScreen, float time) { this.toScreen=toScreen; this.game=toScreen.game; this.fromScreen=game.getScreen(); // If this is null, someone is using the engine in a funky way. state=FADEOUT; screenWidth=Gdx.graphics.getWidth(); screenHeight=Gdx.graphics.getWidth(); fadeDuration=time; fadePercent=0; fadeTime=0; Gdx.input.setInputProcessor(null); game.setScreen(this); } @Override public void render(float delta) { switch(state) { case FADEOUT: { fromScreen.render(delta); if (fade(delta)) { state=MIDWAY; } break; } case MIDWAY: { // The overlay is fully opaque now -> we can get away with stalling the rendering thread // without the player noticing. fadePercent=0; fadeTime=0; if ((fromScreen instanceof BureauScreen) && ((BureauScreen)fromScreen).music!=null) { ((BureauScreen)fromScreen).music.stop(); } toScreen.prepareAssets(true); toScreen.readyScreen(); if (toScreen.music!=null) { toScreen.music.setVolume(0); if (!game.muteManager.isMusicMuted()) toScreen.music.play(); } fromScreen.dispose(); fromScreen=null; System.gc(); state=SKIP; break; } case SKIP: { // Skip the first frame on toScreen. We have to do this since textures can only be loaded // on the GL thread, potentially stalling it and resulting in a spike in delta time. Such // spikes mess with the fading math. Easiest way to compensate is by throwing one frame away. // Note: an alternative approach would be to call AssetManager.update() on every odd frame while // fading out and skip the actual rendering for those frames. However, tests showed that this // produces very choppy fading. state=FADEIN; break; } case FADEIN: { toScreen.render(delta); if (fade(delta)) { // We have to do a little dance with the music here. Game.setScreen() will call // BureauScreen.show() and that will call Music.play(). Ideally, Music.play() should // do nothing if it is already playing, but not all backends seem to implement it that // way. So, in order to prevent an audio glitch, take it away temporarily. // FIXME: Wouldn't it be better to use a NullMusic object here? Someone implementing a // screen may not be aware of this little hack and run into a nullpointerexcpetion. On // the other hand: its another object to lug around and may cause even weirder and harder // to debug side effects. Music tmp = toScreen.music; toScreen.music=null; game.setScreen(toScreen); toScreen.music=tmp; toScreen=null; } break; } } } /** * Fade this screen in or out. * @param delta detla from <code>render(delta)</code> * @return true when the screen is fully faded. */ private boolean fade(float delta) { fadeTime += delta; if (fadeTime>=fadeDuration) { fadePercent = 1; } else { fadePercent = fadeTime / fadeDuration; } Color tmp = game.spriteBatch.getColor(); game.spriteBatch.begin(); if (state==FADEOUT) { if ((fromScreen instanceof BureauScreen) && ((BureauScreen)fromScreen).music!=null) { ((BureauScreen)fromScreen).music.setVolume(1f-fadePercent); } game.spriteBatch.setColor(0,0,0,fadePercent); } if (state==FADEIN) { if (toScreen.music!=null) { toScreen.music.setVolume(fadePercent); } game.spriteBatch.setColor(0,0,0,1f-fadePercent); } game.spriteBatch.draw(blankTexture,0,0,screenWidth,screenHeight); game.spriteBatch.end(); game.spriteBatch.setColor(tmp); return fadePercent==1; } /** * (re-)Create the blank texture */ private void dynamicTextures() { if (blankTexture!=null) blankTexture.dispose(); Pixmap pix = new Pixmap(2, 2, Format.RGBA8888); pix.setColor(Color.BLACK); pix.fill(); blankTexture=new Texture(pix); pix.dispose(); } @Override public void show() { dynamicTextures(); } @Override public void hide() { } @Override public void resume() { dynamicTextures(); } @Override public void resize(int w, int h) { dynamicTextures(); screenWidth=w; screenHeight=h; } @Override public void pause() {} @Override public void dispose() { if (blankTexture!=null) blankTexture.dispose(); } }