Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[#643] Fix Actor updates happening more than once per frame #655

Merged
merged 8 commits into from
Oct 4, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ This project adheres to [Semantic Versioning](http://semver.org/).
- `Actor.debugDraw` now works properly for child actors ([#505](https://github.com/excaliburjs/Excalibur/issues/505), [#645](https://github.com/excaliburjs/Excalibur/issues/645))
- Sprite culling was double scaling calculations ([#646](https://github.com/excaliburjs/Excalibur/issues/646))
- Fix negative zoom sprite culling ([#539](https://github.com/excaliburjs/Excalibur/issues/539))
- Fix Actor updates happening more than once per frame, causing multiple pointer events to trigger ([#643](https://github.com/excaliburjs/Excalibur/issues/643))
- Fix `Actor.on('pointerup')` capturePointer events opt-in on event handler. The opt-in was triggering correctly for handlers on 'pointerdown' and 'pointermove', but not 'pointerup'.

## [0.7.0] - 2016-08-29
### Breaking Changes
Expand Down
1 change: 0 additions & 1 deletion sandbox/web/src/game.js
Original file line number Diff line number Diff line change
Expand Up @@ -300,7 +300,6 @@ player.on('postupdate', function (data) {
data.target.acc.y = 800; // * data.delta/1000;
}
else {
data.target.acc.y = 0;
}
// Reset values because we don't know until we check the next update
// inAir = true;
Expand Down
2 changes: 1 addition & 1 deletion sandbox/web/src/game.ts
Original file line number Diff line number Diff line change
Expand Up @@ -353,7 +353,7 @@ player.on('postupdate', (data?: ex.PostUpdateEvent) => {
if (!isColliding) {
data.target.acc.y = 800;// * data.delta/1000;
} else {
data.target.acc.y = 0;
//data.target.acc.y = 0;
}

// Reset values because we don't know until we check the next update
Expand Down
38 changes: 36 additions & 2 deletions src/engine/Actor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -663,7 +663,7 @@ module ex {
this.opacity = color.a;
}
// Build default pipeline
this.traits.push(new ex.Traits.EulerMovement());
//this.traits.push(new ex.Traits.EulerMovement());
// TODO: TileMaps should be converted to a collision area
this.traits.push(new ex.Traits.TileMapCollisionDetection());
this.traits.push(new ex.Traits.OffscreenCulling());
Expand All @@ -690,7 +690,7 @@ module ex {

private _checkForPointerOptIn(eventName: string) {
if (eventName && (eventName.toLowerCase() === 'pointerdown' ||
eventName.toLowerCase() === 'pointerdown' ||
eventName.toLowerCase() === 'pointerup' ||
eventName.toLowerCase() === 'pointermove')) {
this.enableCapturePointer = true;
if (eventName.toLowerCase() === 'pointermove') {
Expand Down Expand Up @@ -1157,6 +1157,38 @@ module ex {
private _getCalculatedAnchor(): Vector {
return new ex.Vector(this.getWidth() * this.anchor.x, this.getHeight() * this.anchor.y);
}

/**
* Perform euler integration at the specified time step
*/
public integrate(delta: number) {
// Update placements based on linear algebra
var seconds = delta / 1000;

var totalAcc = this.acc.clone();
// Only active vanilla actors are affected by global acceleration
if (this.collisionType === ex.CollisionType.Active &&
!(this instanceof UIActor) &&
!(this instanceof Trigger) &&
!(this instanceof Label)) {
totalAcc.addEqual(ex.Physics.acc);
}

this.oldVel = this.vel;
this.vel.addEqual(totalAcc.scale(seconds));

this.pos.addEqual(this.vel.scale(seconds)).addEqual(totalAcc.scale(0.5 * seconds * seconds));

this.rx += this.torque * (1.0 / this.moi) * seconds;
this.rotation += this.rx * seconds;

this.scale.x += this.sx * delta / 1000;
this.scale.y += this.sy * delta / 1000;

// Update physics body
this.body.update();
}

/**
* Called by the Engine, updates the state of the actor
* @param engine The reference to the current game engine
Expand Down Expand Up @@ -1197,6 +1229,8 @@ module ex {
}
}

this.integrate(delta);

// Update actor pipeline (movement, collision detection, event propagation, offscreen culling)
for (var trait of this.traits) {
trait.update(this, engine, delta);
Expand Down
9 changes: 9 additions & 0 deletions src/engine/Collision/Body.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,15 @@ module ex {
}
}

/**
* Updates the collision area geometry and internal caches
*/
public update() {
if (this.collisionArea) {
this.collisionArea.recalc();
}
}


/**
* Sets up a box collision area based on the current bounds of the associated actor of this physics body.
Expand Down
31 changes: 20 additions & 11 deletions src/engine/Collision/CollisionContact.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,31 +85,40 @@ module ex {
if (this.mtv.x !== 0) {
var velX = 0;
// both bodies are traveling in the same direction (negative or positve)
if (bodyA.vel.x <= 0 && bodyB.vel.x <= 0) {
if (bodyA.vel.x < 0 && bodyB.vel.x < 0) {
velX = Math.min(bodyA.vel.x, bodyB.vel.x);
velX = bodyA.vel.x + bodyA.vel.x;
} else if (bodyA.vel.x >= 0 && bodyB.vel.x >= 0) {
} else if (bodyA.vel.x > 0 && bodyB.vel.x > 0) {
velX = Math.max(bodyA.vel.x, bodyB.vel.x);
velX = bodyA.vel.x + bodyA.vel.x;
}else {
} else if (bodyB.collisionType === ex.CollisionType.Fixed) {
// bodies are traveling in opposite directions
velX = 0;
if (bodyA.pos.sub(bodyB.pos).dot(bodyA.vel) > 0) {
velX = bodyA.vel.x;
} else {
// bodyA is heading towards b
velX = bodyB.vel.x;
}
}
bodyA.vel.x = velX;
}


if (this.mtv.y !== 0) {
var velY = 0;

// both bodies are traveling in the same direction (negative or positive)
if (bodyA.vel.y <= 0 && bodyB.vel.y <= 0) {
if (bodyA.vel.y < 0 && bodyB.vel.y < 0) {
velY = Math.min(bodyA.vel.y, bodyB.vel.y);
} else if (bodyA.vel.y >= 0 && bodyB.vel.y >= 0) {
} else if (bodyA.vel.y > 0 && bodyB.vel.y > 0) {
velY = Math.max(bodyA.vel.y, bodyB.vel.y);
} else {
} else if (bodyB.collisionType === ex.CollisionType.Fixed) {
// bodies are traveling in opposite directions
velY = 0;
}
if (bodyA.pos.sub(bodyB.pos).dot(bodyA.vel) > 0) {
velY = bodyA.vel.y;
} else {
// bodyA is heading towards b
velY = bodyB.vel.y;
}
}

bodyA.vel.y = velY;
}
Expand Down
6 changes: 3 additions & 3 deletions src/engine/Collision/DynamicTree.ts
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,7 @@ module ex {

}

public registerBody(body: Body) {
public trackBody(body: Body) {
var node = new TreeNode();
node.body = body;
node.bounds = body.getBounds();
Expand Down Expand Up @@ -226,7 +226,7 @@ module ex {
return true;
}

public removeBody(body: Body) {
public untrackBody(body: Body) {
var node = this.nodes[body.actor.id];
if (!node) { return; }
this.remove(node);
Expand Down Expand Up @@ -360,7 +360,7 @@ module ex {

public query(body: Body, callback: (other: Body) => boolean): void {
var bounds = body.getBounds();
var helper = currentNode => {
var helper = (currentNode: TreeNode) => {
if (currentNode && currentNode.bounds.collides(bounds)) {
if (currentNode.isLeaf() && currentNode.body !== body) {
if (callback.call(body, currentNode.body)) {
Expand Down
27 changes: 22 additions & 5 deletions src/engine/Collision/DynamicTreeCollisionBroadphase.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,26 @@ module ex {
private _collisionHash: { [key: string]: boolean; } = {};
private _collisionContactCache: CollisionContact[] = [];

public register(target: Actor): void {
this._dynamicCollisionTree.registerBody(target.body);
/**
* Tracks a physics body for collisions
*/
public track(target: Body): void {
if (!target) {
ex.Logger.getInstance().warn('Cannot track null physics body');
return;
}
this._dynamicCollisionTree.trackBody(target);
}

public remove(target: Actor): void {
this._dynamicCollisionTree.removeBody(target.body);
/**
* Untracks a physics body
*/
public untrack(target: Body): void {
if (!target) {
ex.Logger.getInstance().warn('Cannot untrack a null physics body');
return;
}
this._dynamicCollisionTree.untrackBody(target);
}

private _canCollide(actorA: Actor, actorB: Actor) {
Expand All @@ -35,7 +49,7 @@ module ex {
return true;
}

public findCollisionContacts(targets: Actor[], delta: number): CollisionContact[] {
public detect(targets: Actor[], delta: number): CollisionContact[] {
// TODO optimization use only the actors that are moving to start
// Retrieve the list of potential colliders, exclude killed, prevented, and self
var potentialColliders = targets.filter((other) => {
Expand Down Expand Up @@ -108,6 +122,9 @@ module ex {
return this._collisionContactCache;
}

/**
* Update the dynamic tree positions
*/
public update(targets: Actor[], delta: number): number {
var updated = 0, i = 0, len = targets.length;

Expand Down
9 changes: 5 additions & 4 deletions src/engine/Collision/ICollisionResolver.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
module ex {

export interface ICollisionBroadphase {
register(target: Actor);
remove(tartet: Actor);

findCollisionContacts(targets: Actor[], delta: number): CollisionContact[];
track(target: Body);
untrack(tartet: Body);

//getPairs(): CollisionContact[];
detect(targets: Actor[], delta: number): CollisionContact[];
update(targets: Actor[], delta: number): number;

debugDraw(ctx, delta): void;
Expand Down
6 changes: 3 additions & 3 deletions src/engine/Collision/NaiveCollisionBroadphase.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,15 @@
module ex {
export class NaiveCollisionBroadphase implements ICollisionBroadphase {

public register(target: Actor) {
public track(target: Body) {
// pass
}

public remove(tartet: Actor) {
public untrack(tartet: Body) {
// pass
}

public findCollisionContacts(targets: Actor[], delta: number): CollisionContact[] {
public detect(targets: Actor[], delta: number): CollisionContact[] {

// Retrieve the list of potential colliders, exclude killed, prevented, and self
var potentialColliders = targets.filter((other) => {
Expand Down
5 changes: 5 additions & 0 deletions src/engine/Physics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -198,5 +198,10 @@ module ex {
public static useRigidBodyPhysics(): void {
ex.Physics.collisionResolutionStrategy = ex.CollisionResolutionStrategy.RigidBody;
}

/**
* Small value to help collision passes settle themselves after the narrowphase.
*/
public static collisionShift = .001;
};
}
38 changes: 22 additions & 16 deletions src/engine/Scene.ts
Original file line number Diff line number Diff line change
Expand Up @@ -254,25 +254,31 @@ module ex {
for (i = 0, len = this.tileMaps.length; i < len; i++) {
this.tileMaps[i].update(engine, delta);
}

// Cycle through actors updating actors
for (i = 0, len = this.children.length; i < len; i++) {
this.children[i].update(engine, delta);
}

// Run the broadphase
if (this._broadphase) {
this._broadphase.update(this.children, delta);
}

// Run the narrowphase
var iter: number = Physics.collisionPasses;
var collisionDelta = delta / iter;
while (iter > 0) {
// Cycle through actors updating actors
for (i = 0, len = this.children.length; i < len; i++) {
this.children[i].update(engine, collisionDelta);
this.children[i].collisionArea.recalc();
}

// TODO meh I don't like how this works... maybe find a way to make collisions
// a trait
while (iter > 0) {
// Run collision resolution strategy
if (this._broadphase && Physics.enabled) {
this._broadphase.update(this.children, collisionDelta);
this._broadphase.findCollisionContacts(this.children, collisionDelta);
if (this._broadphase && Physics.enabled) {
this._broadphase.detect(this.children, collisionDelta);
for (i = 0, len = this.children.length; i < len; i++) {
// helps move settle collisions, really there is a better way to do this
this.children[i].integrate(collisionDelta * ex.Physics.collisionShift);
}
}
iter--;
}
}

// Remove actors from scene graph after being killed
var actorIndex: number;
Expand Down Expand Up @@ -464,7 +470,7 @@ module ex {
return;
}
if (entity instanceof Actor) {
this._broadphase.remove(entity);
this._broadphase.untrack(entity.body);
this._removeChild(entity);
}
if (entity instanceof Timer) {
Expand Down Expand Up @@ -501,7 +507,7 @@ module ex {
* Adds an actor to the scene, once this is done the actor will be drawn and updated.
*/
protected _addChild(actor: Actor) {
this._broadphase.register(actor);
this._broadphase.track(actor.body);
actor.scene = this;
this.children.push(actor);
this._sortedDrawingTree.add(actor);
Expand Down Expand Up @@ -529,7 +535,7 @@ module ex {
* Removes an actor from the scene, it will no longer be drawn or updated.
*/
protected _removeChild(actor: Actor) {
this._broadphase.remove(actor);
this._broadphase.untrack(actor.body);
this._killQueue.push(actor);
actor.parent = null;
}
Expand Down
Loading