diff --git a/CHANGELOG.md b/CHANGELOG.md
index 35692a1f1..f44055a67 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -15,11 +15,18 @@ This project adheres to [Semantic Versioning](http://semver.org/).
### Added
+- Added ability to apply draw offset to `ex.IsometricMap` and `ex.Tilemap`
+- Added `visibility` and `opacity` to `ex.IsometricMap`
+- Added base elevation for `ex.IsometricMap` so multiple maps can sort correctly
+- Added method to suppress convex polygon warning for library code usage
+- Added more configuration options to debug draw flags, including isometric map controls
- Added `actionstart` and `actioncomplete` events to the Actor that are fired when an action starts and completes
### Fixed
+- Fixed infinite loop :bomb: when certain degenerate polygons were attempted to be triangulated!
+- Fixed incorrect type on `ex.Tilemap.getTileByPoint()`
- Fixed TS type on `GraphicsComponent` and allow `.material` to be null to unset, current workaround is using `.material = null as any`
### Updates
@@ -28,7 +35,8 @@ This project adheres to [Semantic Versioning](http://semver.org/).
### Changed
--
+- Tweaked debug draw to be less noisy by default
+- Removed dependency on `ex.IsometricMap` in the `ex.IsometricEntityComponent`, this allows for greater flexibility when using the component when a map may not be known or constructed.
diff --git a/sandbox/tests/polygon/index.html b/sandbox/tests/polygon/index.html
new file mode 100644
index 000000000..f96a40eba
--- /dev/null
+++ b/sandbox/tests/polygon/index.html
@@ -0,0 +1,12 @@
+
+
+
+
+
+ Polygon Triangulate
+
+
+
+
+
+
\ No newline at end of file
diff --git a/sandbox/tests/polygon/index.ts b/sandbox/tests/polygon/index.ts
new file mode 100644
index 000000000..854e3ccdf
--- /dev/null
+++ b/sandbox/tests/polygon/index.ts
@@ -0,0 +1,95 @@
+///
+
+
+var game = new ex.Engine({
+ width: 800,
+ height: 600,
+ displayMode: ex.DisplayMode.FitScreenAndFill
+});
+game.toggleDebug();
+game.debug.collider.showBounds = false;
+
+var actor = new ex.Actor({pos: ex.vec(200, 100)});
+// var shape = ex.Shape.Polygon([
+// ex.vec(0,0),
+// ex.vec(-1.6875,-25.0625),
+// ex.vec(6.3125,-25.0625),
+// ex.vec(5.625,-33.125),
+// ex.vec(21.5625,-32.8125),
+// ex.vec(21.4375,-40.6875),
+// ex.vec(29.1875,-41.9375),
+// ex.vec(32.8125,-38.3125),
+// ex.vec(33.0625,-21.8125),
+// ex.vec(25.8125,-21.5625),
+// ex.vec(24.125,10.375),
+// ex.vec(-8.75,8.5),
+// ex.vec(-11.8125,5.5625),
+// ex.vec(-10.9375,-0.5625),
+// ex.vec(-3.3125,-2.4375)
+// ]);
+
+// var points = [
+// ex.vec(0,0),
+// ex.vec(-1.6875,-25.0625),
+// ex.vec(6.3125,-25.0625),
+// ex.vec(5.625,-33.125),
+// ex.vec(21.5625,-32.8125),
+// ex.vec(21.4375,-40.6875),
+// ex.vec(29.1875,-41.9375),
+// ex.vec(32.8125,-38.3125),
+// ex.vec(33.0625,-21.8125),
+// ex.vec(25.8125,-21.5625),
+// ex.vec(24.125,10.375),
+// ex.vec(-8.75,8.5),
+// ex.vec(-11.8125,5.5625),
+// ex.vec(-10.9375,-0.5625),
+// ex.vec(-3.3125,-2.4375)
+// ]
+
+// var points = [
+// ex.vec(200,100),
+// ex.vec(300,320),
+// ex.vec(400,100),
+// ex.vec(500,300),
+// ex.vec(350,300),
+// ex.vec(300,500),
+// ex.vec(250,300),
+// ex.vec(100,300)];
+
+var points = [
+ ex.vec(343,392),
+ ex.vec(475,103),
+ ex.vec(245,151),
+ ex.vec(193,323),
+ ex.vec(91, 279),
+ ex.vec(51, 301),
+ ex.vec(25, 381),
+ ex.vec(80, 334),
+ ex.vec(142,418),
+ ex.vec(325,480),
+ ex.vec(340,564),
+ ex.vec(468,597)
+]
+
+var colinear = [
+ ex.vec(160, 80),
+ ex.vec(80, 40),
+ ex.vec(0, 0),
+];
+
+function triangleArea(a: ex.Vector, b: ex.Vector, c: ex.Vector) {
+ return Math.abs((a.x * (b.y - c.y) + b.x * (c.y - a.y) + c.x * (a.y - c.y)))/2
+}
+
+console.log('triangle area', triangleArea(ex.vec(160, 80),
+ex.vec(80, 40),
+ex.vec(0, 0)));
+
+var shape = ex.Shape.Polygon(points, ex.Vector.Zero, true);
+
+var triangulated = shape.triangulate();
+actor.collider.set(triangulated);
+game.add(actor);
+
+
+game.start();
\ No newline at end of file
diff --git a/src/engine/Collision/Colliders/CircleCollider.ts b/src/engine/Collision/Colliders/CircleCollider.ts
index 7ec9b6458..bd1f5bcd0 100644
--- a/src/engine/Collision/Colliders/CircleCollider.ts
+++ b/src/engine/Collision/Colliders/CircleCollider.ts
@@ -253,7 +253,8 @@ export class CircleCollider extends Collider {
return new Projection(Math.min.apply(Math, scalars), Math.max.apply(Math, scalars));
}
- public debug(ex: ExcaliburGraphicsContext, color: Color) {
+ public debug(ex: ExcaliburGraphicsContext, color: Color, options?: { lineWidth: number }) {
+ const { lineWidth } = { ...{ lineWidth: 1 }, ...options };
const tx = this._transform;
const scale = tx?.globalScale ?? Vector.One;
const rotation = tx?.globalRotation ?? 0;
@@ -262,7 +263,7 @@ export class CircleCollider extends Collider {
ex.translate(pos.x, pos.y);
ex.rotate(rotation);
ex.scale(scale.x, scale.y);
- ex.drawCircle((this.offset ?? Vector.Zero), this._naturalRadius, Color.Transparent, color, 2);
+ ex.drawCircle((this.offset ?? Vector.Zero), this._naturalRadius, Color.Transparent, color, lineWidth);
ex.restore();
}
}
diff --git a/src/engine/Collision/Colliders/Collider.ts b/src/engine/Collision/Colliders/Collider.ts
index 7b5c1e826..726b4518b 100644
--- a/src/engine/Collision/Colliders/Collider.ts
+++ b/src/engine/Collision/Colliders/Collider.ts
@@ -113,7 +113,7 @@ export abstract class Collider implements Clonable {
abstract update(transform: Transform): void;
- abstract debug(ex: ExcaliburGraphicsContext, color: Color): void;
+ abstract debug(ex: ExcaliburGraphicsContext, color: Color, options?: { lineWidth: number, pointSize: number }): void;
abstract clone(): Collider;
}
diff --git a/src/engine/Collision/Colliders/CompositeCollider.ts b/src/engine/Collision/Colliders/CompositeCollider.ts
index 6ed7b7d6e..c8701dd65 100644
--- a/src/engine/Collision/Colliders/CompositeCollider.ts
+++ b/src/engine/Collision/Colliders/CompositeCollider.ts
@@ -236,10 +236,10 @@ export class CompositeCollider extends Collider {
}
}
- public debug(ex: ExcaliburGraphicsContext, color: Color) {
+ public debug(ex: ExcaliburGraphicsContext, color: Color, options?: { lineWidth: number, pointSize: number }) {
const colliders = this.getColliders();
for (const collider of colliders) {
- collider.debug(ex, color);
+ collider.debug(ex, color, options);
}
}
diff --git a/src/engine/Collision/Colliders/PolygonCollider.ts b/src/engine/Collision/Colliders/PolygonCollider.ts
index 26959e044..a9a12f22c 100644
--- a/src/engine/Collision/Colliders/PolygonCollider.ts
+++ b/src/engine/Collision/Colliders/PolygonCollider.ts
@@ -11,7 +11,7 @@ import { AffineMatrix } from '../../Math/affine-matrix';
import { Ray } from '../../Math/ray';
import { ClosestLineJumpTable } from './ClosestLineJumpTable';
import { Collider } from './Collider';
-import { ExcaliburGraphicsContext, Logger, range } from '../..';
+import { ExcaliburGraphicsContext, Logger } from '../..';
import { CompositeCollider } from './CompositeCollider';
import { Shape } from './Shape';
import { Transform } from '../../Math/transform';
@@ -25,6 +25,11 @@ export interface PolygonColliderOptions {
* Points in the polygon in order around the perimeter in local coordinates. These are relative from the body transform position.
*/
points: Vector[];
+
+ /**
+ * Suppresses convexity warning
+ */
+ suppressConvexWarning?: boolean;
}
/**
@@ -72,10 +77,13 @@ export class PolygonCollider extends Collider {
if (!counterClockwise) {
this.points.reverse();
}
+
if (!this.isConvex()) {
- this._logger.warn(
- 'Excalibur only supports convex polygon colliders and will not behave properly.'+
- 'Call PolygonCollider.triangulate() to build a new collider composed of smaller convex triangles');
+ if (!options.suppressConvexWarning) {
+ this._logger.warn(
+ 'Excalibur only supports convex polygon colliders and will not behave properly.' +
+ 'Call PolygonCollider.triangulate() to build a new collider composed of smaller convex triangles');
+ }
}
// calculate initial transformation
@@ -109,13 +117,13 @@ export class PolygonCollider extends Collider {
for (const [i, point] of this.points.entries()) {
oldPoint = newPoint;
oldDirection = direction;
- newPoint = point;
+ newPoint = point;
direction = Math.atan2(newPoint.y - oldPoint.y, newPoint.x - oldPoint.x);
if (oldPoint.equals(newPoint)) {
return false; // repeat point
}
let angle = direction - oldDirection;
- if (angle <= -Math.PI){
+ if (angle <= -Math.PI) {
angle += Math.PI * 2;
} else if (angle > Math.PI) {
angle -= Math.PI * 2;
@@ -124,7 +132,7 @@ export class PolygonCollider extends Collider {
if (angle === 0.0) {
return false;
}
- orientation = angle > 0 ? 1 : -1;
+ orientation = angle > 0 ? 1 : -1;
} else {
if (orientation * angle <= 0) {
return false;
@@ -158,19 +166,48 @@ export class PolygonCollider extends Collider {
throw Error('Invalid polygon');
}
+ const triangles: [Vector, Vector, Vector][] = [];
+ // algorithm likes clockwise
+ const vertices = [...this.points].reverse();
+ let vertexCount = vertices.length;
+
/**
- * Helper to get a vertex in the list
+ * Returns the previous index based on the current vertex
*/
- function getItem(index: number, list: T[]) {
- if (index >= list.length) {
- return list[index % list.length];
- } else if (index < 0) {
- return list[index % list.length + list.length];
- } else {
- return list[index];
+ function getPrevIndex(index: number) {
+ return index === 0 ? vertexCount - 1 : index - 1;
+ }
+
+ /**
+ * Retrieves the next index based on the current vertex
+ */
+ function getNextIndex(index: number) {
+ return index === vertexCount - 1 ? 0 : index + 1;
+ }
+
+ /**
+ * Whether or not the angle at this vertex index is convex
+ */
+ function isConvex(index: number) {
+ const prev = getPrevIndex(index);
+ const next = getNextIndex(index);
+
+ const va = vertices[prev];
+ const vb = vertices[index];
+ const vc = vertices[next];
+
+ // Check convexity
+ const leftArm = va.sub(vb);
+ const rightArm = vc.sub(vb);
+ // Positive cross product is convex
+ if (leftArm.cross(rightArm) < 0) {
+ return false;
}
+ return true;
}
+ const convexVertices = vertices.map((_,i) => isConvex(i));
+
/**
* Quick test for point in triangle
*/
@@ -193,61 +230,96 @@ export class PolygonCollider extends Collider {
return true;
}
- const triangles: Vector[][] = [];
- const vertices = [...this.points];
- const indices = range(0, this.points.length - 1);
-
- // 1. Loop through vertices clockwise
- // if the vertex is convex (interior angle is < 180) (cross product positive)
- // if the polygon formed by it's edges doesn't contain the points
- // it's an ear add it to our list of triangles, and restart
-
- while (indices.length > 3) {
- for (let i = 0; i < indices.length; i++) {
- const a = indices[i];
- const b = getItem(i - 1, indices);
- const c = getItem(i + 1, indices);
-
- const va = vertices[a];
- const vb = vertices[b];
- const vc = vertices[c];
-
- // Check convexity
- const leftArm = vb.sub(va);
- const rightArm = vc.sub(va);
- const isConvex = rightArm.cross(leftArm) > 0; // positive cross means convex
- if (!isConvex) {
- continue;
- }
+ /**
+ * Calculate the area of the triangle
+ */
+ // function triangleArea(a: Vector, b: Vector, c: Vector) {
+ // return Math.abs(a.x * (b.y - c.y) + b.x * (c.y - a.y) + c.x * (a.y - c.y))/2;
+ // }
- let isEar = true;
- // Check that if any vertices are in the triangle a, b, c
- for (let j = 0; j < indices.length; j++) {
- const vertIndex = indices[j];
- // We can skip these
- if (vertIndex === a || vertIndex === b || vertIndex === c) {
- continue;
+ /**
+ * Find the next suitable ear tip
+ */
+ function findEarTip() {
+ for (let i = 0; i < vertexCount; i++) {
+ if (convexVertices[i]) {
+
+ const prev = getPrevIndex(i);
+ const next = getNextIndex(i);
+
+ const va = vertices[prev];
+ const vb = vertices[i];
+ const vc = vertices[next];
+
+ let isEar = true;
+ // Check that if any vertices are in the triangle a, b, c
+ for (let j = 0; j < vertexCount; j++) {
+ // We can skip these verts because they are the triangle we are testing
+ if (j === i || j === prev || j === next) {
+ continue;
+ }
+ const point = vertices[j];
+ if (isPointInTriangle(point, va, vb, vc)) {
+ isEar = false;
+ break;
+ }
}
- const point = vertices[vertIndex];
- if (isPointInTriangle(point, vb, va, vc)) {
- isEar = false;
- break;
+ // Add ear to polygon list and remove from list
+ if (isEar) {
+ return i;
}
}
+ }
- // Add ear to polygon list and remove from list
- if (isEar) {
- triangles.push([vb, va, vc]);
- indices.splice(i, 1);
- break;
+ // Fall back to any convex vertex
+ for (let i = 0; i < vertexCount; i++) {
+ if (convexVertices[i]) {
+ return i;
}
}
+
+ // bail and return the first one?
+ return 0;
+ }
+
+ /**
+ * Cut the ear and produce a triangle, update internal state
+ */
+ function cutEarTip(index: number) {
+ const prev = getPrevIndex(index);
+ const next = getNextIndex(index);
+
+ const va = vertices[prev];
+ const vb = vertices[index];
+ const vc = vertices[next];
+
+ // Clockwise winding
+ // if (triangleArea(va, vb, vc) > 0) {
+ triangles.push([va, vb, vc]);
+ // }
+ vertices.splice(index, 1);
+ convexVertices.splice(index, 1);
+ vertexCount--;
+ }
+
+ // Loop over all the vertices finding ears
+ while (vertexCount > 3) {
+ const earIndex = findEarTip();
+ cutEarTip(earIndex);
+
+ // reclassify vertices
+ for (let i = 0; i < vertexCount; i++) {
+ convexVertices[i] = isConvex(i);
+ }
}
- triangles.push([vertices[indices[0]], vertices[indices[1]], vertices[indices[2]]]);
+ // Last triangle after the loop
+ triangles.push([vertices[0], vertices[1], vertices[2]]);
- return new CompositeCollider(triangles.map(points => Shape.Polygon(points)));
+ // FIXME: there is a colinear triangle that sneaks in here sometimes
+ return new CompositeCollider(
+ triangles.map(points => Shape.Polygon(points, Vector.Zero, true)));
}
/**
@@ -614,13 +686,14 @@ export class PolygonCollider extends Collider {
return new Projection(min, max);
}
- public debug(ex: ExcaliburGraphicsContext, color: Color) {
+ public debug(ex: ExcaliburGraphicsContext, color: Color, options?: { lineWidth: number, pointSize: number }) {
const firstPoint = this.getTransformedPoints()[0];
const points = [firstPoint, ...this.getTransformedPoints(), firstPoint];
+ const { lineWidth, pointSize } = { ...{ lineWidth: 1, pointSize: 1 }, ...options };
for (let i = 0; i < points.length - 1; i++) {
- ex.drawLine(points[i], points[i + 1], color, 2);
- ex.drawCircle(points[i], 2, color);
- ex.drawCircle(points[i + 1], 2, color);
+ ex.drawLine(points[i], points[i + 1], color, lineWidth);
+ ex.drawCircle(points[i], pointSize, color);
+ ex.drawCircle(points[i + 1], pointSize, color);
}
}
}
diff --git a/src/engine/Collision/Colliders/Shape.ts b/src/engine/Collision/Colliders/Shape.ts
index 65b98d234..3ddcb34e3 100644
--- a/src/engine/Collision/Colliders/Shape.ts
+++ b/src/engine/Collision/Colliders/Shape.ts
@@ -31,10 +31,11 @@ export class Shape {
* @param points Points specified in counter clockwise
* @param offset Optional offset relative to the collider in local coordinates
*/
- static Polygon(points: Vector[], offset: Vector = Vector.Zero): PolygonCollider {
+ static Polygon(points: Vector[], offset: Vector = Vector.Zero, suppressConvexWarning = false): PolygonCollider {
return new PolygonCollider({
points: points,
- offset: offset
+ offset: offset,
+ suppressConvexWarning
});
}
diff --git a/src/engine/Debug/Debug.ts b/src/engine/Debug/Debug.ts
index c2c930e9d..09460943c 100644
--- a/src/engine/Debug/Debug.ts
+++ b/src/engine/Debug/Debug.ts
@@ -246,7 +246,7 @@ export class Debug implements DebugFlags {
*/
public entity = {
showAll: false,
- showId: true,
+ showId: false,
showName: false
};
@@ -256,6 +256,7 @@ export class Debug implements DebugFlags {
public transform = {
showAll: false,
+ debugZIndex: 10_000_000,
showPosition: false,
showPositionLabel: false,
positionColor: Color.Yellow,
@@ -275,7 +276,7 @@ export class Debug implements DebugFlags {
public graphics = {
showAll: false,
- showBounds: true,
+ showBounds: false,
boundsColor: Color.Yellow
};
@@ -285,13 +286,15 @@ export class Debug implements DebugFlags {
public collider = {
showAll: false,
- showBounds: true,
+ showBounds: false,
boundsColor: Color.Blue,
showOwner: false,
showGeometry: true,
- geometryColor: Color.Green
+ geometryColor: Color.Green,
+ geometryLineWidth: 1,
+ geometryPointSize: .5
};
/**
@@ -359,6 +362,20 @@ export class Debug implements DebugFlags {
colliderGeometryColor: Color.Green,
showQuadTree: false
};
+
+ public isometric = {
+ showAll: false,
+ showPosition: false,
+ positionColor: Color.Yellow,
+ positionSize: 1,
+ showGrid: false,
+ gridColor: Color.Red,
+ gridWidth: 1,
+ showColliders: true,
+ colliderColor: Color.Green,
+ colliderLineWidth: 1,
+ colliderPointSize: .5
+ };
}
/**
diff --git a/src/engine/Debug/DebugSystem.ts b/src/engine/Debug/DebugSystem.ts
index 3a4062d43..fcc139321 100644
--- a/src/engine/Debug/DebugSystem.ts
+++ b/src/engine/Debug/DebugSystem.ts
@@ -91,6 +91,7 @@ export class DebugSystem extends System {
this._pushCameraTransform(tx);
this._graphicsContext.save();
+ this._graphicsContext.z = txSettings.debugZIndex;
this._applyTransform(entity);
if (tx) {
@@ -182,6 +183,9 @@ export class DebugSystem extends System {
this._graphicsContext.restore();
+ // World space
+ this._graphicsContext.save();
+ this._graphicsContext.z = txSettings.debugZIndex;
motion = entity.get(MotionComponent);
if (motion) {
if (motionSettings.showAll || motionSettings.showVelocity) {
@@ -200,7 +204,10 @@ export class DebugSystem extends System {
if (colliderComp) {
const collider = colliderComp.get();
if ((colliderSettings.showAll || colliderSettings.showGeometry) && collider) {
- collider.debug(this._graphicsContext, colliderSettings.geometryColor);
+ collider.debug(this._graphicsContext, colliderSettings.geometryColor, {
+ lineWidth: colliderSettings.geometryLineWidth,
+ pointSize: colliderSettings.geometryPointSize
+ });
}
if (colliderSettings.showAll || colliderSettings.showBounds) {
if (collider instanceof CompositeCollider) {
@@ -225,6 +232,7 @@ export class DebugSystem extends System {
}
}
+ this._graphicsContext.restore();
this._popCameraTransform(tx);
}
diff --git a/src/engine/TileMap/IsometricEntityComponent.ts b/src/engine/TileMap/IsometricEntityComponent.ts
index 45fa336a3..5a6d69c64 100644
--- a/src/engine/TileMap/IsometricEntityComponent.ts
+++ b/src/engine/TileMap/IsometricEntityComponent.ts
@@ -1,6 +1,13 @@
import { Component } from '../EntityComponentSystem/Component';
import { IsometricMap } from './IsometricMap';
+export interface IsometricEntityComponentOptions {
+ columns: number;
+ rows: number;
+ tileWidth: number;
+ tileHeight: number;
+}
+
export class IsometricEntityComponent extends Component<'ex.isometricentity'> {
public readonly type = 'ex.isometricentity';
/**
@@ -8,14 +15,20 @@ export class IsometricEntityComponent extends Component<'ex.isometricentity'> {
*/
public elevation: number = 0;
- public map: IsometricMap;
+ public readonly columns: number;
+ public readonly rows: number;
+ public readonly tileWidth: number;
+ public readonly tileHeight: number;
/**
* Specify the isometric map to use to position this entity's z-index
- * @param map
+ * @param mapOrOptions
*/
- constructor(map: IsometricMap) {
+ constructor(mapOrOptions: IsometricMap | IsometricEntityComponentOptions) {
super();
- this.map = map;
+ this.columns = mapOrOptions.columns;
+ this.rows = mapOrOptions.rows;
+ this.tileWidth = mapOrOptions.tileWidth;
+ this.tileHeight = mapOrOptions.tileHeight;
}
}
\ No newline at end of file
diff --git a/src/engine/TileMap/IsometricEntitySystem.ts b/src/engine/TileMap/IsometricEntitySystem.ts
index bb4e4c6d6..9395765c3 100644
--- a/src/engine/TileMap/IsometricEntitySystem.ts
+++ b/src/engine/TileMap/IsometricEntitySystem.ts
@@ -15,7 +15,7 @@ export class IsometricEntitySystem extends System this.debug(ctx), false)
+ new DebugGraphicsComponent((ctx, debugFlags) => this.debug(ctx, debugFlags), false)
], options.name);
- const { pos, tileWidth, tileHeight, columns: width, rows: height, renderFromTopOfGraphic, graphicsOffset } = options;
+ const { pos, tileWidth, tileHeight, columns: width, rows: height, renderFromTopOfGraphic, graphicsOffset, elevation } = options;
this.transform = this.get(TransformComponent);
if (pos) {
@@ -292,6 +311,7 @@ export class IsometricMap extends Entity {
this.renderFromTopOfGraphic = renderFromTopOfGraphic ?? this.renderFromTopOfGraphic;
this.graphicsOffset = graphicsOffset ?? this.graphicsOffset;
+ this.elevation = elevation ?? this.elevation;
this.tileWidth = tileWidth;
this.tileHeight = tileHeight;
this.columns = width;
@@ -305,7 +325,6 @@ export class IsometricMap extends Entity {
const tile = new IsometricTile(x, y, this.graphicsOffset, this);
this.tiles[x + y * width] = tile;
this.addChild(tile);
- // TODO row/columns helpers
}
}
}
@@ -415,23 +434,47 @@ export class IsometricMap extends Entity {
* Debug draw for IsometricMap, called internally by excalibur when debug mode is toggled on
* @param gfx
*/
- public debug(gfx: ExcaliburGraphicsContext) {
+ public debug(gfx: ExcaliburGraphicsContext, debugFlags: Debug) {
+ const {
+ showAll,
+ showPosition,
+ positionColor,
+ positionSize,
+ showGrid,
+ gridColor,
+ gridWidth,
+ showColliders,
+ colliderColor,
+ colliderLineWidth,
+ colliderPointSize
+ } = debugFlags.isometric;
gfx.save();
gfx.z = this._getMaxZIndex() + 0.5;
- for (let y = 0; y < this.rows + 1; y++) {
- const left = this.tileToWorld(vec(0, y));
- const right = this.tileToWorld(vec(this.columns, y));
- gfx.drawLine(left, right, Color.Red, 2);
- }
+ if (showAll || showGrid) {
+ for (let y = 0; y < this.rows + 1; y++) {
+ const left = this.tileToWorld(vec(0, y));
+ const right = this.tileToWorld(vec(this.columns, y));
+ gfx.drawLine(left, right, gridColor, gridWidth);
+ }
- for (let x = 0; x < this.columns + 1; x++) {
- const top = this.tileToWorld(vec(x, 0));
- const bottom = this.tileToWorld(vec(x, this.rows));
- gfx.drawLine(top, bottom, Color.Red, 2);
+ for (let x = 0; x < this.columns + 1; x++) {
+ const top = this.tileToWorld(vec(x, 0));
+ const bottom = this.tileToWorld(vec(x, this.rows));
+ gfx.drawLine(top, bottom, gridColor, gridWidth);
+ }
}
- for (const tile of this.tiles) {
- gfx.drawCircle(this.tileToWorld(vec(tile.x, tile.y)), 3, Color.Yellow);
+ if (showAll || showPosition) {
+ for (const tile of this.tiles) {
+ gfx.drawCircle(this.tileToWorld(vec(tile.x, tile.y)), positionSize, positionColor);
+ }
+ }
+ if (showAll || showColliders) {
+ for (const tile of this.tiles) {
+ for (const collider of tile.getColliders()) {
+ collider.debug(gfx, colliderColor, { lineWidth: colliderLineWidth, pointSize: colliderPointSize });
+ }
+ }
}
gfx.restore();
}
diff --git a/src/engine/TileMap/TileMap.ts b/src/engine/TileMap/TileMap.ts
index a92ae978c..9acf18982 100644
--- a/src/engine/TileMap/TileMap.ts
+++ b/src/engine/TileMap/TileMap.ts
@@ -8,7 +8,6 @@ import { BodyComponent } from '../Collision/BodyComponent';
import { CollisionType } from '../Collision/CollisionType';
import { Shape } from '../Collision/Colliders/Shape';
import { ExcaliburGraphicsContext, Graphic, GraphicsComponent, hasGraphicsTick, ParallaxComponent } from '../Graphics';
-import { removeItemFromArray } from '../Util/Util';
import { MotionComponent } from '../EntityComponentSystem/Components/MotionComponent';
import { ColliderComponent } from '../Collision/ColliderComponent';
import { CompositeCollider } from '../Collision/Colliders/CompositeCollider';
@@ -437,7 +436,7 @@ export class TileMap extends Entity {
* Returns the [[Tile]] by testing a point in world coordinates,
* returns `null` if no Tile was found.
*/
- public getTileByPoint(point: Vector): Tile {
+ public getTileByPoint(point: Vector): Tile | null {
const x = Math.floor((point.x - this.pos.x) / (this.tileWidth * this.scale.x));
const y = Math.floor((point.y - this.pos.y) / (this.tileHeight * this.scale.y));
const tile = this.getTile(x, y);
@@ -507,17 +506,19 @@ export class TileMap extends Entity {
for (let i = 0; i < tiles.length; i++) {
const tile = tiles[i];
// get non-negative tile sprites
+ const offsets = tile.getGraphicsOffsets();
graphics = tile.getGraphics();
for (graphicsIndex = 0, graphicsLen = graphics.length; graphicsIndex < graphicsLen; graphicsIndex++) {
// draw sprite, warning if sprite doesn't exist
const graphic = graphics[graphicsIndex];
+ const offset = offsets[graphicsIndex];
if (graphic) {
if (hasGraphicsTick(graphic)) {
graphic?.tick(delta, this._token);
}
const offsetY = this.renderFromTopOfGraphic ? 0 : (graphic.height - this.tileHeight);
- graphic.draw(ctx, tile.x * this.tileWidth, tile.y * this.tileHeight - offsetY);
+ graphic.draw(ctx, tile.x * this.tileWidth + offset.x, tile.y * this.tileHeight - offsetY + offset.y);
}
}
}
@@ -618,7 +619,6 @@ export class Tile extends Entity {
private _geometry: BoundingBox;
private _pos: Vector;
private _posDirty = false;
- // private _transform: TransformComponent;
/**
* Return the world position of the top left corner of the tile
@@ -678,6 +678,7 @@ export class Tile extends Entity {
}
private _graphics: Graphic[] = [];
+ private _offsets: Vector[] = [];
/**
* Current list of graphics for this tile
@@ -686,19 +687,35 @@ export class Tile extends Entity {
return this._graphics;
}
+ /**
+ * Current list of offsets for this tile's graphics
+ */
+ public getGraphicsOffsets(): readonly Vector[] {
+ return this._offsets;
+ }
+
/**
* Add another [[Graphic]] to this TileMap tile
* @param graphic
*/
- public addGraphic(graphic: Graphic) {
+ public addGraphic(graphic: Graphic, options?: { offset?: Vector }) {
this._graphics.push(graphic);
+ if (options?.offset) {
+ this._offsets.push(options.offset);
+ } else {
+ this._offsets.push(Vector.Zero);
+ }
}
/**
* Remove an instance of a [[Graphic]] from this tile
*/
public removeGraphic(graphic: Graphic) {
- removeItemFromArray(graphic, this._graphics);
+ const index = this._graphics.indexOf(graphic);
+ if (index > -1) {
+ this._graphics.splice(index, 1);
+ this._offsets.splice(index, 1);
+ }
}
/**
@@ -706,6 +723,7 @@ export class Tile extends Entity {
*/
public clearGraphics() {
this._graphics.length = 0;
+ this._offsets.length = 0;
}
/**
diff --git a/src/spec/CollisionShapeSpec.ts b/src/spec/CollisionShapeSpec.ts
index 20c458fc0..afe785726 100644
--- a/src/spec/CollisionShapeSpec.ts
+++ b/src/spec/CollisionShapeSpec.ts
@@ -499,9 +499,9 @@ describe('Collision Shape', () => {
const colliders = composite.getColliders() as ex.PolygonCollider[];
expect(colliders.length).toBe(3);
- expect(colliders[0].points).toEqual([ex.vec(0, 0), ex.vec(10, 0), ex.vec(10, 10)]);
- expect(colliders[1].points).toEqual([ex.vec(0, 0), ex.vec(10, 10), ex.vec(0, 10)]);
- expect(colliders[2].points).toEqual([ex.vec(0, 0), ex.vec(5, 5), ex.vec(0, 10)]);
+ expect(colliders[0].points).toEqual([ex.vec(5, 5), ex.vec(0, 0), ex.vec(10, 0)]);
+ expect(colliders[1].points).toEqual([ex.vec(0, 10), ex.vec(5, 5), ex.vec(10, 0)]);
+ expect(colliders[2].points).toEqual([ex.vec(10, 0), ex.vec(10, 10), ex.vec(0, 10)]);
expect(concave.isConvex()).withContext('Should be concave').toBe(false);
});
diff --git a/src/spec/DebugSystemSpec.ts b/src/spec/DebugSystemSpec.ts
index 238d88baa..6bb2df744 100644
--- a/src/spec/DebugSystemSpec.ts
+++ b/src/spec/DebugSystemSpec.ts
@@ -87,6 +87,8 @@ describe('DebugSystem', () => {
actor.vel = ex.vec(100, 0);
actor.acc = ex.vec(100, -100);
engine.debug.motion.showAll = true;
+ engine.debug.collider.showGeometry = true;
+ engine.debug.collider.geometryLineWidth = 2;
debugSystem.update([actor], 100);
engine.graphicsContext.flush();
@@ -124,6 +126,7 @@ describe('DebugSystem', () => {
const actor = new ex.Actor({ name: 'thingy', x: -100 + center.x, y: center.y, width: 50, height: 50, color: ex.Color.Yellow });
actor.id = 0;
+ engine.debug.entity.showId = true;
engine.debug.collider.showAll = true;
debugSystem.update([actor], 100);
@@ -144,6 +147,7 @@ describe('DebugSystem', () => {
actor.collider.useCompositeCollider([ex.Shape.Circle(50), ex.Shape.Box(150, 20), ex.Shape.Box(10, 150)]);
actor.id = 0;
engine.debug.collider.showAll = true;
+ engine.debug.collider.geometryLineWidth = 3;
debugSystem.update([actor], 100);
engine.graphicsContext.flush();
diff --git a/src/spec/IsometricMapSpec.ts b/src/spec/IsometricMapSpec.ts
index ecee8d6f6..cab7db45c 100644
--- a/src/spec/IsometricMapSpec.ts
+++ b/src/spec/IsometricMapSpec.ts
@@ -111,6 +111,9 @@ describe('A IsometricMap', () => {
engine.debug.entity.showName = false;
engine.debug.entity.showId = false;
engine.debug.graphics.showBounds = false;
+ engine.debug.transform.showPosition = true;
+ engine.debug.isometric.showGrid = true;
+ engine.debug.isometric.showPosition = true;
const clock = engine.clock as ex.TestClock;
const image = new ex.ImageSource('src/spec/images/IsometricMapSpec/cube.png');
await image.load();
diff --git a/src/spec/images/CollisionShapeSpec/circle-debug.png b/src/spec/images/CollisionShapeSpec/circle-debug.png
index 3f75fd172..58fe24424 100644
Binary files a/src/spec/images/CollisionShapeSpec/circle-debug.png and b/src/spec/images/CollisionShapeSpec/circle-debug.png differ
diff --git a/src/spec/images/CompositeColliderSpec/composite.png b/src/spec/images/CompositeColliderSpec/composite.png
index 4387a0118..c2d8204ba 100644
Binary files a/src/spec/images/CompositeColliderSpec/composite.png and b/src/spec/images/CompositeColliderSpec/composite.png differ
diff --git a/src/spec/images/DebugSystemSpec/body.png b/src/spec/images/DebugSystemSpec/body.png
index f03d61788..245812ec0 100644
Binary files a/src/spec/images/DebugSystemSpec/body.png and b/src/spec/images/DebugSystemSpec/body.png differ
diff --git a/src/spec/images/DebugSystemSpec/composite-collider.png b/src/spec/images/DebugSystemSpec/composite-collider.png
index de6a6a034..9a3ed62d0 100644
Binary files a/src/spec/images/DebugSystemSpec/composite-collider.png and b/src/spec/images/DebugSystemSpec/composite-collider.png differ
diff --git a/src/spec/images/DebugSystemSpec/graphics.png b/src/spec/images/DebugSystemSpec/graphics.png
index b60974499..aeda6bc2a 100644
Binary files a/src/spec/images/DebugSystemSpec/graphics.png and b/src/spec/images/DebugSystemSpec/graphics.png differ
diff --git a/src/spec/images/DebugSystemSpec/motion.png b/src/spec/images/DebugSystemSpec/motion.png
index af4ab07d7..c214dcfc9 100644
Binary files a/src/spec/images/DebugSystemSpec/motion.png and b/src/spec/images/DebugSystemSpec/motion.png differ
diff --git a/src/spec/images/IsometricMapSpec/cube-map-debug.png b/src/spec/images/IsometricMapSpec/cube-map-debug.png
index 86a3aee0b..f71adfbd8 100644
Binary files a/src/spec/images/IsometricMapSpec/cube-map-debug.png and b/src/spec/images/IsometricMapSpec/cube-map-debug.png differ