Skip to content

Commit

Permalink
Add full player hitbox
Browse files Browse the repository at this point in the history
  • Loading branch information
Nixinova committed Aug 15, 2024
1 parent 5616736 commit dbf6633
Show file tree
Hide file tree
Showing 4 changed files with 134 additions and 50 deletions.
3 changes: 2 additions & 1 deletion changelog.md
Original file line number Diff line number Diff line change
@@ -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

Expand Down
108 changes: 60 additions & 48 deletions src/com/nixinova/input/ControlsTick.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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);
Expand All @@ -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;
Expand All @@ -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) {
Expand Down Expand Up @@ -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();
Expand All @@ -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();
Expand Down
71 changes: 71 additions & 0 deletions src/com/nixinova/player/Hitbox.java
Original file line number Diff line number Diff line change
@@ -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<Corner> list = new ArrayList<>();

public boolean containsAll(Corner... providedCorners) {
for (Corner corner : providedCorners) {
if (!list.contains(corner))
return false;
}
return true;
}
}

private static Map<Corner, Vector3<Integer>> 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<Integer> 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;
}

}
2 changes: 1 addition & 1 deletion src/com/nixinova/player/Player.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down

0 comments on commit dbf6633

Please sign in to comment.