Created a splash screen state for loading resources.

- Game now boots to splash screen, which displays parser progress.
- Added option "LoadVerbose" to disable rendering the progress text.
- Main Menu backgrounds now fade in regardless of DynamicBackground setting (as a transition from the splash screen).

Signed-off-by: Jeffrey Han <itdelatrisu@gmail.com>
This commit is contained in:
Jeffrey Han 2014-07-05 21:00:52 -04:00
parent 0dbfd18c5f
commit dea7e942a3
7 changed files with 230 additions and 43 deletions

View File

@ -25,6 +25,7 @@ import itdelatrisu.opsu.states.MainMenu;
import itdelatrisu.opsu.states.MainMenuExit; import itdelatrisu.opsu.states.MainMenuExit;
import itdelatrisu.opsu.states.Options; import itdelatrisu.opsu.states.Options;
import itdelatrisu.opsu.states.SongMenu; import itdelatrisu.opsu.states.SongMenu;
import itdelatrisu.opsu.states.Splash;
import java.io.File; import java.io.File;
import java.io.FileNotFoundException; import java.io.FileNotFoundException;
@ -61,6 +62,7 @@ public class Opsu extends StateBasedGame {
* Game states. * Game states.
*/ */
public static final int public static final int
STATE_SPLASH = 0,
STATE_MAINMENU = 1, STATE_MAINMENU = 1,
STATE_MAINMENUEXIT = 2, STATE_MAINMENUEXIT = 2,
STATE_SONGMENU = 3, STATE_SONGMENU = 3,
@ -80,6 +82,7 @@ public class Opsu extends StateBasedGame {
@Override @Override
public void initStatesList(GameContainer container) throws SlickException { public void initStatesList(GameContainer container) throws SlickException {
addState(new Splash(STATE_SPLASH));
addState(new MainMenu(STATE_MAINMENU)); addState(new MainMenu(STATE_MAINMENU));
addState(new MainMenuExit(STATE_MAINMENUEXIT)); addState(new MainMenuExit(STATE_MAINMENUEXIT));
addState(new SongMenu(STATE_SONGMENU)); addState(new SongMenu(STATE_SONGMENU));
@ -125,10 +128,17 @@ public class Opsu extends StateBasedGame {
ResourceLoader.addResourceLocation(new FileSystemLocation(new File("."))); ResourceLoader.addResourceLocation(new FileSystemLocation(new File(".")));
ResourceLoader.addResourceLocation(new FileSystemLocation(new File("./res/"))); ResourceLoader.addResourceLocation(new FileSystemLocation(new File("./res/")));
// clear the cache
if (!Options.TMP_DIR.mkdir()) {
for (File tmp : Options.TMP_DIR.listFiles())
tmp.delete();
}
Options.TMP_DIR.deleteOnExit();
// start the game // start the game
Opsu osuGame = new Opsu("opsu!"); Opsu opsu = new Opsu("opsu!");
try { try {
AppGameContainer app = new AppGameContainer(osuGame); AppGameContainer app = new AppGameContainer(opsu);
// basic game settings // basic game settings
int[] containerSize = Options.getContainerSize(); int[] containerSize = Options.getContainerSize();
@ -136,21 +146,6 @@ public class Opsu extends StateBasedGame {
String[] icons = { "icon16.png", "icon32.png" }; String[] icons = { "icon16.png", "icon32.png" };
app.setIcons(icons); app.setIcons(icons);
// parse song directory
OsuParser.parseAllFiles(Options.getBeatmapDir(),
app.getWidth(), app.getHeight()
);
// clear the cache
if (!Options.TMP_DIR.mkdir()) {
for (File tmp : Options.TMP_DIR.listFiles())
tmp.delete();
}
Options.TMP_DIR.deleteOnExit();
// load sounds
SoundController.init();
app.start(); app.start();
} catch (SlickException e) { } catch (SlickException e) {
Log.error("Error while creating game container.", e); Log.error("Error while creating game container.", e);

View File

@ -34,6 +34,21 @@ import org.newdawn.slick.util.Log;
* Parser for OSU files. * Parser for OSU files.
*/ */
public class OsuParser { public class OsuParser {
/**
* The current file being parsed.
*/
private static File currentFile;
/**
* The current directory number while parsing.
*/
private static int currentDirectoryIndex = -1;
/**
* The total number of directories to parse.
*/
private static int totalDirectories = -1;
/** /**
* The x and y multipliers for hit object coordinates. * The x and y multipliers for hit object coordinates.
*/ */
@ -62,7 +77,13 @@ public class OsuParser {
xOffset = width / 5; xOffset = width / 5;
yOffset = height / 5; yOffset = height / 5;
for (File folder : root.listFiles()) { // progress tracking
File[] folders = root.listFiles();
currentDirectoryIndex = 0;
totalDirectories = folders.length;
for (File folder : folders) {
currentDirectoryIndex++;
if (!folder.isDirectory()) if (!folder.isDirectory())
continue; continue;
File[] files = folder.listFiles(new FilenameFilter() { File[] files = folder.listFiles(new FilenameFilter() {
@ -77,6 +98,8 @@ public class OsuParser {
// create a new group entry // create a new group entry
ArrayList<OsuFile> osuFiles = new ArrayList<OsuFile>(); ArrayList<OsuFile> osuFiles = new ArrayList<OsuFile>();
for (File file : files) { for (File file : files) {
currentFile = file;
// Parse hit objects only when needed to save time/memory. // Parse hit objects only when needed to save time/memory.
// Change boolean to 'true' to parse them immediately. // Change boolean to 'true' to parse them immediately.
parseFile(file, osuFiles, false); parseFile(file, osuFiles, false);
@ -86,6 +109,10 @@ public class OsuParser {
Opsu.groups.addSongGroup(osuFiles); Opsu.groups.addSongGroup(osuFiles);
} }
} }
currentFile = null;
currentDirectoryIndex = -1;
totalDirectories = -1;
} }
/** /**
@ -554,4 +581,22 @@ public class OsuParser {
int i = file.lastIndexOf('.'); int i = file.lastIndexOf('.');
return (i != -1) ? file.substring(i + 1).toLowerCase() : ""; return (i != -1) ? file.substring(i + 1).toLowerCase() : "";
} }
/**
* Returns the name of the current file being parsed, or null if none.
*/
public static String getCurrentFileName() {
return (currentFile != null) ? currentFile.getName() : null;
}
/**
* Returns the progress of file parsing, or -1 if not parsing.
* @return the completion percent [0, 100] or -1
*/
public static int getParserProgress() {
if (currentDirectoryIndex == -1 || totalDirectories == -1)
return -1;
return currentDirectoryIndex * 100 / totalDirectories;
}
} }

View File

@ -521,7 +521,6 @@ public class Slider {
} }
} }
// end of slider // end of slider
if (overlap || trackPosition > hitObject.time + sliderTimeTotal) { if (overlap || trackPosition > hitObject.time + sliderTimeTotal) {
tickIntervals++; tickIntervals++;

View File

@ -96,9 +96,9 @@ public class MainMenu extends BasicGameState {
private Image backgroundImage; private Image backgroundImage;
/** /**
* Alpha level for fade-in effect for dynamic backgrounds. * Background alpha level (for fade-in effect).
*/ */
private float dynamicBackgroundAlpha = 0f; private float bgAlpha = 0f;
// game-related variables // game-related variables
private StateBasedGame game; private StateBasedGame game;
@ -111,8 +111,6 @@ public class MainMenu extends BasicGameState {
@Override @Override
public void init(GameContainer container, StateBasedGame game) public void init(GameContainer container, StateBasedGame game)
throws SlickException { throws SlickException {
Utils.init(container, game);
this.game = game; this.game = game;
osuStartTime = System.currentTimeMillis(); osuStartTime = System.currentTimeMillis();
@ -165,11 +163,12 @@ public class MainMenu extends BasicGameState {
// draw background // draw background
OsuFile osu = MusicController.getOsuFile(); OsuFile osu = MusicController.getOsuFile();
if (Options.isDynamicBackgroundEnabled() && if (Options.isDynamicBackgroundEnabled() &&
osu != null && osu.drawBG(width, height, dynamicBackgroundAlpha)) osu != null && osu.drawBG(width, height, bgAlpha))
; ;
else if (backgroundImage != null) else if (backgroundImage != null) {
backgroundImage.setAlpha(bgAlpha);
backgroundImage.draw(); backgroundImage.draw();
else } else
g.setBackground(Utils.COLOR_BLUE_BACKGROUND); g.setBackground(Utils.COLOR_BLUE_BACKGROUND);
// draw buttons // draw buttons
@ -224,11 +223,11 @@ public class MainMenu extends BasicGameState {
@Override @Override
public void update(GameContainer container, StateBasedGame game, int delta) public void update(GameContainer container, StateBasedGame game, int delta)
throws SlickException { throws SlickException {
// dynamic backgrounds // fade in background
if (Options.isDynamicBackgroundEnabled() && dynamicBackgroundAlpha < 0.9f) { if (bgAlpha < 0.9f) {
dynamicBackgroundAlpha += delta / 1000f; bgAlpha += delta / 1000f;
if (dynamicBackgroundAlpha > 0.9f) if (bgAlpha > 0.9f)
dynamicBackgroundAlpha = 0.9f; bgAlpha = 0.9f;
} }
// buttons // buttons
@ -296,12 +295,14 @@ public class MainMenu extends BasicGameState {
OsuGroupNode node = menu.setFocus(Opsu.groups.getRandomNode(), -1, true); OsuGroupNode node = menu.setFocus(Opsu.groups.getRandomNode(), -1, true);
if (node != null) if (node != null)
previous.add(node.index); previous.add(node.index);
dynamicBackgroundAlpha = 0f; if (Options.isDynamicBackgroundEnabled())
bgAlpha = 0f;
} else if (musicPrevious.contains(x, y)) { } else if (musicPrevious.contains(x, y)) {
if (!previous.isEmpty()) { if (!previous.isEmpty()) {
SongMenu menu = (SongMenu) game.getState(Opsu.STATE_SONGMENU); SongMenu menu = (SongMenu) game.getState(Opsu.STATE_SONGMENU);
menu.setFocus(Opsu.groups.getBaseNode(previous.pop()), -1, true); menu.setFocus(Opsu.groups.getBaseNode(previous.pop()), -1, true);
dynamicBackgroundAlpha = 0f; if (Options.isDynamicBackgroundEnabled())
bgAlpha = 0f;
} else } else
MusicController.setPosition(0); MusicController.setPosition(0);
} }

View File

@ -134,7 +134,8 @@ public class Options extends BasicGameState {
FIXED_CS, FIXED_CS,
FIXED_HP, FIXED_HP,
FIXED_AR, FIXED_AR,
FIXED_OD; FIXED_OD,
LOAD_VERBOSE;
}; };
/** /**
@ -177,7 +178,8 @@ public class Options extends BasicGameState {
GameOption.SHOW_FPS, GameOption.SHOW_FPS,
GameOption.SCREENSHOT_FORMAT, GameOption.SCREENSHOT_FORMAT,
GameOption.NEW_CURSOR, GameOption.NEW_CURSOR,
GameOption.DYNAMIC_BACKGROUND GameOption.DYNAMIC_BACKGROUND,
GameOption.LOAD_VERBOSE
}; };
/** /**
@ -341,6 +343,11 @@ public class Options extends BasicGameState {
fixedCS = 0f, fixedHP = 0f, fixedCS = 0f, fixedHP = 0f,
fixedAR = 0f, fixedOD = 0f; fixedAR = 0f, fixedOD = 0f;
/**
* Whether or not to render loading text in the splash screen.
*/
private static boolean loadVerbose = true;
/** /**
* Game option coordinate modifiers (for drawing). * Game option coordinate modifiers (for drawing).
*/ */
@ -584,6 +591,9 @@ public class Options extends BasicGameState {
case IGNORE_BEATMAP_SKINS: case IGNORE_BEATMAP_SKINS:
ignoreBeatmapSkins = !ignoreBeatmapSkins; ignoreBeatmapSkins = !ignoreBeatmapSkins;
break; break;
case LOAD_VERBOSE:
loadVerbose = !loadVerbose;
break;
default: default:
break; break;
} }
@ -750,6 +760,12 @@ public class Options extends BasicGameState {
"The song background will be used as the main menu background." "The song background will be used as the main menu background."
); );
break; break;
case LOAD_VERBOSE:
drawOption(pos, "Display Loading Text",
loadVerbose ? "Yes" : "No",
"Display loading progress in the splash screen."
);
break;
case MUSIC_VOLUME: case MUSIC_VOLUME:
drawOption(pos, "Music Volume", drawOption(pos, "Music Volume",
String.format("%d%%", musicVolume), String.format("%d%%", musicVolume),
@ -1048,6 +1064,12 @@ public class Options extends BasicGameState {
*/ */
public static float getFixedOD() { return fixedOD; } public static float getFixedOD() { return fixedOD; }
/**
* Returns whether or not to render loading text in the splash screen.
* @return true if enabled
*/
public static boolean isLoadVerbose() { return loadVerbose; }
/** /**
* Returns the current beatmap directory. * Returns the current beatmap directory.
* If invalid, this will attempt to search for the directory, * If invalid, this will attempt to search for the directory,
@ -1144,6 +1166,9 @@ public class Options extends BasicGameState {
case "DynamicBackground": case "DynamicBackground":
dynamicBackground = Boolean.parseBoolean(value); dynamicBackground = Boolean.parseBoolean(value);
break; break;
case "LoadVerbose":
loadVerbose = Boolean.parseBoolean(value);
break;
case "VolumeMusic": case "VolumeMusic":
i = Integer.parseInt(value); i = Integer.parseInt(value);
if (i >= 0 && i <= 100) if (i >= 0 && i <= 100)
@ -1243,6 +1268,8 @@ public class Options extends BasicGameState {
writer.newLine(); writer.newLine();
writer.write(String.format("DynamicBackground = %b", dynamicBackground)); writer.write(String.format("DynamicBackground = %b", dynamicBackground));
writer.newLine(); writer.newLine();
writer.write(String.format("LoadVerbose = %b", loadVerbose));
writer.newLine();
writer.write(String.format("VolumeMusic = %d", musicVolume)); writer.write(String.format("VolumeMusic = %d", musicVolume));
writer.newLine(); writer.newLine();
writer.write(String.format("VolumeEffect = %d", effectVolume)); writer.write(String.format("VolumeEffect = %d", effectVolume));

View File

@ -85,7 +85,7 @@ public class SongMenu extends BasicGameState {
/** /**
* The current sort order (SORT_* constant). * The current sort order (SORT_* constant).
*/ */
private byte currentSort; private byte currentSort = OsuGroupList.SORT_TITLE;
/** /**
* The options button (to enter the "Game Options" menu). * The options button (to enter the "Game Options" menu).
@ -135,11 +135,6 @@ public class SongMenu extends BasicGameState {
this.game = game; this.game = game;
this.input = container.getInput(); this.input = container.getInput();
// initialize song list
currentSort = OsuGroupList.SORT_TITLE;
Opsu.groups.init(currentSort);
setFocus(Opsu.groups.getRandomNode(), -1, true);
int width = container.getWidth(); int width = container.getWidth();
int height = container.getHeight(); int height = container.getHeight();

View File

@ -0,0 +1,125 @@
/*
* opsu! - an open-source osu! client
* Copyright (C) 2014 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 <http://www.gnu.org/licenses/>.
*/
package itdelatrisu.opsu.states;
import itdelatrisu.opsu.Opsu;
import itdelatrisu.opsu.OsuGroupList;
import itdelatrisu.opsu.OsuParser;
import itdelatrisu.opsu.SoundController;
import itdelatrisu.opsu.Utils;
import org.newdawn.slick.Color;
import org.newdawn.slick.GameContainer;
import org.newdawn.slick.Graphics;
import org.newdawn.slick.Image;
import org.newdawn.slick.SlickException;
import org.newdawn.slick.state.BasicGameState;
import org.newdawn.slick.state.StateBasedGame;
public class Splash extends BasicGameState {
/**
* Logo image.
*/
private Image logo;
/**
* Whether or not loading has completed.
*/
private boolean finished = false;
// game-related variables
private int state;
public Splash(int state) {
this.state = state;
}
@Override
public void init(GameContainer container, StateBasedGame game)
throws SlickException {
final int width = container.getWidth();
final int height = container.getHeight();
logo = new Image("logo.png");
logo = logo.getScaledCopy((height / 1.2f) / logo.getHeight());
logo.setAlpha(0f);
// load Utils class first (needed in other 'init' methods)
Utils.init(container, game);
// load other resources in a new thread
final SongMenu menu = (SongMenu) game.getState(Opsu.STATE_SONGMENU);
new Thread() {
@Override
public void run() {
// parse song directory
OsuParser.parseAllFiles(Options.getBeatmapDir(), width, height);
// initialize song list
Opsu.groups.init(OsuGroupList.SORT_TITLE);
menu.setFocus(Opsu.groups.getRandomNode(), -1, true);
// load sounds
SoundController.init();
finished = true;
}
}.start();
}
@Override
public void render(GameContainer container, StateBasedGame game, Graphics g)
throws SlickException {
g.setBackground(Color.black);
int width = container.getWidth();
int height = container.getHeight();
logo.drawCentered(width / 2, height / 2);
// progress tracking
String currentFile = OsuParser.getCurrentFileName();
if (currentFile != null && Options.isLoadVerbose()) {
g.setColor(Color.white);
g.setFont(Utils.FONT_MEDIUM);
int lineHeight = Utils.FONT_MEDIUM.getLineHeight();
g.drawString(
String.format("Loading... (%s%%)", OsuParser.getParserProgress()),
25, height - 25 - (lineHeight * 2)
);
g.drawString(currentFile, 25, height - 25 - lineHeight);
}
}
@Override
public void update(GameContainer container, StateBasedGame game, int delta)
throws SlickException {
// fade in logo
float alpha = logo.getAlpha();
if (alpha < 1f)
logo.setAlpha(alpha + (delta / 400f));
// change states when loading complete
if (finished && alpha >= 1f)
game.enterState(Opsu.STATE_MAINMENU);
}
@Override
public int getID() { return state; }
}