Added initial support for loading beatmap skins.

- Added a GameImage enum for more organized loading of image resources.
- Game image loading now takes place directly before each beatmap is loaded.
- Added option 'IGNORE_BEATMAP_SKINS' to disable this feature.

Other changes:
- Slight correction in readme file: apparently the JAR will not run in the osu! program folder.

Signed-off-by: Jeffrey Han <itdelatrisu@gmail.com>
This commit is contained in:
Jeffrey Han
2014-07-04 16:41:52 -04:00
parent c72b9b955a
commit 16afcaf3e6
10 changed files with 536 additions and 346 deletions

View File

@@ -18,6 +18,7 @@
package itdelatrisu.opsu.objects;
import itdelatrisu.opsu.GameImage;
import itdelatrisu.opsu.GameScore;
import itdelatrisu.opsu.MusicController;
import itdelatrisu.opsu.OsuHitObject;
@@ -27,21 +28,12 @@ import itdelatrisu.opsu.states.Options;
import org.newdawn.slick.Color;
import org.newdawn.slick.GameContainer;
import org.newdawn.slick.Image;
import org.newdawn.slick.SlickException;
/**
* Data type representing a circle object.
*/
public class Circle {
/**
* Images related to hit circles.
*/
private static Image
hitCircle, // hit circle
hitCircleOverlay, // hit circle overlay
approachCircle; // approach circle
/**
* The associated OsuHitObject.
*/
@@ -76,26 +68,11 @@ public class Circle {
public static void init(GameContainer container, float circleSize) throws SlickException {
int diameter = (int) (96 - (circleSize * 8));
diameter = diameter * container.getWidth() / 640; // convert from Osupixels (640x480)
hitCircle = new Image("hitcircle.png").getScaledCopy(diameter, diameter);
hitCircleOverlay = new Image("hitcircleoverlay.png").getScaledCopy(diameter, diameter);
approachCircle = new Image("approachcircle.png").getScaledCopy(diameter, diameter);
GameImage.HITCIRCLE.setImage(GameImage.HITCIRCLE.getImage().getScaledCopy(diameter, diameter));
GameImage.HITCIRCLE_OVERLAY.setImage(GameImage.HITCIRCLE_OVERLAY.getImage().getScaledCopy(diameter, diameter));
GameImage.APPROACHCIRCLE.setImage(GameImage.APPROACHCIRCLE.getImage().getScaledCopy(diameter, diameter));
}
/**
* Returns the hit circle image.
*/
public static Image getHitCircle() { return hitCircle; }
/**
* Returns the hit circle overlay image.
*/
public static Image getHitCircleOverlay() { return hitCircleOverlay; }
/**
* Returns the approach circle image.
*/
public static Image getApproachCircle() { return approachCircle; }
/**
* Constructor.
* @param hitObject the associated OsuHitObject
@@ -121,11 +98,12 @@ public class Circle {
if (timeDiff >= 0) {
float approachScale = 1 + (timeDiff * 2f / game.getApproachTime());
Utils.drawCentered(approachCircle.getScaledCopy(approachScale), hitObject.x, hitObject.y, color);
Utils.drawCentered(hitCircleOverlay, hitObject.x, hitObject.y, Color.white);
Utils.drawCentered(hitCircle, hitObject.x, hitObject.y, color);
Utils.drawCentered(GameImage.APPROACHCIRCLE.getImage().getScaledCopy(approachScale),
hitObject.x, hitObject.y, color);
Utils.drawCentered(GameImage.HITCIRCLE_OVERLAY.getImage(), hitObject.x, hitObject.y, Color.white);
Utils.drawCentered(GameImage.HITCIRCLE.getImage(), hitObject.x, hitObject.y, color);
score.drawSymbolNumber(hitObject.comboNumber, hitObject.x, hitObject.y,
hitCircle.getWidth() * 0.40f / score.getDefaultSymbolImage(0).getHeight());
GameImage.HITCIRCLE.getImage().getWidth() * 0.40f / score.getDefaultSymbolImage(0).getHeight());
}
}
@@ -162,7 +140,7 @@ public class Circle {
*/
public boolean mousePressed(int x, int y) {
double distance = Math.hypot(hitObject.x - x, hitObject.y - y);
int circleRadius = hitCircle.getWidth() / 2;
int circleRadius = GameImage.HITCIRCLE.getImage().getWidth() / 2;
if (distance < circleRadius) {
int result = hitResult(hitObject.time);
if (result > -1) {

View File

@@ -18,6 +18,7 @@
package itdelatrisu.opsu.objects;
import itdelatrisu.opsu.GameImage;
import itdelatrisu.opsu.GameScore;
import itdelatrisu.opsu.MusicController;
import itdelatrisu.opsu.OsuFile;
@@ -26,6 +27,8 @@ import itdelatrisu.opsu.Utils;
import itdelatrisu.opsu.states.Game;
import itdelatrisu.opsu.states.Options;
import java.io.File;
import org.newdawn.slick.Animation;
import org.newdawn.slick.Color;
import org.newdawn.slick.GameContainer;
@@ -36,14 +39,6 @@ import org.newdawn.slick.SlickException;
* Data type representing a slider object.
*/
public class Slider {
/**
* Images related to sliders.
*/
private static Image
sliderFollowCircle, // slider follow circle
reverseArrow, // reverse arrow (for repeats)
sliderTick; // slider tick
/**
* Slider ball animation.
*/
@@ -261,8 +256,8 @@ public class Slider {
* Draws the full Bezier curve to the graphics context.
*/
public void draw() {
Image hitCircle = Circle.getHitCircle();
Image hitCircleOverlay = Circle.getHitCircleOverlay();
Image hitCircle = GameImage.HITCIRCLE.getImage();
Image hitCircleOverlay = GameImage.HITCIRCLE_OVERLAY.getImage();
// draw overlay and hit circle
for (int i = curveX.length - 1; i >= 0; i--)
@@ -283,12 +278,38 @@ public class Slider {
int diameter = (int) (96 - (circleSize * 8));
diameter = diameter * container.getWidth() / 640; // convert from Osupixels (640x480)
// slider ball
if (sliderBall != null) {
for (int i = 0; i < sliderBall.getFrameCount(); i++) {
Image img = sliderBall.getImage(i);
if (!img.isDestroyed())
img.destroy();
}
}
sliderBall = new Animation();
for (int i = 0; i <= 9; i++)
sliderBall.addFrame(new Image(String.format("sliderb%d.png", i)).getScaledCopy(diameter * 118 / 128, diameter * 118 / 128), 60);
sliderFollowCircle = new Image("sliderfollowcircle.png").getScaledCopy(diameter * 259 / 128, diameter * 259 / 128);
reverseArrow = new Image("reversearrow.png").getScaledCopy(diameter, diameter);
sliderTick = new Image("sliderscorepoint.png").getScaledCopy(diameter / 4, diameter / 4);
String sliderFormat = "sliderb%d.png";
int sliderIndex = 0;
File dir = MusicController.getOsuFile().getFile().getParentFile();
File slider = new File(dir, String.format(sliderFormat, sliderIndex));
if (slider.isFile()) {
do {
sliderBall.addFrame(new Image(slider.getAbsolutePath()).getScaledCopy(diameter * 118 / 128, diameter * 118 / 128), 60);
slider = new File(dir, String.format(sliderFormat, ++sliderIndex));
} while (slider.isFile());
} else {
while (true) {
try {
Image sliderFrame = new Image(String.format(sliderFormat, sliderIndex++));
sliderBall.addFrame(sliderFrame.getScaledCopy(diameter * 118 / 128, diameter * 118 / 128), 60);
} catch (Exception e) {
break;
}
}
}
GameImage.SLIDER_FOLLOWCIRCLE.setImage(GameImage.SLIDER_FOLLOWCIRCLE.getImage().getScaledCopy(diameter * 259 / 128, diameter * 259 / 128));
GameImage.REVERSEARROW.setImage(GameImage.REVERSEARROW.getImage().getScaledCopy(diameter, diameter));
GameImage.SLIDER_TICK.setImage(GameImage.SLIDER_TICK.getImage().getScaledCopy(diameter / 4, diameter / 4));
sliderMultiplier = osu.sliderMultiplier;
sliderTickRate = osu.sliderTickRate;
@@ -310,8 +331,6 @@ public class Slider {
this.comboEnd = comboEnd;
this.bezier = new Bezier();
// calculate slider time and ticks upon first update call
}
/**
@@ -322,8 +341,8 @@ public class Slider {
public void draw(int trackPosition, boolean currentObject) {
int timeDiff = hitObject.time - trackPosition;
Image hitCircleOverlay = Circle.getHitCircleOverlay();
Image hitCircle = Circle.getHitCircle();
Image hitCircleOverlay = GameImage.HITCIRCLE_OVERLAY.getImage();
Image hitCircle = GameImage.HITCIRCLE.getImage();
// bezier
bezier.draw();
@@ -332,7 +351,7 @@ public class Slider {
if (currentObject && ticksT != null) {
for (int i = 0; i < ticksT.length; i++) {
float[] c = bezier.pointAt(ticksT[i]);
sliderTick.drawCentered(c[0], c[1]);
GameImage.SLIDER_TICK.getImage().drawCentered(c[0], c[1]);
}
}
@@ -352,19 +371,20 @@ public class Slider {
// repeats
if (hitObject.repeat - 1 > currentRepeats) {
Image arrow = GameImage.REVERSEARROW.getImage();
if (currentRepeats % 2 == 0) { // last circle
reverseArrow.setRotation(bezier.getEndAngle());
reverseArrow.drawCentered(hitObject.sliderX[lastIndex], hitObject.sliderY[lastIndex]);
arrow.setRotation(bezier.getEndAngle());
arrow.drawCentered(hitObject.sliderX[lastIndex], hitObject.sliderY[lastIndex]);
} else { // first circle
reverseArrow.setRotation(bezier.getStartAngle());
reverseArrow.drawCentered(hitObject.x, hitObject.y);
arrow.setRotation(bezier.getStartAngle());
arrow.drawCentered(hitObject.x, hitObject.y);
}
}
if (timeDiff >= 0) {
// approach circle
float approachScale = 1 + (timeDiff * 2f / game.getApproachTime());
Utils.drawCentered(Circle.getApproachCircle().getScaledCopy(approachScale),
Utils.drawCentered(GameImage.APPROACHCIRCLE.getImage().getScaledCopy(approachScale),
hitObject.x, hitObject.y, color);
} else {
float[] c = bezier.pointAt(getT(trackPosition, false));
@@ -374,7 +394,7 @@ public class Slider {
// follow circle
if (followCircleActive)
sliderFollowCircle.drawCentered(c[0], c[1]);
GameImage.SLIDER_FOLLOWCIRCLE.getImage().drawCentered(c[0], c[1]);
}
}
@@ -421,7 +441,7 @@ public class Slider {
return false;
double distance = Math.hypot(hitObject.x - x, hitObject.y - y);
int circleRadius = Circle.getHitCircle().getWidth() / 2;
int circleRadius = GameImage.HITCIRCLE.getImage().getWidth() / 2;
if (distance < circleRadius) {
int trackPosition = MusicController.getPosition();
int timeDiff = Math.abs(trackPosition - hitObject.time);
@@ -513,7 +533,7 @@ public class Slider {
// check if cursor pressed and within end circle
else if (game.isInputKeyPressed()) {
double distance = Math.hypot(hitObject.sliderX[lastIndex] - mouseX, hitObject.sliderY[lastIndex] - mouseY);
int followCircleRadius = sliderFollowCircle.getWidth() / 2;
int followCircleRadius = GameImage.SLIDER_FOLLOWCIRCLE.getImage().getWidth() / 2;
if (distance < followCircleRadius)
ticksHit++;
}
@@ -550,7 +570,7 @@ public class Slider {
// holding slider...
float[] c = bezier.pointAt(getT(trackPosition, false));
double distance = Math.hypot(c[0] - mouseX, c[1] - mouseY);
int followCircleRadius = sliderFollowCircle.getWidth() / 2;
int followCircleRadius = GameImage.SLIDER_FOLLOWCIRCLE.getImage().getWidth() / 2;
if ((game.isInputKeyPressed() && distance < followCircleRadius) || isAutoMod) {
// mouse pressed and within follow circle
followCircleActive = true;

View File

@@ -18,6 +18,7 @@
package itdelatrisu.opsu.objects;
import itdelatrisu.opsu.GameImage;
import itdelatrisu.opsu.GameScore;
import itdelatrisu.opsu.MusicController;
import itdelatrisu.opsu.OsuHitObject;
@@ -36,17 +37,6 @@ import org.newdawn.slick.SlickException;
* Data type representing a spinner object.
*/
public class Spinner {
/**
* Images related to spinners.
*/
private static Image
spinnerCircle, // spinner
spinnerApproachCircle, // spinner approach circle (for end time)
spinnerMetre, // spinner meter (subimage based on completion ratio)
// spinnerOsuImage, // spinner "OSU!" text (complete)
spinnerSpinImage, // spinner "SPIN!" text (start)
spinnerClearImage; // spinner "CLEAR" text (passed)
/**
* Container dimensions.
*/
@@ -91,12 +81,10 @@ public class Spinner {
width = container.getWidth();
height = container.getHeight();
spinnerCircle = new Image("spinner-circle.png").getScaledCopy(height * 9 / 10, height * 9 / 10);
spinnerApproachCircle = new Image("spinner-approachcircle.png").getScaledCopy(spinnerCircle.getWidth(), spinnerCircle.getHeight());
spinnerMetre = new Image("spinner-metre.png").getScaledCopy(width, height);
spinnerSpinImage = new Image("spinner-spin.png");
spinnerClearImage = new Image("spinner-clear.png");
// spinnerOsuImage = new Image("spinner-osu.png");
Image spinnerCircle = GameImage.SPINNER_CIRCLE.getImage();
GameImage.SPINNER_CIRCLE.setImage(spinnerCircle.getScaledCopy(height * 9 / 10, height * 9 / 10));
GameImage.SPINNER_APPROACHCIRCLE.setImage(GameImage.SPINNER_APPROACHCIRCLE.getImage().getScaledCopy(spinnerCircle.getWidth(), spinnerCircle.getHeight()));
GameImage.SPINNER_METRE.setImage(GameImage.SPINNER_METRE.getImage().getScaledCopy(width, height));
}
/**
@@ -135,6 +123,7 @@ public class Spinner {
return;
// spinner meter (subimage)
Image spinnerMetre = GameImage.SPINNER_METRE.getImage();
int spinnerMetreY = (spinnerComplete) ? 0 : (int) (spinnerMetre.getHeight() * (1 - (rotations / rotationsNeeded)));
Image spinnerMetreSub = spinnerMetre.getSubImage(
0, spinnerMetreY,
@@ -143,12 +132,12 @@ public class Spinner {
spinnerMetreSub.draw(0, height - spinnerMetreSub.getHeight());
// main spinner elements
spinnerCircle.drawCentered(width / 2, height / 2);
spinnerApproachCircle.getScaledCopy(1 - ((float) timeDiff / (hitObject.time - hitObject.endTime))).drawCentered(width / 2, height / 2);
spinnerSpinImage.drawCentered(width / 2, height * 3 / 4);
GameImage.SPINNER_CIRCLE.getImage().drawCentered(width / 2, height / 2);
GameImage.SPINNER_APPROACHCIRCLE.getImage().getScaledCopy(1 - ((float) timeDiff / (hitObject.time - hitObject.endTime))).drawCentered(width / 2, height / 2);
GameImage.SPINNER_SPIN.getImage().drawCentered(width / 2, height * 3 / 4);
if (spinnerComplete) {
spinnerClearImage.drawCentered(width / 2, height / 4);
GameImage.SPINNER_CLEAR.getImage().drawCentered(width / 2, height / 4);
int extraRotations = (int) (rotations - rotationsNeeded);
if (extraRotations > 0)
score.drawSymbolNumber(extraRotations * 1000, width / 2, height * 2 / 3, 1.0f);