From 7bd9eba2f5509c63cd2bafc368dff21f4b987926 Mon Sep 17 00:00:00 2001 From: Jeffrey Han Date: Fri, 16 Jan 2015 03:47:37 -0500 Subject: [PATCH] Added HitObject interface for OsuHitObjects during gameplay. This greatly simplifies object-handling code, and is slightly more time and memory efficient. Signed-off-by: Jeffrey Han --- src/itdelatrisu/opsu/objects/Circle.java | 25 ++----- src/itdelatrisu/opsu/objects/HitObject.java | 52 ++++++++++++++ src/itdelatrisu/opsu/objects/Slider.java | 29 ++------ src/itdelatrisu/opsu/objects/Spinner.java | 26 +++---- src/itdelatrisu/opsu/states/Game.java | 76 +++++---------------- 5 files changed, 90 insertions(+), 118 deletions(-) create mode 100644 src/itdelatrisu/opsu/objects/HitObject.java diff --git a/src/itdelatrisu/opsu/objects/Circle.java b/src/itdelatrisu/opsu/objects/Circle.java index f51ec849..30ec6b8d 100644 --- a/src/itdelatrisu/opsu/objects/Circle.java +++ b/src/itdelatrisu/opsu/objects/Circle.java @@ -28,12 +28,13 @@ import itdelatrisu.opsu.states.Game; import org.newdawn.slick.Color; import org.newdawn.slick.GameContainer; +import org.newdawn.slick.Graphics; import org.newdawn.slick.SlickException; /** * Data type representing a circle object. */ -public class Circle { +public class Circle implements HitObject { /** * The associated OsuHitObject. */ @@ -89,11 +90,7 @@ public class Circle { this.comboEnd = comboEnd; } - /** - * Draws the circle to the graphics context. - * @param trackPosition the current track position - */ - public void draw(int trackPosition) { + public void draw(int trackPosition, boolean currentObject, Graphics g) { int timeDiff = hitObject.getTime() - trackPosition; if (timeDiff >= 0) { @@ -117,7 +114,7 @@ public class Circle { * @param time the hit object time (difference between track time) * @return the hit result (GameScore.HIT_* constants) */ - public int hitResult(int time) { + private int hitResult(int time) { int trackPosition = MusicController.getPosition(); int timeDiff = Math.abs(trackPosition - time); @@ -136,13 +133,6 @@ public class Circle { return result; } - /** - * Processes a mouse click. - * @param x the x coordinate of the mouse - * @param y the y coordinate of the mouse - * @param comboEnd if this is the last object in the combo - * @return true if a hit result was processed - */ public boolean mousePressed(int x, int y) { double distance = Math.hypot(hitObject.getX() - x, hitObject.getY() - y); int circleRadius = GameImage.HITCIRCLE.getImage().getWidth() / 2; @@ -160,12 +150,7 @@ public class Circle { return false; } - /** - * Updates the circle object. - * @param overlap true if the next object's start time has already passed - * @return true if a hit result (miss) was processed - */ - public boolean update(boolean overlap) { + public boolean update(boolean overlap, int delta, int mouseX, int mouseY) { int time = hitObject.getTime(); float x = hitObject.getX(), y = hitObject.getY(); byte hitSound = hitObject.getHitSoundType(); diff --git a/src/itdelatrisu/opsu/objects/HitObject.java b/src/itdelatrisu/opsu/objects/HitObject.java new file mode 100644 index 00000000..669d19a9 --- /dev/null +++ b/src/itdelatrisu/opsu/objects/HitObject.java @@ -0,0 +1,52 @@ +/* + * 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 . + */ + +package itdelatrisu.opsu.objects; + +import org.newdawn.slick.Graphics; + +/** + * Hit object interface. + */ +public interface HitObject { + /** + * Draws the hit object to the graphics context. + * @param trackPosition the current track position + * @param currentObject true if this is the current hit object + * @param g the graphics context + */ + public void draw(int trackPosition, boolean currentObject, Graphics g); + + /** + * Updates the hit object. + * @param overlap true if the next object's start time has already passed + * @param delta the delta interval since the last call + * @param mouseX the x coordinate of the mouse + * @param mouseY the y coordinate of the mouse + * @return true if object ended + */ + public boolean update(boolean overlap, int delta, int mouseX, int mouseY); + + /** + * Processes a mouse click. + * @param x the x coordinate of the mouse + * @param y the y coordinate of the mouse + * @return true if a hit result was processed + */ + public boolean mousePressed(int x, int y); +} diff --git a/src/itdelatrisu/opsu/objects/Slider.java b/src/itdelatrisu/opsu/objects/Slider.java index 14b86cdc..37cfdfa9 100644 --- a/src/itdelatrisu/opsu/objects/Slider.java +++ b/src/itdelatrisu/opsu/objects/Slider.java @@ -32,13 +32,14 @@ import java.io.File; import org.newdawn.slick.Animation; 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; /** * Data type representing a slider object. */ -public class Slider { +public class Slider implements HitObject { /** * Slider ball animation. */ @@ -335,12 +336,7 @@ public class Slider { this.bezier = new Bezier(); } - /** - * Draws the slider to the graphics context. - * @param trackPosition the current track position - * @param currentObject true if this is the current hit object - */ - public void draw(int trackPosition, boolean currentObject) { + public void draw(int trackPosition, boolean currentObject, Graphics g) { float x = hitObject.getX(), y = hitObject.getY(); float[] sliderX = hitObject.getSliderX(), sliderY = hitObject.getSliderY(); int timeDiff = hitObject.getTime() - trackPosition; @@ -417,7 +413,7 @@ public class Slider { * @param lastCircleHit true if the cursor was held within the last circle * @return the hit result (GameScore.HIT_* constants) */ - public int hitResult() { + private int hitResult() { int lastIndex = hitObject.getSliderX().length - 1; float tickRatio = (float) ticksHit / tickIntervals; @@ -442,13 +438,6 @@ public class Slider { return result; } - /** - * Processes a mouse click. - * @param x the x coordinate of the mouse - * @param y the y coordinate of the mouse - * @param comboEnd if this is the last object in the combo - * @return true if a hit result was processed - */ public boolean mousePressed(int x, int y) { if (sliderClicked) // first circle already processed return false; @@ -478,14 +467,6 @@ public class Slider { return false; } - /** - * Updates the slider object. - * @param overlap true if the next object's start time has already passed - * @param delta the delta interval since the last call - * @param mouseX the x coordinate of the mouse - * @param mouseY the y coordinate of the mouse - * @return true if slider ended - */ public boolean update(boolean overlap, int delta, int mouseX, int mouseY) { int repeatCount = hitObject.getRepeatCount(); @@ -629,7 +610,7 @@ public class Slider { * @param raw if false, ensures that the value lies within [0, 1] by looping repeats * @return the t value: raw [0, repeats] or looped [0, 1] */ - public float getT(int trackPosition, boolean raw) { + private float getT(int trackPosition, boolean raw) { float t = (trackPosition - hitObject.getTime()) / sliderTime; if (raw) return t; diff --git a/src/itdelatrisu/opsu/objects/Spinner.java b/src/itdelatrisu/opsu/objects/Spinner.java index 0b98d086..bf10e98f 100644 --- a/src/itdelatrisu/opsu/objects/Spinner.java +++ b/src/itdelatrisu/opsu/objects/Spinner.java @@ -37,7 +37,7 @@ import org.newdawn.slick.SlickException; /** * Data type representing a spinner object. */ -public class Spinner { +public class Spinner implements HitObject { /** * Container dimensions. */ @@ -99,12 +99,11 @@ public class Spinner { rotationsNeeded = spinsPerMinute * (hitObject.getEndTime() - hitObject.getTime()) / 60000f; } - /** - * Draws the spinner to the graphics context. - * @param trackPosition the current track position - * @param g the graphics context - */ - public void draw(int trackPosition, Graphics g) { + public void draw(int trackPosition, boolean currentObject, Graphics g) { + // only draw spinners if current object + if (!currentObject) + return; + int timeDiff = hitObject.getTime() - trackPosition; boolean spinnerComplete = (rotations >= rotationsNeeded); @@ -145,7 +144,7 @@ public class Spinner { * Calculates and sends the spinner hit result. * @return the hit result (GameScore.HIT_* constants) */ - public int hitResult() { + private int hitResult() { // TODO: verify ratios int result; @@ -166,14 +165,9 @@ public class Spinner { return result; } - /** - * Updates the spinner by a delta interval. - * @param overlap true if the next object's start time has already passed - * @param delta the delta interval since the last call - * @param mouseX the x coordinate of the mouse - * @param mouseY the y coordinate of the mouse - * @return true if spinner ended - */ + // not used + public boolean mousePressed(int x, int y) { return false; } + public boolean update(boolean overlap, int delta, int mouseX, int mouseY) { int trackPosition = MusicController.getPosition(); if (overlap) diff --git a/src/itdelatrisu/opsu/states/Game.java b/src/itdelatrisu/opsu/states/Game.java index 7c3ade0a..a63c6cce 100644 --- a/src/itdelatrisu/opsu/states/Game.java +++ b/src/itdelatrisu/opsu/states/Game.java @@ -33,11 +33,11 @@ import itdelatrisu.opsu.audio.MusicController; import itdelatrisu.opsu.audio.SoundController; import itdelatrisu.opsu.audio.SoundEffect; import itdelatrisu.opsu.objects.Circle; +import itdelatrisu.opsu.objects.HitObject; import itdelatrisu.opsu.objects.Slider; import itdelatrisu.opsu.objects.Spinner; import java.io.File; -import java.util.HashMap; import java.util.Stack; import java.util.concurrent.TimeUnit; @@ -89,19 +89,9 @@ public class Game extends BasicGameState { private int objectIndex = 0; /** - * This map's hit circles objects, keyed by objectIndex. + * The map's HitObjects, indexed by objectIndex. */ - private HashMap circles; - - /** - * This map's slider objects, keyed by objectIndex. - */ - private HashMap sliders; - - /** - * This map's spinner objects, keyed by objectIndex. - */ - private HashMap spinners; + private HitObject[] hitObjects; /** * Delay time, in milliseconds, before song starts. @@ -405,21 +395,8 @@ public class Game extends BasicGameState { for (int i = objectIndex; i < osu.objects.length && osu.objects[i].getTime() < trackPosition + approachTime; i++) stack.add(i); - while (!stack.isEmpty()) { - int i = stack.pop(); - OsuHitObject hitObject = osu.objects[i]; - - if (hitObject.isCircle()) - circles.get(i).draw(trackPosition); - else if (hitObject.isSlider()) - sliders.get(i).draw(trackPosition, stack.isEmpty()); - else if (hitObject.isSpinner()) { - if (stack.isEmpty()) // only draw spinner at objectIndex - spinners.get(i).draw(trackPosition, g); - else - continue; - } - } + while (!stack.isEmpty()) + hitObjects[stack.pop()].draw(trackPosition, stack.isEmpty(), g); // draw OsuHitObjectResult objects score.drawHitResults(trackPosition); @@ -577,24 +554,13 @@ public class Game extends BasicGameState { // update objects (loop in unlikely event of any skipped indexes) while (objectIndex < osu.objects.length && trackPosition > osu.objects[objectIndex].getTime()) { - OsuHitObject hitObject = osu.objects[objectIndex]; - // check if we've already passed the next object's start time boolean overlap = (objectIndex + 1 < osu.objects.length && trackPosition > osu.objects[objectIndex + 1].getTime() - hitResultOffset[GameScore.HIT_300]); - // check completion status of the hit object - boolean done = false; - if (hitObject.isCircle()) - done = circles.get(objectIndex).update(overlap); - else if (hitObject.isSlider()) - done = sliders.get(objectIndex).update(overlap, delta, mouseX, mouseY); - else if (hitObject.isSpinner()) - done = spinners.get(objectIndex).update(overlap, delta, mouseX, mouseY); - - // increment object index? - if (done) - objectIndex++; + // update hit object and check completion status + if (hitObjects[objectIndex].update(overlap, delta, mouseX, mouseY)) + objectIndex++; // done, so increment object index else break; } @@ -731,15 +697,12 @@ public class Game extends BasicGameState { return; // circles - if (hitObject.isCircle()) { - boolean hit = circles.get(objectIndex).mousePressed(x, y); - if (hit) - objectIndex++; - } + if (hitObject.isCircle() && hitObjects[objectIndex].mousePressed(x, y)) + objectIndex++; // circle hit // sliders else if (hitObject.isSlider()) - sliders.get(objectIndex).mousePressed(x, y); + hitObjects[objectIndex].mousePressed(x, y); } @Override @@ -781,13 +744,12 @@ public class Game extends BasicGameState { comboEnd = true; Color color = osu.combo[hitObject.getComboIndex()]; - if (hitObject.isCircle()) { - circles.put(i, new Circle(hitObject, this, score, color, comboEnd)); - } else if (hitObject.isSlider()) { - sliders.put(i, new Slider(hitObject, this, score, color, comboEnd)); - } else if (hitObject.isSpinner()) { - spinners.put(i, new Spinner(hitObject, this, score)); - } + if (hitObject.isCircle()) + hitObjects[i] = new Circle(hitObject, this, score, color, comboEnd); + else if (hitObject.isSlider()) + hitObjects[i] = new Slider(hitObject, this, score, color, comboEnd); + else if (hitObject.isSpinner()) + hitObjects[i] = new Spinner(hitObject, this, score); } // load the first timingPoint @@ -818,9 +780,7 @@ public class Game extends BasicGameState { * Resets all game data and structures. */ public void resetGameData() { - circles = new HashMap(); - sliders = new HashMap(); - spinners = new HashMap(); + hitObjects = new HitObject[osu.objects.length]; score.clear(); objectIndex = 0; breakIndex = 0;