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 ArrayListx1 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