This diff adds:
* `type` support to `createFromObjects`. See https://doc.mapeditor.org/en/stable/manual/custom-properties/#typed-tiles for the description in Tiled; this is currently not supported as a parameter, and its usage is documented within the diff.
* `createFromObjects` will also examine the tileset. As per the above link, created objects are supposed to inherit properties from the tile with the same gid, but Phaser doesn't do that right now. After this diff it optionally will, by passing an additional parameter to `createFromObjects`.
> NOTE: I'd be very happy to make this a default behavior, but that's somewhat backwards breaking
* `createFromObjects` will attempt to use the same texture and frame as Tiled when creating the object. This will behave poorly if there is no such frame (for instance, if the texture were loaded with `this.load.image` instead of `this.load.spritesheet`, or using different geometry. However, the current behavior draws missingimage, so this is a strong improvement.
This commit is contained in:
Brown Dragon 2021-05-13 16:23:16 -04:00
parent 699e81e008
commit 02e67e682f
3 changed files with 197 additions and 42 deletions

View file

@ -0,0 +1,158 @@
/**
* @author Richard Davey <rich@photonstorm.com>
* @copyright 2021 Photon Storm Ltd.
* @license {@link https://opensource.org/licenses/MIT|MIT License}
*/
var Class = require('../utils/Class');
/**
* @classdesc
* An ObjectHelper helps tie objects with `gids` into the tileset that sits behind them.
*
* @class ObjectHelper
* @memberof Phaser.Tilemaps
* @ignore
* @constructor
* @since 3.54.0
*
* @param {Phaser.Tilemaps.Tileset[]} tilesets - The backing tileset data.
*/
var ObjectHelper = new Class({
initialize: function ObjectHelper (tilesets)
{
this.gids = [];
if (tilesets !== undefined)
{
for (var t = 0; t < tilesets.length; ++t)
{
var tileset = tilesets[t];
for (var i = 0; i < tileset.total; ++i)
{
this.gids[tileset.firstgid + i] = tileset;
}
}
}
this._gids = this.gids;
},
enabled: {
get: function ()
{
return !!this.gids;
},
set: function (v)
{
this.gids = v ? this._gids : undefined;
}
},
getTypeIncludingTile: function (obj)
{
if (obj.type !== undefined)
{
return obj.type;
}
if (!this.gids || obj.gid === undefined)
{
return undefined;
}
var tileset = this.gids[obj.gid];
if (!tileset)
{
return undefined;
}
var tileData = tileset.getTileData(obj.gid);
if (!tileData)
{
return undefined;
}
return tileData.type;
},
setTextureAndFrame: function (sprite, key, frame, obj)
{
if (((key === null) || (frame === null)) && this.gids && obj.gid !== undefined)
{
var tileset = this.gids[obj.gid];
if (tileset)
{
if (key === null && tileset.image !== undefined)
{
key = tileset.image.key;
}
if (frame === null)
{
frame = obj.gid - tileset.firstgid;
}
// If we can't satisfy the request, probably best to null it out rather than set a whole spritesheet or something.
if (!sprite.scene.textures.getFrame(key, frame))
{
key = null;
frame = null;
}
}
}
sprite.setTexture(key, frame);
},
setProperties: function (sprite, obj)
{
var propertieses = [];
if (this.gids !== undefined && obj.gid !== undefined)
{
var tileset = this.gids[obj.gid];
if (tileset !== undefined)
{
propertieses.push(tileset.getTileProperties(obj.gid));
}
}
propertieses.push(obj.properties);
for (var p = 0; p < propertieses.length; ++p)
{
var properties = propertieses[p];
if (properties === undefined)
{
continue;
}
// Set properties the class may have, or setData those it doesn't
if (Array.isArray(properties))
{
// Tiled objects custom properties format
properties.forEach(function (propData)
{
var key = propData['name'];
if (sprite[key] !== undefined)
{
sprite[key] = propData['value'];
}
else
{
sprite.setData(key, propData['value']);
}
});
}
else
{
for (var key in properties)
{
if (sprite[key] !== undefined)
{
sprite[key] = properties[key];
}
else
{
sprite.setData(key, properties[key]);
}
}
}
}
}
});
module.exports = ObjectHelper;

View file

@ -9,6 +9,7 @@ var DegToRad = require('../math/DegToRad');
var Formats = require('./Formats');
var GetFastValue = require('../utils/object/GetFastValue');
var LayerData = require('./mapdata/LayerData');
var ObjectHelper = require('./ObjectHelper');
var ORIENTATION = require('./const/ORIENTATION_CONST');
var Rotate = require('../math/Rotate');
var SpliceOne = require('../utils/array/SpliceOne');
@ -635,11 +636,12 @@ var Tilemap = new Class({
* This method will iterate through all of the objects defined in a Tiled Object Layer and then
* convert the matching results into Phaser Game Objects (by default, Sprites)
*
* Objects are matched on one of 3 criteria: The Object ID, the Object GID or the Object Name.
* Objects are matched on one of 4 criteria: The Object ID, the Object GID, the Object Name, or the Object Type.
*
* Within Tiled, Object IDs are unique per Object. Object GIDs, however, are shared by all objects
* using the same image. Finally, Object Names are strings and the same name can be used on multiple
* Objects in Tiled, they do not have to be unique.
* using the same image. Finally, Object Names and Types are strings and the same name can be used on multiple
* Objects in Tiled, they do not have to be unique; Names are specific to Objects while Types can be inherited
* from Object GIDs using the same image.
*
* You set the configuration parameter accordingly, based on which type of criteria you wish
* to match against. For example, to convert all items on an Object Layer with a `gid` of 26:
@ -666,7 +668,7 @@ var Tilemap = new Class({
* });
* ```
*
* You should only specify either `id`, `gid`, `name`, or none of them. Do not add more than
* You should only specify either `id`, `gid`, `name`, `type`, or none of them. Do not add more than
* one criteria to your config. If you do not specify any criteria, then _all_ objects in the
* Object Layer will be converted.
*
@ -692,8 +694,21 @@ var Tilemap = new Class({
* Custom object properties that do not exist as a Game Object property are set in the
* Game Objects {@link Phaser.GameObjects.GameObject#data data store}.
*
* You can use set a `container` property in the config. If given, the class will be added to
* Objects that are based on tiles (tilemap objects that are defined using the `gid` property) can be considered "hierarchical" by passing the third parameter `useTileset` true.
* Data such as texture, frame (assuming you've matched tileset and spritesheet geometries),
* `type` and `properties` will use the tileset information first and then override it with data set at the object level.
* For instance, a tileset which includes
* `[... a tileset of 16 elements...], [...ids 0, 1, and 2...], {id: 3, type: 'treadmill', speed:4}`
* and an object layer which includes
* `{id: 7, gid: 19, speed:5, rotation:90}`
* will be interpreted as though it were
* `{id: 7, gid:19, speed:5, rotation:90, type:'treadmill', texture:..., frame:3}`.
* You can then suppress this behavior by setting the boolean `ignoreTileset` for each `config` that should ignore
* object gid tilesets.
*
* You can set a `container` property in the config. If given, the class will be added to
* the Container instance instead of the Scene.
* You can set named texture-`key` and texture-`frame` properties, which will be set on the resultant object.
*
* Finally, you can provide an array of config objects, to convert multiple types of object in
* a single call:
@ -711,6 +726,10 @@ var Tilemap = new Class({
* {
* name: 'lava',
* classType: LavaTile
* },
* {
* type: 'endzone',
* classType: Phaser.GameObjects.Zone
* }
* ]);
* ```
@ -722,10 +741,11 @@ var Tilemap = new Class({
*
* @param {string} objectLayerName - The name of the Tiled object layer to create the Game Objects from.
* @param {Phaser.Types.Tilemaps.CreateFromObjectLayerConfig|Phaser.Types.Tilemaps.CreateFromObjectLayerConfig[]} config - A CreateFromObjects configuration object, or an array of them.
* @param {boolean} useTileset - True if objects that set gids should also search the underlying tile for properties and data.
*
* @return {Phaser.GameObjects.GameObject[]} An array containing the Game Objects that were created. Empty if invalid object layer, or no matching id/gid/name was found.
*/
createFromObjects: function (objectLayerName, config)
createFromObjects: function (objectLayerName, config, useTileset)
{
var results = [];
@ -738,6 +758,8 @@ var Tilemap = new Class({
return results;
}
var objectHelper = new ObjectHelper(useTileset ? this.tilesets : undefined);
if (!Array.isArray(config))
{
config = [ config ];
@ -752,6 +774,8 @@ var Tilemap = new Class({
var id = GetFastValue(singleConfig, 'id', null);
var gid = GetFastValue(singleConfig, 'gid', null);
var name = GetFastValue(singleConfig, 'name', null);
var type = GetFastValue(singleConfig, 'type', null);
objectHelper.enabled = !GetFastValue(singleConfig, 'ignoreTileset', null);
var obj;
var toConvert = [];
@ -762,10 +786,11 @@ var Tilemap = new Class({
obj = objects[s];
if (
(id === null && gid === null && name === null) ||
(id === null && gid === null && name === null && type === null) ||
(id !== null && obj.id === id) ||
(gid !== null && obj.gid === gid) ||
(name !== null && obj.name === name)
(name !== null && obj.name === name) ||
(type !== null && objectHelper.getTypeIncludingTile(obj) === type)
)
{
toConvert.push(obj);
@ -788,7 +813,7 @@ var Tilemap = new Class({
sprite.setName(obj.name);
sprite.setPosition(obj.x, obj.y);
sprite.setTexture(texture, frame);
objectHelper.setTextureAndFrame(sprite, texture, frame, obj);
if (obj.width)
{
@ -830,37 +855,7 @@ var Tilemap = new Class({
sprite.visible = false;
}
// Set properties the class may have, or setData those it doesn't
if (Array.isArray(obj.properties))
{
// Tiled objects custom properties format
obj.properties.forEach(function (propData)
{
var key = propData['name'];
if (sprite[key] !== undefined)
{
sprite[key] = propData['value'];
}
else
{
sprite.setData(key, propData['value']);
}
});
}
else
{
for (var key in obj.properties)
{
if (sprite[key] !== undefined)
{
sprite[key] = obj.properties[key];
}
else
{
sprite.setData(key, obj.properties[key]);
}
}
}
objectHelper.setProperties(sprite, obj);
if (container)
{

View file

@ -5,9 +5,11 @@
* @property {number} [id] - A unique Object ID to convert.
* @property {number} [gid] - An Object GID to convert.
* @property {string} [name] - An Object Name to convert.
* @property {string} [type] - An Object Type to convert.
* @property {Phaser.GameObjects.GameObject} [classType=Phaser.GameObjects.Sprite] - A custom class type to convert the objects in to.
* @property {boolean} [ignoreTileset] - By default, gid-based objects copy properties and respect the type of the tile at that gid and treat the object as an override. If this is true, they don't, and use only the fields set on the object itself.
* @property {Phaser.Scene} [scene] - A Scene reference, passed to the Game Objects constructors.
* @property {Phaser.GameObjects.Container} [container] - Optional Container to which the Game Objects are added.
* @property {(string|Phaser.Textures.Texture)} [key] - Optional key of a Texture to be used, as stored in the Texture Manager, or a Texture instance.
* @property {(string|number)} [frame] - Optional name or index of the frame within the Texture.
* @property {(string|Phaser.Textures.Texture)} [key] - Optional key of a Texture to be used, as stored in the Texture Manager, or a Texture instance. If omitted, the object's gid's tileset key is used if available.
* @property {(string|number)} [frame] - Optional name or index of the frame within the Texture. If omitted, the tileset index is used, assuming that spritesheet frames exactly match tileset indices & geometries -- if available.
*/