diff --git a/src/game/Beat.java b/src/game/Beat.java new file mode 100644 index 0000000..c4d0e1f --- /dev/null +++ b/src/game/Beat.java @@ -0,0 +1,20 @@ +package game; + +public class Beat { + int x; //x position of the beat circle + int y; //y position of the beat circle + int time; //time, in ms, of the beat from the beginning of the song + //Clip hitSound; to be used later, when sounds are added + + public Beat() { + this.x = 0; + this.y = 0; + this.time = 0; + } + + public Beat(int x, int y, int time) { + this.x = x; + this.y = y; + this.time = time; + } +} diff --git a/src/game/BeatmapParser.java b/src/game/BeatmapParser.java new file mode 100644 index 0000000..3ebf5a8 --- /dev/null +++ b/src/game/BeatmapParser.java @@ -0,0 +1,27 @@ +package game; + +import java.util.LinkedList; + +public class BeatmapParser { + + public LinkedList parseBeatmap() { + LinkedList beatlist = new LinkedList(); + beatlist.add(new Beat(100,78,854)); + beatlist.add(new Beat(42,276,3132)); + beatlist.add(new Beat(235,324,3891)); + beatlist.add(new Beat(465,104,5410)); + beatlist.add(new Beat(256,192,5790)); + beatlist.add(new Beat(263,259,9398)); + beatlist.add(new Beat(476,172,10347)); + beatlist.add(new Beat(359,44,11106)); + beatlist.add(new Beat(9,68,12436)); + beatlist.add(new Beat(41,308,14144)); + beatlist.add(new Beat(367,364,15474)); + beatlist.add(new Beat(384,114,16423)); + beatlist.add(new Beat(312,43,16803)); + beatlist.add(new Beat(225,92,17182)); + beatlist.add(new Beat(67,248,18701)); + beatlist.add(new Beat(141,314,19081)); + return beatlist; + } +} diff --git a/src/game/HitObject.java b/src/game/HitObject.java new file mode 100644 index 0000000..abe2a2d --- /dev/null +++ b/src/game/HitObject.java @@ -0,0 +1,24 @@ +package game; + +public class HitObject { + int x; + int y; + float radius; + float duration; + boolean clicked = false; + + public HitObject() { + this.x = 0; + this.y = 0; + this.radius = 0; + this.duration = 0; + } + + public HitObject(int x, int y, float radius, float duration, boolean clicked) { + this.x = x; + this.y = y; + this.radius = radius; + this.duration = duration; + this.clicked = clicked; + } +} diff --git a/src/game/RhythmState.java b/src/game/RhythmState.java index 1f4aca7..57a1578 100644 --- a/src/game/RhythmState.java +++ b/src/game/RhythmState.java @@ -1,5 +1,8 @@ package game; +import java.util.LinkedList; +import java.util.concurrent.CopyOnWriteArrayList; + import org.newdawn.slick.Color; import org.newdawn.slick.GameContainer; import org.newdawn.slick.Graphics; @@ -7,29 +10,30 @@ import org.newdawn.slick.Input; import org.newdawn.slick.SlickException; import org.newdawn.slick.geom.Circle; +import org.newdawn.slick.geom.Line; import org.newdawn.slick.geom.Vector2f; import org.newdawn.slick.state.StateBasedGame; public class RhythmState extends DefaultGameState{ public final int[] CLICK_KEYS = {Input.KEY_Z, Input.KEY_X}; - public final float CIRCLE_TIME = 400f; //time it takes for circles to shrink. the time the circles are on screen is double this, as the circles grow, then shrink. - - private Vector2f curpos = new Vector2f(); //position of the current circle (shrinking circle) - private Vector2f nextCircle = new Vector2f(); //position of the next circle (growing circle) - private float prevCircleSize = 0; //current circle size - private float nextCircleSize = 0; //next circle size + public final float CIRCLE_TIME = 1000f; //time it takes for approach circles to shrink. + public final float LENIENCE_TIME = 200f; //lenience, in ms, given to the player to click the hit object - private float circleTime = -1f; //changing this doesn't do anything. must be negative, however private Image background; //background image - private float timescale = .10f; //rate at which circles shrink. doesn't change the time circles are on screen, however + private float timescale = .08f; //rate at which approach circles shrink. doesn't change the time circles are on screen, however private int points = 0; //total number of points - private boolean clicked = false; //whether or not the current circle has been clicked - private boolean nextcircleclicked = false; //whether or not the next circle has been clicked - private float maxRadius = CIRCLE_TIME * timescale; //maximum radius of the circles + + private float maxRadius = CIRCLE_TIME * timescale; //maximum radius of the approach circles + private float innerRadius = 20f; //radius of the inner hit circles which you click private long starttime = System.currentTimeMillis(); //initial time + private long songtime = 0; //time since beginning of the song, in ms + + private CopyOnWriteArrayList hitobjects = new CopyOnWriteArrayList<>(); + private LinkedList beatmap = new LinkedList<>(); + private int beatmapindex = 0; private Input inp; // Get various information about input (e.g. mouse position) @@ -48,6 +52,9 @@ public int getID() { public void init(GameContainer gc, StateBasedGame arg1) throws SlickException { background = new Image("res/background.jpg"); background = background.getScaledCopy(gc.getWidth(), gc.getHeight()); + + BeatmapParser beatmapparser = new BeatmapParser(); + beatmap = beatmapparser.parseBeatmap(); } @Override @@ -57,21 +64,24 @@ public void leave(GameContainer arg0, StateBasedGame arg1) throws SlickException @Override public void render(GameContainer gc, StateBasedGame arg1, Graphics g) throws SlickException { g.drawImage(background, 0, 0); - g.setColor(Color.cyan); - g.setLineWidth(10f); - g.drawLine(curpos.x, curpos.y, nextCircle.x, nextCircle.y); - if (nextcircleclicked) { - g.setColor(Color.green); - } else { - g.setColor(Color.cyan); - } - g.fill(new Circle(nextCircle.x, nextCircle.y, nextCircleSize)); - if (clicked) { - g.setColor(Color.green); - } else { - g.setColor(Color.cyan); + + for (HitObject hitobject:hitobjects) { //this for loop draws all the hit objects + int index = hitobjects.indexOf(hitobject); + if (index != hitobjects.size() - 1) { //check to make sure hitobject is not the last in the list + g.setLineWidth(5f); + g.setColor(Color.white); + g.draw(new Line(new Vector2f(hitobject.x, hitobject.y), new Vector2f(hitobjects.get(index+1).x, hitobjects.get(index+1).y))); //draws a line in between consecutive hit objects + } + if (hitobject.clicked) { //changes color based on the state of the circle + g.setColor(Color.green); + } else { + g.setColor(Color.white); + } + g.setLineWidth(2f); + g.draw(new Circle(hitobject.x, hitobject.y, hitobject.radius+innerRadius)); //draws approach circle + g.fill(new Circle(hitobject.x, hitobject.y, innerRadius)); //draws hit circle } - g.fill(new Circle(curpos.x, curpos.y, prevCircleSize)); + g.setColor(Color.white); g.drawString("Points: " + points, gc.getWidth() - 200, 50); g.drawString("Points per second " + 1000f * points / (System.currentTimeMillis() - starttime), gc.getWidth() - 600, 70); @@ -79,26 +89,25 @@ public void render(GameContainer gc, StateBasedGame arg1, Graphics g) throws Sli @Override public void update(GameContainer gc, StateBasedGame sbg, int delta) throws SlickException { - if (circleTime < 0) { - // interval from radius to width - radius - maxRadius = CIRCLE_TIME * timescale; - curpos.set(nextCircle); - nextCircle = new Vector2f((float) randomRange(maxRadius, gc.getWidth() - maxRadius), - (float) randomRange(maxRadius, gc.getHeight() - maxRadius)); - circleTime = CIRCLE_TIME * timescale; - clicked = nextcircleclicked; - nextcircleclicked = false; - } - else { - circleTime -= delta * timescale; + songtime += delta; + + if (beatmapindex<=beatmap.size()-1&&beatmap.get(beatmapindex).time<=CIRCLE_TIME+songtime) { //puts objects in the beatmap into the hitobjects list when needed + Beat currentbeat = beatmap.get(beatmapindex); + HitObject hitobject = new HitObject(currentbeat.x, currentbeat.y, maxRadius, CIRCLE_TIME, false); + hitobjects.add(hitobject); + beatmapindex++; } - prevCircleSize = circleTime; - nextCircleSize = maxRadius - prevCircleSize; - } + if (!hitobjects.isEmpty()) { + for (HitObject hitobject:hitobjects) { + int index = hitobjects.indexOf(hitobject); + hitobjects.set(index, new HitObject(hitobject.x, hitobject.y, maxRadius * (hitobject.duration / CIRCLE_TIME), hitobject.duration - delta, hitobject.clicked)); //shrinks approach circle and reduces remaining time on screen + if (hitobject.duration<=-LENIENCE_TIME||hitobject.clicked&&hitobject.duration<=0) { //removes hit circle if time left is less than or equal to the lenience time, or the circle has already been clicked and it's time left is less than zero + hitobjects.remove(index); + } + } + } - private double randomRange(float lower, float upper) { - return Math.random() * (upper - lower) + lower; } @Override @@ -108,15 +117,16 @@ public boolean isAcceptingInput() { public void click() { int x = inp.getMouseX(), y= inp.getMouseY(); - if (!clicked&&new Vector2f(x, y).distance(curpos) < prevCircleSize) { //checks if current circle has already been clicked, then checks if click is within the circle - clicked = true; - points ++; - } else if (!nextcircleclicked&&new Vector2f(x, y).distance(nextCircle) < nextCircleSize){ //checks if next circle has already been clicked, then checks if click is within the circle - nextcircleclicked = true; - points ++; + for (HitObject hitobject:hitobjects) { + if (!hitobject.clicked&&new Vector2f(x, y).distance(new Vector2f(hitobject.x, hitobject.y)) < innerRadius) { //checks if current circle has already been clicked, then checks if click is within the circle + hitobjects.set(hitobjects.indexOf(hitobject), new HitObject(hitobject.x, hitobject.y, hitobject.radius, hitobject.duration, true)); //changes hitobject click state + points++; //increments points + break; //breaks out of for loop so that only one hit object is clicked at once + } } } + @Override public void mousePressed(int button, int x, int y) { if (button == Input.MOUSE_LEFT_BUTTON) {