created physics system and new Drawable interface

This commit is contained in:
nora 2020-12-12 14:34:15 +01:00
parent 6bc1f74016
commit 87ca37c9ca
19 changed files with 390 additions and 43 deletions

View file

@ -0,0 +1,18 @@
package core;
import java.awt.*;
public interface Drawable {
/**
* <p>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.</p>
* <p>No general calculations should be made in this method. The game should be able to work just
* fine even without this method being called.</p>
* <p>This function is <i>NOT</i> intended to be called manually.</p>
*
* @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);
}

View file

@ -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<GameObject> objects;
/**
* All GameObjects that can be drawn
*/
private final ArrayList<Drawable> drawables;
/**
* All physics objects that exist
*/
private final ArrayList<Collidable> 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) {
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;
}
}

View file

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

View file

@ -1,5 +1,12 @@
package core.physics;
import core.math.Vector2D;
public interface Collidable {
boolean collidesWith(Collidable o);
Hitbox getHitbox();
Vector2D getCenterPos();
Vector2D getSize();
}

View file

@ -1,5 +1,8 @@
package core.physics;
public abstract class Hitbox {
import core.Drawable;
import objects.GameObject;
public abstract class Hitbox implements Drawable {
}

View file

@ -1,4 +1,154 @@
package core.physics;
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:
* <p>x1 x2</p>
* <p>y1 y2</p>
*/
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
* <p>0 1</p>
* <p>2 3</p>
*
* @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));
}
}

View file

@ -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,37 +34,36 @@ public abstract class GameObject {
mainColor = Color.BLACK;
}
/**
* <p>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.</p>
* <p>No general calculations should be made in this method. The game should be able to work just
* fine even without this method being called.</p>
* <p>This function is <i>NOT</i> intended to be called manually.</p>
* @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);
/**
* <p>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.</p>
* <p>No drawing should be made in this method. The {@code debug} method can be called on the master.</p>
* <p>This function is <i>NOT</i> intended to be called manually.</p>
*
* @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
*/
@ -80,6 +81,7 @@ 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 arcW The arc width of the rectangle
@ -137,4 +139,8 @@ public abstract class GameObject {
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);
}
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -1,4 +1,4 @@
package core;
package core.math;
import core.math.Vector2D;
import org.junit.jupiter.api.Test;

View file

@ -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));
}
}

Binary file not shown.

Binary file not shown.