diff --git a/CHANGELOG.md b/CHANGELOG.md
index 2e8313b0..4c64aa5c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -70,6 +70,12 @@ This project adheres to [Semantic Versioning](http://semver.org/).
### Fixed
+- Fixed issue with `ex.ArcadeSolver` based collisions where colliders were catching on seams when sliding along a floor of multiple colliders. This was by sorting contacts by distance between bodies.
+ 
+
+- Fixed issue with `ex.ArcadeSolver` where corner contacts would zero out velocity even if the bodies were already moving away from the contact "divergent contacts".
+ 
+
- Fixed issue where `ex.Sound` wasn't being paused when the browser window lost focus
### Updates
diff --git a/sandbox/tests/side-collision/index.html b/sandbox/tests/side-collision/index.html
new file mode 100644
index 00000000..be7b8895
--- /dev/null
+++ b/sandbox/tests/side-collision/index.html
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+ Side Collision
+
+
+
+
+
+
\ No newline at end of file
diff --git a/sandbox/tests/side-collision/index.ts b/sandbox/tests/side-collision/index.ts
new file mode 100644
index 00000000..d54847e1
--- /dev/null
+++ b/sandbox/tests/side-collision/index.ts
@@ -0,0 +1,89 @@
+
+var game = new ex.Engine({
+ width: 400,
+ height: 400,
+ displayMode: ex.DisplayMode.FitScreenAndFill
+});
+
+ex.Physics.acc = new ex.Vector(0, 800);
+
+class Player2 extends ex.Actor {
+ onGround = false;
+ constructor() {
+ super({
+ x: 100,
+ y: 100,
+ width: 40,
+ height: 40,
+ collisionType: ex.CollisionType.Active,
+ color: ex.Color.Red
+ });
+
+
+ }
+
+ onInitialize() {
+ // onPostCollision is an event, not a lifecycle meaning it can be subscribed to by other things
+ this.on("postcollision", (evt) => this.onPostCollision(evt));
+ }
+
+ onPostCollision(evt) {
+ if (evt.side === "Left" || evt.side === "Right") {
+ console.log(evt.side);
+ }
+
+ if (evt.side === ex.Side.Bottom) {
+ this.onGround = true;
+ }
+ }
+
+ // After main update, once per frame execute this code
+ onPreUpdate(engine) {
+ // Reset x velocity
+ // this.vel.x = 0;
+
+ // Player input
+ if (engine.input.keyboard.isHeld(ex.Input.Keys.Left)) {
+ this.vel.x = -150;
+ }
+
+ if (engine.input.keyboard.isHeld(ex.Input.Keys.Right)) {
+ this.vel.x = 150;
+ }
+
+ if (engine.input.keyboard.isHeld(ex.Input.Keys.Up) && this.onGround) {
+ this.vel.y = -400;
+ this.onGround = false;
+ }
+ }
+}
+
+// many small tiles
+for (let x = 0; x < 500; x += 16) {
+ game.add(
+ new ex.Actor({
+ x,
+ y: 300,
+ width: 16,
+ height: 16,
+ color: ex.Color.Green,
+ collisionType: ex.CollisionType.Fixed
+ })
+ );
+}
+
+// single large floor
+game.add(
+ new ex.Actor({
+ x: 300,
+ y: 220,
+ width: 200,
+ height: 16,
+ color: ex.Color.Green,
+ collisionType: ex.CollisionType.Fixed
+ })
+);
+
+game.add(new Player2());
+
+game.start();
diff --git a/sandbox/tests/side-collision2/index.html b/sandbox/tests/side-collision2/index.html
new file mode 100644
index 00000000..be7b8895
--- /dev/null
+++ b/sandbox/tests/side-collision2/index.html
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+ Side Collision
+
+
+
+
+
+
\ No newline at end of file
diff --git a/sandbox/tests/side-collision2/index.ts b/sandbox/tests/side-collision2/index.ts
new file mode 100644
index 00000000..76619740
--- /dev/null
+++ b/sandbox/tests/side-collision2/index.ts
@@ -0,0 +1,77 @@
+
+var game = new ex.Engine({
+ width: 400,
+ height: 400,
+ displayMode: ex.DisplayMode.FitScreenAndFill
+});
+
+ex.Physics.acc = new ex.Vector(0, 800);
+
+class Player3 extends ex.Actor {
+ onGround = false;
+ constructor() {
+ super({
+ x: 100,
+ y: 100,
+ width: 40,
+ height: 40,
+ collisionType: ex.CollisionType.Active,
+ color: ex.Color.Red
+ });
+ }
+
+ onInitialize() {
+ // onPostCollision is an event, not a lifecycle meaning it can be subscribed to by other things
+ this.on("postcollision", (evt) => this.onPostCollision(evt));
+ }
+
+ onPostCollision(evt) {
+ if (evt.side === ex.Side.Bottom) {
+ this.onGround = true;
+ }
+ }
+
+ // After main update, once per frame execute this code
+ onPreUpdate(engine) {
+ // Reset x velocity
+ this.vel.x = 0;
+
+ // Player input
+ if (engine.input.keyboard.isHeld(ex.Input.Keys.Left)) {
+ this.vel.x = -150;
+ }
+
+ if (engine.input.keyboard.isHeld(ex.Input.Keys.Right)) {
+ this.vel.x = 150;
+ }
+
+ if (engine.input.keyboard.isHeld(ex.Input.Keys.Up) && this.onGround) {
+ this.vel.y = -400;
+ this.onGround = false;
+ }
+ }
+}
+
+const objects = [
+ new ex.Actor({
+ x: 200,
+ y: 300,
+ width: 400,
+ height: 20,
+ color: ex.Color.Green,
+ collisionType: ex.CollisionType.Fixed
+ }),
+ new ex.Actor({
+ x: 300,
+ y: 260,
+ width: 200,
+ height: 60,
+ color: ex.Color.Green,
+ collisionType: ex.CollisionType.Fixed
+ }),
+ new Player3()
+];
+
+objects.forEach((i) => game.add(i));
+
+game.start();
diff --git a/sandbox/tests/sound/sound.ts b/sandbox/tests/sound/sound.ts
index b35bf46d..171cbd78 100644
--- a/sandbox/tests/sound/sound.ts
+++ b/sandbox/tests/sound/sound.ts
@@ -1,9 +1,11 @@
-
+ex.Physics.enabled = false;
var game = new ex.Engine({
width: 600,
height: 400
});
+
+
var sound = new ex.Sound('./preview.ogg');
sound.playbackRate = 2.0;
var loader = new ex.Loader([
@@ -37,7 +39,8 @@ var play = new ex.Actor({
y: 200,
width: 40,
height: 40,
- color: ex.Color.Green
+ color: ex.Color.Green,
+ collisionType: ex.CollisionType.PreventCollision
});
play.on('pointerdown', () => {
sound.play();
diff --git a/src/engine/Collision/Solver/ArcadeSolver.ts b/src/engine/Collision/Solver/ArcadeSolver.ts
index e6743286..d0fc9a6e 100644
--- a/src/engine/Collision/Solver/ArcadeSolver.ts
+++ b/src/engine/Collision/Solver/ArcadeSolver.ts
@@ -12,11 +12,48 @@ import { BodyComponent } from '../BodyComponent';
* This is usually the type of collisions used for 2D games that don't need a more realistic collision simulation.
*
*/
-export class ArcadeSolver extends CollisionSolver {
+export class ArcadeSolver implements CollisionSolver {
+ directionMap = new Map();
+ distanceMap = new Map();
+
+ public solve(contacts: CollisionContact[]): CollisionContact[] {
+ // Events and init
+ this.preSolve(contacts);
+
+ // Remove any canceled contacts
+ contacts = contacts.filter(c => !c.isCanceled());
+
+ // Sort contacts by distance to avoid artifacts with seams
+ // It's important to solve in a specific order
+ contacts.sort((a, b) => {
+ const aDist = this.distanceMap.get(a.id);
+ const bDist = this.distanceMap.get(b.id);
+ return aDist - bDist;
+ });
+
+ for (const contact of contacts) {
+ // Solve position first in arcade
+ this.solvePosition(contact);
+
+ // Solve velocity first
+ this.solveVelocity(contact);
+ }
+
+ // Events and any contact house-keeping the solver needs
+ this.postSolve(contacts);
+
+ return contacts;
+ }
+
public preSolve(contacts: CollisionContact[]) {
+
for (const contact of contacts) {
const side = Side.fromDirection(contact.mtv);
const mtv = contact.mtv.negate();
+
+ const distance = contact.colliderA.worldPos.squareDistance(contact.colliderB.worldPos);
+ this.distanceMap.set(contact.id, distance);
+
// Publish collision events on both participants
contact.colliderA.events.emit('precollision', new PreCollisionEvent(contact.colliderA, contact.colliderB, side, mtv));
contact.colliderB.events.emit(
@@ -28,6 +65,9 @@ export class ArcadeSolver extends CollisionSolver {
public postSolve(contacts: CollisionContact[]) {
for (const contact of contacts) {
+ if (contact.isCanceled()) {
+ continue;
+ }
const colliderA = contact.colliderA;
const colliderB = contact.colliderB;
const bodyA = colliderA.owner?.get(BodyComponent);
@@ -37,6 +77,7 @@ export class ArcadeSolver extends CollisionSolver {
continue;
}
}
+
const side = Side.fromDirection(contact.mtv);
const mtv = contact.mtv.negate();
// Publish collision events on both participants
@@ -48,69 +89,83 @@ export class ArcadeSolver extends CollisionSolver {
}
}
- public solvePosition(contacts: CollisionContact[]) {
- for (const contact of contacts) {
- // if bounds no longer interesect skip to the next
- // this removes jitter from overlapping/stacked solid tiles or a wall of solid tiles
- if (!contact.colliderA.bounds.overlaps(contact.colliderB.bounds)) {
- continue;
+ public solvePosition(contact: CollisionContact) {
+ // if bounds no longer intersect skip to the next
+ // this removes jitter from overlapping/stacked solid tiles or a wall of solid tiles
+ if (!contact.colliderA.bounds.overlaps(contact.colliderB.bounds)) {
+ // Cancel the contact to prevent and solving
+ contact.cancel();
+ return;
+ }
+ let mtv = contact.mtv;
+ const colliderA = contact.colliderA;
+ const colliderB = contact.colliderB;
+ const bodyA = colliderA.owner?.get(BodyComponent);
+ const bodyB = colliderB.owner?.get(BodyComponent);
+ if (bodyA && bodyB) {
+ if (bodyA.collisionType === CollisionType.Passive || bodyB.collisionType === CollisionType.Passive) {
+ return;
}
- let mtv = contact.mtv;
- const colliderA = contact.colliderA;
- const colliderB = contact.colliderB;
- const bodyA = colliderA.owner?.get(BodyComponent);
- const bodyB = colliderB.owner?.get(BodyComponent);
- if (bodyA && bodyB) {
- if (bodyA.collisionType === CollisionType.Passive || bodyB.collisionType === CollisionType.Passive) {
- continue;
- }
- if (bodyA.collisionType === CollisionType.Active && bodyB.collisionType === CollisionType.Active) {
- // split overlaps if both are Active
- mtv = mtv.scale(0.5);
- }
+ if (bodyA.collisionType === CollisionType.Active && bodyB.collisionType === CollisionType.Active) {
+ // split overlaps if both are Active
+ mtv = mtv.scale(0.5);
+ }
- // Resolve overlaps
- if (bodyA.collisionType === CollisionType.Active) {
- bodyA.pos.x -= mtv.x;
- bodyA.pos.y -= mtv.y;
- colliderA.update(bodyA.transform);
- }
+ // Resolve overlaps
+ if (bodyA.collisionType === CollisionType.Active) {
+ bodyA.pos.x -= mtv.x;
+ bodyA.pos.y -= mtv.y;
+ colliderA.update(bodyA.transform);
+ }
- if (bodyB.collisionType === CollisionType.Active) {
- bodyB.pos.x += mtv.x;
- bodyB.pos.y += mtv.y;
- colliderB.update(bodyB.transform);
- }
+ if (bodyB.collisionType === CollisionType.Active) {
+ bodyB.pos.x += mtv.x;
+ bodyB.pos.y += mtv.y;
+ colliderB.update(bodyB.transform);
}
}
}
- public solveVelocity(contacts: CollisionContact[]) {
- for (const contact of contacts) {
- const colliderA = contact.colliderA;
- const colliderB = contact.colliderB;
- const bodyA = colliderA.owner?.get(BodyComponent);
- const bodyB = colliderB.owner?.get(BodyComponent);
+ public solveVelocity(contact: CollisionContact) {
+ if (contact.isCanceled()) {
+ return;
+ }
- if (bodyA && bodyB) {
+ const colliderA = contact.colliderA;
+ const colliderB = contact.colliderB;
+ const bodyA = colliderA.owner?.get(BodyComponent);
+ const bodyB = colliderB.owner?.get(BodyComponent);
- if (bodyA.collisionType === CollisionType.Passive || bodyB.collisionType === CollisionType.Passive) {
- continue;
- }
+ if (bodyA && bodyB) {
- const normal = contact.normal;
- const opposite = normal.negate();
+ if (bodyA.collisionType === CollisionType.Passive || bodyB.collisionType === CollisionType.Passive) {
+ return;
+ }
- // Cancel out velocity opposite direction of collision normal
- if (bodyA.collisionType === CollisionType.Active) {
+ const normal = contact.normal;
+ const opposite = normal.negate();
+
+ if (bodyA.collisionType === CollisionType.Active) {
+ // only adjust velocity if the contact normal is opposite to the current velocity
+ // this avoids catching edges on a platform when sliding off
+ if (bodyA.vel.normalize().dot(opposite) < 0) {
+ // Cancel out velocity opposite direction of collision normal
const velAdj = normal.scale(normal.dot(bodyA.vel.negate()));
bodyA.vel = bodyA.vel.add(velAdj);
+ } else {
+ contact.cancel();
}
+ }
- if (bodyB.collisionType === CollisionType.Active) {
+ if (bodyB.collisionType === CollisionType.Active) {
+ // only adjust velocity if the contact normal is opposite to the current velocity
+ // this avoids catching edges on a platform
+ if (bodyB.vel.normalize().dot(normal) < 0) {
const velAdj = opposite.scale(opposite.dot(bodyB.vel.negate()));
bodyB.vel = bodyB.vel.add(velAdj);
+ } else {
+ contact.cancel();
}
}
}
diff --git a/src/engine/Collision/Solver/RealisticSolver.ts b/src/engine/Collision/Solver/RealisticSolver.ts
index 5ca2101a..37405a7a 100644
--- a/src/engine/Collision/Solver/RealisticSolver.ts
+++ b/src/engine/Collision/Solver/RealisticSolver.ts
@@ -9,7 +9,7 @@ import { CollisionSolver } from './Solver';
import { BodyComponent } from '../BodyComponent';
import { CollisionJumpTable } from '../Colliders/CollisionJumpTable';
-export class RealisticSolver extends CollisionSolver {
+export class RealisticSolver implements CollisionSolver {
lastFrameContacts: Map = new Map();
// map contact id to contact points
@@ -19,6 +19,25 @@ export class RealisticSolver extends CollisionSolver {
return this.idToContactConstraint.get(id) ?? [];
}
+ public solve(contacts: CollisionContact[]): CollisionContact[] {
+ // Events and init
+ this.preSolve(contacts);
+
+ // Remove any canceled contacts
+ contacts = contacts.filter(c => !c.isCanceled());
+
+ // Solve velocity first
+ this.solveVelocity(contacts);
+
+ // Solve position last because non-overlap is the most important
+ this.solvePosition(contacts);
+
+ // Events and any contact house-keeping the solver needs
+ this.postSolve(contacts);
+
+ return contacts;
+ }
+
preSolve(contacts: CollisionContact[]) {
for (const contact of contacts) {
// Publish collision events on both participants
diff --git a/src/engine/Collision/Solver/Solver.ts b/src/engine/Collision/Solver/Solver.ts
index 94c72921..817dd9ce 100644
--- a/src/engine/Collision/Solver/Solver.ts
+++ b/src/engine/Collision/Solver/Solver.ts
@@ -11,35 +11,35 @@ import { CollisionContact } from '../Detection/CollisionContact';
* 4. postSolve
* @inheritdoc
*/
-export abstract class CollisionSolver {
- /**
- * Pre-solve is fired on contacts before any resolution is started. It is used for any setup work before collision resolution
- * can proceed. Optionally contacts can be "opted out" of any collision processing by calling `contact.cancel()`
- * @param contacts
- * @inheritdoc
- */
- abstract preSolve(contacts: CollisionContact[]): void;
-
- /**
- * Post-solve is fired after all resolution is complete
- * @param contacts
- * @inheritdoc
- */
- abstract postSolve(contacts: CollisionContact[]): void;
-
- /**
- * Solve velocity adjusts the velocity of colliders so that they are not overlapping or will not be overlapping from velocity
- * @param contacts
- * @inheritdoc
- */
- abstract solveVelocity(contacts: CollisionContact[]): void;
-
- /**
- * Solve position adjust the position of colliders so that they are not overlapping
- * @param contacts
- * @inheritdoc
- */
- abstract solvePosition(contacts: CollisionContact[]): void;
+export interface CollisionSolver {
+ // /**
+ // * Pre-solve is fired on contacts before any resolution is started. It is used for any setup work before collision resolution
+ // * can proceed. Optionally contacts can be "opted out" of any collision processing by calling `contact.cancel()`
+ // * @param contacts
+ // * @inheritdoc
+ // */
+ // abstract preSolve(contacts: CollisionContact[]): void;
+
+ // /**
+ // * Post-solve is fired after all resolution is complete
+ // * @param contacts
+ // * @inheritdoc
+ // */
+ // abstract postSolve(contacts: CollisionContact[]): void;
+
+ // /**
+ // * Solve velocity adjusts the velocity of colliders so that they are not overlapping or will not be overlapping from velocity
+ // * @param contacts
+ // * @inheritdoc
+ // */
+ // abstract solveVelocity(contacts: CollisionContact[]): void;
+
+ // /**
+ // * Solve position adjust the position of colliders so that they are not overlapping
+ // * @param contacts
+ // * @inheritdoc
+ // */
+ // abstract solvePosition(contacts: CollisionContact[]): void;
/**
* Solves overlapping contact in
@@ -51,22 +51,5 @@ export abstract class CollisionSolver {
* 4. postSolve
* @param contacts
*/
- public solve(contacts: CollisionContact[]): CollisionContact[] {
- // Events and init
- this.preSolve(contacts);
-
- // Remove any canceled contacts
- contacts = contacts.filter(c => !c.isCanceled());
-
- // Solve velocity first
- this.solveVelocity(contacts);
-
- // Solve position last because non-overlap is the most important
- this.solvePosition(contacts);
-
- // Events and any contact house-keeping the solver needs
- this.postSolve(contacts);
-
- return contacts;
- }
+ solve(contacts: CollisionContact[]): CollisionContact[]
}
\ No newline at end of file
diff --git a/src/spec/ArcadeSolverSpec.ts b/src/spec/ArcadeSolverSpec.ts
index 6112eeaf..887aeb1a 100644
--- a/src/spec/ArcadeSolverSpec.ts
+++ b/src/spec/ArcadeSolverSpec.ts
@@ -1,5 +1,6 @@
import * as ex from '@excalibur';
import { ExcaliburMatchers } from 'excalibur-jasmine';
+import { TestUtils } from './util/TestUtils';
describe('An ArcadeSolver', () => {
beforeAll(() => {
@@ -25,7 +26,9 @@ describe('An ArcadeSolver', () => {
const sut = new ex.ArcadeSolver();
- sut.solvePosition(contacts);
+ for (const contact of contacts) {
+ sut.solvePosition(contact);
+ }
// Each contact has the same mtv
expect(contacts[0].mtv).toBeVector(ex.vec(0, -1));
@@ -35,4 +38,130 @@ describe('An ArcadeSolver', () => {
expect(player.pos.y).toBe(100);
expect(player.pos.x).toBe(50);
});
+
+ it('should not catch on a seam (left/right)', async () => {
+ ex.Physics.acc = new ex.Vector(0, 800);
+ const game = TestUtils.engine({
+ width: 1000,
+ height: 1000
+ });
+ const clock = game.clock as ex.TestClock;
+ await TestUtils.runToReady(game);
+ // many small tiles
+ for (let x = 0; x < 500; x += 16) {
+ game.add(
+ new ex.Actor({
+ name: 'floor-tile',
+ x,
+ y: 300,
+ width: 16,
+ height: 16,
+ color: ex.Color.Green,
+ collisionType: ex.CollisionType.Fixed
+ })
+ );
+ }
+
+ const player = new ex.Actor({
+ x: 100,
+ y: 100,
+ width: 40,
+ height: 40,
+ collisionType: ex.CollisionType.Active,
+ color: ex.Color.Red
+ });
+
+ // place player on tiles
+ player.pos.y = 270;
+ game.add(player);
+
+ // run simulation and ensure now left/right contacts are generated
+ player.on('postcollision', evt => {
+ expect(evt.side).not.toBe(ex.Side.Left);
+ expect(evt.side).not.toBe(ex.Side.Right);
+ expect(evt.side).toBe(ex.Side.Bottom);
+ });
+
+ for (let i = 0; i < 200; i++) {
+ clock.step(16);
+ }
+
+ // give player right velocity
+ ex.Physics.acc = ex.Vector.Zero;
+ });
+
+ it('should cancel collision contacts where there is no more overlap', () => {
+ const arcadeSolver = new ex.ArcadeSolver();
+
+ const player = new ex.Actor({
+ x: 0,
+ y: 0,
+ width: 40,
+ height: 40,
+ collisionType: ex.CollisionType.Active,
+ color: ex.Color.Red
+ });
+ player.vel = ex.vec(0, 10); // moving away from the contact
+
+ const block = new ex.Actor({
+ x: 0,
+ y: 39,
+ width: 40,
+ height: 40,
+ collisionType: ex.CollisionType.Fixed,
+ color: ex.Color.Green
+ });
+
+ // 1 pixel overlap
+ const contact = new ex.CollisionContact(
+ player.collider.get(), block.collider.get(), ex.Vector.Down, ex.Vector.Down, ex.Vector.Down.perpendicular(), [], [], null);
+ arcadeSolver.solvePosition(contact);
+ expect(player.pos).toBeVector(ex.vec(0, -1));
+
+ // No more overlap
+ const contact2 = new ex.CollisionContact(
+ player.collider.get(), block.collider.get(), ex.Vector.Down, ex.Vector.Down, ex.Vector.Down.perpendicular(), [], [], null);
+ arcadeSolver.solvePosition(contact2);
+ expect(contact2.isCanceled()).toBeTrue();
+ });
+
+ it('should cancel collisions where the bodies are moving away from the contact', () => {
+
+ const arcadeSolver = new ex.ArcadeSolver();
+
+ const player = new ex.Actor({
+ x: 0,
+ y: 0,
+ width: 40,
+ height: 40,
+ collisionType: ex.CollisionType.Active,
+ color: ex.Color.Red
+ });
+ player.vel = ex.vec(0, -10); // moving away from the contact
+
+ const block = new ex.Actor({
+ x: 39,
+ y: 39,
+ width: 40,
+ height: 40,
+ collisionType: ex.CollisionType.Fixed,
+ color: ex.Color.Green
+ });
+
+ // Player moving away from contact
+ const contact = new ex.CollisionContact(
+ player.collider.get(), block.collider.get(), ex.Vector.Down, ex.Vector.Down, ex.Vector.Up.perpendicular(), [], [], null);
+ arcadeSolver.solveVelocity(contact);
+ expect(contact.isCanceled()).toBeTrue();
+ expect(player.vel).toBeVector(ex.vec(0, -10));
+
+ // Player moving towards contact
+ player.vel = player.vel.negate();
+ const contact2 = new ex.CollisionContact(
+ player.collider.get(), block.collider.get(), ex.Vector.Down, ex.Vector.Down, ex.Vector.Down.perpendicular(), [], [], null);
+
+ arcadeSolver.solveVelocity(contact2);
+ expect(contact2.isCanceled()).toBeFalse();
+ expect(player.vel).toBeVector(ex.Vector.Zero);
+ });
});
\ No newline at end of file
diff --git a/src/spec/CollisionSpec.ts b/src/spec/CollisionSpec.ts
index 707e1d9b..9b31c16f 100644
--- a/src/spec/CollisionSpec.ts
+++ b/src/spec/CollisionSpec.ts
@@ -270,17 +270,17 @@ describe('A Collision', () => {
it('should cancel out velocity when objects collide', () => {
ex.Physics.collisionResolutionStrategy = ex.CollisionResolutionStrategy.Arcade;
-
- const activeBlock = new ex.Actor({x: 200, y: 200, width: 50, height: 50, color: ex.Color.Red.clone()});
+ engine.currentScene.clear();
+ const activeBlock = new ex.Actor({name: 'active-block', x: 200, y: 200, width: 50, height: 50, color: ex.Color.Red.clone()});
activeBlock.body.collisionType = ex.CollisionType.Active;
activeBlock.vel.x = 100;
engine.add(activeBlock);
- const fixedBlock = new ex.Actor({x: 400, y: 200, width: 50, height: 50, color: ex.Color.DarkGray.clone()});
+ const fixedBlock = new ex.Actor({name: 'fixed-block', x: 400, y: 200, width: 50, height: 50, color: ex.Color.DarkGray.clone()});
fixedBlock.body.collisionType = ex.CollisionType.Fixed;
engine.add(fixedBlock);
- clock.run(15, 100);
+ clock.run(25, 100);
expect(activeBlock.vel.x).toBe(0);
});