Skip to content

Commit

Permalink
Start to move to using RapierJS
Browse files Browse the repository at this point in the history
  • Loading branch information
Half-Shot committed Sep 19, 2024
1 parent 9ab2f7a commit 65d1a4b
Show file tree
Hide file tree
Showing 17 changed files with 203 additions and 265 deletions.
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,18 +10,18 @@
"lint": "eslint src/"
},
"dependencies": {
"pixi.js": "^8.4.0",
"@dimforge/rapier2d": "^0.14.0",
"@pixi/sound": "^6.0.1",
"@timohausmann/quadtree-ts": "^2.2.2",
"matter-js": "^0.20.0",
"pathseg": "^1.2.1",
"pixi-viewport": "^5.0.3",
"pixi.js": "^8.4.0",
"poly-decomp-es": "^0.4.2",
"preact": "^10.23.2"
},
"devDependencies": {
"@preact/preset-vite": "^2.5.0",
"@types/matter-js": "^0.18.2",
"@typescript-eslint/eslint-plugin": "^5.59.2",
"@typescript-eslint/parser": "^5.59.2",
"eslint": "^8.40.0",
Expand Down
7 changes: 3 additions & 4 deletions src/entities/background.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
import { ColorSource, Container, Geometry, Graphics, Mesh, Shader, UPDATE_PRIORITY } from "pixi.js";
import { ColorSource, Container, Geometry, Graphics, Mesh, Point, Shader, UPDATE_PRIORITY } from "pixi.js";
import { IGameEntity } from "./entity";
import { Vector } from "matter-js";
import { GradientShader } from "../shaders";
import { BitmapTerrain } from "./bitmapTerrain";
import { Viewport } from "pixi-viewport";

interface RainParticle {
position: Vector;
position: Point;
length: number;
angle: number;
speed: number;
Expand Down Expand Up @@ -73,7 +72,7 @@ export class Background implements IGameEntity {
const x = this.viewport.center.x + Math.round(Math.random()*this.viewport.screenWidth) - this.viewport.screenWidth/2;
const y = this.viewport.center.y + (0-Math.round(Math.random()*this.viewport.screenHeight) - 200);
this.rainParticles.push({
position: Vector.create(x, y),
position: new Point(x,y),
length: MIN_RAIN_LENGTH + Math.round(Math.random()*(MAX_RAIN_LENGTH-MIN_RAIN_LENGTH)),
angle: (Math.random()-0.5)* 15,
speed: (this.rainSpeed*(0.5 + (Math.random()*this.rainSpeedVariation)))
Expand Down
49 changes: 29 additions & 20 deletions src/entities/bitmapTerrain.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { Body, Vector, Bodies, Query, Collision } from "matter-js";
import { UPDATE_PRIORITY, Container, Graphics, Rectangle, Texture, Sprite } from "pixi.js";
import { IMatterEntity } from "./entity";
import { generateQuadTreeFromTerrain, imageDataToTerrainBoundaries } from "../terrain";
import Flags from "../flags";
import { GameWorld } from "../world";
import { GameWorld, RapierPhysicsObject } from "../world";
import { Collider, ColliderDesc, Cuboid, RigidBodyDesc, Vector2 } from "@dimforge/rapier2d";

export type OnDamage = () => void;
export class BitmapTerrain implements IMatterEntity {
Expand All @@ -15,9 +15,9 @@ export class BitmapTerrain implements IMatterEntity {
}

private readonly gfx: Graphics = new Graphics();
private parts: Body[] = [];
private parts: RapierPhysicsObject[] = [];
private nearestTerrainPositionBodies = new Set();
private nearestTerrainPositionPoint = Vector.create();
private nearestTerrainPositionPoint = new Vector2(0,0);

private bounds: Rectangle;

Expand All @@ -26,7 +26,8 @@ export class BitmapTerrain implements IMatterEntity {
private textureBackdrop: Texture;
private readonly sprite: Sprite;
private readonly spriteBackdrop: Sprite;
private registeredDamageFunctions = new Map<string,OnDamage>();
// collider.handle -> fn
private registeredDamageFunctions = new Map<number,OnDamage>();

static create(viewWidth: number, viewHeight: number, gameWorld: GameWorld, texture: Texture) {
return new BitmapTerrain(viewWidth, viewHeight, gameWorld, texture);
Expand Down Expand Up @@ -82,17 +83,18 @@ export class BitmapTerrain implements IMatterEntity {

// Remove everything within the boundaries
const removableBodies = this.parts.filter(
(b) => (b.position.x >= boundaryX && b.position.x <= boundaryX + boundaryWidth) &&
(b.position.y >= boundaryY && b.position.y <= boundaryY + boundaryHeight)
(b) => {
const tr = b.body.translation();
return (tr.x >= boundaryX && tr.x <= boundaryX + boundaryWidth) &&
(tr.y >= boundaryY && tr.y <= boundaryY + boundaryHeight)
)

Check failure on line 90 in src/entities/bitmapTerrain.ts

View workflow job for this annotation

GitHub Actions / build

Declaration or statement expected.

console.log("Removing", removableBodies.length, "bodies");
for (const body of removableBodies) {
this.gameWorld.removeBody(body);
const key = body.position.x + "," + body.position.y;
const damageFn = this.registeredDamageFunctions.get(key);
const damageFn = this.registeredDamageFunctions.get(body.collider.handle);
if (damageFn) {
this.registeredDamageFunctions.delete(key);
this.registeredDamageFunctions.delete(body.collider.handle);
damageFn?.();
}
}
Expand All @@ -108,14 +110,17 @@ export class BitmapTerrain implements IMatterEntity {
console.log(this.sprite.x, this.sprite.y);

// Now create the pieces
const newParts: Body[] = [];
const newParts: RapierPhysicsObject[] = [];
for (const quad of quadtreeRects) {
const body = Bodies.rectangle(quad.x + this.sprite.x, quad.y + this.sprite.y, quad.width, quad.height, { label: 'terrain', isStatic: true });
const body = this.gameWorld.createRigidBodyCollider(
ColliderDesc.cuboid(quad.width/2, quad.height/2),
RigidBodyDesc.fixed().setTranslation(quad.x + this.sprite.x, quad.y + this.sprite.y)
)
newParts.push(body);
}
this.parts.push(...newParts);

this.gameWorld.addBody(this, ...newParts);
this.gameWorld.addBody(this, ...newParts.map(p => p.collider));
console.timeEnd("Generating terrain");
}

Expand Down Expand Up @@ -186,15 +191,17 @@ export class BitmapTerrain implements IMatterEntity {
let color = 0xFFBD01;
if (this.nearestTerrainPositionBodies.has(rect)) {
color = 0x00AA00;
} else if (rect.isSleeping) {
} else if (!rect.collider.isEnabled()) {
color = 0x0000FF;
}
this.gfx.rect(rect.position.x, rect.position.y, rect.bounds.max.x - rect.bounds.min.x, rect.bounds.max.y - rect.bounds.min.y).stroke({ width: 1, color });
const position = rect.body.translation();
const shape = rect.collider.shape as Cuboid;
this.gfx.rect(position.x, position.y, shape.halfExtents.x * 2, shape.halfExtents.y * 2).stroke({ width: 1, color });
}
this.gfx.rect(this.nearestTerrainPositionPoint.x, this.nearestTerrainPositionPoint.y, 1, 1).stroke({width: 5, color: 0xFF0000});
}

public getNearestTerrainPosition(point: Vector, width: number, maxHeightDiff: number, xDirection = 0): {point: Vector, fell: false}|{fell: true, point: null} {
public getNearestTerrainPosition(point: Vector2, width: number, maxHeightDiff: number, xDirection = 0): {point: Vector, fell: false}|{fell: true, point: null} {
// This needs a rethink, we really want to have it so that the character's "platform" is visualised
// by this algorithm. We want to figure out if we can move left or right, and if not if we're going to fall.

Expand Down Expand Up @@ -250,16 +257,18 @@ export class BitmapTerrain implements IMatterEntity {
};
}

public pointInTerrain(point: Vector, radius: number): Collision[] {
public pointInTerrain(point: Vector2, radius: number): never[] {
// Avoid costly iteration with this one neat trick.
if (!this.bounds.contains(point.x, point.y)) {
return [];
}
return Query.collides(Bodies.circle(point.x, point.y, radius), this.parts);
// TODO: Fix
return [];
//return Query.collides(Bodies.circle(point.x, point.y, radius), this.parts);
}

public registerDamageListener(point: Vector, fn: OnDamage) {
this.registeredDamageFunctions.set(point.x + "," + point.y, fn);
public registerDamageListener(collider: Collider, fn: OnDamage) {
this.registeredDamageFunctions.set(collider.handle, fn);
}

destroy(): void {
Expand Down
6 changes: 3 additions & 3 deletions src/entities/entity.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Vector2 } from "@dimforge/rapier2d";
import { UPDATE_PRIORITY } from "pixi.js";
import { Vector } from "matter-js";

/**
* Base entity which all game objects implement
Expand All @@ -19,12 +19,12 @@ export interface IGameEntity {
*/
export interface IMatterEntity extends IGameEntity {
// TODO: Wrong shape?
explodeHandler?: (point: Vector, radius: number) => void;
explodeHandler?: (point: Vector2, radius: number) => void;
/**
*
* @param other
* @param contactPoint
* @returns True if the collision should stop being processed
*/
onCollision?(other: IMatterEntity, contactPoint: Vector): boolean;
onCollision?(other: IMatterEntity, contactPoint: Vector2): boolean;
}
21 changes: 10 additions & 11 deletions src/entities/explosion.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { Vector } from "matter-js";
import { Container, Graphics, Ticker, UPDATE_PRIORITY } from "pixi.js";
import { Container, Graphics, Point, Ticker, UPDATE_PRIORITY } from "pixi.js";
import { IGameEntity } from "./entity";
import { Sound } from "@pixi/sound";

Expand All @@ -16,32 +15,32 @@ export class Explosion implements IGameEntity {
private timer: number;
private radiusExpandBy: number;
private shrapnel: {
point: Vector,
speed: Vector,
accel: Vector,
point: Point,
speed: Point,
accel: Point,
radius: number,
alpha: number,
kind: "fire"|"pop"
}[] = []

static create(parent: Container, point: Vector, initialRadius: number, shrapnelMin = 8, shrapnelMax = 25) {
static create(parent: Container, point: Point, initialRadius: number, shrapnelMin = 8, shrapnelMax = 25) {
const ent = new Explosion(point, initialRadius, shrapnelMin, shrapnelMax);
parent.addChild(ent.gfx);
return ent;
}

private constructor(point: Vector, private initialRadius: number, shrapnelMin: number, shrapnelMax: number) {
private constructor(point: Point, private initialRadius: number, shrapnelMin: number, shrapnelMax: number) {
for (let index = 0; index < (shrapnelMin + Math.ceil(Math.random() * (shrapnelMax-shrapnelMin))); index++) {
const xSpeed = (Math.random()*7)-3.5;
const kind = Math.random() >= 0.75 ? "fire" : "pop";
this.shrapnel.push({
alpha: 1,
point: Vector.create(),
speed: Vector.create(
point: new Point(),
speed: new Point(
xSpeed,
(Math.random()*0.5)-7,
),
accel: Vector.create(
accel: new Point(
// Invert the accel
-(xSpeed/120),
Math.random(),
Expand All @@ -51,7 +50,7 @@ export class Explosion implements IGameEntity {
})

}
this.gfx = new Graphics({ position: Vector.clone(point)});
this.gfx = new Graphics({ position: point.clone()});
this.timer = Ticker.targetFPMS * this.explosionMs;
this.radiusExpandBy = initialRadius * 0.2;
const soundIndex = Math.floor(Math.random()*Explosion.explosionSounds.length);
Expand Down
36 changes: 20 additions & 16 deletions src/entities/phys/bazookaShell.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,19 @@
import { Container, Graphics, Sprite, Texture } from 'pixi.js';
import grenadePaths from "../../assets/bazooka.svg";
import { Body, Bodies, Vector } from "matter-js";
import { TimedExplosive } from "./timedExplosive";
import { loadSvg } from '../../loadSvg';
import { Game } from '../../game';
import { GameWorld } from '../../world';
import { ColliderDesc, RigidBodyDesc, Vector2, VectorOps } from '@dimforge/rapier2d';

// TODO: This is buggy as all hell.

export class BazookaShell extends TimedExplosive {
public static texture: Texture;
private static bodyVertices = loadSvg(grenadePaths, 50, 1.75, 1.75, Vector.create(0.5, 0.5));
//private static bodyVertices = loadSvg(grenadePaths, 50, 1.75, 1.75, Vector.create(0.5, 0.5));

private readonly force: Vector = Vector.create(0);
private readonly force: Vector2 = VectorOps.zeros();
private readonly gfx = new Graphics();

static async create(parent: Container, gameWorld: GameWorld, position: {x: number, y: number}, initialAngle: number, initialForce: number, wind: number) {
const ent = new BazookaShell(position, await BazookaShell.bodyVertices, initialAngle, gameWorld, initialForce, wind);
const ent = new BazookaShell(position, initialAngle, gameWorld, initialForce, wind);
gameWorld.addBody(ent, ent.body);
parent.addChild(ent.sprite);
parent.addChild(ent.wireframe.renderable);
Expand All @@ -25,19 +22,25 @@ export class BazookaShell extends TimedExplosive {
return ent;
}

private constructor(position: { x: number, y: number }, bodyVerticies: Vector[][], initialAngle: number, gameWorld: GameWorld, initialForce: number, private readonly wind: number) {
const body = Bodies.fromVertices(position.x, position.y, bodyVerticies, {
position,
});
private constructor(position: { x: number, y: number }, initialAngle: number, world: GameWorld, initialForce: number, private readonly wind: number) {
const sprite = new Sprite(BazookaShell.texture);
super(sprite, body, gameWorld, {
const body = world.createRigidBodyCollider(
ColliderDesc.cuboid(sprite.width/2, sprite.height/2),
RigidBodyDesc
.dynamic()
.setTranslation(position.x, position.y)
.setLinvel(initialForce / 10, initialForce / 100)
// TODO: Check
// TODO: Friction
.setAngvel(initialAngle)
.setLinearDamping(0.05)
);

super(sprite, body, world, {
explosionRadius: 100,
explodeOnContact: true,
timerSecs: 30,
});
body.frictionAir = 0.05;
body.angle = initialAngle;
this.force = Vector.create(initialForce / 10, initialForce / 100);
this.sprite.x = position.x;
this.sprite.y = position.y;
this.sprite.scale.set(0.5, 0.5);
Expand All @@ -53,7 +56,8 @@ export class BazookaShell extends TimedExplosive {
// Fix for other angles.
this.force.x *= Math.min(1, dt * 3);
this.force.y *= Math.min(1, dt * 3);
Body.applyForce(this.body, Vector.create(this.body.position.x, this.body.position.y), this.force);
// TODO: Fix
// Body.applyForce(this.body, Vector.create(this.body.position.x, this.body.position.y), this.force);
}

destroy(): void {
Expand Down
13 changes: 7 additions & 6 deletions src/entities/water.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { Body, Bodies } from "matter-js";
import { Container, Filter, Geometry, Mesh, Shader, UPDATE_PRIORITY } from "pixi.js";
import { IGameEntity } from "./entity";
import vertex from '../shaders/water.vert?raw';
import fragment from '../shaders/water.frag?raw';
import { GameWorld } from "../world";
import { GameWorld, RapierPhysicsObject } from "../world";
import { ColliderDesc, RigidBodyDesc } from "@dimforge/rapier2d";

export class Water implements IGameEntity {
public readonly priority: UPDATE_PRIORITY = UPDATE_PRIORITY.LOW;
Expand All @@ -13,10 +13,10 @@ export class Water implements IGameEntity {
return false;
}

private readonly body: Body;
private readonly body: RapierPhysicsObject;
private readonly shader: Shader;

constructor(private readonly width: number, private readonly height: number) {
constructor(private readonly width: number, private readonly height: number, world: GameWorld) {
const indexBuffer = ['a','b'].flatMap((_v, i) => {
i = i * 3;
if (i === 0) {
Expand Down Expand Up @@ -52,7 +52,8 @@ export class Water implements IGameEntity {
}
}
});
this.body = Bodies.rectangle(width/2,height-60,width,100, { isStatic: true });
// TODO: Potentially optimise into a polyline?
this.body = world.createRigidBodyCollider(ColliderDesc.cuboid(width/2, 100), RigidBodyDesc.fixed().setTranslation(width/2, height-60))
this.waterMesh = new Mesh({
geometry: this.geometry,
shader: this.shader,
Expand All @@ -65,7 +66,7 @@ export class Water implements IGameEntity {

async create(parent: Container, world: GameWorld) {
parent.addChild(this.waterMesh);
world.addBody(this, this.body);
world.addBody(this, this.body.collider);
}

update(): void {
Expand Down
Loading

0 comments on commit 65d1a4b

Please sign in to comment.