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:
parent
e6f85e9c5c
commit
020dfbde32
BIN
res/spinner-rpm.png
Normal file
BIN
res/spinner-rpm.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 6.7 KiB |
|
@ -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));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -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"),
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user