Spinner updates and animated score percentage display.

- Added spinner RPM display. (image by @kouyang)
- Added "osu!" image for completed spinners.
- Changed spinner hit result ratios to be slightly harder.

Signed-off-by: Jeffrey Han <itdelatrisu@gmail.com>
This commit is contained in:
Jeffrey Han 2015-02-20 16:56:41 -05:00
parent e6f85e9c5c
commit 020dfbde32
6 changed files with 73 additions and 34 deletions

BIN
res/spinner-rpm.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

View File

@ -39,6 +39,9 @@ public class GameData {
/** Delta multiplier for steady HP drain. */ /** Delta multiplier for steady HP drain. */
public static final float HP_DRAIN_MULTIPLIER = 1 / 200f; public static final float HP_DRAIN_MULTIPLIER = 1 / 200f;
/** Time, in milliseconds, for a hit result to fade. */
public static final int HITRESULT_FADE_TIME = 500;
/** Time, in milliseconds, for a hit error tick to fade. */ /** Time, in milliseconds, for a hit error tick to fade. */
private static final int HIT_ERROR_FADE_TIME = 5000; private static final int HIT_ERROR_FADE_TIME = 5000;
@ -207,6 +210,9 @@ public class GameData {
/** Combo color. */ /** Combo color. */
public Color color; public Color color;
/** Whether the hit object was a spinner. */
public boolean isSpinner;
/** Alpha level (for fading out). */ /** Alpha level (for fading out). */
public float alpha = 1f; public float alpha = 1f;
@ -217,13 +223,15 @@ public class GameData {
* @param x the center x coordinate * @param x the center x coordinate
* @param y the center y coordinate * @param y the center y coordinate
* @param color the color of the hit object * @param color the color of the hit object
* @param isSpinner whether the hit object was a spinner
*/ */
public OsuHitObjectResult(int time, int result, float x, float y, Color color) { public OsuHitObjectResult(int time, int result, float x, float y, Color color, boolean isSpinner) {
this.time = time; this.time = time;
this.result = result; this.result = result;
this.x = x; this.x = x;
this.y = y; this.y = y;
this.color = color; this.color = color;
this.isSpinner = isSpinner;
} }
} }
@ -233,6 +241,9 @@ public class GameData {
/** Displayed game score (for animation, slightly behind score). */ /** Displayed game score (for animation, slightly behind score). */
private long scoreDisplay; private long scoreDisplay;
/** Displayed game score percent (for animation, slightly behind score percent). */
private float scorePercentDisplay;
/** Current health bar percentage. */ /** Current health bar percentage. */
private float health; private float health;
@ -311,6 +322,7 @@ public class GameData {
public void clear() { public void clear() {
score = 0; score = 0;
scoreDisplay = 0; scoreDisplay = 0;
scorePercentDisplay = 0f;
health = 100f; health = 100f;
healthDisplay = 100f; healthDisplay = 100f;
hitResultCount = new int[HIT_MAX]; hitResultCount = new int[HIT_MAX];
@ -435,7 +447,7 @@ public class GameData {
* @param scale the scale to apply * @param scale the scale to apply
* @param rightAlign align right (true) or left (false) * @param rightAlign align right (true) or left (false)
*/ */
private void drawSymbolString(String str, int x, int y, float scale, boolean rightAlign) { public void drawSymbolString(String str, int x, int y, float scale, boolean rightAlign) {
char[] c = str.toCharArray(); char[] c = str.toCharArray();
int cx = x; int cx = x;
if (rightAlign) { if (rightAlign) {
@ -466,7 +478,7 @@ public class GameData {
* @param fixedsize the width to use for all symbols * @param fixedsize the width to use for all symbols
* @param rightAlign align right (true) or left (false) * @param rightAlign align right (true) or left (false)
*/ */
private void drawFixedSizeSymbolString(String str, int x, int y, float scale, float fixedsize, boolean rightAlign) { public void drawFixedSizeSymbolString(String str, int x, int y, float scale, float fixedsize, boolean rightAlign) {
char[] c = str.toCharArray(); char[] c = str.toCharArray();
int cx = x; int cx = x;
if (rightAlign) { if (rightAlign) {
@ -506,11 +518,9 @@ public class GameData {
// score percentage // score percentage
int symbolHeight = getScoreSymbolImage('0').getHeight(); int symbolHeight = getScoreSymbolImage('0').getHeight();
float scorePercent = getScorePercent();
drawSymbolString( drawSymbolString(
String.format((scorePercent < 10f) ? "0%.2f%%" : "%.2f%%", scorePercent), String.format((scorePercentDisplay < 10f) ? "0%.2f%%" : "%.2f%%", scorePercentDisplay),
width - marginX, symbolHeight, 0.60f, true width - marginX, symbolHeight, 0.60f, true);
);
// map progress circle // map progress circle
g.setAntiAlias(true); g.setAntiAlias(true);
@ -771,20 +781,27 @@ public class GameData {
* @param trackPosition the current track position * @param trackPosition the current track position
*/ */
public void drawHitResults(int trackPosition) { public void drawHitResults(int trackPosition) {
final int fadeDelay = 500;
Iterator<OsuHitObjectResult> iter = hitResultList.iterator(); Iterator<OsuHitObjectResult> iter = hitResultList.iterator();
while (iter.hasNext()) { while (iter.hasNext()) {
OsuHitObjectResult hitResult = iter.next(); OsuHitObjectResult hitResult = iter.next();
if (hitResult.time + fadeDelay > trackPosition) { if (hitResult.time + HITRESULT_FADE_TIME > trackPosition) {
// hit result
hitResults[hitResult.result].setAlpha(hitResult.alpha); hitResults[hitResult.result].setAlpha(hitResult.alpha);
hitResult.alpha = 1 - ((float) (trackPosition - hitResult.time) / fadeDelay);
hitResults[hitResult.result].drawCentered(hitResult.x, hitResult.y); hitResults[hitResult.result].drawCentered(hitResult.x, hitResult.y);
hitResults[hitResult.result].setAlpha(1f);
// spinner
if (hitResult.isSpinner && hitResult.result != HIT_MISS) {
Image spinnerOsu = GameImage.SPINNER_OSU.getImage();
spinnerOsu.setAlpha(hitResult.alpha);
spinnerOsu.drawCentered(width / 2, height / 4);
spinnerOsu.setAlpha(1f);
}
// hit lighting // hit lighting
if (Options.isHitLightingEnabled() && hitResult.result != HIT_MISS && else if (Options.isHitLightingEnabled() && hitResult.result != HIT_MISS &&
hitResult.result != HIT_SLIDER30 && hitResult.result != HIT_SLIDER10) { hitResult.result != HIT_SLIDER30 && hitResult.result != HIT_SLIDER10) {
float scale = 1f + ((trackPosition - hitResult.time) / (float) fadeDelay); float scale = 1f + ((trackPosition - hitResult.time) / (float) HITRESULT_FADE_TIME);
Image scaledLighting = GameImage.LIGHTING.getImage().getScaledCopy(scale); Image scaledLighting = GameImage.LIGHTING.getImage().getScaledCopy(scale);
Image scaledLighting1 = GameImage.LIGHTING1.getImage().getScaledCopy(scale); Image scaledLighting1 = GameImage.LIGHTING1.getImage().getScaledCopy(scale);
scaledLighting.setAlpha(hitResult.alpha); scaledLighting.setAlpha(hitResult.alpha);
@ -795,6 +812,8 @@ public class GameData {
scaledLighting1.draw(hitResult.x - (scaledLighting1.getWidth() / 2f), scaledLighting1.draw(hitResult.x - (scaledLighting1.getWidth() / 2f),
hitResult.y - (scaledLighting1.getHeight() / 2f), hitResult.color); hitResult.y - (scaledLighting1.getHeight() / 2f), hitResult.color);
} }
hitResult.alpha = 1 - ((float) (trackPosition - hitResult.time) / HITRESULT_FADE_TIME);
} else } else
iter.remove(); iter.remove();
} }
@ -901,7 +920,7 @@ public class GameData {
} }
/** /**
* Updates the score, health, and combo burst displays based on a delta value. * Updates displayed elements based on a delta value.
* @param delta the delta interval since the last call * @param delta the delta interval since the last call
*/ */
public void updateDisplays(int delta) { public void updateDisplays(int delta) {
@ -912,6 +931,20 @@ public class GameData {
scoreDisplay = score; scoreDisplay = score;
} }
// score percent display
float scorePercent = getScorePercent();
if (scorePercentDisplay != scorePercent) {
if (scorePercentDisplay < scorePercent) {
scorePercentDisplay += (scorePercent - scorePercentDisplay) * delta / 50f + 0.01f;
if (scorePercentDisplay > scorePercent)
scorePercentDisplay = scorePercent;
} else {
scorePercentDisplay -= (scorePercentDisplay - scorePercent) * delta / 50f + 0.01f;
if (scorePercentDisplay < scorePercent)
scorePercentDisplay = scorePercent;
}
}
// health display // health display
if (healthDisplay != health) { if (healthDisplay != health) {
float shift = delta / 15f; float shift = delta / 15f;
@ -1027,7 +1060,7 @@ public class GameData {
if (!Options.isPerfectHitBurstEnabled()) if (!Options.isPerfectHitBurstEnabled())
; // hide perfect hit results ; // hide perfect hit results
else else
hitResultList.add(new OsuHitObjectResult(time, result, x, y, null)); hitResultList.add(new OsuHitObjectResult(time, result, x, y, null, false));
} }
} }
@ -1040,9 +1073,10 @@ public class GameData {
* @param color the combo color * @param color the combo color
* @param end true if this is the last hit object in the combo * @param end true if this is the last hit object in the combo
* @param hitSound the object's hit sound * @param hitSound the object's hit sound
* @param isSpinner whether the hit object was a spinner
*/ */
public void hitResult(int time, int result, float x, float y, Color color, public void hitResult(int time, int result, float x, float y, Color color,
boolean end, byte hitSound) { boolean end, byte hitSound, boolean isSpinner) {
int hitValue = 0; int hitValue = 0;
boolean perfectHit = false; boolean perfectHit = false;
switch (result) { switch (result) {
@ -1109,7 +1143,7 @@ public class GameData {
if (perfectHit && !Options.isPerfectHitBurstEnabled()) if (perfectHit && !Options.isPerfectHitBurstEnabled())
; // hide perfect hit results ; // hide perfect hit results
else else
hitResultList.add(new OsuHitObjectResult(time, result, x, y, color)); hitResultList.add(new OsuHitObjectResult(time, result, x, y, color, isSpinner));
} }
/** /**

View File

@ -134,6 +134,12 @@ public enum GameImage {
SPINNER_SPIN ("spinner-spin", "png"), SPINNER_SPIN ("spinner-spin", "png"),
SPINNER_CLEAR ("spinner-clear", "png"), SPINNER_CLEAR ("spinner-clear", "png"),
SPINNER_OSU ("spinner-osu", "png"), SPINNER_OSU ("spinner-osu", "png"),
SPINNER_RPM ("spinner-rpm", "png", false, true) {
@Override
protected Image process_sub(Image img, int w, int h) {
return img.getScaledCopy((SCORE_0.getImage().getHeight() * 1.05f) / img.getHeight());
}
},
// Game Data // Game Data
COMBO_BURST ("comboburst", "comboburst-%d", "png"), COMBO_BURST ("comboburst", "comboburst-%d", "png"),

View File

@ -141,7 +141,7 @@ public class Circle implements HitObject {
data.hitResult( data.hitResult(
hitObject.getTime(), result, hitObject.getTime(), result,
hitObject.getX(), hitObject.getY(), hitObject.getX(), hitObject.getY(),
color, comboEnd, hitObject.getHitSoundType() color, comboEnd, hitObject.getHitSoundType(), false
); );
return true; return true;
} }
@ -161,17 +161,17 @@ public class Circle implements HitObject {
if (overlap || trackPosition > time + hitResultOffset[GameData.HIT_50]) { if (overlap || trackPosition > time + hitResultOffset[GameData.HIT_50]) {
if (isAutoMod) // "auto" mod: catch any missed notes due to lag if (isAutoMod) // "auto" mod: catch any missed notes due to lag
data.hitResult(time, GameData.HIT_300, x, y, color, comboEnd, hitSound); data.hitResult(time, GameData.HIT_300, x, y, color, comboEnd, hitSound, false);
else // no more points can be scored, so send a miss else // no more points can be scored, so send a miss
data.hitResult(time, GameData.HIT_MISS, x, y, null, comboEnd, hitSound); data.hitResult(time, GameData.HIT_MISS, x, y, null, comboEnd, hitSound, false);
return true; return true;
} }
// "auto" mod: send a perfect hit result // "auto" mod: send a perfect hit result
else if (isAutoMod) { else if (isAutoMod) {
if (Math.abs(trackPosition - time) < hitResultOffset[GameData.HIT_300]) { if (Math.abs(trackPosition - time) < hitResultOffset[GameData.HIT_300]) {
data.hitResult(time, GameData.HIT_300, x, y, color, comboEnd, hitSound); data.hitResult(time, GameData.HIT_300, x, y, color, comboEnd, hitSound, false);
return true; return true;
} }
} }

View File

@ -255,10 +255,10 @@ public class Slider implements HitObject {
if (currentRepeats % 2 == 0) { // last circle if (currentRepeats % 2 == 0) { // last circle
float[] lastPos = curve.pointAt(1); float[] lastPos = curve.pointAt(1);
data.hitResult(hitObject.getTime() + (int) sliderTimeTotal, result, data.hitResult(hitObject.getTime() + (int) sliderTimeTotal, result,
lastPos[0],lastPos[1], color, comboEnd, hitObject.getHitSoundType()); lastPos[0],lastPos[1], color, comboEnd, hitObject.getHitSoundType(), false);
} else { // first circle } else { // first circle
data.hitResult(hitObject.getTime() + (int) sliderTimeTotal, result, data.hitResult(hitObject.getTime() + (int) sliderTimeTotal, result,
hitObject.getX(), hitObject.getY(), color, comboEnd, hitObject.getHitSoundType()); hitObject.getX(), hitObject.getY(), color, comboEnd, hitObject.getHitSoundType(), false);
} }
return result; return result;

View File

@ -106,9 +106,6 @@ public class Spinner implements HitObject {
int timeDiff = hitObject.getTime() - trackPosition; int timeDiff = hitObject.getTime() - trackPosition;
boolean spinnerComplete = (rotations >= rotationsNeeded); boolean spinnerComplete = (rotations >= rotationsNeeded);
// TODO: draw "OSU!" image after spinner ends
//GameImage.SPINNER_OSU.getImage().drawCentered(width / 2, height / 4);
// darken screen // darken screen
g.setColor(Utils.COLOR_BLACK_ALPHA); g.setColor(Utils.COLOR_BLACK_ALPHA);
g.fillRect(0, 0, width, height); g.fillRect(0, 0, width, height);
@ -116,6 +113,13 @@ public class Spinner implements HitObject {
if (timeDiff > 0) if (timeDiff > 0)
return; return;
// rpm (TODO: make this work for Auto/Spun-Out mods)
int rpm = Math.abs(Math.round(sumVelocity / storedVelocities.length * 60));
Image rpmImg = GameImage.SPINNER_RPM.getImage();
rpmImg.drawCentered(width / 2f, height - rpmImg.getHeight() / 2f);
data.drawSymbolString(Integer.toString(rpm), (int) ((width + rpmImg.getWidth() * 0.95f) / 2f),
(int) (height - data.getScoreSymbolImage('0').getHeight() * 1.025f), 1f, true);
// spinner meter (subimage) // spinner meter (subimage)
Image spinnerMetre = GameImage.SPINNER_METRE.getImage(); Image spinnerMetre = GameImage.SPINNER_METRE.getImage();
int spinnerMetreY = (spinnerComplete) ? 0 : (int) (spinnerMetre.getHeight() * (1 - (rotations / rotationsNeeded))); int spinnerMetreY = (spinnerComplete) ? 0 : (int) (spinnerMetre.getHeight() * (1 - (rotations / rotationsNeeded)));
@ -138,10 +142,6 @@ public class Spinner implements HitObject {
if (extraRotations > 0) if (extraRotations > 0)
data.drawSymbolNumber(extraRotations * 1000, width / 2, height * 2 / 3, 1.0f); data.drawSymbolNumber(extraRotations * 1000, width / 2, height * 2 / 3, 1.0f);
} }
// TODO: add rpm meter at bottom of spinner
// TODO 2: make this work for Auto/Spun-Out mods
// int rpm = Math.abs(Math.round(sumVelocity / storedVelocities.length * 60));
} }
/** /**
@ -150,22 +150,21 @@ public class Spinner implements HitObject {
*/ */
private int hitResult() { private int hitResult() {
// TODO: verify ratios // TODO: verify ratios
int result; int result;
float ratio = rotations / rotationsNeeded; float ratio = rotations / rotationsNeeded;
if (ratio >= 1.0f || if (ratio >= 1.0f ||
GameMod.AUTO.isActive() || GameMod.SPUN_OUT.isActive()) { GameMod.AUTO.isActive() || GameMod.SPUN_OUT.isActive()) {
result = GameData.HIT_300; result = GameData.HIT_300;
SoundController.playSound(SoundEffect.SPINNEROSU); SoundController.playSound(SoundEffect.SPINNEROSU);
} else if (ratio >= 0.8f) } else if (ratio >= 0.9f)
result = GameData.HIT_100; result = GameData.HIT_100;
else if (ratio >= 0.5f) else if (ratio >= 0.75f)
result = GameData.HIT_50; result = GameData.HIT_50;
else else
result = GameData.HIT_MISS; result = GameData.HIT_MISS;
data.hitResult(hitObject.getEndTime(), result, width / 2, height / 2, data.hitResult(hitObject.getEndTime(), result, width / 2, height / 2,
Color.transparent, true, (byte) -1); Color.transparent, true, (byte) -1, true);
return result; return result;
} }