diff --git a/src/itdelatrisu/opsu/GameImage.java b/src/itdelatrisu/opsu/GameImage.java index 0cfa2208..6b81a375 100644 --- a/src/itdelatrisu/opsu/GameImage.java +++ b/src/itdelatrisu/opsu/GameImage.java @@ -407,6 +407,9 @@ protected Image process_sub(Image img, int w, int h) { /** The unscaled container height that uiscale is based on. */ private static final int UNSCALED_HEIGHT = 768; + /** Value to scale backgrounds for the parallax effect. */ + public static final float PARALLAX_SCALE = 1.008f; + /** Filename suffix for HD images. */ public static final String HD_SUFFIX = "@2x"; diff --git a/src/itdelatrisu/opsu/OptionGroup.java b/src/itdelatrisu/opsu/OptionGroup.java index c511115a..741d9590 100644 --- a/src/itdelatrisu/opsu/OptionGroup.java +++ b/src/itdelatrisu/opsu/OptionGroup.java @@ -36,7 +36,8 @@ public class OptionGroup { GameOption.SCREENSHOT_FORMAT, GameOption.DYNAMIC_BACKGROUND, GameOption.LOAD_HD_IMAGES, - GameOption.LOAD_VERBOSE + GameOption.LOAD_VERBOSE, + GameOption.PARALLAX }), new OptionGroup("Music", new GameOption[] { GameOption.MASTER_VOLUME, diff --git a/src/itdelatrisu/opsu/Options.java b/src/itdelatrisu/opsu/Options.java index 8a46e8ba..8caa4e09 100644 --- a/src/itdelatrisu/opsu/Options.java +++ b/src/itdelatrisu/opsu/Options.java @@ -617,6 +617,7 @@ public String getValueString() { val - TimeUnit.MINUTES.toSeconds(TimeUnit.SECONDS.toMinutes(val))); } }, + PARALLAX ("Parallax", "MenuParallax", "Add a parallax effect based on the current cursor position.", true), ENABLE_THEME_SONG ("Enable Theme Song", "MenuMusic", "Whether to play the theme song upon starting opsu!", true), REPLAY_SEEKING ("Replay Seeking", "ReplaySeeking", "Enable a seeking bar on the left side of the screen during replays.", false), DISABLE_UPDATER ("Disable Automatic Updates", "DisableUpdater", "Disable automatic checking for updates upon starting opsu!.", false), @@ -1144,6 +1145,12 @@ public static void setDisplayMode(Container app) { */ public static boolean useUnicodeMetadata() { return GameOption.SHOW_UNICODE.getBooleanValue(); } + /** + * Returns whether parallax is enabled. + * @return true if enabled + */ + public static boolean isParallaxEnabled() { return GameOption.PARALLAX.getBooleanValue(); } + /** * Returns whether or not to play the theme song. * @return true if enabled diff --git a/src/itdelatrisu/opsu/beatmap/Beatmap.java b/src/itdelatrisu/opsu/beatmap/Beatmap.java index dde7725d..ea7ecdce 100644 --- a/src/itdelatrisu/opsu/beatmap/Beatmap.java +++ b/src/itdelatrisu/opsu/beatmap/Beatmap.java @@ -18,6 +18,7 @@ package itdelatrisu.opsu.beatmap; +import itdelatrisu.opsu.GameImage; import itdelatrisu.opsu.Options; import java.io.File; @@ -323,11 +324,13 @@ public boolean isBackgroundLoading() { * Draws the beatmap background image. * @param width the container width * @param height the container height + * @param offsetX the x offset (from the screen center) + * @param offsetY the y offset (from the screen center) * @param alpha the alpha value * @param stretch if true, stretch to screen dimensions; otherwise, maintain aspect ratio * @return true if successful, false if any errors were produced */ - public boolean drawBackground(int width, int height, float alpha, boolean stretch) { + public boolean drawBackground(int width, int height, float offsetX, float offsetY, float alpha, boolean stretch) { if (bg == null) return false; @@ -354,9 +357,16 @@ public boolean drawBackground(int width, int height, float alpha, boolean stretc else sheight = (int) (width * bgImage.getHeight() / (float) bgImage.getWidth()); } + if (Options.isParallaxEnabled()) { + swidth = (int) (swidth * GameImage.PARALLAX_SCALE); + sheight = (int) (sheight * GameImage.PARALLAX_SCALE); + } bgImage = bgImage.getScaledCopy(swidth, sheight); bgImage.setAlpha(alpha); - bgImage.drawCentered(width / 2, height / 2); + if (!Options.isParallaxEnabled() && offsetX == 0f && offsetY == 0f) + bgImage.drawCentered(width / 2, height / 2); + else + bgImage.drawCentered(width / 2 + offsetX, height / 2 + offsetY); return true; } diff --git a/src/itdelatrisu/opsu/states/DownloadsMenu.java b/src/itdelatrisu/opsu/states/DownloadsMenu.java index da005f08..7d9af2ed 100644 --- a/src/itdelatrisu/opsu/states/DownloadsMenu.java +++ b/src/itdelatrisu/opsu/states/DownloadsMenu.java @@ -417,7 +417,15 @@ public void render(GameContainer container, StateBasedGame game, Graphics g) boolean inDropdownMenu = serverMenu.contains(mouseX, mouseY); // background - GameImage.SEARCH_BG.getImage().draw(); + Image bg = GameImage.SEARCH_BG.getImage(); + if (Options.isParallaxEnabled()) { + int offset = (int) (height * (GameImage.PARALLAX_SCALE - 1f)); + float parallaxX = -offset / 2f * (mouseX - width / 2) / (width / 2); + float parallaxY = -offset / 2f * (mouseY - height / 2) / (height / 2); + bg = bg.getScaledCopy(GameImage.PARALLAX_SCALE); + bg.drawCentered(width / 2 + parallaxX, height / 2 + parallaxY); + } else + bg.drawCentered(width / 2, height / 2); // title Fonts.LARGE.drawString(width * 0.024f, height * 0.03f, "Download Beatmaps!", Color.white); diff --git a/src/itdelatrisu/opsu/states/Game.java b/src/itdelatrisu/opsu/states/Game.java index 892a673c..ab026d4a 100644 --- a/src/itdelatrisu/opsu/states/Game.java +++ b/src/itdelatrisu/opsu/states/Game.java @@ -367,11 +367,11 @@ else if (deathTime > -1) // "Easy" mod: health bar increasing else dimLevel = 1f; } - if (Options.isDefaultPlayfieldForced() || !beatmap.drawBackground(width, height, dimLevel, false)) { - Image playfield = GameImage.MENU_BG.getImage(); - playfield.setAlpha(dimLevel); - playfield.draw(); - playfield.setAlpha(1f); + if (Options.isDefaultPlayfieldForced() || !beatmap.drawBackground(width, height, 0, 0, dimLevel, false)) { + Image bg = GameImage.MENU_BG.getImage(); + bg.setAlpha(dimLevel); + bg.drawCentered(width / 2, height / 2); + bg.setAlpha(1f); } if (GameMod.FLASHLIGHT.isActive()) diff --git a/src/itdelatrisu/opsu/states/GamePauseMenu.java b/src/itdelatrisu/opsu/states/GamePauseMenu.java index 0aac857c..4b728028 100644 --- a/src/itdelatrisu/opsu/states/GamePauseMenu.java +++ b/src/itdelatrisu/opsu/states/GamePauseMenu.java @@ -83,7 +83,7 @@ public void render(GameContainer container, StateBasedGame game, Graphics g) GameImage.PAUSE_RETRY.hasBeatmapSkinImage() || GameImage.PAUSE_BACK.hasBeatmapSkinImage(); if (!buttonsSkinned || bg.hasBeatmapSkinImage()) - bg.getImage().draw(); + bg.getImage().drawCentered(container.getWidth() / 2, container.getHeight() / 2); else g.setBackground(Color.black); diff --git a/src/itdelatrisu/opsu/states/GameRanking.java b/src/itdelatrisu/opsu/states/GameRanking.java index d17e6854..7cbeab1b 100644 --- a/src/itdelatrisu/opsu/states/GameRanking.java +++ b/src/itdelatrisu/opsu/states/GameRanking.java @@ -105,15 +105,28 @@ public void render(GameContainer container, StateBasedGame game, Graphics g) throws SlickException { int width = container.getWidth(); int height = container.getHeight(); + int mouseX = input.getMouseX(), mouseY = input.getMouseY(); Beatmap beatmap = MusicController.getBeatmap(); // background - if (!beatmap.drawBackground(width, height, 0.5f, true)) { - Image playfield = GameImage.MENU_BG.getImage(); - playfield.setAlpha(0.5f); - playfield.draw(); - playfield.setAlpha(1f); + float parallaxX = 0, parallaxY = 0; + if (Options.isParallaxEnabled()) { + int offset = (int) (height * (GameImage.PARALLAX_SCALE - 1f)); + parallaxX = -offset / 2f * (mouseX - width / 2) / (width / 2); + parallaxY = -offset / 2f * (mouseY - height / 2) / (height / 2); + } + if (!beatmap.drawBackground(width, height, parallaxX, parallaxY, 0.5f, true)) { + Image bg = GameImage.MENU_BG.getImage(); + if (Options.isParallaxEnabled()) { + bg = bg.getScaledCopy(GameImage.PARALLAX_SCALE); + bg.setAlpha(0.5f); + bg.drawCentered(width / 2 + parallaxX, height / 2 + parallaxY); + } else { + bg.setAlpha(0.5f); + bg.drawCentered(width / 2, height / 2); + bg.setAlpha(1f); + } } // ranking screen elements diff --git a/src/itdelatrisu/opsu/states/MainMenu.java b/src/itdelatrisu/opsu/states/MainMenu.java index d166ef22..812df5d0 100644 --- a/src/itdelatrisu/opsu/states/MainMenu.java +++ b/src/itdelatrisu/opsu/states/MainMenu.java @@ -238,16 +238,30 @@ public void render(GameContainer container, StateBasedGame game, Graphics g) throws SlickException { int width = container.getWidth(); int height = container.getHeight(); + int mouseX = input.getMouseX(), mouseY = input.getMouseY(); - // draw background Beatmap beatmap = MusicController.getBeatmap(); - if (Options.isDynamicBackgroundEnabled() && - beatmap != null && beatmap.drawBackground(width, height, bgAlpha.getValue(), true)) - ; + + // draw background + float parallaxX = 0, parallaxY = 0; + if (Options.isParallaxEnabled()) { + int offset = (int) (height * (GameImage.PARALLAX_SCALE - 1f)); + parallaxX = -offset / 2f * (mouseX - width / 2) / (width / 2); + parallaxY = -offset / 2f * (mouseY - height / 2) / (height / 2); + } + if (Options.isDynamicBackgroundEnabled() && beatmap != null && + beatmap.drawBackground(width, height, parallaxX, parallaxY, bgAlpha.getValue(), true)) + ; else { Image bg = GameImage.MENU_BG.getImage(); - bg.setAlpha(bgAlpha.getValue()); - bg.draw(); + if (Options.isParallaxEnabled()) { + bg = bg.getScaledCopy(GameImage.PARALLAX_SCALE); + bg.setAlpha(bgAlpha.getValue()); + bg.drawCentered(width / 2 + parallaxX, height / 2 + parallaxY); + } else { + bg.setAlpha(bgAlpha.getValue()); + bg.drawCentered(width / 2, height / 2); + } } // top/bottom horizontal bars @@ -289,7 +303,6 @@ public void render(GameContainer container, StateBasedGame game, Graphics g) musicPrevious.draw(); // draw music position bar - int mouseX = input.getMouseX(), mouseY = input.getMouseY(); g.setColor((musicPositionBarContains(mouseX, mouseY)) ? Colors.BLACK_BG_HOVER : Colors.BLACK_BG_NORMAL); g.fillRoundRect(musicBarX, musicBarY, musicBarWidth, musicBarHeight, 4); g.setColor(Color.white); diff --git a/src/itdelatrisu/opsu/states/SongMenu.java b/src/itdelatrisu/opsu/states/SongMenu.java index 2417cf1c..ada9e9a0 100644 --- a/src/itdelatrisu/opsu/states/SongMenu.java +++ b/src/itdelatrisu/opsu/states/SongMenu.java @@ -496,11 +496,23 @@ public void render(GameContainer container, StateBasedGame game, Graphics g) // background if (focusNode != null) { Beatmap focusNodeBeatmap = focusNode.getSelectedBeatmap(); - if (!focusNodeBeatmap.drawBackground(width, height, bgAlpha.getValue(), true)) { - Image playfield = GameImage.MENU_BG.getImage(); - playfield.setAlpha(bgAlpha.getValue()); - playfield.draw(); - playfield.setAlpha(1f); + float parallaxX = 0, parallaxY = 0; + if (Options.isParallaxEnabled()) { + int offset = (int) (height * (GameImage.PARALLAX_SCALE - 1f)); + parallaxX = -offset / 2f * (mouseX - width / 2) / (width / 2); + parallaxY = -offset / 2f * (mouseY - height / 2) / (height / 2); + } + if (!focusNodeBeatmap.drawBackground(width, height, parallaxX, parallaxY, bgAlpha.getValue(), true)) { + Image bg = GameImage.MENU_BG.getImage(); + if (Options.isParallaxEnabled()) { + bg = bg.getScaledCopy(GameImage.PARALLAX_SCALE); + bg.setAlpha(bgAlpha.getValue()); + bg.drawCentered(width / 2 + parallaxX, height / 2 + parallaxY); + } else { + bg.setAlpha(bgAlpha.getValue()); + bg.drawCentered(width / 2, height / 2); + bg.setAlpha(1f); + } } }