Skip to content

Commit

Permalink
Perfected movement controller.
Browse files Browse the repository at this point in the history
  • Loading branch information
Half-Shot committed Sep 28, 2024
1 parent d0e628c commit d08bcc1
Show file tree
Hide file tree
Showing 3 changed files with 57 additions and 63 deletions.
28 changes: 21 additions & 7 deletions spec/unit/movementContoller.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ async function visualisePhysics(world: GameWorld, fileOut: string) {
const cls = buffers.colors;

for (let i = 0; i < vtx.length / 4; i += 1) {
// Pad the canvas so we can see it.
const vtxA = 250 + Math.round(vtx[i * 4] * 50);
const vtxB = 250 + Math.round(vtx[i * 4 + 1] * 50);
const vtxC = 250 + Math.round(vtx[i * 4 + 2] * 50);
Expand Down Expand Up @@ -63,6 +64,15 @@ function constructTestEnv(): { world: GameWorld, player: RapierPhysicsObject, ti
ColliderDesc.cuboid(3, 0.25),
RigidBodyDesc.fixed().setTranslation(1, 2).lockRotations()
);
const playerEnt = {
priority: 0,
body: player.body,
destroyed: false,
destroy() {

},
} satisfies IPhysicalEntity;
world.addBody(playerEnt, player.collider);
return {
world,
player,
Expand All @@ -72,15 +82,15 @@ function constructTestEnv(): { world: GameWorld, player: RapierPhysicsObject, ti
do {
world.step();
step++;
} while(player.body.isMoving() && step <= MaxSteps);
} while(world.areEntitiesMoving() && step <= MaxSteps);
expect(step).toBeLessThan(MaxSteps);
return player.body.translation();
}
}
}

