Skip to content

Commit

Permalink
Feat: [#3157] return target on trigger (#3176)
Browse files Browse the repository at this point in the history
Closes #3157

## Changes:

- The action callback of Trigger now returns the entity that triggered it.
- General cleanup of Trigger class
  - `target` is now 100% separate from `filter`, because that's what the functionality described in the documentation implied (to me).
  - Converted all the different "default value fallbacks" to a single setup.
  - Trigger used `Entity` and `Actor` interchangeably. Rewrote it to be 100% Entity (Although Entity doesn't have any collision mechanics build in, so an argument could be made to go for 100% Actor)
  • Loading branch information
Autsider666 authored Aug 29, 2024
1 parent 46ba314 commit 29e8317
Show file tree
Hide file tree
Showing 4 changed files with 50 additions and 69 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ This project adheres to [Semantic Versioning](http://semver.org/).

### Breaking Changes

- `Trigger` API has been slightly changed:
- `action` now returns the triggering entity: `(entity: Entity) => void`
- `target` now works in conjunction with `filter` instead of overwriting it.
- `EnterTriggerEvent` and `ExitTriggerEvent` now contain a `entity: Entity` property instead of `actor: Actor`
- `ex.Vector.normalize()` return zero-vector (`(0,0)`) instead of `(0,1)` when normalizing a vector with a magnitude of 0
- `ex.Gif` transparent color constructor arg is removed in favor of the built in Gif file mechanism
- Remove core-js dependency, it is no longer necessary in modern browsers. Technically a breaking change for older browsers
Expand Down
10 changes: 8 additions & 2 deletions sandbox/tests/trigger/trigger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,18 @@ var trigger = new ex.Trigger({
});

trigger.on('collisionstart', (evt: ex.EnterTriggerEvent) => {
evt.actor.color = ex.Color.Green;
if (evt.entity instanceof ex.Actor) {
evt.entity.color = ex.Color.Green;
}

console.log(evt);
});

trigger.on('collisionend', (evt: ex.ExitTriggerEvent) => {
evt.actor.color = ex.Color.Red;
if (evt.entity instanceof ex.Actor) {
evt.entity.color = ex.Color.Red;
}

console.log(evt);
});

Expand Down
8 changes: 4 additions & 4 deletions src/engine/Events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -665,19 +665,19 @@ export class EnterViewPortEvent extends GameEvent<Entity> {
}
}

export class EnterTriggerEvent extends GameEvent<Actor> {
export class EnterTriggerEvent extends GameEvent<Trigger> {
constructor(
public target: Trigger,
public actor: Actor
public entity: Entity
) {
super();
}
}

export class ExitTriggerEvent extends GameEvent<Actor> {
export class ExitTriggerEvent extends GameEvent<Trigger> {
constructor(
public target: Trigger,
public actor: Actor
public entity: Entity
) {
super();
}
Expand Down
97 changes: 34 additions & 63 deletions src/engine/Trigger.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { Engine } from './Engine';
import { Vector } from './Math/vector';
import { ExitTriggerEvent, EnterTriggerEvent, CollisionEndEvent, CollisionStartEvent } from './Events';
import { CollisionType } from './Collision/CollisionType';
Expand Down Expand Up @@ -29,106 +28,78 @@ export interface TriggerOptions {
// whether the trigger is visible or not
visible: boolean;
// action to take when triggered
action: () => void;
// if specified the trigger will only fire on a specific actor and overrides any filter
action: (entity: Entity) => void;
// if specified the trigger will only fire on a specific entity and overrides any filter
target: Entity;
// Returns true if the triggers should fire on the collided actor
filter: (actor: Entity) => boolean;
// Returns true if the triggers should fire on the collided entity
filter: (entity: Entity) => boolean;
// -1 if it should repeat forever
repeat: number;
}

const triggerDefaults: Partial<TriggerOptions> = {
pos: Vector.Zero,
width: 10,
height: 10,
visible: false,
action: () => {
return;
},
filter: () => true,
repeat: -1
};

/**
* Triggers are a method of firing arbitrary code on collision. These are useful
* as 'buttons', 'switches', or to trigger effects in a game. By default triggers
* are invisible, and can only be seen when {@apilink Trigger.visible} is set to `true`.
*/
export class Trigger extends Actor {
public events = new EventEmitter<TriggerEvents & ActorEvents>();
private _target: Entity;
public target?: Entity;
/**
* Action to fire when triggered by collision
*/
public action: () => void = () => {
return;
};
public action: (entity: Entity) => void;
/**
* Filter to add additional granularity to action dispatch, if a filter is specified the action will only fire when
* filter return true for the collided actor.
* filter return true for the collided entity.
*/
public filter: (actor: Entity) => boolean = () => true;
public filter: (entity: Entity) => boolean;
/**
* Number of times to repeat before killing the trigger,
*/
public repeat: number = -1;
public repeat: number;

/**
*
* @param opts Trigger options
* @param options Trigger options
*/
constructor(opts: Partial<TriggerOptions>) {
super({ x: opts.pos.x, y: opts.pos.y, width: opts.width, height: opts.height });
opts = {
...triggerDefaults,
...opts
};
constructor(options: Partial<TriggerOptions>) {
super({ x: options.pos?.x, y: options.pos?.y, width: options.width, height: options.height });

this.filter = opts.filter || this.filter;
this.repeat = opts.repeat || this.repeat;
this.action = opts.action || this.action;
if (opts.target) {
this.target = opts.target;
}
this.filter = options.filter ?? (() => true);
this.repeat = options.repeat ?? -1;
this.action = options.action ?? (() => undefined);
this.target = options.target;

this.graphics.visible = opts.visible;
this.graphics.visible = options.visible ?? false;
this.body.collisionType = CollisionType.Passive;

this.events.on('collisionstart', (evt: CollisionStartEvent<Actor>) => {
if (this.filter(evt.other)) {
this.events.emit('enter', new EnterTriggerEvent(this, evt.other));
this._dispatchAction();
// remove trigger if its done, -1 repeat forever
if (this.repeat === 0) {
this.kill();
}
this.events.on('collisionstart', ({ other: collider }: CollisionStartEvent<Entity>) => {
if (!this._matchesTarget(collider)) {
return;
}
});

this.events.on('collisionend', (evt: CollisionEndEvent<Actor>) => {
if (this.filter(evt.other)) {
this.events.emit('exit', new ExitTriggerEvent(this, evt.other));
this.events.emit('enter', new EnterTriggerEvent(this, collider));
this._dispatchAction(collider);
// remove trigger if its done, -1 repeat forever
if (this.repeat === 0) {
this.kill();
}
});
}

public set target(target: Entity) {
this._target = target;
this.filter = (actor: Entity) => actor === target;
}

public get target() {
return this._target;
this.events.on('collisionend', ({ other: collider }: CollisionEndEvent<Entity>) => {
if (this._matchesTarget(collider)) {
this.events.emit('exit', new ExitTriggerEvent(this, collider));
}
});
}

public _initialize(engine: Engine) {
super._initialize(engine);
private _matchesTarget(entity: Entity): boolean {
return this.filter(entity) && (this.target === undefined || this.target === entity);
}

private _dispatchAction() {
private _dispatchAction(target: Entity) {
if (this.repeat !== 0) {
this.action.call(this);
this.action.call(this, target);
this.repeat--;
}
}
Expand Down

0 comments on commit 29e8317

Please sign in to comment.