Merge pull request #20 from fluddokt/omaster

Visual changes:
- Larger hit circles
- Circle overlay after drawing circle
- Fade in approach circles
- Fade in sliders
- Fade out hit lighting
- Hit Error Bar
- Reverse Slider ball rotation on repeats
- Fill bg image to screen while keeping aspect ratio
- Added a few more resolutions
This commit is contained in:
Jeffrey Han 2015-02-15 00:45:43 -05:00
commit b7f86f1962
7 changed files with 167 additions and 23 deletions

View File

@ -153,6 +153,27 @@ public class GameData {
/** List of hit result objects associated with hit objects. */ /** List of hit result objects associated with hit objects. */
private LinkedList<OsuHitObjectResult> hitResultList; private LinkedList<OsuHitObjectResult> hitResultList;
/**
* class to store Hit Error information.
* @author fluddokt
*/
class HitErrorInfo {
int time, x, y, timeDiff;
public HitErrorInfo(int time, int x, int y, int timeDiff) {
this.time = time;
this.x = x;
this.y = y;
this.timeDiff = timeDiff;
}
}
/**
* List of stored Hit Error Info
*/
private LinkedList<HitErrorInfo> hitErrorList = new LinkedList<HitErrorInfo>();
/** /**
* Hit result helper class. * Hit result helper class.
*/ */
@ -225,6 +246,10 @@ public class GameData {
/** Container dimensions. */ /** Container dimensions. */
private int width, height; private int width, height;
/** Time offsets for obtaining each hit result (indexed by HIT_* constants). */
private int[] hitResultOffset;
/** /**
* Constructor for gameplay. * Constructor for gameplay.
* @param width container width * @param width container width
@ -283,6 +308,7 @@ public class GameData {
comboEnd = 0; comboEnd = 0;
comboBurstIndex = -1; comboBurstIndex = -1;
scoreData = null; scoreData = null;
hitErrorList.clear();
} }
/** /**
@ -364,6 +390,10 @@ public class GameData {
public void setDifficulty(float difficulty) { this.difficulty = difficulty; } public void setDifficulty(float difficulty) { this.difficulty = difficulty; }
public float getDifficulty() { return difficulty; } public float getDifficulty() { return difficulty; }
/** Sets the HitResultOffset */
public void setHitResultOffset(int[] hitResultOffset) {this.hitResultOffset = hitResultOffset; }
/** /**
* Draws a number with defaultSymbols. * Draws a number with defaultSymbols.
* @param n the number to draw * @param n the number to draw
@ -517,6 +547,50 @@ public class GameData {
// combo count // combo count
if (combo > 0) // 0 isn't a combo if (combo > 0) // 0 isn't a combo
drawSymbolString(String.format("%dx", combo), 10, height - 10 - symbolHeight, 1.0f, false); drawSymbolString(String.format("%dx", combo), 10, height - 10 - symbolHeight, 1.0f, false);
//*
//Draw Hit Error bar
final int fadeDelay = 5000;
int hitErrorY = 30;
Iterator<HitErrorInfo> iter2 = hitErrorList.iterator();
g.setColor(Color.black);
g.fillRect(width / 2f - 3 - hitResultOffset[HIT_50],
height - marginX - hitErrorY - 10,
hitResultOffset[HIT_50] * 2,
20);
g.setColor(Utils.COLOR_LIGHT_ORANGE);
g.fillRect(width / 2f - 3 - hitResultOffset[HIT_50],
height - marginX - hitErrorY - 3,
hitResultOffset[HIT_50] * 2,
6);
g.setColor(Utils.COLOR_LIGHT_GREEN);
g.fillRect(width / 2f - 3 - hitResultOffset[HIT_100],
height - marginX - hitErrorY - 3,
hitResultOffset[HIT_100] * 2,
6);
g.setColor(Utils.COLOR_LIGHT_BLUE);
g.fillRect(width / 2f - 3 - hitResultOffset[HIT_300],
height- marginX - hitErrorY - 3,
hitResultOffset[HIT_300] * 2,
6);
g.setColor(Color.white);
g.drawRect(width / 2f - 3, height - marginX - hitErrorY - 10, 6, 20);
while (iter2.hasNext()) {
HitErrorInfo info = iter2.next();
int time = info.time;
if (Math.abs(info.timeDiff) < hitResultOffset[GameData.HIT_50]
&& time + fadeDelay > trackPosition) {
float alpha = 1 - ((float) (trackPosition - time) / fadeDelay);
Color col = new Color(Color.white);
col.a = alpha;
g.setColor(col);
g.fillRect(width / 2 + info.timeDiff - 1, height - marginX
- hitErrorY - 10, 2, 20);
} else {
iter2.remove();
}
}
//*/
} else { } else {
// grade // grade
Grade grade = getGrade(); Grade grade = getGrade();
@ -528,6 +602,9 @@ public class GameData {
); );
} }
} }
} }
/** /**
@ -649,6 +726,9 @@ public class GameData {
float scale = 1f + ((trackPosition - hitResult.time) / (float) fadeDelay); float scale = 1f + ((trackPosition - hitResult.time) / (float) fadeDelay);
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);
scaledLighting1.setAlpha(hitResult.alpha);
scaledLighting.draw(hitResult.x - (scaledLighting.getWidth() / 2f), scaledLighting.draw(hitResult.x - (scaledLighting.getWidth() / 2f),
hitResult.y - (scaledLighting.getHeight() / 2f), hitResult.color); hitResult.y - (scaledLighting.getHeight() / 2f), hitResult.color);
scaledLighting1.draw(hitResult.x - (scaledLighting1.getWidth() / 2f), scaledLighting1.draw(hitResult.x - (scaledLighting1.getWidth() / 2f),
@ -1008,4 +1088,15 @@ public class GameData {
* @return true if gameplay, false if score viewing * @return true if gameplay, false if score viewing
*/ */
public boolean isGameplay() { return gameplay; } public boolean isGameplay() { return gameplay; }
/**
* add a Hit Error Info to the list.
* @param time when it should have been hit
* @param x, y the location that it hit
* @param timeDiff the difference from the time from when it was actually hit
*/
public void addHitError(int time, int x, int y, int timeDiff) {
hitErrorList.add(new HitErrorInfo(time, x, y, timeDiff));
}
} }