function createBlock(world: GameWorld, x: number,y: number, width = 1, height = 1) {
const body = world.createRigidBodyCollider(ColliderDesc.cuboid(width, height), RigidBodyDesc.fixed().setUserData({'test':'block'}).setTranslation(
const body = world.createRigidBodyCollider(ColliderDesc.cuboid(width, height), RigidBodyDesc.fixed().setTranslation(
x, y
));
const ent = {
Expand All @@ -93,6 +103,8 @@ function createBlock(world: GameWorld, x: number,y: number, width = 1, height =
},
} satisfies IPhysicalEntity&{collider: Collider};
world.addBody(ent, ent.collider);
// REQUIRED: So the collider has time to be registered.
world.step();
return ent;
}

Expand All @@ -116,7 +128,9 @@ describe('calculateMovement', () => {
const testName = currentTestName?.replaceAll(/\s/g, '_') ?? "unknown";
const filename = `./test-out/${testName}.webp`;
// Show debug shape.
env.world.createRigidBodyCollider(new ColliderDesc(debugData.shape), RigidBodyDesc.fixed().setTranslation(debugData.rayCoodinate.worldX, debugData.rayCoodinate.worldY));
if (debugData) {
env.world.createRigidBodyCollider(new ColliderDesc(debugData.shape), RigidBodyDesc.fixed().setTranslation(debugData.rayCoodinate.worldX, debugData.rayCoodinate.worldY));
}
await visualisePhysics(env.world, filename);
const fullPath = resolve(filename);
if (assertionCalls !== numPassingAsserts) {
Expand All @@ -134,7 +148,7 @@ describe('calculateMovement', () => {
test('should be able to move left when there are no obstacles', () => {
const move = calculateMovement(env.player, new Vector2(-1, 0), maxStep, env.world);
env.player.body.setTranslation(move, false);
const {x, y} =env.waitUntilStopped();
const {x, y} = env.waitUntilStopped();
expect(x).toBeCloseTo(0);
expect(y).toBeCloseTo(1, 0);
});
Expand All @@ -147,7 +161,7 @@ describe('calculateMovement', () => {
expect(y).toBeCloseTo(1, 0);
});

test.only('should not be able to move if an obstacle is in the way', () => {
test('should not be able to move if an obstacle is in the way', () => {
createBlock(env.world, 0, 1.25, 0.5, 0.5);
const originalTranslation = env.player.body.translation();
const move = calculateMovement(env.player, new Vector2(-0.5, 0), maxStep, env.world);
Expand All @@ -157,7 +171,7 @@ describe('calculateMovement', () => {
expect(originalTranslation.y-y).toBeCloseTo(0, 1);
});

test('should be able to step over obstacles', () => {
test('should be able to step on top of obstacles', () => {
createBlock(env.world, 0.5, 1.5, 0.25, 0.25);
env.waitUntilStopped();
const move = calculateMovement(env.player, new Vector2(-0.5, 0), maxStep, env.world);
Expand All @@ -183,7 +197,7 @@ describe('calculateMovement', () => {
expect(y).toBeCloseTo(0, 0.5);
});

test('should not be able to enter cave-like entrances', () => {
test.only('should not be able to enter cave-like entrances', () => {
createBlock(env.world, 0.5, 1.5, 0.25, 0.25);
createBlock(env.world, 0.5, 0.5, 0.25, 0.25);
const { y: originalY } = env.waitUntilStopped();
Expand Down
67 changes: 31 additions & 36 deletions src/movementController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,52 +17,47 @@ export function calculateMovement(physObject: RapierPhysicsObject, movement: Vec
const {y: objHalfHeight, x: objHalfWidth } = (physObject.collider.shape as Cuboid).halfExtents;
// Get the extremity.
const rayCoodinate = new Coordinate(
move.x - 0.5,
move.y - 0.25,
move.x,
// Increase the bounds to the steppy position.
move.y - maxSteppy.value,
);

// Always move to whatever y delta is lower ( as in higher )
let topYDelta = move.y;


const initialCollisionShape = new Cuboid(objHalfWidth, objHalfHeight);
console.log(rayCoodinate.worldX, rayCoodinate.worldY);
// Increase by steppy amount.
const initialCollisionShape = new Cuboid(objHalfWidth, objHalfHeight - maxSteppy.value);
debugData = { rayCoodinate, shape: initialCollisionShape };

// TODO: Render this shape!
const collides = world.checkCollisionShape(new Coordinate(currentTranslation.x, currentTranslation.y), rayCoodinate.toWorldVector(), initialCollisionShape, physObject.collider);
const collides = world.checkCollisionShape(rayCoodinate, initialCollisionShape, physObject.collider);
let canTravel = collides.length === 0;

Check failure on line 31 in src/movementController.ts

View workflow job for this annotation

GitHub Actions / lint

'canTravel' is never reassigned. Use 'const' instead
for (const c of collides) {
const shape = c.collider.shape;
const bodyT = c.collider.translation();
if (currentTranslation.y - bodyT.y < maxSteppy.value) {
// TODO: Support more types.
const halfHeight = shape.type === ShapeType.Cuboid ? (shape as Cuboid).halfExtents.y : (shape as Ball).radius;
// Pop the highest collider
const highestCollider = collides.sort((a,b) => a.collider.translation().y-b.collider.translation().y)[0];

// Step
const potentialX = bodyT.x;
const potentialY = bodyT.y - halfHeight - objHalfHeight;
const yDelta = move.y-bodyT.y;
if (yDelta < topYDelta) {
continue;
}

// Check step is safe
// if (world.checkCollisionShape(new Coordinate(potentialX, potentialY), physObject.collider.shape, physObject.collider).length){
// continue;
// }
move.y = potentialY;
move.x = potentialX;
topYDelta = yDelta;
canTravel = true;
console.log('canTravel');
}
// No collisions, go go go!
if (!highestCollider) {
return move;
}

if (!canTravel) {
const shape = highestCollider.collider.shape;
const bodyT = highestCollider.collider.translation();
if (currentTranslation.y - bodyT.y > maxSteppy.value) {
console.log('too big to steppy');
return currentTranslation;
}
// TODO: Support more types.
const halfHeight = shape.type === ShapeType.Cuboid ? (shape as Cuboid).halfExtents.y : (shape as Ball).radius;

console.log("numColliders", collides.length);

// Step
const potentialX = bodyT.x;
// Crop a bit off the top to avoid colliding with it.
const potentialY = bodyT.y - halfHeight - objHalfHeight - 0.01;

// Check step is safe
debugData = { rayCoodinate: new Coordinate(potentialX, potentialY), shape: physObject.collider.shape }
if (world.checkCollisionShape(new Coordinate(potentialX, potentialY), physObject.collider.shape, physObject.collider).length){
console.log('step is not safe');
return currentTranslation;
}
move.y = potentialY;
move.x = potentialX;
return move;
}
25 changes: 5 additions & 20 deletions src/world.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import { IGameEntity, IPhysicalEntity } from "./entities/entity";
import { Ticker, UPDATE_PRIORITY } from "pixi.js";
import { Ball, Collider, ColliderDesc, EventQueue, QueryFilterFlags, RigidBody, RigidBodyDesc, Shape, Vector2, World } from "@dimforge/rapier2d-compat";
import { Coordinate, MetersValue } from "./utils/coodinate";
import { sub } from "./utils";

/**
* Utility class holding the matterjs composite and entity map.
Expand Down Expand Up @@ -152,36 +151,22 @@ export class GameWorld {
return found;
}

public checkCollisionShape(start: Coordinate, dir: Vector2, shape: Shape, ownCollier: Collider): {collider: Collider, entity: IPhysicalEntity}[] {
public checkCollisionShape(position: Coordinate, shape: Shape, ownCollier: Collider): {collider: Collider, entity: IPhysicalEntity}[] {
// Ensure a unique set of results.
const results = new Array<{collider: Collider, entity: IPhysicalEntity}>();
// console.log(position.worldX, position.worldY, shape, results, this.rapierWorld.colliders.forEach(c =>
// console.log("=>", c.translation(), c.shape)
// ));
this.rapierWorld.castShape(
start.toWorldVector(),
this.rapierWorld.intersectionsWithShape(
new Vector2(position.worldX, position.worldY),
0,
dir,
shape,
1,
1,
false,
undefined,
undefined,
undefined,
undefined,
(collider) => {
console.log("Got collier", collider.handle, ownCollier.handle);
if (collider.handle !== ownCollier.handle) {
console.log('BARK2!');
const entity = this.bodyEntityMap.get(collider.handle);
if (entity) {
results.push({entity, collider});
}
console.log('nono', entity);
} else {
console.log('collided with self');
}
return false;
return true;
},
);
return [...results];
Expand Down

0 comments on commit d08bcc1

Please sign in to comment.