-
@@ -287,4 +294,9 @@
+
+
+ sk.bytecode.bludisko.rt.desktop.*
+
+
\ No newline at end of file
diff --git a/aseprite/crosshair.aseprite b/aseprite/crosshair.aseprite
new file mode 100644
index 0000000..c0374f4
Binary files /dev/null and b/aseprite/crosshair.aseprite differ
diff --git a/aseprite/logo.aseprite b/aseprite/logo.aseprite
new file mode 100644
index 0000000..ba7e1e1
Binary files /dev/null and b/aseprite/logo.aseprite differ
diff --git a/bludisko_rt.iml b/bludisko_rt.iml
index 5221434..b617bab 100644
--- a/bludisko_rt.iml
+++ b/bludisko_rt.iml
@@ -4,10 +4,9 @@
-
-
+
@@ -34,48 +33,5 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/res/maps/chamber1.map b/res/maps/chamber1.map
index 4924106..5a747f0 100644
Binary files a/res/maps/chamber1.map and b/res/maps/chamber1.map differ
diff --git a/res/run.sh b/res/run.sh
index 568a32a..bceb00e 100644
--- a/res/run.sh
+++ b/res/run.sh
@@ -1,2 +1,3 @@
-#!/usr/bin/env bash
-java --enable-preview -jar bludisko_rt.jar
\ No newline at end of file
+#!/bin/bash
+
+java --enable-preview -jar bludisko_rt.jar
diff --git a/res/textures/other/crosshair.png b/res/textures/other/crosshair.png
new file mode 100644
index 0000000..3060180
Binary files /dev/null and b/res/textures/other/crosshair.png differ
diff --git a/res/textures/other/logo.png b/res/textures/other/logo.png
new file mode 100644
index 0000000..66426b3
Binary files /dev/null and b/res/textures/other/logo.png differ
diff --git a/src/module-info.java b/src/module-info.java
new file mode 100644
index 0000000..662bcf6
--- /dev/null
+++ b/src/module-info.java
@@ -0,0 +1,5 @@
+open module bludisko.rt {
+ requires jdk.unsupported;
+ requires java.desktop;
+ requires org.jetbrains.annotations;
+}
\ No newline at end of file
diff --git a/src/sk/bytecode/bludisko/rt/desktop/DesktopLauncher.java b/src/sk/bytecode/bludisko/rt/desktop/DesktopLauncher.java
index 565d8e5..6e61a3d 100644
--- a/src/sk/bytecode/bludisko/rt/desktop/DesktopLauncher.java
+++ b/src/sk/bytecode/bludisko/rt/desktop/DesktopLauncher.java
@@ -2,7 +2,7 @@
import sk.bytecode.bludisko.rt.game.graphics.TextureManager;
import sk.bytecode.bludisko.rt.game.window.Window;
-import sk.bytecode.bludisko.rt.game.window.screens.GameScreen;
+import sk.bytecode.bludisko.rt.game.window.screens.MenuScreen;
public class DesktopLauncher {
@@ -13,7 +13,7 @@ public class DesktopLauncher {
public static void main(String[] args) {
TextureManager.loadAll();
- Window window = new Window(new GameScreen());
+ Window window = new Window(new MenuScreen());
window.start();
}
diff --git a/src/sk/bytecode/bludisko/rt/game/blocks/Block.java b/src/sk/bytecode/bludisko/rt/game/blocks/Block.java
index d8ab6c2..566f744 100644
--- a/src/sk/bytecode/bludisko/rt/game/blocks/Block.java
+++ b/src/sk/bytecode/bludisko/rt/game/blocks/Block.java
@@ -63,7 +63,6 @@ public Side getSide(Vector2 position) {
} else {
return Side.SOUTH;
}
-
}
if(position.y % 1 == 0) {
// East-West
@@ -73,7 +72,6 @@ public Side getSide(Vector2 position) {
return Side.EAST;
}
}
-
return Side.NONE;
}
diff --git a/src/sk/bytecode/bludisko/rt/game/blocks/BlockManager.java b/src/sk/bytecode/bludisko/rt/game/blocks/BlockManager.java
index e4e36c6..5bb5177 100644
--- a/src/sk/bytecode/bludisko/rt/game/blocks/BlockManager.java
+++ b/src/sk/bytecode/bludisko/rt/game/blocks/BlockManager.java
@@ -32,9 +32,6 @@ public static Block createBlock(int id, int x, int y) {
case 5 -> new GlassPane(Side.NORTH, coordinates);
case 6 -> new GlassPane(Side.EAST, coordinates);
- case 7 -> new Portal(Side.EAST, Portal.Color.ORANGE, coordinates);
- case 8 -> new Portal(Side.SOUTH, Portal.Color.BLUE, coordinates);
-
case 9 -> new WallWindow(coordinates, false);
case 10 -> new WallWindow(coordinates, true);
case 11 -> new Board(coordinates, 0);
diff --git a/src/sk/bytecode/bludisko/rt/game/blocks/game/Portal.java b/src/sk/bytecode/bludisko/rt/game/blocks/game/Portal.java
index fb3a8e4..13fe916 100644
--- a/src/sk/bytecode/bludisko/rt/game/blocks/game/Portal.java
+++ b/src/sk/bytecode/bludisko/rt/game/blocks/game/Portal.java
@@ -1,21 +1,34 @@
package sk.bytecode.bludisko.rt.game.blocks.game;
import sk.bytecode.bludisko.rt.game.blocks.Block;
-import sk.bytecode.bludisko.rt.game.graphics.*;
+import sk.bytecode.bludisko.rt.game.graphics.Ray;
+import sk.bytecode.bludisko.rt.game.graphics.RayAction;
+import sk.bytecode.bludisko.rt.game.graphics.Side;
+import sk.bytecode.bludisko.rt.game.graphics.Texture;
+import sk.bytecode.bludisko.rt.game.graphics.TextureManager;
import sk.bytecode.bludisko.rt.game.math.MathUtils;
import sk.bytecode.bludisko.rt.game.math.Vector2;
public class Portal extends Block {
+ /**
+ * Color of the portal. Also serves as a portal pair identifier.
+ */
public enum Color {
BLUE,
ORANGE
}
+ // MARK: - Attributes
+
private final Vector2 coordinates;
private final Side side;
private final Portal.Color color;
+
private Portal otherPortal;
+ private Block originalWall;
+
+ // MARK: - Constructor
/**
* Constructs a new portal.
@@ -29,6 +42,8 @@ public Portal(Side side, Portal.Color color, Vector2 coordinates) {
this.side = side;
}
+ // MARK: - Override
+
@Override
public Texture getTexture(Side side) {
if(side == this.side) {
@@ -55,17 +70,17 @@ public Vector2 getCoordinates() {
public RayAction hitAction(Ray ray) {
var rayPosition = ray.getPosition();
var hitSide = getSide(rayPosition);
- if(hitSide == side && otherPortal != null) {
- var thisExit = this.getExitRotation();
- var otherExit = otherPortal.getExitRotation();
- var exitRotation = thisExit.angleRad() - otherExit.angleRad();
+ if(hitSide == side && otherPortal != null && insidePortalFrame(rayPosition)) {
+ var coordinates = otherPortal.getCoordinates();
+ var rotation = getRotation(side) - getPiComplementaryRotation(otherPortal.side);
+ var offset = getOffset(side, otherPortal.side);
rayPosition.set(MathUtils.decimalPart(rayPosition));
- rayPosition.rotateRad(exitRotation);
- rayPosition.add(otherPortal.getCoordinates());
- rayPosition.add(otherPortal.getExitOffset());
+ rayPosition.rotateRad(rotation);
+ rayPosition.add(coordinates);
+ rayPosition.add(offset);
- ray.updateDirection(ray.getDirection().cpy().rotateRad(exitRotation));
+ ray.updateDirection(ray.getDirection().cpy().rotateRad(rotation));
return RayAction.TELEPORT;
} else {
@@ -73,26 +88,102 @@ public RayAction hitAction(Ray ray) {
}
}
- private Vector2 getExitOffset() {
+ // MARK: - Static
+
+ /**
+ * When moving a ray from one portal to another and when the portals are on
+ * different sides, the ray will end up misaligned. Adding this offset
+ * corrects the difference. Since the ray will then also end up inside a wall
+ * (on a coordinate exactly divisible by an integer, a block edge), the
+ * smallest possible float-expressible value will be added to the result
+ * to fix Z-fighting.
+ * @param entrySide Side of a block the entrance portal is on
+ * @param exitSide Side of a block the exit portal is on
+ * @return [x, y] offset to add
+ * @see Side
+ * @see Portal#getRotation(Side)
+ */
+ private static Vector2 getOffset(Side entrySide, Side exitSide) {
+ return (switch(entrySide) {
+ case NONE -> new Vector2();
+ case NORTH, EAST -> switch(exitSide) {
+ case NONE -> new Vector2();
+ case NORTH -> new Vector2(0f, 1f);
+ case EAST -> new Vector2(1f, 1f);
+ case SOUTH -> new Vector2(1f, 0f);
+ case WEST -> new Vector2(0f, 0f);
+ };
+ case SOUTH, WEST -> switch(exitSide) {
+ case NONE -> new Vector2();
+ case NORTH -> new Vector2(0f, 0f);
+ case EAST -> new Vector2(0f, 1f);
+ case SOUTH -> new Vector2(1f, 1f);
+ case WEST -> new Vector2(1f, 0f);
+ };
+ }).add(switch(exitSide) {
+ case NONE -> new Vector2();
+ case NORTH -> new Vector2(-MathUtils.FLOAT_ROUNDING_ERROR, 0f);
+ case EAST -> new Vector2(0f, MathUtils.FLOAT_ROUNDING_ERROR);
+ case SOUTH -> new Vector2(MathUtils.FLOAT_ROUNDING_ERROR, 0f);
+ case WEST -> new Vector2(0f, -MathUtils.FLOAT_ROUNDING_ERROR);
+ });
+ }
+
+ /**
+ * Converts {@link Side} to rotation. North is at 0°, rotation increases in
+ * a clockwise direction.
+ * @param side Side of the block the portal is on
+ * @return Rotation of the side in radians
+ */
+ private static float getRotation(Side side) {
return switch(side) {
- case NONE -> null;
- case NORTH -> null;
- case EAST -> new Vector2(0f, 1f);
- case SOUTH -> new Vector2(1f, 0f);
- case WEST -> null;
+ case NONE -> 0f;
+ case NORTH -> 0f;
+ case EAST -> MathUtils.PI / 2f;
+ case SOUTH -> MathUtils.PI;
+ case WEST -> 3 * MathUtils.PI / 2f;
};
}
- private Vector2 getExitRotation() {
+ /**
+ * Degrees of rotation left to the nearest n*PI value where n*PI ≠ 0.
+ * When used in combination as
+ * {@link Portal#getRotation(Side)} - getPiComplementaryRotation(Side),
+ * result is the angle required to rotate from facing entrance side to exit side.
+ *
+ * @param side Side of the block the portal is on
+ * @return Rotation in radians
+ *
+ * @see Side
+ * @see Portal#getRotation(Side)
+ */
+ private static float getPiComplementaryRotation(Side side) {
return switch(side) {
- case NONE -> null;
- case NORTH -> null;
- case EAST -> new Vector2(0f, 1f);
- case SOUTH -> new Vector2(1f, 0f);
- case WEST -> null;
+ case NONE -> 0f;
+ case NORTH -> MathUtils.PI;
+ case EAST -> 3 * MathUtils.PI / 2f;
+ case SOUTH -> 0f;
+ case WEST -> MathUtils.PI / 2;
};
}
+ private static boolean insidePortalFrame(Vector2 location) {
+ final float frameSize = 0.2f;
+
+ var decimalPart = MathUtils.decimalPart(location);
+ if(decimalPart.x == 0) {
+ return decimalPart.y > frameSize && decimalPart.y < 1f - frameSize;
+ } else {
+ return decimalPart.x > frameSize && decimalPart.x < 1f - frameSize;
+ }
+ }
+
+ // MARK: - Public
+
+ public Portal getOtherPortal() {
+ return otherPortal;
+ }
+
/**
* Link two portals together.
* @param otherPortal Paired portal
@@ -100,4 +191,17 @@ private Vector2 getExitRotation() {
public void setOtherPortal(Portal otherPortal) {
this.otherPortal = otherPortal;
}
+
+ public Block getOriginalWall() {
+ return originalWall;
+ }
+
+ public void setOriginalWall(Block originalWall) {
+ this.originalWall = originalWall;
+ }
+
+ public Portal.Color getColor() {
+ return color;
+ }
+
}
diff --git a/src/sk/bytecode/bludisko/rt/game/blocks/game/WhiteTiles.java b/src/sk/bytecode/bludisko/rt/game/blocks/game/WhiteTiles.java
index 83d97c4..13de73d 100644
--- a/src/sk/bytecode/bludisko/rt/game/blocks/game/WhiteTiles.java
+++ b/src/sk/bytecode/bludisko/rt/game/blocks/game/WhiteTiles.java
@@ -1,10 +1,15 @@
package sk.bytecode.bludisko.rt.game.blocks.game;
import sk.bytecode.bludisko.rt.game.blocks.Block;
-import sk.bytecode.bludisko.rt.game.graphics.*;
+import sk.bytecode.bludisko.rt.game.blocks.group.PortalPlaceable;
+import sk.bytecode.bludisko.rt.game.graphics.Ray;
+import sk.bytecode.bludisko.rt.game.graphics.RayAction;
+import sk.bytecode.bludisko.rt.game.graphics.Side;
+import sk.bytecode.bludisko.rt.game.graphics.Texture;
+import sk.bytecode.bludisko.rt.game.graphics.TextureManager;
import sk.bytecode.bludisko.rt.game.math.Vector2;
-public class WhiteTiles extends Block {
+public final class WhiteTiles extends Block implements PortalPlaceable {
private final Vector2 coordinates;
diff --git a/src/sk/bytecode/bludisko/rt/game/blocks/group/PortalPlaceable.java b/src/sk/bytecode/bludisko/rt/game/blocks/group/PortalPlaceable.java
new file mode 100644
index 0000000..8a8c0e9
--- /dev/null
+++ b/src/sk/bytecode/bludisko/rt/game/blocks/group/PortalPlaceable.java
@@ -0,0 +1,9 @@
+package sk.bytecode.bludisko.rt.game.blocks.group;
+
+import sk.bytecode.bludisko.rt.game.blocks.game.WhiteTiles;
+
+/**
+ * Interface defining all blocks a Portal can be placed on.
+ * @see sk.bytecode.bludisko.rt.game.blocks.game.Portal
+ */
+public sealed interface PortalPlaceable permits WhiteTiles {}
diff --git a/src/sk/bytecode/bludisko/rt/game/entities/Entity.java b/src/sk/bytecode/bludisko/rt/game/entities/Entity.java
index 33d7829..4937a4b 100644
--- a/src/sk/bytecode/bludisko/rt/game/entities/Entity.java
+++ b/src/sk/bytecode/bludisko/rt/game/entities/Entity.java
@@ -1,12 +1,13 @@
package sk.bytecode.bludisko.rt.game.entities;
+import sk.bytecode.bludisko.rt.game.graphics.Tickable;
import sk.bytecode.bludisko.rt.game.map.World;
import sk.bytecode.bludisko.rt.game.math.Vector2;
/**
* Class representing an entity in the world. Contains its position.
*/
-public abstract class Entity {
+public abstract class Entity implements Tickable {
protected World world;
protected Vector2 position;
diff --git a/src/sk/bytecode/bludisko/rt/game/entities/Player.java b/src/sk/bytecode/bludisko/rt/game/entities/Player.java
index f630566..6b1b3a5 100644
--- a/src/sk/bytecode/bludisko/rt/game/entities/Player.java
+++ b/src/sk/bytecode/bludisko/rt/game/entities/Player.java
@@ -1,6 +1,5 @@
package sk.bytecode.bludisko.rt.game.entities;
-import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import sk.bytecode.bludisko.rt.game.graphics.Camera;
import sk.bytecode.bludisko.rt.game.graphics.DistanceRay;
@@ -12,7 +11,6 @@
import sk.bytecode.bludisko.rt.game.math.Vector2;
import sk.bytecode.bludisko.rt.game.util.NullSafe;
-import java.awt.*;
import java.lang.ref.WeakReference;
/**
@@ -20,14 +18,16 @@
*/
public class Player extends Entity implements GameInputManagerDelegate {
+ private static final float WALKING_SPEED = 1.75f;
+ private static final float RUNNING_SPEED = 2.5f;
+
private Map worldWallMap;
private WeakReference camera;
- private Rectangle screenSize;
private Vector2 movementVector;
private Item heldItem;
- private float walkingSpeed = 1.75f;
+ private float movementSpeed = 1.75f;
// MARK: - Constructor
@@ -91,27 +91,6 @@ public void tick(float dt) {
NullSafe.acceptWeak(camera, camera -> camera.bind(this));
}
- /**
- * Draws player overlay - currently held item to current graphics content.
- * @param graphics Graphics content to draw on
- */
- public void drawItemOverlay(@NotNull Graphics graphics) {
- NullSafe.accept(heldItem, heldItem -> {
- var texture = heldItem.getOverlay();
- var image = texture.asImage();
-
- float resizingRatio = screenSize.height / (float)texture.getHeight();
- int scaledWidth = (int) (texture.getWidth() * resizingRatio);
- int offset = (screenSize.width - scaledWidth) / 2;
-
- graphics.drawImage(image, offset, 0, scaledWidth, screenSize.height, null);
- });
- }
-
- public void setItemOverlayScreenSizeInformation(@NotNull Rectangle bounds) {
- screenSize = bounds;
- }
-
// MARK: - Input
@Override
@@ -121,19 +100,24 @@ public void didUpdateMovementDirection(Vector2 direction) {
@Override
public void didUpdateRotation(Vector2 rotation) {
- this.rotate(rotation.x * MathUtils.degreesToRadians, rotation.y * 0.1f);
+ rotate(rotation.x * MathUtils.degreesToRadians, rotation.y * 0.1f/*MathUtils.degreesToRadians*/);
}
@Override
public void didUpdateSprintingStatus(boolean isSprinting) {
- this.walkingSpeed = isSprinting ? 2.5f : 1.75f;
+ this.movementSpeed = isSprinting ? Player.RUNNING_SPEED : Player.WALKING_SPEED;
+ }
+
+ @Override
+ public void didToggleMouseButton(boolean rmb) {
+ NullSafe.accept(heldItem, rmb ? Item::useSecondary : Item::use);
}
// MARK: - Private
private void move(float dt) {
var movementVector = this.movementVector.cpy()
- .scl(walkingSpeed)
+ .scl(movementSpeed)
.scl(dt)
.rotateRad(direction.angleRad());
@@ -141,8 +125,10 @@ private void move(float dt) {
var nextHitDistance = movementRay.cast(movementVector.len());
if(Float.isNaN(nextHitDistance)) {
+ var portalRotation = movementRay.getDirection().angleRad() - movementVector.angleRad();
+
position.set(movementRay.getPosition());
- direction.set(movementRay.getDirection());
+ direction.rotateRad(portalRotation);
} else if(nextHitDistance == -1) {
position.add(movementVector);
}
@@ -158,6 +144,7 @@ private void rotate(float angleDeg, float pitch) {
if(this.pitch > 200) {
this.pitch = 200;
}
+ // this.pitch = MathUtils.clamp(this.pitch + pitch, -22.5f * MathUtils.degreesToRadians, 22.5f * MathUtils.degreesToRadians);
}
}
diff --git a/src/sk/bytecode/bludisko/rt/game/graphics/Actor.java b/src/sk/bytecode/bludisko/rt/game/graphics/Actor.java
new file mode 100644
index 0000000..b3d0859
--- /dev/null
+++ b/src/sk/bytecode/bludisko/rt/game/graphics/Actor.java
@@ -0,0 +1,31 @@
+package sk.bytecode.bludisko.rt.game.graphics;
+
+import org.jetbrains.annotations.NotNull;
+import sk.bytecode.bludisko.rt.game.window.Window;
+import sk.bytecode.bludisko.rt.game.window.screens.Screen;
+
+import java.awt.Graphics;
+import java.awt.Rectangle;
+
+public interface Actor extends Tickable {
+
+ /**
+ * Game loop - graphics.
+ * Called every frame after updating drawing information.
+ * Graphics object should not be finalized or closed inside draw() methods,
+ * in case another object wants to draw more information into Canvas.
+ * Finalizing and showing the image is handled automatically by Window.
+ * @param graphics Java AWT graphics to draw into. Will be displayed on the Window's Canvas
+ * @see Window
+ * @see Screen#tick(float)
+ */
+ void draw(@NotNull Graphics graphics);
+
+ /**
+ * Called every time the drawing quality is updated or the Window is resized.
+ * @param bounds Updated bounds rectangle
+ * @see Window#canvasBounds()
+ */
+ void screenDidChangeBounds(@NotNull Rectangle bounds);
+
+}
diff --git a/src/sk/bytecode/bludisko/rt/game/graphics/Camera.java b/src/sk/bytecode/bludisko/rt/game/graphics/Camera.java
index f4f7bd2..774ae8c 100644
--- a/src/sk/bytecode/bludisko/rt/game/graphics/Camera.java
+++ b/src/sk/bytecode/bludisko/rt/game/graphics/Camera.java
@@ -16,12 +16,12 @@
/**
* Virtual three-dimensional camera that draws into a {@link sk.bytecode.bludisko.rt.game.window.Window} canvas.
- * Can be moved and rotated in a 3D space. Pitch is somewhat limited to prevent image distortion at large angles.
- *
- * Uses a traditional raycasting algorithm to calculate sizes of objects to draw on the screen.
+ * Can be moved and rotated in a 3D space. Pitch is limited to prevent image distortion at large angles.
+ *
+ * Uses raycasting algorithm to calculate sizes of objects to draw on the screen.
* Uses affine texture mapping to map textures of objects on a virtual 3D surface.
* Can draw on any arbitrary-size canvas with different amount of rays cast and pixels drawn.
- * Has a separated viewport from screen size to be able to scale the resulting image.
+ * Has separate viewport from screen size to be able to scale the resulting image.
*/
public class Camera {
@@ -75,9 +75,7 @@ public synchronized void draw(@NotNull Graphics graphics) {
drawFloor(screenBuffer);
drawWalls(screenBuffer);
- graphics.setColor(java.awt.Color.green);
graphics.drawImage(bufferedImage, 0, 0, screenSize.width, screenSize.height, null);
- graphics.drawString(this.position.toString(), 0, 50);
}
private void drawWalls(final int[] screenBuffer) {
@@ -107,8 +105,8 @@ private void drawWalls(final int[] screenBuffer) {
int textureWidth = texture.getWidth();
int textureHeight = texture.getHeight();
- int texelX_X = textureWidth - (int)(wallHitCoordinates.x * textureWidth) - 1;
- int texelX_Y = textureWidth - (int)(wallHitCoordinates.y * textureWidth) - 1;
+ int texelX_X = textureWidth - (int) (wallHitCoordinates.x * textureWidth) - 1;
+ int texelX_Y = textureWidth - (int) (wallHitCoordinates.y * textureWidth) - 1;
int texelX = Math.min(texelX_X, texelX_Y);
int screenWidth = (int) viewportSize.x;
@@ -132,7 +130,7 @@ private void drawWalls(final int[] screenBuffer) {
float colorScale = 1 - ((hitSide == Side.EAST || hitSide == Side.WEST ? 0 : 1) * 0.33f);
for(int k = loopStart; k < loopEnd; k++) {
- int texelY = (int)texelPosition & (textureHeight - 1);
+ int texelY = (int) texelPosition & (textureHeight - 1);
texelPosition += texelStep;
Color color = texture.getColor(texelX, texelY);
@@ -156,7 +154,7 @@ private void drawFloor(final int[] screenBuffer) {
int horizon = screenHeight / 2;
for(int y = horizon + (int) pitch; y < (int) viewportSize.y; y++) {
- if(y < 0) continue;
+ if(y < 0) { y = 0; }
float cameraY = y - (screenHeight / 2f) - pitch;
float cameraZ = (screenHeight / 2f) + positionZ;
@@ -192,14 +190,18 @@ private void drawCeiling(int[] buffer) {
// MARK: - Private
private void updateCameraPlane() {
- this.plane.set(
- direction.cpy()
- .rotate90(-1)
- .scl(2f / 3f)
- .scl((float) screenSize.width / (float) screenSize.height / 1.33333f)
+ this.plane.set(direction.cpy()
+ .rotate90(-1)
+ .scl(1f / 2f)
+ .scl((float) screenSize.width / (float) screenSize.height)
);
}
+ private float pitchAngleToPixelOffset(float angle) {
+ // var result = (float) Math.tan(angle);
+ return angle;
+ }
+
// MARK: - Modifiers
/**
@@ -210,22 +212,24 @@ public synchronized void move(Vector2 length) {
position.add(length);
}
+ /**
+ * Moves camera up or down by the given amount.
+ * @param height Height distance to move by
+ */
+ public synchronized void move(float height) {
+ positionZ += height;
+ }
+
/**
* Rotate the camera's yaw and pitch by the given amount.
* @param angleDeg Yaw
- * @param pitch Pitch
+ * @param pitchPx Pitch
*/
- public synchronized void rotate(float angleDeg, float pitch) {
+ public synchronized void rotate(float angleDeg, float pitchPx) {
direction.rotateDeg(angleDeg);
plane.rotateDeg(angleDeg);
-
- this.pitch += pitch;
- if(this.pitch < -200) {
- this.pitch = -200;
- }
- if(this.pitch > 200) {
- this.pitch = 200;
- }
+ pitch += pitchAngleToPixelOffset(pitchPx);
+ updateCameraPlane();
}
/**
@@ -238,11 +242,29 @@ public void bind(Entity entity) {
this.direction.set(entity.getDirection());
this.positionZ = entity.getPositionZ();
- this.pitch = entity.getPitch();
+ this.pitch = pitchAngleToPixelOffset(entity.getPitch());
updateCameraPlane();
}
+ // MARK: - Getters
+
+ public Vector2 getPosition() {
+ return position;
+ }
+
+ public Vector2 getDirection() {
+ return direction;
+ }
+
+ public float getPitch() {
+ return pitch;
+ }
+
+ public float getPositionZ() {
+ return positionZ;
+ }
+
// MARK: - Setters
/**
@@ -252,15 +274,20 @@ public void bind(Entity entity) {
*/
public synchronized void setScreenSize(Rectangle size) {
this.screenSize = size;
-
- var width = Math.max(size.width, size.height);
- var height = width / 1.33333f;
-
this.viewportSize.set(
- width * Config.Display.DRAWING_QUALITY,
- height * Config.Display.DRAWING_QUALITY
+ size.width * Config.Display.DRAWING_QUALITY,
+ size.height * Config.Display.DRAWING_QUALITY
);
updateCameraPlane();
}
+ /**
+ * Sets a map for the camera to use. Camera depends on a GameMap
+ * to be able to draw images from it.
+ * @param map Map to set
+ */
+ public synchronized void setMap(GameMap map) {
+ this.map = map;
+ }
+
}
diff --git a/src/sk/bytecode/bludisko/rt/game/graphics/Color.java b/src/sk/bytecode/bludisko/rt/game/graphics/Color.java
index ac8db6d..448bd43 100644
--- a/src/sk/bytecode/bludisko/rt/game/graphics/Color.java
+++ b/src/sk/bytecode/bludisko/rt/game/graphics/Color.java
@@ -37,9 +37,9 @@ public Color fade(int color) {
float blue2 = (0x000000FF & color) * alpha2;
int alpha = 255 << 24;
- int red = (int)(red1 + red2) << 16;
- int green = (int)(green1 + green2) << 8;
- int blue = (int)(blue1 + blue2);
+ int red = (int) (red1 + red2) << 16;
+ int green = (int) (green1 + green2) << 8;
+ int blue = (int) (blue1 + blue2);
return new Color(alpha | red | green | blue);
}
diff --git a/src/sk/bytecode/bludisko/rt/game/graphics/Overlay.java b/src/sk/bytecode/bludisko/rt/game/graphics/Overlay.java
new file mode 100644
index 0000000..4aa8c6e
--- /dev/null
+++ b/src/sk/bytecode/bludisko/rt/game/graphics/Overlay.java
@@ -0,0 +1,92 @@
+package sk.bytecode.bludisko.rt.game.graphics;
+
+import org.jetbrains.annotations.NotNull;
+import sk.bytecode.bludisko.rt.game.entities.Player;
+import sk.bytecode.bludisko.rt.game.util.NullSafe;
+
+import java.awt.Graphics;
+import java.awt.Point;
+import java.awt.Rectangle;
+import java.lang.ref.WeakReference;
+
+/**
+ * Class responsible for drawing overlays to the screen. Overlays are
+ * always drawn after the main frame has finished rendering.
+ * Includes drawing currently held item by the player and a crosshair.
+ * @see Player#getHeldItem()
+ * @see sk.bytecode.bludisko.rt.game.window.screens.GameScreen
+ */
+public final class Overlay implements Actor {
+
+ // MARK: - Attributes
+
+ private final Texture crosshairTexture = TextureManager.getTexture(14);
+ private Rectangle crosshairSize;
+ private Point crosshairPosition;
+
+ private WeakReference player;
+ private Rectangle screenSize;
+
+ // MARK: - Override
+
+ @Override
+ public void tick(float dt) {}
+
+ @Override
+ public void draw(@NotNull Graphics graphics) {
+ drawItemOverlay(graphics);
+ drawCrosshair(graphics);
+ drawCoordinates(graphics);
+ }
+
+ @Override
+ public void screenDidChangeBounds(@NotNull Rectangle canvas) {
+ screenSize = canvas;
+
+ crosshairSize = new Rectangle(crosshairTexture.getWidth(), crosshairTexture.getHeight());
+ crosshairPosition = new Point(
+ (screenSize.width / 2) - (crosshairTexture.getWidth() / 2),
+ (screenSize.height / 2) - (crosshairTexture.getHeight() / 2)
+ );
+ }
+
+ // MARK: - Private
+
+ private void drawItemOverlay(Graphics graphics) {
+ NullSafe.acceptWeak(player, player -> NullSafe.accept(player.getHeldItem(), heldItem -> {
+ var texture = heldItem.getOverlay();
+ var image = texture.asImage();
+
+ float resizingRatio = screenSize.height / (float)texture.getHeight();
+ int scaledWidth = (int) (texture.getWidth() * resizingRatio);
+ int offset = (screenSize.width - scaledWidth) / 2;
+
+ graphics.drawImage(image, offset, 0, scaledWidth, screenSize.height, null);
+ }));
+ }
+
+ private void drawCrosshair(Graphics graphics) {
+ graphics.drawImage(
+ crosshairTexture.asImage(),
+ crosshairPosition.x,
+ crosshairPosition.y,
+ crosshairSize.width,
+ crosshairSize.height,
+ null
+ );
+ }
+
+ private void drawCoordinates(Graphics graphics) {
+ NullSafe.acceptWeak(player, player -> {
+ graphics.setColor(java.awt.Color.green);
+ graphics.drawString(player.getPosition().toString(), 0, 50);
+ });
+ }
+
+ // MARK: - Public
+
+ public void connectPlayer(Player player) {
+ this.player = new WeakReference<>(player);
+ }
+
+}
diff --git a/src/sk/bytecode/bludisko/rt/game/graphics/Ray.java b/src/sk/bytecode/bludisko/rt/game/graphics/Ray.java
index 8158d2c..77cbd50 100644
--- a/src/sk/bytecode/bludisko/rt/game/graphics/Ray.java
+++ b/src/sk/bytecode/bludisko/rt/game/graphics/Ray.java
@@ -25,7 +25,7 @@ public class Ray {
// MARK: - Constructor
/**
- * Constructs a new ray with a given tile-size.
+ * Constructs a new ray with a given tile size (step size).
* @param position Starting position
* @param direction Starting direction
* @param tileSize Size of tiles in a 2D plane
diff --git a/src/sk/bytecode/bludisko/rt/game/graphics/RayHit.java b/src/sk/bytecode/bludisko/rt/game/graphics/RayHit.java
index 4f9b404..3abc871 100644
--- a/src/sk/bytecode/bludisko/rt/game/graphics/RayHit.java
+++ b/src/sk/bytecode/bludisko/rt/game/graphics/RayHit.java
@@ -4,8 +4,8 @@
/**
* Record containing the object the Ray had hit, position of the ray
- * at the critical moment and the distance it had travelled from its origin
- * until it had hit the object.
+ * at the hit moment and the distance it had travelled from its origin
+ * until it had reached the hit location with the object.
* @param Type of the object
*/
public record RayHit(T result, Vector2 position, float distance) {}
diff --git a/src/sk/bytecode/bludisko/rt/game/graphics/TextureManager.java b/src/sk/bytecode/bludisko/rt/game/graphics/TextureManager.java
index 078b867..f19f2bb 100644
--- a/src/sk/bytecode/bludisko/rt/game/graphics/TextureManager.java
+++ b/src/sk/bytecode/bludisko/rt/game/graphics/TextureManager.java
@@ -53,6 +53,8 @@ private static void loadTextures() {
loadedTextures[12] = new Texture("wall/doors");
loadedTextures[13] = new Texture("items/portalGun");
+ loadedTextures[14] = new Texture("other/crosshair");
+ loadedTextures[15] = new Texture("other/logo");
}
private static void generateTextures() {
diff --git a/src/sk/bytecode/bludisko/rt/game/graphics/Tickable.java b/src/sk/bytecode/bludisko/rt/game/graphics/Tickable.java
new file mode 100644
index 0000000..df72a5f
--- /dev/null
+++ b/src/sk/bytecode/bludisko/rt/game/graphics/Tickable.java
@@ -0,0 +1,14 @@
+package sk.bytecode.bludisko.rt.game.graphics;
+
+public interface Tickable {
+
+ /**
+ * Game loop - logic.
+ * Called every frame before rendering. Should contain
+ * logic that's used to update information for renderer to draw
+ * a new frame.
+ * @param dt Time difference between the last and this frame (delta time)
+ */
+ void tick(float dt);
+
+}
diff --git a/src/sk/bytecode/bludisko/rt/game/input/GameInputManager.java b/src/sk/bytecode/bludisko/rt/game/input/GameInputManager.java
index 8d8aede..27fe2c9 100644
--- a/src/sk/bytecode/bludisko/rt/game/input/GameInputManager.java
+++ b/src/sk/bytecode/bludisko/rt/game/input/GameInputManager.java
@@ -2,19 +2,34 @@
import sk.bytecode.bludisko.rt.game.math.Vector2;
import sk.bytecode.bludisko.rt.game.util.Config;
+import sk.bytecode.bludisko.rt.game.util.NullSafe;
import java.awt.event.KeyEvent;
import java.awt.event.MouseEvent;
-import java.util.function.Consumer;
+import java.lang.ref.WeakReference;
/**
* Custom implementation of Input Manager for
* a Game Screen.
*/
-public class GameInputManager extends InputManager {
+public final class GameInputManager extends InputManager {
+
+ private WeakReference delegate;
private int direction = 0b0000;
+ // MARK: - Public
+
+ /**
+ * Sets the delegate whose methods are called, when a change in input
+ * is detected.
+ * @param delegate Object implementing the delegate methods.
+ * @see GameInputManagerDelegate
+ */
+ public void setDelegate(GameInputManagerDelegate delegate) {
+ this.delegate = new WeakReference<>(delegate);
+ }
+
// MARK: - Private
private void toggleDirectionOn(int keyCode) {
@@ -52,14 +67,7 @@ private Vector2 toVector(int direction) {
}
private void toggleSprint(boolean toggle) {
- withDelegate(d -> d.didUpdateSprintingStatus(toggle));
- }
-
- private void withDelegate(Consumer action) {
- GameInputManagerDelegate delegate;
- if((this.delegate != null) && (delegate = this.delegate.get()) != null) {
- action.accept(delegate);
- }
+ NullSafe.acceptWeak(delegate, d -> d.didUpdateSprintingStatus(toggle));
}
// MARK: - KeyListener
@@ -79,7 +87,7 @@ public void keyPressed(KeyEvent e) {
toggleSprint(true);
}
- withDelegate(d -> d.didUpdateMovementDirection(toVector(this.direction)));
+ NullSafe.acceptWeak(delegate, d -> d.didUpdateMovementDirection(toVector(this.direction)));
}
@Override
@@ -90,7 +98,7 @@ public void keyReleased(KeyEvent e) {
toggleSprint(false);
}
- withDelegate(d -> d.didUpdateMovementDirection(toVector(this.direction)));
+ NullSafe.acceptWeak(delegate, d -> d.didUpdateMovementDirection(toVector(this.direction)));
}
// MARK: - MouseListener
@@ -100,7 +108,10 @@ public void mouseClicked(MouseEvent e) {}
@Override
public void mousePressed(MouseEvent e) {
-
+ switch(e.getButton()) {
+ case Config.Keybinds.USE_PRIMARY -> NullSafe.acceptWeak(delegate, d -> d.didToggleMouseButton(false));
+ case Config.Keybinds.USE_SECONDARY -> NullSafe.acceptWeak(delegate, d -> d.didToggleMouseButton(true));
+ }
}
@Override
@@ -115,15 +126,18 @@ public void mouseExited(MouseEvent e) {}
// MARK: - MouseMotionListener
@Override
- public void mouseDragged(MouseEvent e) {}
+ public void mouseDragged(MouseEvent e) {
+ mouseMoved(e);
+ }
@Override
public void mouseMoved(MouseEvent e) {
var newPosition = e.getLocationOnScreen();
- newPosition.translate(-this.windowDimensions.x, this.windowDimensions.y);
+ newPosition.translate(-this.windowDimensions.x, -this.windowDimensions.y);
newPosition.translate(-this.windowDimensions.width / 2, -this.windowDimensions.height / 2);
- withDelegate(d -> d.didUpdateRotation(new Vector2(-newPosition.x, -newPosition.y)));
+ var rotationVector = new Vector2(newPosition.x, newPosition.y).scl(-1);
+ NullSafe.acceptWeak(delegate, d -> d.didUpdateRotation(rotationVector));
}
}
diff --git a/src/sk/bytecode/bludisko/rt/game/input/GameInputManagerDelegate.java b/src/sk/bytecode/bludisko/rt/game/input/GameInputManagerDelegate.java
index 5abc026..1e32566 100644
--- a/src/sk/bytecode/bludisko/rt/game/input/GameInputManagerDelegate.java
+++ b/src/sk/bytecode/bludisko/rt/game/input/GameInputManagerDelegate.java
@@ -26,10 +26,18 @@ public interface GameInputManagerDelegate {
void didUpdateRotation(Vector2 rotation);
/**
- * Called when the player starts sprinting.
- * Check Config to set the key.
+ * Called when player presses the sprinting key.
+ * Check {@link sk.bytecode.bludisko.rt.game.util.Config} to set the key.
* @param isSprinting Whether the player is sprinting
*/
void didUpdateSprintingStatus(boolean isSprinting);
+ /**
+ * Called when player pushes one of the use buttons.
+ * Check {@link sk.bytecode.bludisko.rt.game.util.Config} to set the
+ * mouse buttons.
+ * @param rmb If the button pushed was a secondary action
+ */
+ void didToggleMouseButton(boolean rmb);
+
}
diff --git a/src/sk/bytecode/bludisko/rt/game/input/InputManager.java b/src/sk/bytecode/bludisko/rt/game/input/InputManager.java
index 5842c30..478575c 100644
--- a/src/sk/bytecode/bludisko/rt/game/input/InputManager.java
+++ b/src/sk/bytecode/bludisko/rt/game/input/InputManager.java
@@ -1,5 +1,7 @@
package sk.bytecode.bludisko.rt.game.input;
+import sk.bytecode.bludisko.rt.game.graphics.Tickable;
+
import javax.swing.event.MouseInputListener;
import java.awt.AWTException;
import java.awt.Rectangle;
@@ -15,9 +17,7 @@
* @see sk.bytecode.bludisko.rt.game.window.Window
* @see GameInputManagerDelegate
*/
-public abstract class InputManager implements KeyListener, MouseInputListener {
-
- protected WeakReference delegate;
+public abstract class InputManager implements Tickable, KeyListener, MouseInputListener {
protected Robot robot;
protected Rectangle windowDimensions;
@@ -59,28 +59,19 @@ private void centerMouse() {
* update input information accordingly.
* @param dt Time passed since the last frame has finished processing.
*/
+ @Override
public void tick(float dt) {
if(mouseLocked) {
centerMouse();
}
}
- /**
- * Sets the delegate whose methods are called, when a change in input
- * is detected.
- * @param delegate Object implementing the delegate methods.
- * @see GameInputManagerDelegate
- */
- public void setDelegate(GameInputManagerDelegate delegate) {
- this.delegate = new WeakReference<>(delegate);
- }
-
/**
* Notify the Input Manager of new Window dimensions.
- * @param newDimensions New Window bounds.
+ * @param canvas New Window bounds.
*/
- public void updateWindowDimensions(Rectangle newDimensions) {
- this.windowDimensions = newDimensions;
+ public void screenDidChangeBounds(Rectangle canvas) {
+ this.windowDimensions = canvas;
}
/**
diff --git a/src/sk/bytecode/bludisko/rt/game/input/MenuInputManager.java b/src/sk/bytecode/bludisko/rt/game/input/MenuInputManager.java
new file mode 100644
index 0000000..e486e27
--- /dev/null
+++ b/src/sk/bytecode/bludisko/rt/game/input/MenuInputManager.java
@@ -0,0 +1,69 @@
+package sk.bytecode.bludisko.rt.game.input;
+
+import sk.bytecode.bludisko.rt.game.util.NullSafe;
+
+import java.awt.event.KeyEvent;
+import java.awt.event.MouseEvent;
+import java.lang.ref.WeakReference;
+
+public class MenuInputManager extends InputManager {
+
+ private WeakReference delegate;
+
+ // MARK: - Public
+
+ /**
+ * Sets the delegate whose methods are called, when a change in input
+ * is detected.
+ * @param delegate Object implementing the delegate methods.
+ * @see GameInputManagerDelegate
+ */
+ public void setDelegate(MenuInputManagerDelegate delegate) {
+ this.delegate = new WeakReference<>(delegate);
+ }
+
+ // MARK: - KeyListener
+
+ @Override
+ public void keyTyped(KeyEvent e) {}
+
+ @Override
+ public void keyPressed(KeyEvent e) {}
+
+ @Override
+ public void keyReleased(KeyEvent e) {}
+
+ // MARK: - MouseListener
+
+ @Override
+ public void mouseClicked(MouseEvent e) {}
+
+ @Override
+ public void mousePressed(MouseEvent e) {
+ NullSafe.acceptWeak(delegate, delegate -> delegate.touchesBegan(e.getPoint()));
+ }
+
+ @Override
+ public void mouseReleased(MouseEvent e) {
+ NullSafe.acceptWeak(delegate, delegate -> delegate.touchesEnded(e.getPoint()));
+ }
+
+ @Override
+ public void mouseEntered(MouseEvent e) {}
+
+ @Override
+ public void mouseExited(MouseEvent e) {
+ NullSafe.acceptWeak(delegate, delegate -> delegate.touchesCancelled(e.getPoint()));
+ }
+
+ // MARK: - MouseMotionListener
+
+ @Override
+ public void mouseDragged(MouseEvent e) {
+ NullSafe.acceptWeak(delegate, delegate -> delegate.touchesCancelled(e.getPoint()));
+ }
+
+ @Override
+ public void mouseMoved(MouseEvent e) {}
+
+}
diff --git a/src/sk/bytecode/bludisko/rt/game/input/MenuInputManagerDelegate.java b/src/sk/bytecode/bludisko/rt/game/input/MenuInputManagerDelegate.java
new file mode 100644
index 0000000..0a8f3d4
--- /dev/null
+++ b/src/sk/bytecode/bludisko/rt/game/input/MenuInputManagerDelegate.java
@@ -0,0 +1,25 @@
+package sk.bytecode.bludisko.rt.game.input;
+
+import java.awt.Point;
+
+public interface MenuInputManagerDelegate {
+
+ /**
+ * Called when the user pushes down the mouse button.
+ * @param point Location of the click event
+ */
+ void touchesBegan(Point point);
+
+ /**
+ * Called when the user stops pushing the mouse button.
+ * @param point
+ */
+ void touchesEnded(Point point);
+
+ /**
+ * Called when the user drags the mouse outside the window.
+ * @param point
+ */
+ void touchesCancelled(Point point);
+
+}
diff --git a/src/sk/bytecode/bludisko/rt/game/items/Item.java b/src/sk/bytecode/bludisko/rt/game/items/Item.java
index 003b5bd..e0b97b7 100644
--- a/src/sk/bytecode/bludisko/rt/game/items/Item.java
+++ b/src/sk/bytecode/bludisko/rt/game/items/Item.java
@@ -2,6 +2,10 @@
import sk.bytecode.bludisko.rt.game.graphics.Texture;
+/**
+ * Abstract class representing a common and default implementation
+ * for all other in-game items.
+ */
public abstract class Item {
// MARK: - Attributes
@@ -10,6 +14,25 @@ public abstract class Item {
// MARK: - Public
+ /**
+ * Default item use action.
+ * Called after a user input, usually LMB.
+ * @see sk.bytecode.bludisko.rt.game.entities.Player#didToggleMouseButton(boolean)
+ * @see sk.bytecode.bludisko.rt.game.input.GameInputManagerDelegate
+ */
+ public void use() {}
+
+ /**
+ * Secondary item use action.
+ * Called after a user input, usually RMB.
+ * @see sk.bytecode.bludisko.rt.game.entities.Player#didToggleMouseButton(boolean)
+ * @see sk.bytecode.bludisko.rt.game.input.GameInputManagerDelegate
+ */
+ public void useSecondary() {}
+
+ /**
+ * @return Texture of the item to be rendered as an overlay
+ */
public Texture getOverlay() {
return overlay;
}
diff --git a/src/sk/bytecode/bludisko/rt/game/items/PortalGun.java b/src/sk/bytecode/bludisko/rt/game/items/PortalGun.java
index ff6e8fe..1662982 100644
--- a/src/sk/bytecode/bludisko/rt/game/items/PortalGun.java
+++ b/src/sk/bytecode/bludisko/rt/game/items/PortalGun.java
@@ -1,11 +1,49 @@
package sk.bytecode.bludisko.rt.game.items;
+import sk.bytecode.bludisko.rt.game.blocks.game.Portal;
+import sk.bytecode.bludisko.rt.game.entities.Player;
+import sk.bytecode.bludisko.rt.game.graphics.BlockRay;
+import sk.bytecode.bludisko.rt.game.graphics.RayHit;
import sk.bytecode.bludisko.rt.game.graphics.TextureManager;
+import sk.bytecode.bludisko.rt.game.map.PortalManager;
+import sk.bytecode.bludisko.rt.game.util.NullSafe;
+
+import java.lang.ref.WeakReference;
public class PortalGun extends Item {
- public PortalGun() {
+ private static final int RANGE = 100;
+
+ private WeakReference player;
+
+ public PortalGun(Player owner) {
this.overlay = TextureManager.getTexture(13);
+ this.player = new WeakReference<>(owner);
+ }
+
+ @Override
+ public void use() {
+ placePortal(Portal.Color.ORANGE);
+ }
+
+ @Override
+ public void useSecondary() {
+ placePortal(Portal.Color.BLUE);
+ }
+
+ // MARK: - Private
+
+ private void placePortal(Portal.Color color) {
+ NullSafe.acceptWeak(player, player -> {
+ var map = player.getWorld().getMap();
+ var ray = new BlockRay(map.walls(), player.getPosition(), player.getDirection());
+ var hitList = ray.cast(RANGE);
+
+ hitList.stream()
+ .findFirst()
+ .map(RayHit::position)
+ .ifPresent(position -> PortalManager.createPortal(map, position, color));
+ });
}
}
diff --git a/src/sk/bytecode/bludisko/rt/game/map/Chamber1.java b/src/sk/bytecode/bludisko/rt/game/map/Chamber1.java
index e4b9dee..ee21ec5 100644
--- a/src/sk/bytecode/bludisko/rt/game/map/Chamber1.java
+++ b/src/sk/bytecode/bludisko/rt/game/map/Chamber1.java
@@ -1,8 +1,7 @@
package sk.bytecode.bludisko.rt.game.map;
-import sk.bytecode.bludisko.rt.game.blocks.game.Portal;
import sk.bytecode.bludisko.rt.game.entities.Player;
-import sk.bytecode.bludisko.rt.game.items.PortalGun;
+import sk.bytecode.bludisko.rt.game.math.Vector2;
import javax.swing.JOptionPane;
@@ -40,11 +39,7 @@ public void tick(float dt) {
private void setupMap() {
this.map = GameMap.load("chamber1");
- var p1 = ((Portal) map.walls().getBlock(7, 7));
- var p2 = ((Portal) map.walls().getBlock(4, 9));
-
- p1.setOtherPortal(p2);
- p2.setOtherPortal(p1);
+ PortalManager.createPortal(map, new Vector2(7.5f, 8.0f), new Vector2(5.0f, 9.5f));
}
private boolean playerAtDoors(Player player) {
diff --git a/src/sk/bytecode/bludisko/rt/game/map/Map.java b/src/sk/bytecode/bludisko/rt/game/map/Map.java
index d9437c6..ea32cc8 100644
--- a/src/sk/bytecode/bludisko/rt/game/map/Map.java
+++ b/src/sk/bytecode/bludisko/rt/game/map/Map.java
@@ -1,12 +1,17 @@
package sk.bytecode.bludisko.rt.game.map;
+import org.jetbrains.annotations.NotNull;
import sk.bytecode.bludisko.rt.game.blocks.Block;
import sk.bytecode.bludisko.rt.game.blocks.BlockManager;
+import sk.bytecode.bludisko.rt.game.blocks.game.Portal;
import sk.bytecode.bludisko.rt.game.blocks.technical.Air;
import sk.bytecode.bludisko.rt.game.math.MathUtils;
import sk.bytecode.bludisko.rt.game.math.Vector2;
import sk.bytecode.bludisko.rt.game.serialization.Serializable;
+import java.util.Arrays;
+import java.util.stream.Stream;
+
/**
* Map object containing Blocks. Used for serialization
* of maps from files.
@@ -71,6 +76,10 @@ public int getHeight() {
return blocks.length;
}
+ public boolean coordinateValid(int x, int y) {
+ return (x >= 0 && y >= 0) && (x < getHeight() && y < getWidth());
+ }
+
/**
* Returns block at coordinates [x, y] or returns an Air block
* if it does not exist/the position is invalid.
@@ -79,13 +88,10 @@ public int getHeight() {
* @return Block at given coordinates
*/
public Block getBlock(int x, int y) {
- if(x < 0 || y < 0) {
- return new Air(new Vector2(x, y));
- }
- if(x >= getHeight() || y >= getWidth()) {
- return new Air(new Vector2(x, y));
+ if(coordinateValid(x, y)) {
+ return blocks[x][y];
}
- return blocks[x][y];
+ return new Air(new Vector2(x, y));
}
/**
@@ -111,4 +117,58 @@ public Block[] getBlocksAt(Vector2 position) {
}
}
+ /**
+ * @return A stream of all blocks in a map
+ */
+ public Stream blockStream() {
+ return Arrays.stream(blocks).flatMap(Arrays::stream);
+ }
+
+ // MARK: - Setters
+
+ /**
+ * Sets block at coordinates [X, Y] to a new block.
+ * @param x x coordinate
+ * @param y y coordinate
+ * @param newBlock Block to set
+ * @return true if action succeeded, false otherwise
+ */
+ public boolean setBlock(int x, int y, Block newBlock) {
+ if(coordinateValid(x, y)) {
+ blocks[x][y] = newBlock;
+ return true;
+ }
+ return false;
+ }
+
+ // MARK: - Block manipulation
+
+ /**
+ * General function to replace a block in a map. Swaps a block
+ * for another one as long as coordinates of the first block are valid.
+ * @param oldBlock Block to replace
+ * @param newBlock Block to be replaced by
+ * @return true if action succeeded, false otherwise
+ */
+ public boolean replaceBlock(@NotNull Block oldBlock, @NotNull Block newBlock) {
+ var coordinates = oldBlock.getCoordinates();
+ var oldX = (int) coordinates.x;
+ var oldY = (int) coordinates.y;
+
+ return setBlock(oldX, oldY, newBlock);
+ }
+
+ /**
+ * Specific overload when a block is being replaced by a Portal. Stores the
+ * original block reference inside a Portals' attribute.
+ * @param oldBlock Block to replace
+ * @param portal Portal to be replaced by
+ * @return true if action succeeded, false otherwise
+ * @see Portal#setOriginalWall(Block)
+ */
+ public boolean replaceBlock(@NotNull Block oldBlock, @NotNull Portal portal) {
+ portal.setOriginalWall(oldBlock);
+ return replaceBlock(oldBlock, (Block) portal);
+ }
+
}
diff --git a/src/sk/bytecode/bludisko/rt/game/map/PortalManager.java b/src/sk/bytecode/bludisko/rt/game/map/PortalManager.java
new file mode 100644
index 0000000..9df7309
--- /dev/null
+++ b/src/sk/bytecode/bludisko/rt/game/map/PortalManager.java
@@ -0,0 +1,129 @@
+package sk.bytecode.bludisko.rt.game.map;
+
+import sk.bytecode.bludisko.rt.game.blocks.game.Portal;
+import sk.bytecode.bludisko.rt.game.blocks.group.PortalPlaceable;
+import sk.bytecode.bludisko.rt.game.math.Vector2;
+import sk.bytecode.bludisko.rt.game.util.NullSafe;
+
+import java.util.Arrays;
+
+public final class PortalManager {
+
+ private PortalManager() {}
+
+ /**
+ * Force-creates a new pair of portals between given coordinates.
+ * Equivalent to REST-POST.
+ * @param map {@link GameMap} to create portals in
+ * @param orangePortalCoordinates Coordinates of the {@link Portal.Color#ORANGE} portal
+ * @param bluePortalCoordinates Coordinates of the {@link Portal.Color#BLUE} portal
+ * @see Portal
+ */
+ public static void createPortal(GameMap map, Vector2 orangePortalCoordinates, Vector2 bluePortalCoordinates) {
+ removeAllPortals(map);
+ var walls = map.walls();
+
+ var blocksAtOrangePortalCoordinates = walls.getBlocksAt(orangePortalCoordinates);
+ var blocksAtBluePortalCoordinates = walls.getBlocksAt(bluePortalCoordinates);
+
+ var orangePortalWall = Arrays.stream(blocksAtOrangePortalCoordinates)
+ .filter(block -> block instanceof PortalPlaceable)
+ .findFirst();
+
+ var bluePortalWall = Arrays.stream(blocksAtBluePortalCoordinates)
+ .filter(block -> block instanceof PortalPlaceable)
+ .findFirst();
+
+ var orangePortalSide = orangePortalWall.map(wall -> wall.getSide(orangePortalCoordinates));
+ var bluePortalSide = bluePortalWall.map(wall -> wall.getSide(bluePortalCoordinates));
+
+ var orangePortal = NullSafe.apply(
+ orangePortalWall,
+ orangePortalSide,
+ (wall, side) -> new Portal(side, Portal.Color.ORANGE, wall.getCoordinates())
+ );
+ var bluePortal = NullSafe.apply(
+ bluePortalWall,
+ bluePortalSide,
+ (wall, side) -> new Portal(side, Portal.Color.BLUE, wall.getCoordinates())
+ );
+
+ NullSafe.accept(orangePortalWall, orangePortal, walls::replaceBlock);
+ NullSafe.accept(bluePortalWall, bluePortal, walls::replaceBlock);
+
+ NullSafe.accept(orangePortal, bluePortal, (orange, blue) -> {
+ orange.setOtherPortal(blue);
+ blue.setOtherPortal(orange);
+ });
+ }
+
+ /**
+ * Creates a new portal or moves an existing one to a new location. Portal
+ * is identified by its {@link Portal.Color}.
+ * Equivalent to REST-PUT.
+ * @param map {@link GameMap} to create the portal in
+ * @param portalCoordinates Coordinates of the portal
+ * @param portalColor Color of the portal
+ */
+ public static void createPortal(GameMap map, Vector2 portalCoordinates, Portal.Color portalColor) {
+ var walls = map.walls();
+ var portalBlocks = walls.getBlocksAt(portalCoordinates);
+
+ var portalWall = Arrays.stream(portalBlocks)
+ .filter(block -> block instanceof PortalPlaceable)
+ .findFirst();
+
+ var portalSide = portalWall.map(wall -> wall.getSide(portalCoordinates));
+
+ var newPortal = NullSafe.apply(
+ portalWall,
+ portalSide,
+ (wall, side) -> new Portal(side, portalColor, wall.getCoordinates())
+ );
+
+ var otherPortal = walls.blockStream()
+ .filter(block -> block instanceof Portal)
+ .map(block -> (Portal) block)
+ .filter(portal -> portal.getColor() != portalColor)
+ .findFirst();
+
+ if(newPortal.isEmpty()) return;
+ removePortal(map, portalColor);
+
+ NullSafe.accept(portalWall, newPortal, walls::replaceBlock);
+ NullSafe.accept(newPortal, otherPortal, Portal::setOtherPortal);
+ NullSafe.accept(otherPortal, newPortal, Portal::setOtherPortal);
+ }
+
+ /**
+ * Removes a portal of a given color from a map.
+ * @param map {@link GameMap} to remove a portal from
+ * @param portalColor {@link Portal.Color} of the portal
+ */
+ public static void removePortal(GameMap map, Portal.Color portalColor) {
+ var walls = map.walls();
+ var portalWall = walls.blockStream()
+ .filter(block -> block instanceof Portal)
+ .map(block -> (Portal) block)
+ .filter(portal -> portal.getColor() == portalColor)
+ .findFirst();
+
+ portalWall.ifPresent(portal -> {
+ portal.getOtherPortal().setOtherPortal(null);
+ walls.replaceBlock(portal, portal.getOriginalWall());
+ });
+ }
+
+ /**
+ * Removes all portals from a {@link GameMap}.
+ * @param map GameMap to remove all portals from
+ */
+ public static void removeAllPortals(GameMap map) {
+ var walls = map.walls();
+ walls.blockStream()
+ .filter(block -> block instanceof Portal)
+ .map(block -> (Portal) block)
+ .forEach(portal -> walls.replaceBlock(portal, portal.getOriginalWall()));
+ }
+
+}
diff --git a/src/sk/bytecode/bludisko/rt/game/math/MathUtils.java b/src/sk/bytecode/bludisko/rt/game/math/MathUtils.java
index 547ed80..719ef95 100644
--- a/src/sk/bytecode/bludisko/rt/game/math/MathUtils.java
+++ b/src/sk/bytecode/bludisko/rt/game/math/MathUtils.java
@@ -1,14 +1,14 @@
package sk.bytecode.bludisko.rt.game.math;
-import static java.lang.Math.PI;
-
/**
* Collection of handy Math constants and shortcuts.
*/
public final class MathUtils {
- public static final float radiansToDegrees = (float) (180f / PI);
- public static final float degreesToRadians = (float) (PI / 180f);
+ public static final float PI = (float) Math.PI;
+
+ public static final float radiansToDegrees = 180f / PI;
+ public static final float degreesToRadians = PI / 180f;
public static final float FLOAT_ROUNDING_ERROR = 0.000001f;
public static final int INT_MSB_MASK = 0xFF000000;
@@ -79,7 +79,7 @@ public static int gcd(int a, int b) {
* @return Clamped number
*/
public static int clamp(int value, int min, int max) {
- return value > max ? max : Math.max(value, min);
+ return Math.min(Math.max(value, min), max);
}
}
diff --git a/src/sk/bytecode/bludisko/rt/game/util/Config.java b/src/sk/bytecode/bludisko/rt/game/util/Config.java
index 13f0f6d..a643673 100644
--- a/src/sk/bytecode/bludisko/rt/game/util/Config.java
+++ b/src/sk/bytecode/bludisko/rt/game/util/Config.java
@@ -1,6 +1,7 @@
package sk.bytecode.bludisko.rt.game.util;
import java.awt.event.KeyEvent;
+import java.awt.event.MouseEvent;
public class Config {
@@ -11,6 +12,9 @@ public static class Keybinds {
public static final int DOWN = KeyEvent.VK_S;
public static final int RIGHT = KeyEvent.VK_D;
+ public static final int USE_PRIMARY = MouseEvent.BUTTON1;
+ public static final int USE_SECONDARY = MouseEvent.BUTTON3;
+
public static final int LOCK_MOUSE = KeyEvent.VK_ESCAPE;
public static final int SPRINT = KeyEvent.VK_CONTROL;
diff --git a/src/sk/bytecode/bludisko/rt/game/util/NullSafe.java b/src/sk/bytecode/bludisko/rt/game/util/NullSafe.java
index 2b24f83..d7d4b74 100644
--- a/src/sk/bytecode/bludisko/rt/game/util/NullSafe.java
+++ b/src/sk/bytecode/bludisko/rt/game/util/NullSafe.java
@@ -3,6 +3,9 @@
import org.jetbrains.annotations.Nullable;
import java.lang.ref.WeakReference;
+import java.util.Optional;
+import java.util.function.BiConsumer;
+import java.util.function.BiFunction;
import java.util.function.Consumer;
public final class NullSafe {
@@ -22,4 +25,17 @@ public static void acceptWeak(@Nullable WeakReference weakNullable, Consu
}
}
+ public static void accept(Optional optionalT, Optional optionalU, BiConsumer biConsumer) {
+ if(optionalT.isPresent() && optionalU.isPresent()) {
+ biConsumer.accept(optionalT.get(), optionalU.get());
+ }
+ }
+
+ public static Optional apply(Optional optionalT, Optional optionalU, BiFunction biFunction) {
+ if(optionalT.isPresent() && optionalU.isPresent()) {
+ return Optional.of(biFunction.apply(optionalT.get(), optionalU.get()));
+ }
+ return Optional.empty();
+ }
+
}
diff --git a/src/sk/bytecode/bludisko/rt/game/window/Window.java b/src/sk/bytecode/bludisko/rt/game/window/Window.java
index 22ab063..81e04a5 100644
--- a/src/sk/bytecode/bludisko/rt/game/window/Window.java
+++ b/src/sk/bytecode/bludisko/rt/game/window/Window.java
@@ -3,14 +3,24 @@
import org.jetbrains.annotations.NotNull;
import sk.bytecode.bludisko.rt.game.input.InputManager;
import sk.bytecode.bludisko.rt.game.util.Config;
+import sk.bytecode.bludisko.rt.game.util.NullSafe;
import sk.bytecode.bludisko.rt.game.window.screens.Screen;
-import javax.swing.*;
-import java.awt.*;
+import javax.swing.JFrame;
+import java.awt.Canvas;
+import java.awt.Color;
+import java.awt.Component;
+import java.awt.Cursor;
+import java.awt.Dimension;
+import java.awt.Frame;
+import java.awt.Graphics;
+import java.awt.Point;
+import java.awt.Rectangle;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.image.BufferStrategy;
import java.awt.image.BufferedImage;
+import java.util.ArrayDeque;
/**
* Main application window containing a {@link Canvas} to be drawn on.
@@ -34,6 +44,8 @@ public final class Window {
private final Dimension windowSize;
private final Canvas canvas;
private final JFrame frame;
+
+ private final ArrayDeque screenQueue;
private Screen screen;
private InputManager inputManager;
@@ -54,6 +66,8 @@ public Window(@NotNull Screen screen) {
this.frame = new JFrame("rt_portal_demo");
this.canvas = new Canvas();
+ this.screenQueue = new ArrayDeque<>(2);
+
this.setupCanvas();
this.setupFrame();
this.setupScreen(screen);
@@ -68,7 +82,7 @@ private void setupCanvas() {
private void setupFrame() {
this.frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
- this.frame.addComponentListener(getResizeListener());
+ this.canvas.addComponentListener(getResizeListener());
this.frame.add(this.canvas);
@@ -81,11 +95,15 @@ private void setupScreen(Screen screen) {
this.canvas.removeMouseListener(this.inputManager);
this.canvas.removeMouseMotionListener(this.inputManager);
- screen.screenWillAppear(this);
-
this.inputManager = screen.getInputManager();
this.screen = screen;
+ synchronized(frame.getTreeLock()) {
+ this.updateBounds(frame.getComponent(0));
+ }
+
+ screen.screenWillAppear(this);
+
this.canvas.addKeyListener(this.inputManager);
this.canvas.addMouseListener(this.inputManager);
this.canvas.addMouseMotionListener(this.inputManager);
@@ -142,7 +160,7 @@ private void updateBounds(@NotNull Component component) {
* @param screen Replacement screen
*/
public void setScreen(@NotNull Screen screen) {
- setupScreen(screen);
+ screenQueue.addLast(screen);
}
/**
@@ -183,6 +201,8 @@ public void start() {
private void startGameCycle() throws InterruptedException {
long lastFrameTime = 0;
while(running) {
+ NullSafe.accept(screenQueue.pollFirst(), this::setupScreen);
+
long currentTime = System.currentTimeMillis();
float deltaMilliseconds = currentTime - lastFrameTime;
@@ -192,6 +212,7 @@ private void startGameCycle() throws InterruptedException {
lastFrameTime = System.currentTimeMillis();
float deltaSeconds = deltaMilliseconds * 0.001f;
+
tick(deltaSeconds);
drawFrame();
} else {
diff --git a/src/sk/bytecode/bludisko/rt/game/window/screens/GameScreen.java b/src/sk/bytecode/bludisko/rt/game/window/screens/GameScreen.java
index a9b5a08..4895e3e 100644
--- a/src/sk/bytecode/bludisko/rt/game/window/screens/GameScreen.java
+++ b/src/sk/bytecode/bludisko/rt/game/window/screens/GameScreen.java
@@ -1,32 +1,36 @@
package sk.bytecode.bludisko.rt.game.window.screens;
+import org.jetbrains.annotations.NotNull;
import sk.bytecode.bludisko.rt.game.entities.Player;
import sk.bytecode.bludisko.rt.game.graphics.Camera;
+import sk.bytecode.bludisko.rt.game.graphics.Overlay;
import sk.bytecode.bludisko.rt.game.input.GameInputManager;
import sk.bytecode.bludisko.rt.game.input.InputManager;
import sk.bytecode.bludisko.rt.game.items.PortalGun;
import sk.bytecode.bludisko.rt.game.map.Chamber1;
import sk.bytecode.bludisko.rt.game.map.World;
import sk.bytecode.bludisko.rt.game.util.NullSafe;
-import sk.bytecode.bludisko.rt.game.window.Window;
import java.awt.Graphics;
import java.awt.Rectangle;
/**
- * Implemented main game screen.
- * Contains Camera for drawing and game World for ticking
- * and updating the game logic.
+ * Main game screen. Is responsible for managing all rendering and ticking
+ * of the game world. Provides concrete implementation of InputManager.
* @see Camera
+ * @see Player
+ * @see Overlay
* @see World
+ * @see InputManager
*/
public final class GameScreen extends Screen {
- private final InputManager gameInput = new GameInputManager();
+ private final GameInputManager gameInputManager = new GameInputManager();
private World currentWorld;
private Player player;
private Camera camera;
+ private Overlay overlay;
// MARK: - Constructor
@@ -46,23 +50,26 @@ public GameScreen() {
private void setupPlayer() {
player = new Player(currentWorld);
camera = new Camera();
+ overlay = new Overlay();
player.setCamera(camera);
+
+ overlay.connectPlayer(player);
currentWorld.setPlayer(player);
- player.equip(new PortalGun());
+ player.equip(new PortalGun(player));
}
private void setupInput() {
- gameInput.setDelegate(this.player);
- gameInput.setMouseLocked(true);
+ gameInputManager.setDelegate(this.player);
+ gameInputManager.setMouseLocked(true);
}
- // MARK: - Screen
+ // MARK: - Override
@Override
public InputManager getInputManager() {
- return gameInput;
+ return gameInputManager;
}
@Override
@@ -73,13 +80,11 @@ public void screenDidAppear() {
}
@Override
- public void screenDidChangeBounds(Rectangle bounds) {
+ public void screenDidChangeBounds(@NotNull Rectangle bounds) {
super.screenDidChangeBounds(bounds);
- NullSafe.acceptWeak(window, window -> {
- camera.setScreenSize(window.canvasBounds());
- player.setItemOverlayScreenSizeInformation(window.canvasBounds());
- });
+ camera.setScreenSize(bounds);
+ overlay.screenDidChangeBounds(bounds);
}
// MARK: - Game loop
@@ -87,14 +92,16 @@ public void screenDidChangeBounds(Rectangle bounds) {
@Override
public void tick(float dt) {
super.tick(dt);
+
player.tick(dt);
+ overlay.tick(dt);
currentWorld.tick(dt);
}
@Override
- public void draw(Graphics graphics) {
+ public void draw(@NotNull Graphics graphics) {
camera.draw(graphics);
- player.drawItemOverlay(graphics);
+ overlay.draw(graphics);
}
// MARK: - Public
diff --git a/src/sk/bytecode/bludisko/rt/game/window/screens/MenuScreen.java b/src/sk/bytecode/bludisko/rt/game/window/screens/MenuScreen.java
new file mode 100644
index 0000000..0641fa5
--- /dev/null
+++ b/src/sk/bytecode/bludisko/rt/game/window/screens/MenuScreen.java
@@ -0,0 +1,216 @@
+package sk.bytecode.bludisko.rt.game.window.screens;
+
+import org.jetbrains.annotations.NotNull;
+import sk.bytecode.bludisko.rt.game.graphics.Camera;
+import sk.bytecode.bludisko.rt.game.graphics.TextureManager;
+import sk.bytecode.bludisko.rt.game.input.InputManager;
+import sk.bytecode.bludisko.rt.game.input.MenuInputManager;
+import sk.bytecode.bludisko.rt.game.input.MenuInputManagerDelegate;
+import sk.bytecode.bludisko.rt.game.map.Chamber1;
+import sk.bytecode.bludisko.rt.game.map.World;
+import sk.bytecode.bludisko.rt.game.math.Vector2;
+import sk.bytecode.bludisko.rt.game.util.NullSafe;
+import sk.bytecode.bludisko.rt.game.window.screens.components.Button;
+import sk.bytecode.bludisko.rt.game.window.screens.components.ColorView;
+import sk.bytecode.bludisko.rt.game.window.screens.components.Image;
+import sk.bytecode.bludisko.rt.game.window.screens.components.Label;
+import sk.bytecode.bludisko.rt.game.window.screens.components.View;
+
+import java.awt.Color;
+import java.awt.Graphics;
+import java.awt.Point;
+import java.awt.Rectangle;
+
+/**
+ * Main Menu screen.
+ */
+public class MenuScreen extends Screen implements MenuInputManagerDelegate {
+
+ private final MenuInputManager menuInputManager = new MenuInputManager();
+ private final World backgroundWorld;
+
+ private final Camera camera;
+ private final View uiView;
+
+ private final ColorView dimBackground;
+ private final Image logoImage;
+ private final Button playButton;
+ private final Button exitButton;
+ private final Label bottomLabel;
+
+ @SuppressWarnings("FieldCanBeLocal")
+ private final float transitionLength = 2f;
+ private float transitionTime = 0f;
+
+ private boolean transition = false;
+ private float transitionDirection;
+ private float transitionPitch;
+ private float transitionHeight;
+ private Vector2 transitionVector;
+
+ // MARK: - Initalization
+
+ public MenuScreen() {
+ this.backgroundWorld = new Chamber1();
+
+ this.camera = new Camera();
+ this.uiView = new View();
+
+ this.dimBackground = new ColorView();
+ this.logoImage = new Image();
+ this.playButton = new Button();
+ this.exitButton = new Button();
+ this.bottomLabel = new Label();
+
+ setupBackground();
+ setupUI();
+ setupInput();
+ }
+
+ // MARK: - Private
+
+ private void setupBackground() {
+ camera.setMap(backgroundWorld.getMap());
+ camera.move(new Vector2(10.5f, 10.5f));
+ camera.rotate(250, -100);
+ camera.move(500);
+ }
+
+ private void setupUI() {
+ setupAppearance();
+ addSubviews();
+ }
+
+ private void setupAppearance() {
+ dimBackground.setBackgroundColor(new Color(0.2f, 0.2f, 0.2f, 0.75f));
+ logoImage.setTexture(TextureManager.getTexture(15));
+ playButton.setButtonText("Start game");
+ exitButton.setButtonText("Exit");
+ bottomLabel.setText("Filip Šašala, 2022");
+
+ playButton.setAction(this::play);
+ exitButton.setAction(this::exit);
+ }
+
+ private void addSubviews() {
+ uiView.addSubview(dimBackground);
+ uiView.addSubview(logoImage);
+ uiView.addSubview(playButton);
+ uiView.addSubview(exitButton);
+ uiView.addSubview(bottomLabel);
+ }
+
+ private void setupFrames() {
+ var windowFrame = uiView.getFrame();
+
+ dimBackground.setFrame(new Rectangle(15, 35, 390, windowFrame.height - 70));
+ logoImage.setFrame(new Rectangle(30, 50, 360, 120));
+ playButton.setFrame(new Rectangle(50, 190, 150, 40));
+ exitButton.setFrame(new Rectangle(50, 240, 150, 40));
+ bottomLabel.setFrame(new Rectangle(30, windowFrame.height - 70 - 15, 150, 30));
+ }
+
+ private void setupInput() {
+ menuInputManager.setDelegate(this);
+ menuInputManager.setMouseLocked(false);
+ }
+
+ // MARK: - Button actions
+
+ private void play() {
+ NullSafe.acceptWeak(window, window -> window.setCursorVisible(false));
+ uiView.setVisible(false);
+ transition = true;
+ }
+
+ private void exit() {
+ System.exit(0);
+ }
+
+ // MARK: - Override
+
+ @Override
+ public InputManager getInputManager() {
+ return menuInputManager;
+ }
+
+ @Override
+ public void screenDidAppear() {
+ super.screenDidAppear();
+
+ NullSafe.acceptWeak(window, window -> window.setCursorVisible(true));
+ }
+
+ @Override
+ public void screenDidChangeBounds(@NotNull Rectangle bounds) {
+ super.screenDidChangeBounds(bounds);
+
+ camera.setScreenSize(bounds);
+ uiView.setFrame(bounds);
+
+ setupFrames();
+ }
+
+ @Override
+ public void tick(float dt) {
+ super.tick(dt);
+
+ if(transition) {
+ if(transitionTime == 0f) {
+ transitionVector = backgroundWorld.getMap()
+ .getSpawnLocation()
+ .cpy()
+ .sub(camera.getPosition());
+ transitionDirection = backgroundWorld.getMap()
+ .getSpawnDirection()
+ .angleDeg() - camera.getDirection().angleDeg();
+ transitionPitch = 0f - camera.getPitch();
+ transitionHeight = 50f - camera.getPositionZ(); // Player default is 50f
+ }
+
+ camera.move(transitionVector.cpy()
+ .scl(dt)
+ .scl(1f / transitionLength)
+ );
+ camera.move(
+ transitionHeight * dt * (1f / transitionLength)
+ );
+ camera.rotate(
+ transitionDirection * dt * (1f / transitionLength),
+ transitionPitch * dt * (1f / transitionLength)
+ );
+
+ transitionTime += dt * (1f / transitionLength);
+
+ if(transitionTime > 1f) {
+ NullSafe.acceptWeak(window, window -> window.setScreen(new GameScreen()));
+ }
+ } else {
+ camera.rotate(0.025f, 0);
+ }
+ }
+
+ @Override
+ public void draw(@NotNull Graphics graphics) {
+ camera.draw(graphics);
+ uiView.draw(graphics);
+ }
+
+ // MARK: - MenuInputManagerDelegate
+
+ @Override
+ public void touchesBegan(Point point) {
+ uiView.touchesBegan(point);
+ }
+
+ @Override
+ public void touchesEnded(Point point) {
+ uiView.touchesEnded(point);
+ }
+
+ @Override
+ public void touchesCancelled(Point point) {
+ uiView.touchesCancelled(point);
+ }
+
+}
diff --git a/src/sk/bytecode/bludisko/rt/game/window/screens/Screen.java b/src/sk/bytecode/bludisko/rt/game/window/screens/Screen.java
index 515d24d..52b8e50 100644
--- a/src/sk/bytecode/bludisko/rt/game/window/screens/Screen.java
+++ b/src/sk/bytecode/bludisko/rt/game/window/screens/Screen.java
@@ -1,9 +1,10 @@
package sk.bytecode.bludisko.rt.game.window.screens;
-import sk.bytecode.bludisko.rt.game.window.Window;
+import org.jetbrains.annotations.NotNull;
+import sk.bytecode.bludisko.rt.game.graphics.Actor;
import sk.bytecode.bludisko.rt.game.input.InputManager;
+import sk.bytecode.bludisko.rt.game.window.Window;
-import java.awt.Component;
import java.awt.Graphics;
import java.awt.Rectangle;
import java.lang.ref.WeakReference;
@@ -12,35 +13,17 @@
* Empty screen with basic logic implemented. Contains default controls
* of an InputManager.
*/
-public abstract class Screen {
+public abstract class Screen implements Actor {
protected WeakReference window;
// MARK: - Game loop
- /**
- * Game loop - logic.
- * Called every frame before rendering. Should contain
- * logic that's used to update information for renderer to draw
- * a new frame.
- * @param dt
- */
+ @Override
public void tick(float dt) {
- getInputManager().tick(dt);
+ this.getInputManager().tick(dt);
}
- /**
- * Game loop - graphics.
- * Called every frame after updating drawing information.
- * Graphics object should not be finalized or closed inside draw() methods,
- * in case another object wants to draw more information into Canvas.
- * Finalizing and showing the image is handled automatically by Window.
- * @param graphics Java AWT graphics to draw into. Will be displayed on the Window's Canvas.
- * @see Window
- * @see Screen#tick(float)
- */
- public abstract void draw(Graphics graphics);
-
// MARK: - Public
/**
@@ -69,8 +52,9 @@ public void screenDidAppear() {}
* @param bounds Updated Window bounds.
* @see Window#canvasBounds()
*/
- public void screenDidChangeBounds(Rectangle bounds) {
- this.getInputManager().updateWindowDimensions(bounds);
+ @Override
+ public void screenDidChangeBounds(@NotNull Rectangle bounds) {
+ this.getInputManager().screenDidChangeBounds(bounds);
}
}
diff --git a/src/sk/bytecode/bludisko/rt/game/window/screens/components/Button.java b/src/sk/bytecode/bludisko/rt/game/window/screens/components/Button.java
new file mode 100644
index 0000000..16bbcd5
--- /dev/null
+++ b/src/sk/bytecode/bludisko/rt/game/window/screens/components/Button.java
@@ -0,0 +1,82 @@
+package sk.bytecode.bludisko.rt.game.window.screens.components;
+
+import sk.bytecode.bludisko.rt.game.util.NullSafe;
+
+import java.awt.Color;
+import java.awt.Graphics;
+import java.awt.Point;
+import java.awt.Rectangle;
+
+public class Button extends View {
+
+ private final Color buttonColor = Color.getHSBColor(0f, 0f, 0.17f);
+
+ private final Label textLabel = new Label();
+ private float selectedMultiplier = 1f;
+
+ private Runnable action;
+
+ public Button() {
+ addSubview(textLabel);
+ }
+
+ @Override
+ public void draw(Graphics graphics) {
+ int backgroundX = (int) (frame.x + ((frame.width) - (frame.width * selectedMultiplier)) / 2);
+ int backgroundY = (int) (frame.y + ((frame.height) - (frame.height * selectedMultiplier)) / 2);
+
+ graphics.setColor(buttonColor);
+ graphics.fillRoundRect(
+ backgroundX,
+ backgroundY,
+ (int) (frame.width * selectedMultiplier),
+ (int) (frame.height * selectedMultiplier),
+ 12,
+ 12
+ );
+
+ super.draw(graphics);
+ }
+
+ @Override
+ public void setFrame(Rectangle frame) {
+ super.setFrame(frame);
+ textLabel.setFrame(getFrame());
+ }
+
+ // MARK: - Touch handling
+
+ @Override
+ public void touchesBegan(Point point) {
+ super.touchesBegan(point);
+ selectedMultiplier = 1.05f;
+ }
+
+ @Override
+ public void touchesEnded(Point point) {
+ super.touchesEnded(point);
+ selectedMultiplier = 1f;
+
+ NullSafe.accept(action, Runnable::run);
+ }
+
+ @Override
+ public void touchesCancelled(Point point) {
+ super.touchesCancelled(point);
+ selectedMultiplier = 1f;
+ }
+
+ // MARK: - Getters and Setters
+
+ public String getButtonText() {
+ return textLabel.getText();
+ }
+
+ public void setButtonText(String buttonText) {
+ textLabel.setText(buttonText);
+ }
+
+ public void setAction(Runnable action) {
+ this.action = action;
+ }
+}
diff --git a/src/sk/bytecode/bludisko/rt/game/window/screens/components/ColorView.java b/src/sk/bytecode/bludisko/rt/game/window/screens/components/ColorView.java
new file mode 100644
index 0000000..37ea0cf
--- /dev/null
+++ b/src/sk/bytecode/bludisko/rt/game/window/screens/components/ColorView.java
@@ -0,0 +1,25 @@
+package sk.bytecode.bludisko.rt.game.window.screens.components;
+
+import java.awt.Color;
+import java.awt.Graphics;
+
+public class ColorView extends View {
+
+ private Color backgroundColor;
+
+ @Override
+ public void draw(Graphics graphics) {
+ super.draw(graphics);
+
+ graphics.setColor(backgroundColor);
+ graphics.fillRect(frame.x, frame.y, frame.width, frame.height);
+ }
+
+ public Color getBackgroundColor() {
+ return backgroundColor;
+ }
+
+ public void setBackgroundColor(Color backgroundColor) {
+ this.backgroundColor = backgroundColor;
+ }
+}
diff --git a/src/sk/bytecode/bludisko/rt/game/window/screens/components/Image.java b/src/sk/bytecode/bludisko/rt/game/window/screens/components/Image.java
new file mode 100644
index 0000000..e7c9501
--- /dev/null
+++ b/src/sk/bytecode/bludisko/rt/game/window/screens/components/Image.java
@@ -0,0 +1,29 @@
+package sk.bytecode.bludisko.rt.game.window.screens.components;
+
+import sk.bytecode.bludisko.rt.game.graphics.Texture;
+import sk.bytecode.bludisko.rt.game.util.NullSafe;
+
+import java.awt.Graphics;
+
+public class Image extends View {
+
+ private Texture texture;
+
+ @Override
+ public void draw(Graphics graphics) {
+ super.draw(graphics);
+ NullSafe.accept(texture, texture -> graphics.drawImage(
+ texture.asImage(),
+ frame.x,
+ frame.y,
+ frame.width,
+ frame.height,
+ null
+ ));
+ }
+
+ public void setTexture(Texture texture) {
+ this.texture = texture;
+ }
+
+}
diff --git a/src/sk/bytecode/bludisko/rt/game/window/screens/components/Label.java b/src/sk/bytecode/bludisko/rt/game/window/screens/components/Label.java
new file mode 100644
index 0000000..79b70b9
--- /dev/null
+++ b/src/sk/bytecode/bludisko/rt/game/window/screens/components/Label.java
@@ -0,0 +1,34 @@
+package sk.bytecode.bludisko.rt.game.window.screens.components;
+
+import java.awt.Color;
+import java.awt.Graphics;
+
+public class Label extends View {
+
+ private String text = "";
+ private final Color textColor = Color.WHITE;
+
+ @Override
+ public void draw(Graphics graphics) {
+ super.draw(graphics);
+
+ var stringBounds = graphics
+ .getFontMetrics()
+ .getStringBounds(text, graphics);
+
+ var stringX = frame.x + (frame.width / 2) - (int) (stringBounds.getWidth() / 2);
+ var stringY = frame.y + (frame.height / 2) + (int) (stringBounds.getHeight() / 2);
+
+ graphics.setColor(textColor);
+ graphics.drawString(text, stringX, stringY);
+ }
+
+ public String getText() {
+ return text;
+ }
+
+ public void setText(String text) {
+ this.text = text;
+ }
+
+}
diff --git a/src/sk/bytecode/bludisko/rt/game/window/screens/components/View.java b/src/sk/bytecode/bludisko/rt/game/window/screens/components/View.java
new file mode 100644
index 0000000..dd9a62e
--- /dev/null
+++ b/src/sk/bytecode/bludisko/rt/game/window/screens/components/View.java
@@ -0,0 +1,94 @@
+package sk.bytecode.bludisko.rt.game.window.screens.components;
+
+import sk.bytecode.bludisko.rt.game.util.NullSafe;
+
+import java.awt.Graphics;
+import java.awt.Point;
+import java.awt.Rectangle;
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+
+public class View {
+
+ private final float maximumDragDistanceFromOriginalTouchDown = 40f;
+ private Point touchLocation;
+
+ protected Rectangle frame;
+ protected boolean isVisible = true;
+
+ protected WeakReference superview;
+ protected ArrayList subviews = new ArrayList<>();
+
+ // MARK: - Appearance
+
+ public void draw(Graphics graphics) {
+ if(isVisible) { subviews.forEach(view -> view.draw(graphics)); }
+ }
+
+ // MARK: - Touch handling
+
+ public void touchesBegan(Point point) {
+ touchLocation = point;
+ subviews.forEach(subview -> {
+ if(subview.frame.contains(point)) {
+ subview.touchesBegan(point);
+ }
+ });
+ }
+
+ public void touchesEnded(Point point) {
+ NullSafe.accept(touchLocation, touchPoint -> {
+ if(point.distance(touchLocation) < maximumDragDistanceFromOriginalTouchDown) {
+ subviews.forEach(subview -> {
+ if(subview.frame.contains(touchLocation)) {
+ subview.touchesEnded(point);
+ }
+ });
+ } else {
+ touchesCancelled(point);
+ }
+ });
+ }
+
+ public void touchesCancelled(Point point) {
+ NullSafe.accept(touchLocation, touchPoint -> {
+ if(point.distance(touchLocation) >= maximumDragDistanceFromOriginalTouchDown) {
+ subviews.forEach(subview -> subview.touchesCancelled(point));
+ }
+ });
+ }
+
+ // MARK: - Subview manipulation
+
+ public void addSubview(View view) {
+ view.superview = new WeakReference<>(this);
+ subviews.add(view);
+ }
+
+ public void removeFromSuperview() {
+ NullSafe.acceptWeak(superview, this::removeFromSubviews);
+ }
+
+ private void removeFromSubviews(View view) {
+ subviews.remove(view);
+ }
+
+ // MARK: - Getters and Setters
+
+ public Rectangle getFrame() {
+ return new Rectangle(frame.x, frame.y, frame.width, frame.height);
+ }
+
+ public void setFrame(Rectangle frame) {
+ this.frame = frame;
+ }
+
+ public boolean isVisible() {
+ return isVisible;
+ }
+
+ public void setVisible(boolean visible) {
+ isVisible = visible;
+ }
+
+}
diff --git a/test/MapTest.java b/test/MapTest.java
index 2f8a988..ecb070b 100644
--- a/test/MapTest.java
+++ b/test/MapTest.java
@@ -96,10 +96,10 @@ void map1() throws IOException {
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
- { 0, 0, 0, 0, 0, 0, 0, 1, 2, 8, 1, 1, 0, 0, 0, 0, 0 },
+ { 0, 0, 0, 0, 0, 0, 0, 1, 2, 1, 1, 1, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 0, 0,11, 0, 0, 0, 2, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 1, 1, 1, 1, 3, 3, 0, 2, 0, 0, 0, 0, 0 },
- { 0, 0, 0, 0, 1, 0, 0, 7, 0, 0, 4, 2, 0, 0, 0, 0, 0 },
+ { 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 4, 2, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0,13, 0, 0, 6, 0, 0, 4, 2, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 1, 0, 0, 0, 5, 5, 0, 2, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0 },