diff --git a/src/main/java/core/Drawable.java b/src/main/java/core/Drawable.java new file mode 100644 index 0000000..97e36e3 --- /dev/null +++ b/src/main/java/core/Drawable.java @@ -0,0 +1,18 @@ +package core; + +import java.awt.*; + +public interface Drawable { + /** + *

The draw method is called every frame after the update by the master object on each object. Everything + * about drawing should be handled here in this method.

+ *

No general calculations should be made in this method. The game should be able to work just + * fine even without this method being called.

+ *

This function is NOT intended to be called manually.

+ * + * @param g2d The {@code Graphics2D} object given by the master + * @param w The width of the screen + * @param master The master object itself + */ + void draw(Graphics2D g2d, int w, Master master); +} diff --git a/src/main/java/core/Master.java b/src/main/java/core/Master.java index 1e23001..de6056d 100644 --- a/src/main/java/core/Master.java +++ b/src/main/java/core/Master.java @@ -1,6 +1,7 @@ package core; import core.math.Vector2D; +import core.physics.Collidable; import objects.DebugPos; import objects.ships.BattleShip; import objects.GameObject; @@ -11,6 +12,9 @@ import objects.world.Wall; import javax.swing.*; import java.awt.*; import java.util.ArrayList; +import java.util.concurrent.atomic.AtomicBoolean; + +//TODO master better accessible or something /** * The main object that controls everything @@ -35,6 +39,16 @@ public class Master extends JPanel { */ private final ArrayList objects; + /** + * All GameObjects that can be drawn + */ + private final ArrayList drawables; + + /** + * All physics objects that exist + */ + private final ArrayList collidables; + /** * Stores all GameObjects that were created during a frame */ @@ -48,8 +62,10 @@ public class Master extends JPanel { public Master() { objects = new ArrayList<>(); objectBuffer = new ArrayList<>(); + collidables = new ArrayList<>(); + drawables = new ArrayList<>(); - objects.add(new Grid()); + create(new Grid()); BattleShip battleShip = new BattleShip(Color.DARK_GRAY); @@ -57,15 +73,16 @@ public class Master extends JPanel { /*for (int i = 0; i < 10; i++) { bs.addTurret(new Turret(bs, 25, 10 * i + 1, 50, i % 5)); }*/ - objects.add(bs); - objects.add(battleShip); + create(bs); + create(battleShip); - objects.add(new Submarine(new Vector2D(), new Vector2D(20, 20))); - objects.add(new Wall(20, 80, 50, 2)); + create(new Submarine(new Vector2D(), new Vector2D(20, 20))); + create(new Wall(20, 80, 50, 2)); } /** * The mein drawing method, handles everything about drawing + * * @param g */ private void doDrawing(Graphics g) { @@ -81,7 +98,7 @@ public class Master extends JPanel { Graphics2D g2d = (Graphics2D) g.create(); - objects.forEach(o -> o.draw(g2d, w, this)); + drawables.forEach(o -> o.draw(g2d, w, this)); } @@ -93,11 +110,11 @@ public class Master extends JPanel { /** * Debug a position, creates a green dot at the position + * * @param pos */ - public void debugPos(Vector2D pos){ + public void debugPos(Vector2D pos) { create(new DebugPos(pos, new Vector2D(10, 10))); - System.out.println(pos); } /** @@ -120,6 +137,7 @@ public class Master extends JPanel { /** * Get the current location of the mouse relative to the frame + * * @return The location of the mouse, already normalized */ public Point getMouseLocation() { @@ -134,9 +152,33 @@ public class Master extends JPanel { /** * This method has to be called for every newly created GameObject - * @param obj + * + * @param obj The new object */ public void create(GameObject obj) { objectBuffer.add(obj); + if (obj instanceof Collidable) { + collidables.add((Collidable) obj); + } + drawables.add(obj); + + } + + public void addDrawable(Drawable d){ + drawables.add(d); + } + + public boolean doesCollide(Collidable col) { + boolean collides = false; + + for (Collidable c : collidables) { + double distance = Vector2D.distance(c.getCenterPos(), col.getCenterPos()); + if (!(distance > c.getSize().magnitude() && distance > col.getSize().magnitude())) { + if (c.collidesWith(col)) { + collides = true; + } + } + } + return collides; } } diff --git a/src/main/java/core/math/Vector2D.java b/src/main/java/core/math/Vector2D.java index a6ea0ef..c5bfe7a 100644 --- a/src/main/java/core/math/Vector2D.java +++ b/src/main/java/core/math/Vector2D.java @@ -157,6 +157,12 @@ public class Vector2D { return rotateAround(new Vector2D(), new Vector2D(0, 1), direction); } + public static double distance(Vector2D a, Vector2D b){ + Vector2D dif = subtract(a, b); + return Math.sqrt(dif.x * dif.x + dif.y + dif.y); + } + + /** * Copy this object * @return A copy of this object diff --git a/src/main/java/core/physics/Collidable.java b/src/main/java/core/physics/Collidable.java index 3336ffb..8d1c4cd 100644 --- a/src/main/java/core/physics/Collidable.java +++ b/src/main/java/core/physics/Collidable.java @@ -1,5 +1,12 @@ package core.physics; +import core.math.Vector2D; + public interface Collidable { + boolean collidesWith(Collidable o); + Hitbox getHitbox(); + Vector2D getCenterPos(); + Vector2D getSize(); + } diff --git a/src/main/java/core/physics/Hitbox.java b/src/main/java/core/physics/Hitbox.java index 3ea04e1..5d0717d 100644 --- a/src/main/java/core/physics/Hitbox.java +++ b/src/main/java/core/physics/Hitbox.java @@ -1,5 +1,8 @@ package core.physics; -public abstract class Hitbox { +import core.Drawable; +import objects.GameObject; + +public abstract class Hitbox implements Drawable { } diff --git a/src/main/java/core/physics/RectHitBox.java b/src/main/java/core/physics/RectHitBox.java index a5afae6..b295090 100644 --- a/src/main/java/core/physics/RectHitBox.java +++ b/src/main/java/core/physics/RectHitBox.java @@ -1,4 +1,154 @@ package core.physics; -public class RectHitBox extends Hitbox{ +import core.Master; +import core.math.Vector2D; + +import java.awt.*; + +/** + * A rectangular hitbox + */ +public class RectHitBox extends Hitbox { + + /** + * The corners of the rectangle like this: + *

