feat: tileset geometry

This commit is contained in:
cha0s 2019-04-12 20:16:31 -05:00
parent 176d55d78d
commit 2eb2c58054
6 changed files with 137 additions and 14 deletions

View File

@ -5,6 +5,12 @@ import {ShapeList, PolygonShape, RectangleShape} from '@avocado/physics';
import {AbstractBody} from '../abstract/body';
// Trick matter into not needing decomp for our convex polygons.
const g = typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : undefined;
if (g) {
g.decomp = true;
}
export class Body extends AbstractBody {
static collisionCategory(group) {

View File

@ -1,5 +1,6 @@
import {Container, Sprite} from '@avocado/graphics';
import {Vector} from '@avocado/math';
import {BodyView} from '@avocado/physics';
import {Layer} from './layer';
import {TilesRenderer} from './tiles-renderer';
@ -11,20 +12,21 @@ export class LayerView extends Container {
this.layer = layer;
this.entityContainer = new Container();
this.layerContainer = new Container();
this.tileGeometryContainer = new Container();
this._renderer = renderer;
this.tileGeometryViews = [];
// Handle entity add/remove.
layer.on('entityAdded', this.onLayerEntityAdded, this);
layer.on('entityRemoved', this.onLayerEntityRemoved, this);
for (const entity of layer.entityList) {
this.onLayerEntityAdded(entity);
}
// Watch tile data change.
layer.on('tileDataChanged', this.onLayerTileDataChanged, this);
// Watch tileset change.
layer.on('tileGeometryChanged', this.onLayerTileGeometryChanged, this);
layer.on('tilesetChanged', this.onLayerTilesetChanged, this);
// Add to graphics.
this.addChild(this.layerContainer);
this.addChild(this.entityContainer);
this.addChild(this.tileGeometryContainer);
}
destroy() {
@ -33,6 +35,14 @@ export class LayerView extends Container {
layer.off('entityRemoved', this.onLayerEntityRemoved);
layer.off('tileDataChanged', this.onLayerTileDataChanged);
layer.off('tilesetChanged', this.onLayerTilesetChanged);
this.destroyTileGeometryBodies();
}
destroyTileGeometryBodies() {
for (const view of this.tileGeometryViews) {
view.destroy();
}
this.tileGeometryViews = [];
}
onLayerEntityAdded(entity) {
@ -51,6 +61,15 @@ export class LayerView extends Container {
this.render();
}
onLayerTileGeometryChanged() {
this.destroyTileGeometryBodies();
for (const body of this.layer.tileGeometry) {
const bodyView = new BodyView(body);
this.tileGeometryViews.push(bodyView);
this.tileGeometryContainer.addChild(bodyView);
}
}
onLayerTilesetChanged() {
this.render();
}

View File

@ -2,6 +2,7 @@ import * as I from 'immutable';
import {compose} from '@avocado/core';
import {create as createEntity, EntityList} from '@avocado/entity';
import {Vector} from '@avocado/math';
import {EventEmitter, Property} from '@avocado/mixins';
import {Synchronized} from '@avocado/state';
@ -16,6 +17,9 @@ const decorate = compose(
Property('tileset', {
track: true,
}),
Property('world', {
track: true,
}),
);
export class Layer extends decorate(Synchronized) {
@ -23,20 +27,49 @@ export class Layer extends decorate(Synchronized) {
constructor() {
super();
this.entityList = new EntityList();
this.tileGeometry = [];
this.tiles = new Tiles();
// Listeners.
this.entityList.on('entityAdded', this.onEntityAddedToLayer, this);
this.entityList.on('entityRemoved', this.onEntityRemovedFromLayer, this);
this.tiles.on('dataChanged', this.onTileDataChanged, this);
// Watch tileset URI change.
this.on('tilesetChanged', this.onTilesetChanged, this);
this.on('tilesetUriChanged', this.onTilesetUriChanged, this);
this.onTilesetUriChanged();
this.on('worldChanged', this.onWorldChanged, this);
}
addEntity(entity) {
this.entityList.addEntity(entity);
}
addTileGeometry() {
const tileset = this.tileset;
if (!tileset) {
return false;
}
const world = this.world;
if (!world) {
return false;
}
this.tiles.forEachTile((tile, x, y, i) => {
const shape = this.tileset.geometry(tile);
if (!shape) {
return;
}
const body = world.createBody(shape);
const halfTileSize = Vector.scale(tileset.tileSize, 0.5);
body.position = Vector.add(
halfTileSize,
Vector.mul([x, y], tileset.tileSize),
);
body.static = true;
world.addBody(body);
this.tileGeometry.push(body);
});
return true;
}
destroy() {
this.entityList.destroy();
this.entityList.off('entityAdded', this.onEntityAddedToLayer);
@ -85,6 +118,20 @@ export class Layer extends decorate(Synchronized) {
this.emit('tileDataChanged');
}
onTilesetChanged(oldTileset) {
let didChange = false;
if (oldTileset) {
this.removeTileGeometry();
didChange = true;
}
if (this.addTileGeometry()) {
didChange = true;
}
if (didChange) {
this.emit('tileGeometryChanged');
}
}
onTilesetUriChanged() {
if (!this.tilesetUri) {
return;
@ -94,10 +141,28 @@ export class Layer extends decorate(Synchronized) {
});
}
onWorldChanged(oldWorld) {
let didChange = false;
if (oldWorld) {
this.removeTileGeometry();
didChange = true;
}
if (this.addTileGeometry()) {
didChange = true;
}
if (didChange) {
this.emit('tileGeometryChanged');
}
}
removeEntity(entity) {
this.entityList.removeEntity(entity);
}
removeTileGeometry() {
// ... tag geometry in world for removal?
}
setTileAt(x, y, tile) {
this.tiles.setTileAt(x, y, tile);
}

View File

@ -28,6 +28,7 @@ export class Room extends decorate(Synchronized) {
// Listeners.
this.layers.on('entityAdded', this.onEntityAddedToRoom, this);
this.layers.on('entityRemoved', this.onEntityRemovedFromRoom, this);
this.layers.on('layerAdded', this.onLayerAdded, this);
this.on('sizeChanged', this.onSizeChanged, this);
this.on('worldChanged', this.onWorldChanged, this);
}
@ -74,6 +75,10 @@ export class Room extends decorate(Synchronized) {
this.emit('entityRemoved', entity);
}
onLayerAdded(layer, index) {
layer.world = this.world;
}
onSizeChanged() {
this.updateBounds();
}
@ -81,6 +86,7 @@ export class Room extends decorate(Synchronized) {
onWorldChanged() {
const world = this.world;
for (const {layer} of this.layers) {
layer.world = world;
for (const entity of layer.entityList) {
if (entity.is('physical')) {
entity.world = world;

View File

@ -19,6 +19,31 @@ export class Tiles extends decorate(Synchronized) {
this.data = I.List();
}
forEachTile(fn) {
let [x, y] = [0, 0];
let [width, height] = this.size;
let i = 0;
for (let k = 0; k < height; ++k) {
for (let j = 0; j < width; ++j) {
fn(this.data.get(i), x, y, i);
++i;
++x;
}
x = 0;
++y;
}
}
fromJSON(json) {
if (json.size) {
this.size = json.size;
}
if (json.data) {
this.data = I.fromJS(json.data);
}
return this;
}
patchStateStep(key, step) {
if ('data' === key) {
const oldData = this.data;
@ -34,16 +59,6 @@ export class Tiles extends decorate(Synchronized) {
super.patchStateStep(key, step);
}
fromJSON(json) {
if (json.size) {
this.size = json.size;
}
if (json.data) {
this.data = I.fromJS(json.data);
}
return this;
}
get rectangle() {
return Rectangle.compose([0, 0], this.size);
}

View File

@ -1,6 +1,7 @@
import {compose} from '@avocado/core';
import {hasGraphics, Image} from '@avocado/graphics';
import {Rectangle, Vector} from '@avocado/math';
import {shapeFromJSON} from '@avocado/physics';
import {Resource} from '@avocado/resource';
const decorate = compose(
@ -13,6 +14,7 @@ export class Tileset extends decorate(Resource) {
constructor() {
super();
this._geometry = {};
this._image = undefined;
this.subimages = [];
}
@ -26,6 +28,12 @@ export class Tileset extends decorate(Resource) {
fromJSON(json) {
let promise;
if (json.geometry) {
for (const i in json.geometry) {
const shapeJSON = json.geometry[i];
this._geometry[i] = shapeFromJSON(shapeJSON);
}
}
if (json.tileSize) {
this.tileSize = json.tileSize;
}
@ -39,6 +47,10 @@ export class Tileset extends decorate(Resource) {
});
}
geometry(index) {
return this._geometry[index];
}
get image() {
return this._image;
}