View File

@ -418,8 +418,10 @@ public class Options {
RES_800_600 (800, 600), RES_800_600 (800, 600),
RES_1024_600 (1024, 600), RES_1024_600 (1024, 600),
RES_1024_768 (1024, 768), RES_1024_768 (1024, 768),
RES_1280_720 (1280, 720),
RES_1280_800 (1280, 800), RES_1280_800 (1280, 800),
RES_1280_960 (1280, 960), RES_1280_960 (1280, 960),
RES_1280_1024 (1280, 1024),
RES_1366_768 (1366, 768), RES_1366_768 (1366, 768),
RES_1440_900 (1440, 900), RES_1440_900 (1440, 900),
RES_1600_900 (1600, 900), RES_1600_900 (1600, 900),
@ -428,7 +430,9 @@ public class Options {
RES_1920_1080 (1920, 1080), RES_1920_1080 (1920, 1080),
RES_1920_1200 (1920, 1200), RES_1920_1200 (1920, 1200),
RES_2560_1440 (2560, 1440), RES_2560_1440 (2560, 1440),
RES_2560_1600 (2560, 1600); RES_2560_1600 (2560, 1600),
RES_3840_2160 (3840, 2160);
/** Screen dimensions. */ /** Screen dimensions. */
private int width, height; private int width, height;

View File

@ -268,14 +268,20 @@ public class OsuFile implements Comparable<OsuFile> {
bgImageMap.put(this, bgImage); bgImageMap.put(this, bgImage);
} }
// fit image to screen
int swidth = width; int swidth = width;
int sheight = height; int sheight = height;
if (!stretch) { if (!stretch) {
// fit image to screen
if (bgImage.getWidth() / (float) bgImage.getHeight() > width / (float) height) // x > y if (bgImage.getWidth() / (float) bgImage.getHeight() > width / (float) height) // x > y
sheight = (int) (width * bgImage.getHeight() / (float) bgImage.getWidth()); sheight = (int) (width * bgImage.getHeight() / (float) bgImage.getWidth());
else else
swidth = (int) (height * bgImage.getWidth() / (float) bgImage.getHeight()); swidth = (int) (height * bgImage.getWidth() / (float) bgImage.getHeight());
} else {
//fill image to screen while keeping aspect ratio
if (bgImage.getWidth() / (float) bgImage.getHeight() > width / (float) height) // x > y
swidth = (int) (height * bgImage.getWidth() / (float) bgImage.getHeight());
else
sheight = (int) (width * bgImage.getHeight() / (float) bgImage.getWidth());
} }
bgImage = bgImage.getScaledCopy(swidth, sheight); bgImage = bgImage.getScaledCopy(swidth, sheight);

View File

@ -79,7 +79,10 @@ public class Utils {
COLOR_YELLOW_ALPHA = new Color(255, 255, 0, 0.4f), COLOR_YELLOW_ALPHA = new Color(255, 255, 0, 0.4f),
COLOR_WHITE_FADE = new Color(255, 255, 255, 1f), COLOR_WHITE_FADE = new Color(255, 255, 255, 1f),
COLOR_RED_HOVER = new Color(255, 112, 112), COLOR_RED_HOVER = new Color(255, 112, 112),
COLOR_GREEN = new Color(137, 201, 79); COLOR_GREEN = new Color(137, 201, 79),
COLOR_LIGHT_ORANGE = new Color(255,192,128),
COLOR_LIGHT_GREEN = new Color(128,255,128),
COLOR_LIGHT_BLUE = new Color(128,128,255);
/** The default map colors, used when a map does not provide custom colors. */ /** The default map colors, used when a map does not provide custom colors. */
public static final Color[] DEFAULT_COMBO = { public static final Color[] DEFAULT_COMBO = {
@ -872,4 +875,20 @@ public class Utils {
list.add(str); list.add(str);
return list; return list;
} }
/**
* Clamps the value to be in between low and high
* @param val
* @param low
* @param high
* @return val clamped between low and high
* @author fluddokt (https://github.com/fluddokt)
*/
public static float clamp(float val, int low, int high) {
if(val < low)
return low;
if(val > high)
return high;
return val;
}
} }

View File

@ -55,7 +55,7 @@ public class Circle implements HitObject {
* @param circleSize the map's circleSize value * @param circleSize the map's circleSize value
*/ */
public static void init(GameContainer container, float circleSize) { public static void init(GameContainer container, float circleSize) {
int diameter = (int) (96 - (circleSize * 8)); int diameter = (int) (104 - (circleSize * 8));
diameter = (int) (diameter * OsuHitObject.getXMultiplier()); // convert from Osupixels (640x480) diameter = (int) (diameter * OsuHitObject.getXMultiplier()); // convert from Osupixels (640x480)
GameImage.HITCIRCLE.setImage(GameImage.HITCIRCLE.getImage().getScaledCopy(diameter, diameter)); GameImage.HITCIRCLE.setImage(GameImage.HITCIRCLE.getImage().getScaledCopy(diameter, diameter));
GameImage.HITCIRCLE_OVERLAY.setImage(GameImage.HITCIRCLE_OVERLAY.getImage().getScaledCopy(diameter, diameter)); GameImage.HITCIRCLE_OVERLAY.setImage(GameImage.HITCIRCLE_OVERLAY.getImage().getScaledCopy(diameter, diameter));
@ -83,15 +83,20 @@ public class Circle implements HitObject {
int timeDiff = hitObject.getTime() - trackPosition; int timeDiff = hitObject.getTime() - trackPosition;
if (timeDiff >= 0) { if (timeDiff >= 0) {
float x = hitObject.getX(), y = hitObject.getY();
float approachScale = 1 + (timeDiff * 2f / game.getApproachTime());
Utils.drawCentered(GameImage.APPROACHCIRCLE.getImage().getScaledCopy(approachScale), x, y, color);
float alpha = (approachScale > 3.3f) ? 0f : 1f - (approachScale - 1f) / 2.7f;
float oldAlpha = color.a; float oldAlpha = color.a;
float x = hitObject.getX(), y = hitObject.getY();
float scale = timeDiff / (float)game.getApproachTime();
float approachScale = 1 + scale * 3;
color.a = 1 - scale;
Utils.drawCentered(GameImage.APPROACHCIRCLE.getImage().getScaledCopy(approachScale), x, y, color);
float alpha = Utils.clamp((1 - scale) * 2, 0, 1);
color.a = alpha; color.a = alpha;
Utils.COLOR_WHITE_FADE.a = alpha; Utils.COLOR_WHITE_FADE.a = alpha;
Utils.drawCentered(GameImage.HITCIRCLE_OVERLAY.getImage(), x, y, Utils.COLOR_WHITE_FADE);
Utils.drawCentered(GameImage.HITCIRCLE.getImage(), x, y, color); Utils.drawCentered(GameImage.HITCIRCLE.getImage(), x, y, color);
Utils.drawCentered(GameImage.HITCIRCLE_OVERLAY.getImage(), x, y, Utils.COLOR_WHITE_FADE);
color.a = oldAlpha; color.a = oldAlpha;
Utils.COLOR_WHITE_FADE.a = 1f; Utils.COLOR_WHITE_FADE.a = 1f;
data.drawSymbolNumber(hitObject.getComboNumber(), x, y, data.drawSymbolNumber(hitObject.getComboNumber(), x, y,
@ -105,8 +110,7 @@ public class Circle implements HitObject {
* @return the hit result (GameData.HIT_* constants) * @return the hit result (GameData.HIT_* constants)
*/ */
private int hitResult(int time) { private int hitResult(int time) {
int trackPosition = MusicController.getPosition(); int timeDiff = Math.abs(time);
int timeDiff = Math.abs(trackPosition - time);
int[] hitResultOffset = game.getHitResultOffsets(); int[] hitResultOffset = game.getHitResultOffsets();
int result = -1; int result = -1;
@ -128,8 +132,12 @@ public class Circle implements HitObject {
double distance = Math.hypot(hitObject.getX() - x, hitObject.getY() - y); double distance = Math.hypot(hitObject.getX() - x, hitObject.getY() - y);
int circleRadius = GameImage.HITCIRCLE.getImage().getWidth() / 2; int circleRadius = GameImage.HITCIRCLE.getImage().getWidth() / 2;
if (distance < circleRadius) { if (distance < circleRadius) {
int result = hitResult(hitObject.getTime()); int trackPosition = MusicController.getPosition();
int timeDiff = trackPosition - hitObject.getTime();
int result = hitResult(timeDiff);
if (result > -1) { if (result > -1) {
data.addHitError(hitObject.getTime(), x, y, timeDiff);
data.hitResult( data.hitResult(
hitObject.getTime(), result, hitObject.getTime(), result,
hitObject.getX(), hitObject.getY(), hitObject.getX(), hitObject.getY(),

View File

@ -98,7 +98,7 @@ public class Slider implements HitObject {
* @param osu the associated OsuFile object * @param osu the associated OsuFile object
*/ */
public static void init(GameContainer container, float circleSize, OsuFile osu) { public static void init(GameContainer container, float circleSize, OsuFile osu) {
int diameter = (int) (96 - (circleSize * 8)); int diameter = (int) (104 - (circleSize * 8));
diameter = (int) (diameter * OsuHitObject.getXMultiplier()); // convert from Osupixels (640x480) diameter = (int) (diameter * OsuHitObject.getXMultiplier()); // convert from Osupixels (640x480)
// slider ball // slider ball
@ -110,7 +110,7 @@ public class Slider implements HitObject {
sliderBallImages = new Image[]{ GameImage.SLIDER_BALL.getImage() }; sliderBallImages = new Image[]{ GameImage.SLIDER_BALL.getImage() };
for (int i = 0; i < sliderBallImages.length; i++) for (int i = 0; i < sliderBallImages.length; i++)
sliderBallImages[i] = sliderBallImages[i].getScaledCopy(diameter * 118 / 128, diameter * 118 / 128); sliderBallImages[i] = sliderBallImages[i].getScaledCopy(diameter * 118 / 128, diameter * 118 / 128);
sliderBall = new Animation(sliderBallImages, 60); sliderBall = new Animation(sliderBallImages, 30);
GameImage.SLIDER_FOLLOWCIRCLE.setImage(GameImage.SLIDER_FOLLOWCIRCLE.getImage().getScaledCopy(diameter * 259 / 128, diameter * 259 / 128)); 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.REVERSEARROW.setImage(GameImage.REVERSEARROW.getImage().getScaledCopy(diameter, diameter));
@ -143,16 +143,21 @@ public class Slider implements HitObject {
@Override @Override
public void draw(int trackPosition, boolean currentObject, Graphics g) { public void draw(int trackPosition, boolean currentObject, Graphics g) {
float x = hitObject.getX(), y = hitObject.getY();
int timeDiff = hitObject.getTime() - trackPosition; int timeDiff = hitObject.getTime() - trackPosition;
float approachScale = (timeDiff >= 0) ? 1 + (timeDiff * 2f / game.getApproachTime()) : 1f;
float alpha = (approachScale > 3.3f) ? 0f : 1f - (approachScale - 1f) / 2.7f;
float oldAlpha = color.a; float oldAlpha = color.a;
float oldAlphaFade = Utils.COLOR_WHITE_FADE.a; float oldAlphaFade = Utils.COLOR_WHITE_FADE.a;
color.a = alpha;
float x = hitObject.getX(), y = hitObject.getY();
float scale = timeDiff / (float)game.getApproachTime();
float approachScale = 1 + scale*3 ;
float alpha = (1 - scale);//(approachScale > 3.3f) ? 0f : 1f - (approachScale - 1f) / 2.7f;
color.a = Utils.clamp(alpha* 0.5f, 0, 1);
alpha = Utils.clamp(alpha, 0, 1);
Utils.COLOR_WHITE_FADE.a = alpha; Utils.COLOR_WHITE_FADE.a = alpha;
// curve // curve
curve.draw(); curve.draw();
@ -164,6 +169,8 @@ public class Slider implements HitObject {
tick.drawCentered(c[0], c[1]); tick.drawCentered(c[0], c[1]);
} }
} }
color.a = alpha;
Image hitCircleOverlay = GameImage.HITCIRCLE_OVERLAY.getImage(); Image hitCircleOverlay = GameImage.HITCIRCLE_OVERLAY.getImage();
Image hitCircle = GameImage.HITCIRCLE.getImage(); Image hitCircle = GameImage.HITCIRCLE.getImage();
@ -174,8 +181,8 @@ public class Slider implements HitObject {
Utils.drawCentered(hitCircleOverlay, endPos[0], endPos[1], Utils.COLOR_WHITE_FADE); Utils.drawCentered(hitCircleOverlay, endPos[0], endPos[1], Utils.COLOR_WHITE_FADE);
// start circle // start circle
Utils.drawCentered(hitCircleOverlay, x, y, Utils.COLOR_WHITE_FADE);
Utils.drawCentered(hitCircle, x, y, color); Utils.drawCentered(hitCircle, x, y, color);
Utils.drawCentered(hitCircleOverlay, x, y, Utils.COLOR_WHITE_FADE);
if (sliderClicked) if (sliderClicked)
; // don't draw current combo number if already clicked ; // don't draw current combo number if already clicked
else else
@ -208,22 +215,29 @@ public class Slider implements HitObject {
if (timeDiff >= 0) { if (timeDiff >= 0) {
// approach circle // approach circle
color.a = 1 - scale;
Utils.drawCentered(GameImage.APPROACHCIRCLE.getImage().getScaledCopy(approachScale), x, y, color); Utils.drawCentered(GameImage.APPROACHCIRCLE.getImage().getScaledCopy(approachScale), x, y, color);
} else { } else {
float[] c = curve.pointAt(getT(trackPosition, false)); float[] c = curve.pointAt(getT(trackPosition, false));
float[] c2 = curve.pointAt(getT(trackPosition, false) + 0.01f); float[] c2 = curve.pointAt(getT(trackPosition, false) + 0.01f);
// slider ball // slider ball
sliderBall.updateNoDraw();//TODO ..... I can't thing of anything else
//It might be better to make your own animation class or something
//also it might be better to update the animation based on the distance traveled
Image sliderBallFrame = sliderBall.getCurrentFrame(); Image sliderBallFrame = sliderBall.getCurrentFrame();
float angle = (float) (Math.atan2(c2[1] - c[1], c2[0] - c[0]) * 180 / Math.PI); float angle = (float) (Math.atan2(c2[1] - c[1], c2[0] - c[0]) * 180 / Math.PI)
+ (currentRepeats % 2 == 1 ? 180 : 0);
sliderBallFrame.setRotation(angle); sliderBallFrame.setRotation(angle);
sliderBallFrame.drawCentered(c[0], c[1]); sliderBallFrame.drawCentered(c[0], c[1]);
// follow circle // follow circle
if (followCircleActive) if (followCircleActive)
GameImage.SLIDER_FOLLOWCIRCLE.getImage().drawCentered(c[0], c[1]); GameImage.SLIDER_FOLLOWCIRCLE.getImage().drawCentered(c[0], c[1]);
} }
} }
/** /**
* Calculates the slider hit result. * Calculates the slider hit result.
@ -275,6 +289,7 @@ public class Slider implements HitObject {
//else not a hit //else not a hit
if (result > -1) { if (result > -1) {
data.addHitError(hitObject.getTime(), x,y,trackPosition - hitObject.getTime());
sliderClicked = true; sliderClicked = true;
data.sliderTickResult(hitObject.getTime(), result, data.sliderTickResult(hitObject.getTime(), result,
hitObject.getX(), hitObject.getY(), hitObject.getHitSoundType()); hitObject.getX(), hitObject.getY(), hitObject.getHitSoundType());

View File

@ -878,6 +878,7 @@ public class Game extends BasicGameState {
// HPDrainRate (health change), overallDifficulty (scoring) // HPDrainRate (health change), overallDifficulty (scoring)
data.setDrainRate(HPDrainRate); data.setDrainRate(HPDrainRate);
data.setDifficulty(overallDifficulty); data.setDifficulty(overallDifficulty);
data.setHitResultOffset(hitResultOffset);
} }
/** /**