From 5d581131cbce2ca16bb9ca5f9bdb10330bb2551e Mon Sep 17 00:00:00 2001 From: Erik Onarheim Date: Fri, 10 Jun 2022 08:25:36 -0500 Subject: [PATCH] fix: [#2344] [#2343] Arcade Solver sorts contacts by distance and ignores diverging contacts (#2345) Closes #2344 #2343 This PR changes how the `ArcadeSolver` solves collisions to avoid errant collisions on seams in geometry. The idea behind this fix is to sort the collision contacts by distance. Additionally if a contact has no more overlap during processing they are now discarded so errant collisions are even evaluated. Note there are no more left right collisions in the console compared to the [sandbox](https://8wf42u.csb.app/) ![sorted-collisions](https://user-images.githubusercontent.com/612071/172401390-9e9c3490-3566-47bf-b258-6a7da86a3464.gif) This PR also fixes #2343 by ignoring "divergent contacts" which are when the underlying body's are already moving away from the contact, so no velocity adjustment is needed. This was noticeable when clipping corners, but the object was already moving out of the collision. ![cancel-velocity-fix](https://user-images.githubusercontent.com/612071/172500318-539f3a36-31ae-4efc-b6ab-c4524b297adb.gif) TODO: * [x] Look into #2343 to see if this helps * [x] Also sort by collider proximity * [x] Tests ## Changes: - Change the `Solver` from abstract class to an interface to be more flexible about solvers - Sorts contacts by body distance from each other - Cancels "divergent contacts" when the bodies are moving away from the contact --- CHANGELOG.md | 6 + sandbox/tests/side-collision/index.html | 13 ++ sandbox/tests/side-collision/index.ts | 89 +++++++++++ sandbox/tests/side-collision2/index.html | 13 ++ sandbox/tests/side-collision2/index.ts | 77 +++++++++ sandbox/tests/sound/sound.ts | 7 +- src/engine/Collision/Solver/ArcadeSolver.ts | 147 ++++++++++++------ .../Collision/Solver/RealisticSolver.ts | 21 ++- src/engine/Collision/Solver/Solver.ts | 77 ++++----- src/spec/ArcadeSolverSpec.ts | 131 +++++++++++++++- src/spec/CollisionSpec.ts | 8 +- 11 files changed, 488 insertions(+), 101 deletions(-) create mode 100644 sandbox/tests/side-collision/index.html create mode 100644 sandbox/tests/side-collision/index.ts create mode 100644 sandbox/tests/side-collision2/index.html create mode 100644 sandbox/tests/side-collision2/index.ts 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. + ![sorted-collisions](https://user-images.githubusercontent.com/612071/172401390-9e9c3490-3566-47bf-b258-6a7da86a3464.gif) + +- 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". + ![cancel-velocity-fix](https://user-images.githubusercontent.com/612071/172500318-539f3a36-31ae-4efc-b6ab-c4524b297adb.gif) + - 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); }); -- 2.51.2