Follow-up to #150: delayed fade-out, minor style changes.

Signed-off-by: Jeffrey Han <itdelatrisu@gmail.com>
This commit is contained in:
Jeffrey Han 2015-11-19 16:56:28 -05:00
parent b55b6c3079
commit fb789c8fc5
3 changed files with 139 additions and 49 deletions

View File

@ -54,9 +54,9 @@ import itdelatrisu.opsu.ui.UI;
import itdelatrisu.opsu.ui.animations.AnimationEquation;
import java.io.File;
import java.util.IdentityHashMap;
import java.util.LinkedList;
import java.util.Stack;
import java.util.IdentityHashMap;
import org.lwjgl.input.Keyboard;
import org.lwjgl.opengl.Display;
@ -69,6 +69,7 @@ import org.newdawn.slick.Input;
import org.newdawn.slick.SlickException;
import org.newdawn.slick.state.BasicGameState;
import org.newdawn.slick.state.StateBasedGame;
import org.newdawn.slick.state.transition.DelayedFadeOutTransition;
import org.newdawn.slick.state.transition.EmptyTransition;
import org.newdawn.slick.state.transition.FadeInTransition;
import org.newdawn.slick.state.transition.FadeOutTransition;
@ -92,10 +93,13 @@ public class Game extends BasicGameState {
}
/** Music fade-out time, in milliseconds. */
private static final int FADEOUT_TIME = 2000;
private static final int MUSIC_FADEOUT_TIME = 2000;
/** Maximal rotation over fade out, in degrees */
private static final float MAX_ROTATION = 90.0f;
/** Screen fade-out time, in milliseconds, when health hits zero. */
private static final int LOSE_FADEOUT_TIME = 500;
/** Maximum rotation, in degrees, over fade out upon death. */
private static final float MAX_ROTATION = 90f;
/** Minimum time before start of song, in milliseconds, to process skip-related actions. */
private static final int SKIP_OFFSET = 2000;
@ -185,8 +189,11 @@ public class Game extends BasicGameState {
/** System time position at death. */
private long failTime;
/** Track time position at death. */
private int failTrackTime;
/** Rotations for game objects at death. */
private IdentityHashMap<GameObject, Float> rotations;
/** Number of retries. */
@ -663,7 +670,6 @@ public class Game extends BasicGameState {
deathTime = -1;
}
// normal game update
if (!isReplay)
addReplayFrameAndRun(mouseX, mouseY, lastKeysPressed, trackPosition);
@ -823,24 +829,26 @@ public class Game extends BasicGameState {
}
}
// game over, force a restart
if (!isReplay) {
if (restart != Restart.LOSE) {
restart = Restart.LOSE;
failTime = System.currentTimeMillis();
failTrackTime = MusicController.getPosition();
MusicController.fadeOut(FADEOUT_TIME);
MusicController.pitchFadeOut(FADEOUT_TIME);
MusicController.fadeOut(MUSIC_FADEOUT_TIME);
MusicController.pitchFadeOut(MUSIC_FADEOUT_TIME);
rotations = new IdentityHashMap<GameObject, Float>();
SoundController.playSound(SoundEffect.FAIL);
//fade to pause menu
game.enterState(Opsu.STATE_GAMEPAUSEMENU, new FadeOutTransition(Color.black, FADEOUT_TIME), new FadeInTransition(Color.black));
// fade to pause menu
game.enterState(Opsu.STATE_GAMEPAUSEMENU,
new DelayedFadeOutTransition(Color.black, MUSIC_FADEOUT_TIME, MUSIC_FADEOUT_TIME - LOSE_FADEOUT_TIME),
new FadeInTransition(Color.black));
}
}
}
//don't process hit results when already lost
// don't process hit results when already lost
if (restart != Restart.LOSE) {
// update objects (loop in unlikely event of any skipped indexes)
boolean keyPressed = keys != ReplayFrame.KEY_NONE;
@ -1303,17 +1311,17 @@ public class Game extends BasicGameState {
trackPosition < beatmap.objects[objectIndex].getTime() && !beatmap.objects[objectIndex - 1].isSpinner())
lastObjectIndex = objectIndex - 1;
if (restart == Restart.LOSE) {
trackPosition = failTrackTime + (int)(System.currentTimeMillis() - failTime);
}
boolean loseState = (restart == Restart.LOSE);
if (loseState)
trackPosition = failTrackTime + (int) (System.currentTimeMillis() - failTime);
// draw hit objects in reverse order, or else overlapping objects are unreadable
// get hit objects in reverse order, or else overlapping objects are unreadable
Stack<Integer> stack = new Stack<Integer>();
for (int index = objectIndex; index < gameObjects.length && beatmap.objects[index].getTime() < trackPosition + approachTime; index++) {
stack.add(index);
// draw follow points
if (!Options.isFollowPointEnabled())
if (!Options.isFollowPointEnabled() || loseState)
continue;
if (beatmap.objects[index].isSpinner()) {
lastObjectIndex = -1;
@ -1371,40 +1379,47 @@ public class Game extends BasicGameState {
lastObjectIndex = index;
}
// draw hit objects
while (!stack.isEmpty()){
if (restart == Restart.LOSE){
int ix = stack.pop();
GameObject obj = gameObjects[ix];
// the time the object began falling
int objTime = Math.max( beatmap.objects[ix].getTime() - approachTime, failTrackTime );
float dt = (trackPosition - objTime)/(float)(FADEOUT_TIME);
int idx = stack.pop();
GameObject gameObj = gameObjects[idx];
// normal case
if (!loseState)
gameObj.draw(g, trackPosition);
// death: make objects "fall" off the screen
else {
// time the object began falling
int objTime = Math.max(beatmap.objects[idx].getTime() - approachTime, failTrackTime);
float dt = (trackPosition - objTime) / (float) (MUSIC_FADEOUT_TIME);
// would the object already be visible?
if (dt > 0) {
float rotSpeed;
//generate rotation speeds for each objects
if (rotations.containsKey(obj)){
rotSpeed = rotations.get(obj);
} else {
rotSpeed = (float)(2.0f*(Math.random()-0.5f)*MAX_ROTATION);
rotations.put(obj, rotSpeed);
}
if (dt <= 0)
continue;
g.pushTransform();
g.translate(0, dt*dt * container.getHeight());
Vec2f rotationCenter = obj.getPointAt(beatmap.objects[ix].getTime());
g.rotate(rotationCenter.x, rotationCenter.y, rotSpeed * dt);
gameObjects[ix].draw(g, trackPosition);
g.popTransform();
// generate rotation speeds for each objects
final float rotSpeed;
if (rotations.containsKey(gameObj)) {
rotSpeed = rotations.get(gameObj);
} else {
rotSpeed = (float) (2.0f * (Math.random() - 0.5f) * MAX_ROTATION);
rotations.put(gameObj, rotSpeed);
}
} else {
gameObjects[stack.pop()].draw(g, trackPosition);
g.pushTransform();
// translate and rotate the object
g.translate(0, dt * dt * container.getHeight());
Vec2f rotationCenter = gameObj.getPointAt(beatmap.objects[idx].getTime());
g.rotate(rotationCenter.x, rotationCenter.y, rotSpeed * dt);
gameObj.draw(g, trackPosition);
g.popTransform();
}
}
// draw OsuHitObjectResult objects
// draw result objects
data.drawHitResults(trackPosition);
}

View File

@ -119,13 +119,10 @@ public class GamePauseMenu extends BasicGameState {
mousePressed(Input.MOUSE_RIGHT_BUTTON, input.getMouseX(), input.getMouseY());
}
// if music faded out (i.e. health is zero), don't process any state changes before FADEOUT_TIME
boolean loseState = (gameState.getRestart() == Game.Restart.LOSE);
switch (key) {
case Input.KEY_ESCAPE:
// 'esc' will normally unpause, but will return to song menu if health is zero
if (loseState) {
if (gameState.getRestart() == Game.Restart.LOSE) {
SoundController.playSound(SoundEffect.MENUBACK);
((SongMenu) game.getState(Opsu.STATE_SONGMENU)).resetGameDataOnLoad();
MusicController.playAt(MusicController.getBeatmap().previewTime, true);
@ -163,8 +160,6 @@ public class GamePauseMenu extends BasicGameState {
return;
boolean loseState = (gameState.getRestart() == Game.Restart.LOSE);
if (continueButton.contains(x, y) && !loseState) {
SoundController.playSound(SoundEffect.MENUBACK);
gameState.setRestart(Game.Restart.FALSE);
@ -205,7 +200,6 @@ public class GamePauseMenu extends BasicGameState {
backButton.resetHover();
}
/**
* Loads all game pause/fail menu images.
*/

View File

@ -0,0 +1,81 @@
package org.newdawn.slick.state.transition;
import org.newdawn.slick.Color;
import org.newdawn.slick.GameContainer;
import org.newdawn.slick.Graphics;
import org.newdawn.slick.state.GameState;
import org.newdawn.slick.state.StateBasedGame;
/**
* A transition to fade out to a given colour after a delay.
*
* @author kevin (base)
*/
public class DelayedFadeOutTransition implements Transition {
/** The color to fade to */
private final Color color;
/** The time it takes the fade to happen */
private final int fadeTime;
/** The time it takes before the fade starts */
private final int delay;
/** The elapsed time */
private int elapsedTime = 0;
/**
* Create a new delayed fade out transition.
*/
public DelayedFadeOutTransition() { this(Color.black, 500, 0); }
/**
* Create a new delayed fade out transition.
*
* @param color The color we're going to fade out to
*/
public DelayedFadeOutTransition(Color color) { this(color, 500, 0); }
/**
* Create a new delayed fade out transition
*
* @param color The color we're going to fade out to
* @param fadeTime The time it takes the fade to occur
* @param delay The time before the fade starts (must be less than {@code fadeTime})
*/
public DelayedFadeOutTransition(Color color, int fadeTime, int delay) {
if (delay > fadeTime)
throw new IllegalArgumentException();
this.color = new Color(color);
this.color.a = 0;
this.fadeTime = fadeTime;
this.delay = delay;
}
@Override
public boolean isComplete() { return (color.a >= 1); }
@Override
public void postRender(StateBasedGame game, GameContainer container, Graphics g) {
Color old = g.getColor();
g.setColor(color);
g.fillRect(0, 0, container.getWidth() * 2, container.getHeight() * 2);
g.setColor(old);
}
@Override
public void update(StateBasedGame game, GameContainer container, int delta) {
if (elapsedTime < delay) {
elapsedTime += delta;
return;
}
color.a += delta * (1.0f / (fadeTime - delay));
if (color.a > 1)
color.a = 1;
}
@Override
public void preRender(StateBasedGame game, GameContainer container, Graphics g) {}
@Override
public void init(GameState firstState, GameState secondState) {}
}