From dbf6633f2cc9e6944e2d45a87509dc40c7adf691 Mon Sep 17 00:00:00 2001 From: Nixinova Date: Thu, 15 Aug 2024 20:34:23 +1200 Subject: [PATCH] Add full player hitbox --- changelog.md | 3 +- src/com/nixinova/input/ControlsTick.java | 108 +++++++++++++---------- src/com/nixinova/player/Hitbox.java | 71 +++++++++++++++ src/com/nixinova/player/Player.java | 2 +- 4 files changed, 134 insertions(+), 50 deletions(-) create mode 100644 src/com/nixinova/player/Hitbox.java diff --git a/changelog.md b/changelog.md index d019161..085a0f4 100644 --- a/changelog.md +++ b/changelog.md @@ -1,7 +1,8 @@ # Changelog ## Next -- Added side hitboxes to blocks +- Added horizontal collision box to blocks +- Added collision boundary box to the player - Changed renderer to only render blocks within view of the player - Changed player spawn point to centre of world diff --git a/src/com/nixinova/input/ControlsTick.java b/src/com/nixinova/input/ControlsTick.java index 45dd1f8..e4d9673 100644 --- a/src/com/nixinova/input/ControlsTick.java +++ b/src/com/nixinova/input/ControlsTick.java @@ -6,15 +6,21 @@ import com.nixinova.coords.Coord1; import com.nixinova.coords.Coord3; import com.nixinova.coords.PxCoord; -import com.nixinova.coords.SubBlockCoord; import com.nixinova.coords.TxCoord; import com.nixinova.main.Game; import com.nixinova.options.Options; +import com.nixinova.player.Hitbox; +import com.nixinova.player.Hitbox.Corner; +import com.nixinova.player.Hitbox.CornersList; import com.nixinova.player.Hotbar; -import com.nixinova.player.Player; public class ControlsTick { + private class PositionChange { + public Coord3 pos; + public Coord3 dpos; + } + private Game game; private Controller controls; @@ -35,8 +41,8 @@ public void tick(InputHandler input) { this.dpos = new Coord3(); // Tick - boolean aboveGround = aboveGround(); - boolean belowGround = belowGround(); + boolean aboveGround = isAboveGround(); + boolean belowGround = isBelowGround(); boolean onGround = this.game.player.isWithinWorld(this.game.world) && !aboveGround && !belowGround; // Natural events like gravity, etc tickUninputted(aboveGround, belowGround); @@ -51,9 +57,29 @@ private void tickUninputted(boolean aboveGround, boolean belowGround) { double yMove = 0.0D; double zMove = 0.0D; + // Shove in a particular direction if currently inside a block + CornersList collisionPoints = Hitbox.getCollisionPoints(this.game.world, controls.pos); + if (collisionPoints.list.size() > 0) { + // Along all planes at foot + if (collisionPoints.containsAll(Corner.FOOT_xz, Corner.FOOT_xZ, Corner.FOOT_Xz, Corner.FOOT_XZ)) + yMove = +Options.gravity; + // Along negative X plane at foot + else if (collisionPoints.containsAll(Corner.FOOT_xz, Corner.FOOT_xZ)) + xMove = +Options.walkSpeed; + // Along positive X plane at foot + else if (collisionPoints.containsAll(Corner.FOOT_Xz, Corner.FOOT_XZ)) + xMove = -Options.walkSpeed; + // Along negative Z plane at foot + else if (collisionPoints.containsAll(Corner.FOOT_xz, Corner.FOOT_Xz)) + zMove = +Options.walkSpeed; + // Along positive Z plane at foot + else if (collisionPoints.containsAll(Corner.FOOT_xZ, Corner.FOOT_XZ)) + zMove = -Options.walkSpeed; + } + // Ground checks if (this.game.player.isWithinWorld(this.game.world)) { - // Inside a block + // Above ground if (aboveGround) { // Fall due to gravity yMove -= Options.gravity; @@ -78,7 +104,8 @@ else if (belowGround) { yMove *= 1 + Math.pow(1 + Options.gravity, 2); // Update position - updatePos(xMove, yMove, zMove); + PositionChange move = resultFromMove(xMove, yMove, zMove); + makeMove(move); } private void tickInputted(InputHandler input, boolean onGround) { @@ -177,67 +204,52 @@ private void tickInputted(InputHandler input, boolean onGround) { kbd.startKeyCooldown(Keys.F3); } + // Update and decelerate rotation + controls.rot += this.drot; + controls.tilt += this.dtilt; + this.drot *= 0.8D; + this.dtilt *= 0.8D; + controls.rot %= Math.PI * 2; // modulo to be 0..360 + // Update position - updatePos(xMove, yMove, zMove); + PositionChange move = resultFromMove(xMove, yMove, zMove); + makeMove(move); } - private void updatePos(double xMove, double yMove, double zMove) { + private PositionChange resultFromMove(double xMove, double yMove, double zMove) { + PositionChange result = new PositionChange(); + result.pos = controls.pos; + result.dpos = this.dpos; + // differentials for controls - PxCoord curdpos = this.dpos.toPx(); + PxCoord curdpos = result.dpos.toPx(); double ddposX = (xMove * Math.cos(controls.rot) + zMove * Math.sin(controls.rot)) * Options.walkSpeed; double ddposY = yMove; double ddposZ = (zMove * Math.cos(controls.rot) - xMove * Math.sin(controls.rot)) * Options.walkSpeed; PxCoord newdpos = new PxCoord(curdpos.x + ddposX, curdpos.y + ddposY, curdpos.z + ddposZ); // apply differentials - PxCoord curpos = controls.pos.toPx(); + PxCoord curpos = result.pos.toPx(); PxCoord newpos = new PxCoord(curpos.x + newdpos.x, curpos.y + newdpos.y, curpos.z + newdpos.z); - // rotation: update and decelerate - controls.rot += this.drot; - controls.tilt += this.dtilt; - this.drot *= 0.8D; - this.dtilt *= 0.8D; - controls.rot %= Math.PI * 2; // modulo to be 0..360 - - // position: update and decelerate - controls.pos = Coord3.fromPx(newpos); - this.dpos = Coord3.fromPx(newdpos); + // update and decelerate position + result.pos = Coord3.fromPx(newpos); + result.dpos = Coord3.fromPx(newdpos); newdpos.x *= 0.3D; newdpos.y *= 0.3D; newdpos.z *= 0.3D; - this.dpos = Coord3.fromPx(newdpos); + result.dpos = Coord3.fromPx(newdpos); + + return result; } - private boolean insideBlock(PxCoord position) { - SubBlockCoord blockPos = Coord3.fromPx(position).toSubBlock(); - int[][] playerCornerOffsets = { - // X, Y, Z - { -1, -0, -1 }, - { +1, -0, -1 }, - { -1, +1, -1 }, - { -1, -0, +1 }, - { +1, +1, -1 }, - { +1, -0, +1 }, - { -1, +1, +1 }, - { +1, +1, +1 }, - }; - // check each corner of the player's hitbox for being inside a block at the new position - for (int[] corner : playerCornerOffsets) { - int posX = Coord1.fromSubBlock(blockPos.x + Player.PLAYER_RADIUS * corner[0]).toBlock(); - int posY = Coord1.fromSubBlock(blockPos.y + Player.PLAYER_HEIGHT * corner[1]).toBlock(); - int posZ = Coord1.fromSubBlock(blockPos.z + Player.PLAYER_RADIUS * corner[2]).toBlock(); - - // if player ends up inside a block then return - if (!this.game.world.isAir(posX, posY, posZ)) - return true; - } - // if no block collision found then player is not inside a block - return false; + private void makeMove(PositionChange move) { + controls.pos = move.pos; + this.dpos = move.dpos; } // TODO check player hitbox - private boolean aboveGround() { + private boolean isAboveGround() { // Above the ground if the block one texel beneath the player's feet is air TxCoord curTx = controls.pos.toTx(); BlockCoord blockOneTxDown = Coord3.fromTx(curTx.x, curTx.y - 1, curTx.z).toBlock(); @@ -247,7 +259,7 @@ private boolean aboveGround() { } // TODO check player hitbox - private boolean belowGround() { + private boolean isBelowGround() { // Below the ground if the block one texel above the player's feet is air TxCoord footTx = controls.pos.toTx(); BlockCoord blockOneTxUp = Coord3.fromTx(footTx.x, footTx.y + 1, footTx.z).toBlock(); diff --git a/src/com/nixinova/player/Hitbox.java b/src/com/nixinova/player/Hitbox.java new file mode 100644 index 0000000..72ee727 --- /dev/null +++ b/src/com/nixinova/player/Hitbox.java @@ -0,0 +1,71 @@ +package com.nixinova.player; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import com.nixinova.Vector3; +import com.nixinova.coords.Coord1; +import com.nixinova.coords.Coord3; +import com.nixinova.coords.SubBlockCoord; +import com.nixinova.world.World; + +public class Hitbox { + + public static enum Corner { + // capitalisation denotes min/max + FOOT_xz, FOOT_Xz, FOOT_xZ, FOOT_XZ, + HEAD_xz, HEAD_Xz, HEAD_xZ, HEAD_XZ, + } + + public static class CornersList { + public List list = new ArrayList<>(); + + public boolean containsAll(Corner... providedCorners) { + for (Corner corner : providedCorners) { + if (!list.contains(corner)) + return false; + } + return true; + } + } + + private static Map> playerCornerOffsets = new HashMap<>() { + private static final long serialVersionUID = 1L; + { + put(Corner.FOOT_xz, new Vector3<>(-1, 0, -1)); + put(Corner.FOOT_Xz, new Vector3<>(+1, 0, -1)); + put(Corner.FOOT_xZ, new Vector3<>(-1, 0, +1)); + put(Corner.FOOT_XZ, new Vector3<>(+1, 0, +1)); + + put(Corner.HEAD_xz, new Vector3<>(-1, 1, -1)); + put(Corner.HEAD_Xz, new Vector3<>(+1, 1, -1)); + put(Corner.HEAD_xZ, new Vector3<>(-1, 1, +1)); + put(Corner.HEAD_XZ, new Vector3<>(+1, 1, +1)); + } + }; + + public static CornersList getCollisionPoints(World world, Coord3 position) { + CornersList collisionCorners = new CornersList(); + + SubBlockCoord blockPos = position.toSubBlock(); + // check each corner of the player's hitbox for being inside a block at the new position + for (var hitboxEntry : playerCornerOffsets.entrySet()) { + Corner collisionPoint = hitboxEntry.getKey(); + Vector3 corner = hitboxEntry.getValue(); + + // get block at each corner of the player's hitbox + int posX = Coord1.fromSubBlock(blockPos.x + Player.PLAYER_WIDTH / 2 * corner.x).toBlock(); + int posY = Coord1.fromSubBlock(blockPos.y + Player.PLAYER_HEIGHT * corner.y).toBlock(); + int posZ = Coord1.fromSubBlock(blockPos.z + Player.PLAYER_WIDTH / 2 * corner.z).toBlock(); + + // if player ends up inside a block then add its corner to list + if (!world.isAir(posX, posY, posZ)) + collisionCorners.list.add(collisionPoint); + } + + return collisionCorners; + } + +} diff --git a/src/com/nixinova/player/Player.java b/src/com/nixinova/player/Player.java index a422afc..6160314 100644 --- a/src/com/nixinova/player/Player.java +++ b/src/com/nixinova/player/Player.java @@ -8,7 +8,7 @@ public class Player { public static final double PLAYER_HEIGHT = 1.8; - public static final double PLAYER_RADIUS = 0.4; + public static final double PLAYER_WIDTH = 0.8; private Coord3 position; private HoveredBlock lookingAtBlock;