Skip to content

Commit

Permalink
feat: expose platform effector (#413)
Browse files Browse the repository at this point in the history
  • Loading branch information
dragoncoder047 authored Oct 5, 2024
1 parent 4b051ae commit 96c2e0b
Show file tree
Hide file tree
Showing 5 changed files with 210 additions and 14 deletions.
75 changes: 75 additions & 0 deletions examples/platformBox.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
// @ts-check

kaplay();

loadSprite("bean", "/sprites/bean.png");
loadSprite("coin", "/sprites/coin.png");
loadSprite("grass", "/sprites/grass.png");

const SPEED = 480;

setGravity(2400);

const level = addLevel([
"",
"= @ $$ =",
"==========",
], {
tileWidth: 64,
tileHeight: 64,
// Define what each symbol means (in components)
tiles: {
"@": () => [
sprite("bean"),
area(),
body(),
anchor("bot"),
z(2),
"player",
],
"=": () => [
sprite("grass"),
area(),
body({ isStatic: true }),
anchor("bot"),
],
"$": () => [
sprite("coin"),
area(),
body(),
anchor("bot"),
platformEffector({
shouldCollide(obj, normal) {
if (obj !== player) return true;
// Let the player push them if they hold shift
if (isKeyDown("shift")) return true;
if (normal.sub(LEFT).len() < Number.EPSILON) return false;
if (normal.sub(RIGHT).len() < Number.EPSILON) return false;
return true;
}
}),
],
},
});

// Get the player object from tag
const player = level.get("player")[0];

// Movements
onKeyPress("space", () => {
if (player.isGrounded()) {
player.jump(900);
}
});

onKeyDown("left", () => {
player.move(-SPEED, 0);
});

onKeyDown("right", () => {
player.move(SPEED, 0);
});

onUpdate(() => {
camPos(player.worldPos());
});
84 changes: 84 additions & 0 deletions examples/platformEffector.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
// @ts-check

kaplay();

loadSprite("bean", "/sprites/bean.png");
loadSprite("steel", "/sprites/steel.png");
loadSprite("grass", "/sprites/grass.png");

const SPEED = 480;

setGravity(2400);

const level = addLevel([
" ##",
"",
" ##",
"",
" ##",
"",
" ##",
"",
"====##====",
"",
"= @ ## =",
"==========",
], {
tileWidth: 64,
tileHeight: 64,
// Define what each symbol means (in components)
tiles: {
"@": () => [
sprite("bean"),
area(),
body(),
anchor("bot"),
z(2),
"player",
],
"=": () => [
sprite("grass"),
area(),
body({ isStatic: true }),
anchor("bot"),
],
"#": () => [
sprite("steel"),
area(),
body({ isStatic: true }),
anchor("bot"),
platformEffector(),
],
},
});

// Get the player object from tag
const player = level.get("player")[0];

// Always look at player
onUpdate(() => {
camPos(player.worldPos());
});

// Movements
onKeyPress("space", () => {
if (player.isGrounded()) {
player.jump(900);
}
});

onKeyDown("left", () => {
player.move(-SPEED, 0);
});

onKeyDown("right", () => {
player.move(SPEED, 0);
});

// Fall through when down is pressed
onKeyDown("down", () => {
const p = player.curPlatform();
if (p != null && p.is("platformEffector")) {
p.platformIgnore.add(player);
}
});
52 changes: 38 additions & 14 deletions src/components/physics/effectors.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { getGravityDirection } from "../../game";
import { Polygon, Vec2, vec2 } from "../../math";
import type { Comp, GameObj } from "../../types";
import type { PosComp } from "../transform";
Expand Down Expand Up @@ -117,8 +116,8 @@ export function pointEffector(opts: PointEffectorCompOpt): PointEffectorComp {
const forceScale = this.forceMode === "constant"
? 1
: this.forceMode === "inverseLinear"
? 1 / distance
: 1 / distance ** 2;
? 1 / distance
: 1 / distance ** 2;
const force = dir.scale(
this.forceMagnitude * forceScale / length,
);
Expand Down Expand Up @@ -153,31 +152,56 @@ export function constantForce(opts: ConstantForceCompOpt): ConstantForceComp {
}

export type PlatformEffectorCompOpt = {
surfaceArc?: number;
useOneWay?: boolean;
/**
* If the object is about to collide and the collision normal direction is
* in here, the object won't collide.
*
* Should be a list of unit vectors `LEFT`, `RIGHT`, `UP`, or `DOWN`.
*/
ignoreSides?: Vec2[]
/**
* A function that determines whether the object should collide.
*
* If present, it overrides the `ignoreSides`; if absent, it is
* automatically created from `ignoreSides`.
*/
shouldCollide?: (obj: GameObj, normal: Vec2) => boolean
};

export interface PlatformEffectorComp extends Comp {
surfaceArc: number;
useOneWay: boolean;
/**
* A set of the objects that should not collide with this, because `shouldCollide` returned true.
*
* Objects in here are automatically removed when they stop colliding, so the casual user shouldn't
* need to touch this much. However, if an object is added to this set before the object collides
* with the platform effector, it won't collide even if `shouldCollide` returns true.
*/
platformIgnore: Set<GameObj>;
}

export function platformEffector(
opt: PlatformEffectorCompOpt,
opt: PlatformEffectorCompOpt = {},
): PlatformEffectorComp {
opt.ignoreSides ??= [Vec2.UP];
opt.shouldCollide ??= (_, normal) => {
return opt.ignoreSides?.findIndex(s => s.sdist(normal) < Number.EPSILON) == -1;
}
return {
id: "platformEffector",
require: ["area", "body"],
surfaceArc: opt.surfaceArc ?? 180,
useOneWay: opt.useOneWay ?? false,
platformIgnore: new Set<GameObj>(),
add(this: GameObj<BodyComp | AreaComp | PlatformEffectorComp>) {
this.onBeforePhysicsResolve(collision => {
const v = collision.target.vel;
const up = getGravityDirection().scale(-1);
const angle = up.angleBetween(v);
if (Math.abs(angle) > this.surfaceArc / 2) {
if (this.platformIgnore.has(collision.target)) {
collision.preventResolution();
}
else if (!opt.shouldCollide!(collision.target, collision.normal)) {
collision.preventResolution();
this.platformIgnore.add(collision.target);
}
});
this.onCollideEnd(obj => {
this.platformIgnore.delete(obj);
});
},
};
Expand Down
2 changes: 2 additions & 0 deletions src/kaplay.ts
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,7 @@ import {
particles,
patrol,
pointEffector,
platformEffector,
polygon,
pos,
raycast,
Expand Down Expand Up @@ -1102,6 +1103,7 @@ const kaplay = <
areaEffector,
pointEffector,
buoyancyEffector,
platformEffector,
constantForce,
doubleJump,
shader,
Expand Down
11 changes: 11 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ import type {
OutlineComp,
PatrolComp,
PatrolCompOpt,
PlatformEffectorComp,
PlatformEffectorCompOpt,
PointEffectorComp,
PointEffectorCompOpt,
PolygonComp,
Expand Down Expand Up @@ -693,6 +695,15 @@ export interface KAPLAYCtx<
* @group Components
*/
pointEffector(options: PointEffectorCompOpt): PointEffectorComp;
/**
* The platform effector makes it easier to implement one way platforms
* or walls. This effector is typically used with a static body, and it
* will only be solid depending on the direction the object is traveling from.
*
* @since v3001.0
* @group Components
*/
platformEffector(options?: PlatformEffectorCompOpt): PlatformEffectorComp;
/**
* Applies an upwards force (force against gravity) to colliding objects depending on the fluid density and submerged area.
* Good to apply constant thrust.
Expand Down

0 comments on commit 96c2e0b

Please sign in to comment.