diff --git a/res/star2.png b/res/star2.png new file mode 100644 index 00000000..c2160edc Binary files /dev/null and b/res/star2.png differ diff --git a/src/itdelatrisu/opsu/GameImage.java b/src/itdelatrisu/opsu/GameImage.java index 22e20f0e..e40d3874 100644 --- a/src/itdelatrisu/opsu/GameImage.java +++ b/src/itdelatrisu/opsu/GameImage.java @@ -298,6 +298,12 @@ public enum GameImage { return img.getScaledCopy((MENU_BUTTON_BG.getImage().getHeight() * 0.16f) / img.getHeight()); } }, + STAR2 ("star2", "png", false, false) { + @Override + protected Image process_sub(Image img, int w, int h) { + return img.getScaledCopy((MENU_BUTTON_BG.getImage().getHeight() * 0.33f) / img.getHeight()); + } + }, // Music Player Buttons MUSIC_PLAY ("music-play", "png", false, false), diff --git a/src/itdelatrisu/opsu/states/SongMenu.java b/src/itdelatrisu/opsu/states/SongMenu.java index 50b373fa..8fbdf8b1 100644 --- a/src/itdelatrisu/opsu/states/SongMenu.java +++ b/src/itdelatrisu/opsu/states/SongMenu.java @@ -30,23 +30,24 @@ import itdelatrisu.opsu.audio.MultiClip; import itdelatrisu.opsu.audio.MusicController; import itdelatrisu.opsu.audio.SoundController; import itdelatrisu.opsu.audio.SoundEffect; -import itdelatrisu.opsu.beatmap.BeatmapDifficultyCalculator; import itdelatrisu.opsu.beatmap.Beatmap; +import itdelatrisu.opsu.beatmap.BeatmapDifficultyCalculator; import itdelatrisu.opsu.beatmap.BeatmapParser; import itdelatrisu.opsu.beatmap.BeatmapSet; import itdelatrisu.opsu.beatmap.BeatmapSetList; import itdelatrisu.opsu.beatmap.BeatmapSetNode; import itdelatrisu.opsu.beatmap.BeatmapSortOrder; import itdelatrisu.opsu.beatmap.BeatmapWatchService; +import itdelatrisu.opsu.beatmap.BeatmapWatchService.BeatmapWatchServiceListener; import itdelatrisu.opsu.beatmap.LRUCache; import itdelatrisu.opsu.beatmap.OszUnpacker; -import itdelatrisu.opsu.beatmap.BeatmapWatchService.BeatmapWatchServiceListener; import itdelatrisu.opsu.db.BeatmapDB; import itdelatrisu.opsu.db.ScoreDB; import itdelatrisu.opsu.states.ButtonMenu.MenuState; import itdelatrisu.opsu.ui.Colors; import itdelatrisu.opsu.ui.Fonts; import itdelatrisu.opsu.ui.MenuButton; +import itdelatrisu.opsu.ui.StarStream; import itdelatrisu.opsu.ui.UI; import itdelatrisu.opsu.ui.animations.AnimatedValue; import itdelatrisu.opsu.ui.animations.AnimationEquation; @@ -245,6 +246,9 @@ public class SongMenu extends BasicGameState { } }; + /** The star stream. */ + private StarStream starStream; + // game-related variables private GameContainer container; private StateBasedGame game; @@ -336,6 +340,9 @@ public class SongMenu extends BasicGameState { } } }); + + // star stream + starStream = new StarStream(width, height); } @Override @@ -354,6 +361,9 @@ public class SongMenu extends BasicGameState { GameImage.PLAYFIELD.getImage().draw(); } + // star stream + starStream.draw(); + // song buttons BeatmapSetNode node = startNode; int songButtonIndex = 0; @@ -558,6 +568,9 @@ public class SongMenu extends BasicGameState { bgAlpha.update(delta); } + // star stream + starStream.update(delta); + // search search.setFocus(true); searchTimer += delta; @@ -1010,6 +1023,7 @@ public class SongMenu extends BasicGameState { searchTransitionTimer = SEARCH_TRANSITION_TIME; songInfo = null; bgAlpha.setTime(bgAlpha.getDuration()); + starStream.clear(); // reset song stack randomStack = new Stack(); diff --git a/src/itdelatrisu/opsu/ui/StarStream.java b/src/itdelatrisu/opsu/ui/StarStream.java new file mode 100644 index 00000000..3d79aa52 --- /dev/null +++ b/src/itdelatrisu/opsu/ui/StarStream.java @@ -0,0 +1,158 @@ +/* + * opsu! - an open-source osu! client + * Copyright (C) 2014, 2015 Jeffrey Han + * + * opsu! is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * opsu! is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with opsu!. If not, see . + */ + +package itdelatrisu.opsu.ui; + +import itdelatrisu.opsu.GameImage; +import itdelatrisu.opsu.Utils; +import itdelatrisu.opsu.ui.animations.AnimatedValue; +import itdelatrisu.opsu.ui.animations.AnimationEquation; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Random; + +import org.newdawn.slick.Image; + +/** + * Horizontal star stream. + */ +public class StarStream { + /** The container dimensions. */ + private final int containerWidth, containerHeight; + + /** The star image. */ + private final Image starImg; + + /** The current list of stars. */ + private final List stars; + + /** The maximum number of stars to draw at once. */ + private static final int MAX_STARS = 20; + + /** Random number generator instance. */ + private final Random random; + + /** Contains data for a single star. */ + private class Star { + /** The star animation progress. */ + private final AnimatedValue animatedValue; + + /** The star properties. */ + private final int distance, yOffset, angle; + + /** + * Creates a star with the given properties. + * @param duration the time, in milliseconds, to show the star + * @param distance the distance for the star to travel in {@code duration} + * @param yOffset the vertical offset from the center of the container + * @param angle the rotation angle + * @param eqn the animation equation to use + */ + public Star(int duration, int distance, int yOffset, int angle, AnimationEquation eqn) { + this.animatedValue = new AnimatedValue(duration, 0f, 1f, eqn); + this.distance = distance; + this.yOffset = yOffset; + this.angle = angle; + } + + /** + * Draws the star. + */ + public void draw() { + float t = animatedValue.getValue(); + starImg.setAlpha(Math.min((1 - t) * 5f, 1f)); + starImg.setRotation(angle); + starImg.draw(containerWidth - (distance * t), ((containerHeight - starImg.getHeight()) / 2) + yOffset); + } + + /** + * Updates the animation by a delta interval. + * @param delta the delta interval since the last call + * @return true if an update was applied, false if the animation was not updated + */ + public boolean update(int delta) { return animatedValue.update(delta); } + } + + /** + * Initializes the star stream. + * @param width the container width + * @param height the container height + */ + public StarStream(int width, int height) { + this.containerWidth = width; + this.containerHeight = height; + this.starImg = GameImage.STAR2.getImage().copy(); + this.stars = new ArrayList(); + this.random = new Random(); + } + + /** + * Draws the star stream. + */ + public void draw() { + for (Star star : stars) + star.draw(); + } + + /** + * Updates the stars in the stream by a delta interval. + * @param delta the delta interval since the last call + */ + public void update(int delta) { + // update current stars + Iterator iter = stars.iterator(); + while (iter.hasNext()) { + Star star = iter.next(); + if (!star.update(delta)) + iter.remove(); + } + + // create new stars + for (int i = stars.size(); i < MAX_STARS; i++) { + if (Math.random() < ((i < 5) ? 0.25 : 0.66)) + break; + + // generate star properties + float distanceRatio = Utils.clamp((float) getGaussian(0.65, 0.25), 0.2f, 0.925f); + int distance = (int) (containerWidth * distanceRatio); + int duration = (int) (distanceRatio * getGaussian(1300, 300)); + int yOffset = (int) getGaussian(0, containerHeight / 20); + int angle = (int) getGaussian(0, 22.5); + AnimationEquation eqn = random.nextBoolean() ? AnimationEquation.IN_OUT_QUAD : AnimationEquation.OUT_QUAD; + + stars.add(new Star(duration, distance, angle, yOffset, eqn)); + } + } + + /** + * Clears the stars currently in the stream. + */ + public void clear() { stars.clear(); } + + /** + * Returns the next pseudorandom, Gaussian ("normally") distributed {@code double} value + * with the given mean and standard deviation. + * @param mean the mean + * @param stdDev the standard deviation + */ + private double getGaussian(double mean, double stdDev) { + return mean + random.nextGaussian() * stdDev; + } +}