Skip to content

Commit

Permalink
Added star fountains to main menu.
Browse files Browse the repository at this point in the history
- Added MusicController methods to get measure progress (similar to beat progress).
- Workaround for inaccurate track positions after looping by not looping.
- Make sure star duration is positive.

Signed-off-by: Jeffrey Han <[email protected]>
  • Loading branch information
itdelatrisu committed Dec 23, 2016
1 parent 106891a commit 5704b8a
Show file tree
Hide file tree
Showing 5 changed files with 177 additions and 16 deletions.
2 changes: 1 addition & 1 deletion src/itdelatrisu/opsu/Options.java
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ public class Options {
private static String themeString = "theme.mp3,Rainbows,Kevin MacLeod,219350";

/** The theme song timing point string (for computing beats to pulse the logo) . */
private static String themeTimingPoint = "-1100,545.454545454545,4,1,0,100,0,0";
private static String themeTimingPoint = "-3300,545.454545454545,4,1,0,100,0,0";

/**
* Returns whether the XDG flag in the manifest (if any) is set to "true".
Expand Down
54 changes: 46 additions & 8 deletions src/itdelatrisu/opsu/audio/MusicController.java
Original file line number Diff line number Diff line change
Expand Up @@ -195,13 +195,54 @@ public static boolean isTrackLoading() {
/**
* Gets the progress of the current beat.
* @return a beat progress value [0,1) where 0 marks the current beat and
* 1 marks the next beat, or {@code null} if no beat information
* 1 marks the next beat, or {@code null} if no timing information
* is available (e.g. music paused, no timing points)
*/
public static Float getBeatProgress() {
if (!updateTimingPoint())
return null;

// calculate beat progress
int trackPosition = Math.max(0, getPosition());
double beatLength = lastTimingPoint.getBeatLength() * 100.0;
int beatTime = lastTimingPoint.getTime();
return (float) ((((trackPosition - beatTime) * 100.0) % beatLength) / beatLength);
}

/**
* Gets the progress of the current measure.
* @return a measure progress value [0,1) where 0 marks the start of the measure and
* 1 marks the start of the next measure, or {@code null} if no timing information
* is available (e.g. music paused, no timing points)
*/
public static Float getMeasureProgress() { return getMeasureProgress(1); }

/**
* Gets the progress of the current measure.
* @param k the meter multiplier
* @return a measure progress value [0,1) where 0 marks the start of the measure and
* 1 marks the start of the next measure, or {@code null} if no timing information
* is available (e.g. music paused, no timing points)
*/
public static Float getMeasureProgress(int k) {
if (!updateTimingPoint())
return null;

// calculate measure progress
int trackPosition = Math.max(0, getPosition());
double measureLength = lastTimingPoint.getBeatLength() * lastTimingPoint.getMeter() * k * 100.0;
int beatTime = lastTimingPoint.getTime();
return (float) ((((trackPosition - beatTime) * 100.0) % measureLength) / measureLength);
}

/**
* Updates the timing point information for the current track position.
* @return {@code false} if timing point information is not available, {@code true} otherwise
*/
private static boolean updateTimingPoint() {
Beatmap map = getBeatmap();
if (!isPlaying() || map == null || map.timingPoints == null || map.timingPoints.isEmpty())
return null;
return false;

// initialization
if (timingPointIndex == 0 && lastTimingPoint == null && !map.timingPoints.isEmpty()) {
Expand All @@ -221,12 +262,9 @@ public static Float getBeatProgress() {
lastTimingPoint = timingPoint;
}
if (lastTimingPoint == null)
return null; // no timing info
return false; // no timing info

// calculate beat progress
double beatLength = lastTimingPoint.getBeatLength() * 100.0;
int beatTime = lastTimingPoint.getTime();
return (float) ((((trackPosition - beatTime) * 100.0) % beatLength) / beatLength);
return true;
}

/**
Expand Down Expand Up @@ -401,7 +439,7 @@ public static void loopTrackIfEnded(boolean preview) {
public static void playThemeSong() {
Beatmap beatmap = Options.getThemeBeatmap();
if (beatmap != null) {
play(beatmap, true, false);
play(beatmap, false, false);
themePlaying = true;
}
}
Expand Down
43 changes: 38 additions & 5 deletions src/itdelatrisu/opsu/states/MainMenu.java
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
import itdelatrisu.opsu.ui.Fonts;
import itdelatrisu.opsu.ui.MenuButton;
import itdelatrisu.opsu.ui.MenuButton.Expand;
import itdelatrisu.opsu.ui.StarFountain;
import itdelatrisu.opsu.ui.UI;
import itdelatrisu.opsu.ui.animations.AnimatedValue;
import itdelatrisu.opsu.ui.animations.AnimationEquation;
Expand All @@ -53,8 +54,8 @@
import org.newdawn.slick.SlickException;
import org.newdawn.slick.state.BasicGameState;
import org.newdawn.slick.state.StateBasedGame;
import org.newdawn.slick.state.transition.FadeInTransition;
import org.newdawn.slick.state.transition.EasedFadeOutTransition;
import org.newdawn.slick.state.transition.FadeInTransition;

/**
* "Main Menu" state.
Expand Down Expand Up @@ -116,6 +117,12 @@ private enum LogoState { DEFAULT, OPENING, OPEN, CLOSING }
/** Music position bar coordinates and dimensions. */
private float musicBarX, musicBarY, musicBarWidth, musicBarHeight;

/** Last measure progress value. */
private float lastMeasureProgress = 0f;

/** The star fountain. */
private StarFountain starFountain;

// game-related variables
private GameContainer container;
private StateBasedGame game;
Expand Down Expand Up @@ -214,6 +221,9 @@ public void init(GameContainer container, StateBasedGame game)
restartButton.setHoverAnimationEquation(AnimationEquation.LINEAR);
restartButton.setHoverRotate(360);

// initialize star fountain
starFountain = new StarFountain(width, height);

// logo animations
float centerOffsetX = width / 5f;
logoOpen = new AnimatedValue(400, 0, centerOffsetX, AnimationEquation.OUT_QUAD);
Expand Down Expand Up @@ -248,6 +258,9 @@ public void render(GameContainer container, StateBasedGame game, Graphics g)
g.fillRect(0, height * 8 / 9f, width, height / 9f);
Colors.BLACK_ALPHA.a = oldAlpha;

// draw star fountain
starFountain.draw();

// draw downloads button
downloadsButton.draw();

Expand Down Expand Up @@ -329,7 +342,7 @@ public void update(GameContainer container, StateBasedGame game, int delta)
throws SlickException {
UI.update(delta);
if (MusicController.trackEnded())
nextTrack(); // end of track: go to next track
nextTrack(false); // end of track: go to next track
int mouseX = input.getMouseX(), mouseY = input.getMouseY();
logo.hoverUpdate(delta, mouseX, mouseY, 0.25f);
playButton.hoverUpdate(delta, mouseX, mouseY, 0.25f);
Expand All @@ -349,6 +362,7 @@ public void update(GameContainer container, StateBasedGame game, int delta)
noHoverUpdate |= contains;
musicNext.hoverUpdate(delta, !noHoverUpdate && musicNext.contains(mouseX, mouseY));
musicPrevious.hoverUpdate(delta, !noHoverUpdate && musicPrevious.contains(mouseX, mouseY));
starFountain.update(delta);

// window focus change: increase/decrease theme song volume
if (MusicController.isThemePlaying() &&
Expand All @@ -360,6 +374,14 @@ public void update(GameContainer container, StateBasedGame game, int delta)
if (!(Options.isDynamicBackgroundEnabled() && beatmap != null && beatmap.isBackgroundLoading()))
bgAlpha.update(delta);

// check measure progress
Float measureProgress = MusicController.getMeasureProgress(2);
if (measureProgress != null) {
if (measureProgress < lastMeasureProgress)
starFountain.burst(true);
lastMeasureProgress = measureProgress;
}

// buttons
int centerX = container.getWidth() / 2;
float currentLogoButtonAlpha;
Expand Down Expand Up @@ -432,6 +454,10 @@ public void enter(GameContainer container, StateBasedGame game)
}
}

// reset measure info
lastMeasureProgress = 0f;
starFountain.clear();

// reset button hover states if mouse is not currently hovering over the button
int mouseX = input.getMouseX(), mouseY = input.getMouseY();
if (!logo.contains(mouseX, mouseY, 0.25f))
Expand Down Expand Up @@ -489,7 +515,7 @@ public void mousePressed(int button, int x, int y) {
}
return;
} else if (musicNext.contains(x, y)) {
nextTrack();
nextTrack(true);
UI.sendBarNotification(">> Next");
return;
} else if (musicPrevious.contains(x, y)) {
Expand Down Expand Up @@ -598,7 +624,7 @@ public void keyPressed(int key, char c) {
game.enterState(Opsu.STATE_DOWNLOADSMENU, new EasedFadeOutTransition(), new FadeInTransition());
break;
case Input.KEY_R:
nextTrack();
nextTrack(true);
break;
case Input.KEY_UP:
UI.changeVolume(1);
Expand Down Expand Up @@ -656,9 +682,16 @@ public void reset() {

/**
* Plays the next track, and adds the previous one to the stack.
* @param user {@code true} if this was user-initiated, false otherwise (track end)
*/
private void nextTrack() {
private void nextTrack(boolean user) {
boolean isTheme = MusicController.isThemePlaying();
if (isTheme && !user) {
// theme was playing, restart
// NOTE: not looping due to inaccurate track positions after loop
MusicController.playAt(0, false);
return;
}
SongMenu menu = (SongMenu) game.getState(Opsu.STATE_SONGMENU);
BeatmapSetNode node = menu.setFocus(BeatmapSetList.get().getRandomNode(), -1, true, false);
boolean sameAudio = false;
Expand Down
85 changes: 85 additions & 0 deletions src/itdelatrisu/opsu/ui/StarFountain.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
package itdelatrisu.opsu.ui;

import itdelatrisu.opsu.GameImage;
import itdelatrisu.opsu.ui.animations.AnimatedValue;
import itdelatrisu.opsu.ui.animations.AnimationEquation;

import org.newdawn.slick.Image;

/**
* Star fountain consisting of two star streams.
*/
public class StarFountain {
/** The (approximate) number of stars in each burst. */
private static final int BURST_SIZE = 80;

/** Star streams. */
private final StarStream left, right;

/** Burst progress. */
private final AnimatedValue burstProgress = new AnimatedValue(800, 0, 1, AnimationEquation.LINEAR);

/**
* Initializes the star fountain.
* @param containerWidth the container width
* @param containerHeight the container height
*/
public StarFountain(int containerWidth, int containerHeight) {
Image img = GameImage.STAR2.getImage();
float xDir = containerWidth * 0.4f, yDir = containerHeight * 0.75f;
this.left = new StarStream(-img.getWidth(), containerHeight, xDir, -yDir, 0);
this.right = new StarStream(containerWidth, containerHeight, -xDir, -yDir, 0);
setStreamProperties(left);
setStreamProperties(right);
}

/**
* Sets attributes for the given star stream.
*/
private void setStreamProperties(StarStream stream) {
stream.setDirectionSpread(60f);
stream.setDurationSpread(1100, 200);
}

/**
* Draws the star fountain.
*/
public void draw() {
left.draw();
right.draw();
}

/**
* Updates the stars in the fountain by a delta interval.
* @param delta the delta interval since the last call
*/
public void update(int delta) {
left.update(delta);
right.update(delta);
if (burstProgress.update(delta)) {
int size = Math.round((float) delta / burstProgress.getDuration() * BURST_SIZE);
left.burst(size);
right.burst(size);
}
}

/**
* Creates a burst of stars to be processed during the next {@link #update(int)} call.
* @param wait if {@code true}, will not burst if a previous burst is in progress
*/
public void burst(boolean wait) {
if (wait && (burstProgress.getTime() < burstProgress.getDuration() || !left.isEmpty() || !right.isEmpty()))
return;

burstProgress.setTime(0);
}

/**
* Clears the stars currently in the fountain.
*/
public void clear() {
left.clear();
right.clear();
burstProgress.setTime(burstProgress.getDuration());
}
}
9 changes: 7 additions & 2 deletions src/itdelatrisu/opsu/ui/StarStream.java
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
import org.newdawn.slick.Image;

/**
* Horizontal star stream.
* Star stream.
*/
public class StarStream {
/** The origin of the star stream. */
Expand Down Expand Up @@ -196,7 +196,7 @@ private Star createStar() {
Vec2f offset = position.cpy().add(direction.cpy().nor().normalize().scale((float) getGaussian(0, positionSpread)));
Vec2f dir = direction.cpy().scale(distanceRatio).add((float) getGaussian(0, directionSpread), (float) getGaussian(0, directionSpread));
int angle = (int) getGaussian(0, 22.5);
int duration = (int) (distanceRatio * getGaussian(durationBase, durationSpread));
int duration = Math.max(0, (int) (distanceRatio * getGaussian(durationBase, durationSpread)));
AnimationEquation eqn = random.nextBoolean() ? AnimationEquation.IN_OUT_QUAD : AnimationEquation.OUT_QUAD;

return new Star(offset, dir, angle, duration, eqn);
Expand All @@ -216,6 +216,11 @@ public void burst(int count) {
*/
public void clear() { stars.clear(); }

/**
* Returns whether there are any stars currently in this stream.
*/
public boolean isEmpty() { return stars.isEmpty(); }

/**
* Returns the next pseudorandom, Gaussian ("normally") distributed {@code double} value
* with the given mean and standard deviation.
Expand Down

0 comments on commit 5704b8a

Please sign in to comment.