x1 x2

+ *

y1 y2

+ */ + private Vector2D x1, y1, x2, y2; + + /** + * Create a new RectHitbox with the position of the top left point {@code x1} and the size + * + * @param x1 The top left point + * @param size the size + */ + public RectHitBox(Vector2D x1, Vector2D size) { + this.x1 = x1; + this.x2 = Vector2D.add(x1, new Vector2D(size.x, 0)); + this.y1 = Vector2D.add(x1, new Vector2D(0, size.y)); + this.y2 = Vector2D.add(x1, new Vector2D(size.x, size.y)); + } + + /** + * Move the hitbox to a new position + * + * @param x1 + * @param size + */ + public void moveTo(Vector2D x1, Vector2D size) { + this.x1 = x1.copy(); + this.x2 = Vector2D.add(x1, new Vector2D(size.x, 0)); + this.y1 = Vector2D.add(x1, new Vector2D(0, size.y)); + this.y2 = Vector2D.add(x1, new Vector2D(size.x, size.y)); + } + + /** + * Check whether two hitboxes collide with each other + * + * @param o The other rectangular hitbox, should currently only be a {@link RectHitBox} + * @return {@code true} when they collide {@code false} when they don't + */ + public boolean collidesWith(Hitbox o) { + if (o instanceof RectHitBox) { + return collidesWith((RectHitBox) o); + } else { + return false; + } + } + + /** + * Check whether two hitboxes collide with each other + * + * @param o The other rectangular hitbox + * @return {@code true} when they collide {@code false} when they don't + */ + public boolean collidesWith(RectHitBox o) { + + //all points right of x2, left of x1, above x1, below y1 -> no collision + + //if all points are right of x2 + boolean allRight = true; + for (int i = 0; i < 4; i++) { + if (o.getPoint(i).x < x2.x) { + allRight = false; + break; + } + } + + //if all points are left of x1 + boolean allLeft = true; + for (int i = 0; i < 4; i++) { + if (o.getPoint(i).x > x1.x) { + allLeft = false; + break; + } + } + + //if all points are above x1 + boolean allAbove = true; + for (int i = 0; i < 4; i++) { + if (o.getPoint(i).y > x1.y) { + allAbove = false; + break; + } + } + + //if all points are below y1 + boolean allBelow = true; + for (int i = 0; i < 4; i++) { + if (o.getPoint(i).y < y1.y) { + allBelow = false; + break; + } + } + + boolean coll = !(allAbove || allBelow || allLeft || allRight); + return coll; + } + + /** + * Get a point like this + *

0 1

+ *

2 3

+ * + * @param i the point number + * @throws IllegalArgumentException when i is not between 0 and 3 + */ + public Vector2D getPoint(int i) { + return switch (i) { + case 0 -> x1; + case 1 -> x2; + case 2 -> y1; + case 3 -> y2; + default -> throw new IllegalArgumentException("i has to be between 0 and 3"); + }; + } + + public Vector2D getX1() { + return x1; + } + + public Vector2D getY1() { + return y1; + } + + public Vector2D getX2() { + return x2; + } + + public Vector2D getY2() { + return y2; + } + + @Override + public String toString() { + return "RectHitBox{" + x1 + " " + x2 + "\n" + y1 + " " + y2 + "}"; + } + + @Override + public void draw(Graphics2D g2d, int w, Master master) { + g2d.setPaint(Color.BLUE); + g2d.fillRect((int) x1.y, (int) x1.y, (int) (x2.x - x1.x), (int) (y1.y - x1.y)); + } } diff --git a/src/main/java/objects/GameObject.java b/src/main/java/objects/GameObject.java index 6352eb9..218b1cb 100644 --- a/src/main/java/objects/GameObject.java +++ b/src/main/java/objects/GameObject.java @@ -1,7 +1,9 @@ package objects; +import core.Drawable; import core.Master; import core.math.Vector2D; +import core.physics.Collidable; import java.awt.*; @@ -9,7 +11,7 @@ import java.awt.*; * The GameObject class is the superclass of every GameObject that can be displayed on screen. It has the 2 * {@link #update(Master)} and {@link #draw(Graphics2D, int, Master)} methods that have to be */ -public abstract class GameObject { +public abstract class GameObject implements Drawable { protected int w; protected int h; @@ -32,41 +34,40 @@ public abstract class GameObject { mainColor = Color.BLACK; } - /** - *

The draw method is called every frame after the {@link #update(Master)} by the master object on each object. Everything - * about drawing should be handled here in this method.

- *

No general calculations should be made in this method. The game should be able to work just - * fine even without this method being called.

- *

This function is NOT intended to be called manually.

- * @param g2d The {@code Graphics2D} object given by the master - * @param w The width of the screen - * @param master The master object itself - */ - public abstract void draw(Graphics2D g2d, int w, Master master); /** *

The update method is called every frame before the {@link #draw(Graphics2D, int, Master)} method by the master object on each object. Everything * that is needed for the game to work should be here in this method.

*

No drawing should be made in this method. The {@code debug} method can be called on the master.

*

This function is NOT intended to be called manually.

+ * * @param master The master object itself */ public abstract void update(Master master); /** * A simple method to move the object to a Vector2D. This method should be called instead of doing it manually. + * * @param target The target position */ - public void moveTo(Vector2D target){ + public void moveTo(Vector2D target, Master master) { + Vector2D oldPos = position; this.position = target; + + if (this instanceof Collidable) { + if (master.doesCollide((Collidable) this)) { + this.position = oldPos; + } + } } /** * This method draws a rectangle at the current position and size + * * @param g2d The Graphics2D object provided by the master - * @param w The width of the screen + * @param w The width of the screen */ - public void drawRect(Graphics2D g2d, int w){ + public void drawRect(Graphics2D g2d, int w) { this.w = w; h = (int) (this.w / Master.SCREEN_RATIO); int xAbs = (int) getWorldCoords(position.x, true); @@ -80,12 +81,13 @@ public abstract class GameObject { /** * This method draws a rounded rectangle at the current position and size - * @param g2d The Graphics2D object provided by the master - * @param w The width of the screen + * + * @param g2d The Graphics2D object provided by the master + * @param w The width of the screen * @param arcW The arc width of the rectangle * @param arcH The arc height of the rectangle */ - public void drawRoundRect(Graphics2D g2d, int w, int arcW, int arcH){ + public void drawRoundRect(Graphics2D g2d, int w, int arcW, int arcH) { this.w = w; h = (int) (w / Master.SCREEN_RATIO); int xAbs = (int) getWorldCoords(position.x, true); @@ -97,16 +99,16 @@ public abstract class GameObject { g2d.fillRoundRect(xAbs, yAbs, sizeXAbs, sizeYAbs, arcW, arcH); } - public double getWorldCoords(double value, boolean isX){ - if (isX){ + public double getWorldCoords(double value, boolean isX) { + if (isX) { return (value / (Master.SCREEN_Y_COORDINATES * Master.SCREEN_RATIO) * w); } else { return (value / Master.SCREEN_Y_COORDINATES * h); } } - public double getWorldCoordsSize(double value, boolean isX){ - if (isX){ + public double getWorldCoordsSize(double value, boolean isX) { + if (isX) { return (value / Master.SCREEN_Y_COORDINATES * w); } else { return (value / Master.SCREEN_Y_COORDINATES * h); @@ -114,27 +116,31 @@ public abstract class GameObject { } - public double getMapCoords(double value, boolean isX){ - if (isX){ + public double getMapCoords(double value, boolean isX) { + if (isX) { return (position.x + value / 100d * size.x); } else { return (position.y + value / 100d * size.y); } } - public double getMapCoordsSize(double value, boolean useX){ - if (useX){ + public double getMapCoordsSize(double value, boolean useX) { + if (useX) { return (value / 100d * size.x); } else { return (value / 100d * size.y); } } - public int getWorldCoordsFromLocal(double value, boolean isX){ + public int getWorldCoordsFromLocal(double value, boolean isX) { return (int) getWorldCoords(getMapCoords(value, isX), isX); } - public int getWorldCoordsFromLocalSize(double value, boolean useX){ + public int getWorldCoordsFromLocalSize(double value, boolean useX) { return (int) getWorldCoordsSize(getMapCoordsSize(value, useX), useX); } + + public Vector2D getCenterPosition(){ + return new Vector2D(position.x + size.x, position.y + size.y); + } } diff --git a/src/main/java/objects/ships/Submarine.java b/src/main/java/objects/ships/Submarine.java index 01affe5..819ef3a 100644 --- a/src/main/java/objects/ships/Submarine.java +++ b/src/main/java/objects/ships/Submarine.java @@ -2,14 +2,20 @@ package objects.ships; import core.Master; import core.math.Vector2D; +import core.physics.Collidable; +import core.physics.Hitbox; +import core.physics.RectHitBox; import objects.GameObject; import java.awt.*; -public class Submarine extends GameObject { +public class Submarine extends GameObject implements Collidable { + + private RectHitBox hitbox; public Submarine(Vector2D position, Vector2D size) { super(position, size); + this.hitbox = new RectHitBox(position, size); } @Override @@ -21,6 +27,26 @@ public class Submarine extends GameObject { @Override public void update(Master master) { Point mouse = master.getMouseLocation(); - moveTo(new Vector2D(mouse.x, mouse.y)); + moveTo(new Vector2D(mouse.x, mouse.y), master); + } + + @Override + public boolean collidesWith(Collidable o) { + return hitbox.collidesWith(o.getHitbox()); + } + + @Override + public Hitbox getHitbox() { + return hitbox; + } + + @Override + public Vector2D getCenterPos() { + return getCenterPosition(); + } + + @Override + public Vector2D getSize() { + return size; } } diff --git a/src/main/java/objects/world/Wall.java b/src/main/java/objects/world/Wall.java index 4ea16d0..58c5ca5 100644 --- a/src/main/java/objects/world/Wall.java +++ b/src/main/java/objects/world/Wall.java @@ -1,14 +1,21 @@ package objects.world; import core.Master; +import core.math.Vector2D; +import core.physics.Collidable; +import core.physics.Hitbox; +import core.physics.RectHitBox; import objects.GameObject; import java.awt.*; -public class Wall extends GameObject { +public class Wall extends GameObject implements Collidable { + + private RectHitBox hitbox; public Wall(double x, double y, double xSize, double ySize) { super(x, y, xSize, ySize); + this.hitbox = new RectHitBox(new Vector2D(x, y), new Vector2D(xSize, ySize)); } @Override @@ -19,4 +26,24 @@ public class Wall extends GameObject { @Override public void update(Master master) { } + + @Override + public boolean collidesWith(Collidable o) { + return this.hitbox.collidesWith(o.getHitbox()); + } + + @Override + public Hitbox getHitbox() { + return hitbox; + } + + @Override + public Vector2D getCenterPos() { + return position; + } + + @Override + public Vector2D getSize() { + return size; + } } diff --git a/src/test/java/core/Vector2DTest.java b/src/test/java/core/math/Vector2DTest.java similarity index 99% rename from src/test/java/core/Vector2DTest.java rename to src/test/java/core/math/Vector2DTest.java index 1bd52a0..2a71a71 100644 --- a/src/test/java/core/Vector2DTest.java +++ b/src/test/java/core/math/Vector2DTest.java @@ -1,4 +1,4 @@ -package core; +package core.math; import core.math.Vector2D; import org.junit.jupiter.api.Test; diff --git a/src/test/java/core/physics/RectHitBoxTest.java b/src/test/java/core/physics/RectHitBoxTest.java new file mode 100644 index 0000000..b31a5c5 --- /dev/null +++ b/src/test/java/core/physics/RectHitBoxTest.java @@ -0,0 +1,62 @@ +package core.physics; + +import core.math.Vector2D; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +class RectHitBoxTest { + + @Test + void constructorSimple(){ + RectHitBox a = new RectHitBox(new Vector2D(1, 1), new Vector2D(1, 1)); + + assertEquals(a.getX1(), new Vector2D(1, 1)); + assertEquals(a.getX2(), new Vector2D(2, 1)); + assertEquals(a.getY1(), new Vector2D(1, 2)); + assertEquals(a.getY2(), new Vector2D(2, 2)); + } + + @Test + void doCollideNoCross(){ + RectHitBox a = new RectHitBox(new Vector2D(-1, 1), new Vector2D(1, 1)); + RectHitBox b = new RectHitBox(new Vector2D(1, 1), new Vector2D(1, 1)); + assertFalse(a.collidesWith(b)); + } + + @Test + void doCollideCornerTouch(){ + RectHitBox a = new RectHitBox(new Vector2D(0, 0), new Vector2D(1, 1)); + RectHitBox b = new RectHitBox(new Vector2D(1, 1), new Vector2D(1, 1)); + assertFalse(a.collidesWith(b)); + } + + @Test + void doCollideCornerInside(){ + RectHitBox a = new RectHitBox(new Vector2D(0, 0), new Vector2D(2, 2)); + RectHitBox b = new RectHitBox(new Vector2D(1, 1), new Vector2D(2, 2)); + assertTrue(a.collidesWith(b)); + } + + @Test + void doCollideEdgeInside(){ + RectHitBox a = new RectHitBox(new Vector2D(-2, 0), new Vector2D(4, 2)); + RectHitBox b = new RectHitBox(new Vector2D(-1, 1), new Vector2D(2, 2)); + assertTrue(a.collidesWith(b)); + } + + @Test + void doCollideCrossCrossing(){ + RectHitBox a = new RectHitBox(new Vector2D(-1, 2), new Vector2D(2, 4)); + RectHitBox b = new RectHitBox(new Vector2D(-2, 1), new Vector2D(4, 2)); + assertTrue(a.collidesWith(b)); + } + + @Test + void doCollideFullyInside(){ + RectHitBox a = new RectHitBox(new Vector2D(0, 0), new Vector2D(3, 3)); + RectHitBox b = new RectHitBox(new Vector2D(1, 1), new Vector2D(1, 1)); + assertTrue(a.collidesWith(b)); + } + +} \ No newline at end of file diff --git a/target/classes/core/Master.class b/target/classes/core/Master.class index 933f437..4b736b9 100644 Binary files a/target/classes/core/Master.class and b/target/classes/core/Master.class differ diff --git a/target/classes/core/Vector2D.class b/target/classes/core/Vector2D.class deleted file mode 100644 index 8567c8a..0000000 Binary files a/target/classes/core/Vector2D.class and /dev/null differ diff --git a/target/classes/objects/DebugPos.class b/target/classes/objects/DebugPos.class index 89167d6..faaca3e 100644 Binary files a/target/classes/objects/DebugPos.class and b/target/classes/objects/DebugPos.class differ diff --git a/target/classes/objects/GameObject.class b/target/classes/objects/GameObject.class index ffcdc6e..8af8567 100644 Binary files a/target/classes/objects/GameObject.class and b/target/classes/objects/GameObject.class differ diff --git a/target/classes/objects/ships/BattleShip.class b/target/classes/objects/ships/BattleShip.class index 34aa79b..9ddd1a5 100644 Binary files a/target/classes/objects/ships/BattleShip.class and b/target/classes/objects/ships/BattleShip.class differ diff --git a/target/classes/objects/ships/Shell.class b/target/classes/objects/ships/Shell.class index 3f2a361..3d78565 100644 Binary files a/target/classes/objects/ships/Shell.class and b/target/classes/objects/ships/Shell.class differ diff --git a/target/classes/objects/ships/Turret.class b/target/classes/objects/ships/Turret.class index d965239..d9d3099 100644 Binary files a/target/classes/objects/ships/Turret.class and b/target/classes/objects/ships/Turret.class differ diff --git a/target/test-classes/core/Vector2DTest.class b/target/test-classes/core/Vector2DTest.class deleted file mode 100644 index 0d53279..0000000 Binary files a/target/test-classes/core/Vector2DTest.class and /dev/null differ