//@ts-check
///
///
/**
* This is a declaration of an extension for GDevelop 5.
*
* ℹ️ Run `node import-GDJS-Runtime.js` (in newIDE/app/scripts) if you make any change
* to this extension file or to any other *.js file that you reference inside.
*
* The file must be named "JsExtension.js", otherwise GDevelop won't load it.
* ⚠️ If you make a change and the extension is not loaded, open the developer console
* and search for any errors.
*
* More information on https://github.com/4ian/GDevelop/blob/master/newIDE/README-extensions.md
*/
/**
* @param {gd.PlatformExtension} extension
* @param {(translationSource: string) => string} _
* @param {GDNamespace} gd
*/
const defineTileMap = function (extension, _, gd) {
var objectTileMap = new gd.ObjectJsImplementation();
objectTileMap.updateProperty = function (propertyName, newValue) {
const objectContent = this.content;
if (propertyName === 'tilemapJsonFile') {
objectContent.tilemapJsonFile = newValue;
return true;
}
if (propertyName === 'tilesetJsonFile') {
objectContent.tilesetJsonFile = newValue;
return true;
}
if (propertyName === 'tilemapAtlasImage') {
objectContent.tilemapAtlasImage = newValue;
return true;
}
if (propertyName === 'displayMode') {
const normalizedValue = newValue.toLowerCase();
if (
normalizedValue === 'visible' ||
normalizedValue === 'all' ||
normalizedValue === 'index'
) {
objectContent.displayMode = normalizedValue;
return true;
}
return false;
}
if (propertyName === 'layerIndex') {
objectContent.layerIndex = parseFloat(newValue);
return true;
}
if (propertyName === 'levelIndex') {
objectContent.levelIndex = parseFloat(newValue);
return true;
}
if (propertyName === 'animationSpeedScale') {
objectContent.animationSpeedScale = parseFloat(newValue);
return true;
}
if (propertyName === 'animationFps') {
objectContent.animationFps = parseFloat(newValue);
return true;
}
return false;
};
objectTileMap.getProperties = function () {
var objectProperties = new gd.MapStringPropertyDescriptor();
const objectContent = this.content;
objectProperties.set(
'tilemapJsonFile',
new gd.PropertyDescriptor(objectContent.tilemapJsonFile)
.setType('resource')
.addExtraInfo('tilemap')
.addExtraInfo('json')
.setLabel(_('Tilemap file (Tiled or LDtk)'))
.setDescription(
_('This is the file that was saved or exported from Tiled or LDtk.')
)
.setGroup(_('LDtk or Tiled'))
);
objectProperties.set(
'tilesetJsonFile',
new gd.PropertyDescriptor(objectContent.tilesetJsonFile || '')
.setType('resource')
.addExtraInfo('tileset')
.addExtraInfo('json')
.setLabel(_('Tileset JSON file (optional)'))
.setDescription(
_(
"Optional: specify this if you've saved the tileset in a different file as the Tiled tilemap."
)
)
.setGroup(_('Tiled only'))
);
objectProperties.set(
'tilemapAtlasImage',
new gd.PropertyDescriptor(objectContent.tilemapAtlasImage)
.setType('resource')
.addExtraInfo('image')
.setLabel(_('Atlas image'))
.setDescription(_('The Atlas image containing the tileset.'))
.setGroup(_('Tiled only'))
);
objectProperties.set(
'displayMode',
new gd.PropertyDescriptor(objectContent.displayMode)
.setType('choice')
.addChoice('visible', _('Visible layers'))
.addChoice('all', _('All layers'))
.addChoice('index', _('Only the layer with the specified index'))
.setLabel(_('Display mode'))
.setGroup(_('Appearance'))
);
objectProperties.set(
'layerIndex',
new gd.PropertyDescriptor(objectContent.layerIndex.toString())
.setType('number')
.setLabel(_('Layer index to display'))
.setDescription(
_(
'If "index" is selected as the display mode, this is the index of the layer to display.'
)
)
.setGroup(_('Appearance'))
);
objectProperties.set(
'levelIndex',
new gd.PropertyDescriptor((objectContent.levelIndex || 0).toString())
.setType('number')
.setLabel(_('Level index to display'))
.setDescription(_('Select which level to render via its index (LDtk)'))
.setGroup(_('Appearance'))
);
objectProperties.set(
'animationSpeedScale',
new gd.PropertyDescriptor(objectContent.animationSpeedScale.toString())
.setType('number')
.setLabel(_('Animation speed scale'))
.setGroup(_('Animation'))
);
objectProperties.set(
'animationFps',
new gd.PropertyDescriptor(objectContent.animationFps.toString())
.setType('number')
.setLabel(_('Animation FPS'))
.setGroup(_('Animation'))
);
return objectProperties;
};
objectTileMap.content = {
tilemapJsonFile: '',
tilesetJsonFile: '',
tilemapAtlasImage: '',
displayMode: 'visible',
layerIndex: 0,
levelIndex: 0,
animationSpeedScale: 1,
animationFps: 4,
};
objectTileMap.updateInitialInstanceProperty = function (
instance,
propertyName,
newValue
) {
return false;
};
objectTileMap.getInitialInstanceProperties = function (instance) {
const instanceProperties = new gd.MapStringPropertyDescriptor();
return instanceProperties;
};
const object = extension
.addObject(
'TileMap',
_('External Tilemap (Tiled/LDtk)'),
_('Tilemap imported from external editors like LDtk or Tiled.'),
'JsPlatform/Extensions/tile_map.svg',
objectTileMap
)
.setCategory('Advanced')
.addDefaultBehavior('ResizableCapability::ResizableBehavior')
.addDefaultBehavior('ScalableCapability::ScalableBehavior')
.addDefaultBehavior('OpacityCapability::OpacityBehavior')
.setIncludeFile('Extensions/TileMap/tilemapruntimeobject.js')
.addIncludeFile('Extensions/TileMap/TileMapRuntimeManager.js')
.addIncludeFile('Extensions/TileMap/tilemapruntimeobject-pixi-renderer.js')
.addIncludeFile('Extensions/TileMap/pixi-tilemap/dist/pixi-tilemap.umd.js')
.addIncludeFile('Extensions/TileMap/pako/dist/pako.min.js')
.addIncludeFile('Extensions/TileMap/helper/TileMapHelper.js');
object
.addCondition(
'TilemapJsonFile',
_('Tilemap file (Tiled or LDtk)'),
_('Check the tilemap file (Tiled or LDtk) being used.'),
_('The tilemap file of _PARAM0_ is _PARAM1_'),
'',
'JsPlatform/Extensions/tile_map.svg',
'JsPlatform/Extensions/tile_map.svg'
)
.addParameter('object', _('Tile map'), 'TileMap', false)
.addParameter(
'tilemapResource',
_('Tilemap file (Tiled or LDtk)'),
'',
false
)
.getCodeExtraInformation()
.setFunctionName('isTilemapJsonFile');
object
.addAction(
'SetTilemapJsonFile',
_('Tilemap file (Tiled or LDtk)'),
_(
'Set the Tiled or LDtk file containing the Tilemap data to display. This is usually the main file exported from Tiled/LDtk.'
),
_('Set the tilemap file of _PARAM0_ to _PARAM1_'),
'',
'JsPlatform/Extensions/tile_map.svg',
'JsPlatform/Extensions/tile_map.svg'
)
.addParameter('object', _('Tile map'), 'TileMap', false)
.addParameter(
'tilemapResource',
_('Tilemap file (Tiled or LDtk)'),
'',
false
)
.getCodeExtraInformation()
.setFunctionName('setTilemapJsonFile');
object
.addCondition(
'TilesetJsonFile',
_('Tileset JSON file'),
_('Check the tileset JSON file being used.'),
_('The tileset JSON file of _PARAM0_ is _PARAM1_'),
'',
'JsPlatform/Extensions/tile_map.svg',
'JsPlatform/Extensions/tile_map.svg'
)
.addParameter('object', _('Tile map'), 'TileMap', false)
.addParameter('tilesetResource', _('Tileset JSON file'), '', false)
.getCodeExtraInformation()
.setFunctionName('isTilesetJsonFile');
object
.addAction(
'SetTilesetJsonFile',
_('Tileset JSON file'),
_(
'Set the JSON file with the tileset data (sometimes that is embedded in the Tilemap, so not needed)'
),
_('Set the tileset JSON file of _PARAM0_ to _PARAM1_'),
'',
'JsPlatform/Extensions/tile_map.svg',
'JsPlatform/Extensions/tile_map.svg'
)
.addParameter('object', _('Tile map'), 'TileMap', false)
.addParameter('tilesetResource', _('Tileset JSON file'), '', false)
.getCodeExtraInformation()
.setFunctionName('setTilesetJsonFile');
object
.addCondition(
'DisplayMode',
_('Display mode'),
_('Compare the value of the display mode.'),
_('The display mode of _PARAM0_ is _PARAM1_'),
'',
'JsPlatform/Extensions/tile_map.svg',
'JsPlatform/Extensions/tile_map.svg'
)
.addParameter('object', _('Tile map'), 'TileMap', false)
.addParameter(
'stringWithSelector',
_('Display mode'),
'["visible", "all", "index"]',
false
)
.getCodeExtraInformation()
.setFunctionName('isDisplayMode');
object
.addAction(
'SetDisplayMode',
_('Display mode'),
_('Set the display mode'),
_('Set the display mode of _PARAM0_ to _PARAM1_'),
'',
'JsPlatform/Extensions/tile_map.svg',
'JsPlatform/Extensions/tile_map.svg'
)
.addParameter('object', _('Tile map'), 'TileMap', false)
.addParameter(
'stringWithSelector',
_('Display mode'),
'["visible", "all", "index"]',
false
)
.getCodeExtraInformation()
.setFunctionName('setDisplayMode');
object
.addCondition(
'LayerIndex',
_('Layer index'),
_('Compare the value of the layer index.'),
_('the layer index'),
'',
'JsPlatform/Extensions/tile_map.svg',
'JsPlatform/Extensions/tile_map.svg'
)
.addParameter('object', _('Tile map'), 'TileMap', false)
.useStandardRelationalOperatorParameters(
'number',
gd.ParameterOptions.makeNewOptions()
)
.getCodeExtraInformation()
.setFunctionName('getLayerIndex');
object
.addAction(
'SetLayerIndex',
_('Layer index'),
_('Set the layer index of the Tilemap.'),
_('the layer index'),
'',
'JsPlatform/Extensions/tile_map.svg',
'JsPlatform/Extensions/tile_map.svg'
)
.addParameter('object', _('Tile map'), 'TileMap', false)
.useStandardOperatorParameters(
'number',
gd.ParameterOptions.makeNewOptions()
)
.getCodeExtraInformation()
.setFunctionName('setLayerIndex')
.setGetter('getLayerIndex');
object
.addExpression(
'LayerIndex',
_('Layer index'),
_('Get the layer index being displayed'),
'',
'JsPlatform/Extensions/tile_map.svg'
)
.addParameter('object', _('Tile map'), 'TileMap', false)
.getCodeExtraInformation()
.setFunctionName('getLayerIndex');
object
.addExpressionAndCondition(
'number',
'LevelIndex',
_('Level index'),
_('the level index being displayed.'),
_('the level index'),
'',
'JsPlatform/Extensions/tile_map.svg'
)
.addParameter('object', _('Tile map'), 'TileMap', false)
.useStandardParameters('number', gd.ParameterOptions.makeNewOptions())
.setFunctionName('getLevelIndex');
object
.addCondition(
'AnimationSpeedScale',
_('Animation speed scale'),
_('Compare the animation speed scale.'),
_('the animation speed scale'),
'',
'JsPlatform/Extensions/tile_map.svg',
'JsPlatform/Extensions/tile_map.svg'
)
.addParameter('object', _('Tile map'), 'TileMap', false)
.useStandardRelationalOperatorParameters(
'number',
gd.ParameterOptions.makeNewOptions().setDescription(
_('Speed scale to compare to (1 by default)')
)
)
.getCodeExtraInformation()
.setFunctionName('getAnimationSpeedScale');
object
.addAction(
'SetAnimationSpeedScale',
_('Animation speed scale'),
_('Set the animation speed scale of the Tilemap.'),
_('the animation speed scale'),
'',
'JsPlatform/Extensions/tile_map.svg',
'JsPlatform/Extensions/tile_map.svg'
)
.addParameter('object', _('Tile map'), 'TileMap', false)
.useStandardOperatorParameters(
'number',
gd.ParameterOptions.makeNewOptions().setDescription(
_('Speed scale (1 by default)')
)
)
.getCodeExtraInformation()
.setFunctionName('setAnimationSpeedScale')
.setGetter('getAnimationSpeedScale');
object
.addExpression(
'AnimationSpeedScale',
_('Animation speed scale'),
_('Get the Animation speed scale'),
'',
'JsPlatform/Extensions/tile_map.svg'
)
.addParameter('object', _('Tile map'), 'TileMap', false)
.getCodeExtraInformation()
.setFunctionName('getAnimationSpeedScale');
object
.addCondition(
'AnimationFps',
_('Animation speed (FPS)'),
_('Compare the animation speed.'),
_('the animation speed (FPS)'),
'',
'JsPlatform/Extensions/tile_map.svg',
'JsPlatform/Extensions/tile_map.svg'
)
.addParameter('object', _('Tile map'), 'TileMap', false)
.useStandardRelationalOperatorParameters(
'number',
gd.ParameterOptions.makeNewOptions().setDescription(
_('Animation speed to compare to (in frames per second)')
)
)
.getCodeExtraInformation()
.setFunctionName('getAnimationFps');
object
.addAction(
'SetAnimationFps',
_('Animation speed (FPS)'),
_('Set the animation speed of the Tilemap.'),
_('the animation speed (FPS)'),
'',
'JsPlatform/Extensions/tile_map.svg',
'JsPlatform/Extensions/tile_map.svg'
)
.addParameter('object', _('Tile map'), 'TileMap', false)
.useStandardOperatorParameters(
'number',
gd.ParameterOptions.makeNewOptions().setDescription(
_('Animation speed (in frames per second)')
)
)
.getCodeExtraInformation()
.setFunctionName('setAnimationFps')
.setGetter('getAnimationFps');
object
.addExpression(
'AnimationFps',
_('Animation speed (FPS)'),
_('Get the animation speed (in frames per second)'),
'',
'JsPlatform/Extensions/tile_map.svg'
)
.addParameter('object', _('Tile map'), 'TileMap', false)
.getCodeExtraInformation()
.setFunctionName('getAnimationFps');
// Deprecated
object
.addAction(
'Scale',
_('Scale'),
_('Modify the scale of the specified object.'),
_('the scale'),
_('Size'),
'res/actions/scale24_black.png',
'res/actions/scale_black.png'
)
.addParameter('object', _('Tile map'), 'TileMap', false)
.useStandardOperatorParameters(
'number',
gd.ParameterOptions.makeNewOptions().setDescription(
_('Scale (1 by default)')
)
)
.markAsAdvanced()
.setHidden()
.getCodeExtraInformation()
.setFunctionName('setScale')
.setGetter('getScale');
// Deprecated
object
.addExpressionAndConditionAndAction(
'number',
'ScaleX',
_('Scale on X axis'),
_("the width's scale of an object"),
_("the width's scale"),
_('Size'),
'res/actions/scaleWidth24_black.png'
)
.addParameter('object', _('Tile map'), 'TileMap', false)
.useStandardParameters(
'number',
gd.ParameterOptions.makeNewOptions().setDescription(
_('Scale (1 by default)')
)
)
.markAsAdvanced()
.setHidden()
.setFunctionName('setScaleX')
.setGetter('getScaleX');
// Deprecated
object
.addExpressionAndConditionAndAction(
'number',
'ScaleY',
_('Scale on Y axis'),
_("the height's scale of an object"),
_("the height's scale"),
_('Size'),
'res/actions/scaleHeight24_black.png'
)
.addParameter('object', _('Tile map'), 'TileMap', false)
.useStandardParameters(
'number',
gd.ParameterOptions.makeNewOptions().setDescription(
_('Scale (1 by default)')
)
)
.markAsAdvanced()
.setHidden()
.setFunctionName('setScaleY')
.setGetter('getScaleY');
// Deprecated
object
.addAction(
'Width',
_('Width'),
_('Change the width of an object.'),
_('the width'),
_('Size'),
'res/actions/scaleWidth24_black.png',
'res/actions/scaleWidth_black.png'
)
.addParameter('object', _('Tile map'), 'TileMap', false)
.useStandardOperatorParameters(
'number',
gd.ParameterOptions.makeNewOptions()
)
.markAsAdvanced()
.setHidden()
.getCodeExtraInformation()
.setFunctionName('setWidth');
// Deprecated
object
.addAction(
'Height',
_('Height'),
_('Change the height of an object.'),
_('the height'),
_('Size'),
'res/actions/scaleHeight24_black.png',
'res/actions/scaleHeight_black.png'
)
.addParameter('object', _('Tile map'), 'TileMap', false)
.useStandardOperatorParameters(
'number',
gd.ParameterOptions.makeNewOptions()
)
.markAsAdvanced()
.setHidden()
.getCodeExtraInformation()
.setFunctionName('setHeight');
};
/**
* @param {gd.PlatformExtension} extension
* @param {(translationSource: string) => string} _
* @param {GDNamespace} gd
*/
const defineSimpleTileMap = function (extension, _, gd) {
var objectSimpleTileMap = new gd.ObjectJsImplementation();
objectSimpleTileMap.updateProperty = function (propertyName, newValue) {
const objectContent = this.content;
if (propertyName === 'atlasImage') {
objectContent.atlasImage = newValue;
return true;
}
if (propertyName === 'columnCount') {
objectContent.columnCount = parseFloat(newValue);
return true;
}
if (propertyName === 'rowCount') {
objectContent.rowCount = parseFloat(newValue);
return true;
}
if (propertyName === 'tileSize') {
objectContent.tileSize = parseFloat(newValue);
return true;
}
if (propertyName === 'tilesWithHitBox') {
objectContent.tilesWithHitBox = newValue;
return true;
}
return false;
};
objectSimpleTileMap.getProperties = function () {
var objectProperties = new gd.MapStringPropertyDescriptor();
const objectContent = this.content;
objectProperties.set(
'columnCount',
new gd.PropertyDescriptor(
(typeof objectContent.columnCount === 'undefined'
? 4
: objectContent.columnCount
).toString()
)
.setType('number')
.setLabel(_('Columns'))
.setDescription(_('Number of columns.'))
.setHidden(true)
);
objectProperties.set(
'rowCount',
new gd.PropertyDescriptor(
(typeof objectContent.rowCount === 'undefined'
? 4
: objectContent.rowCount
).toString()
)
.setType('number')
.setLabel(_('Rows'))
.setDescription(_('Number of rows.'))
.setHidden(true)
);
objectProperties.set(
'tileSize',
new gd.PropertyDescriptor(
(typeof objectContent.tileSize === 'undefined'
? 8
: objectContent.tileSize
).toString()
)
.setType('number')
.setLabel(_('Tile size'))
.setDescription(_('Tile size in pixels.'))
.setHidden(true) // Hidden because a full editor is needed to recompute column/row counts
);
objectProperties.set(
'tilesWithHitBox',
new gd.PropertyDescriptor(objectContent.tilesWithHitBox || '')
.setType('string')
.setLabel(_('Tile ids with hit box'))
.setDescription(
_('The list of tile ids with a hit box (separated by commas).')
)
.setHidden(true)
);
objectProperties.set(
'atlasImage',
new gd.PropertyDescriptor(objectContent.atlasImage)
.setType('resource')
.addExtraInfo('image')
.setLabel(_('Atlas image'))
.setDescription(_('The Atlas image containing the tileset.'))
.setHidden(true) // Hidden because a full editor is needed to recompute column/row counts
);
return objectProperties;
};
objectSimpleTileMap.content = {
atlasImage: '',
rowCount: 1,
columnCount: 1,
tileSize: 8,
tilesWithHitBox: '',
};
objectSimpleTileMap.updateInitialInstanceProperty = function (
instance,
propertyName,
newValue
) {
if (propertyName === 'tilemap') {
instance.setRawStringProperty('tilemap', newValue);
return true;
}
return false;
};
objectSimpleTileMap.getInitialInstanceProperties = function (instance) {
var instanceProperties = new gd.MapStringPropertyDescriptor();
instanceProperties
.getOrCreate('tilemap')
.setValue(instance.getRawStringProperty('tileMap'))
.setType('string')
.setLabel('Tilemap')
.setHidden(true);
return instanceProperties;
};
const object = extension
.addObject(
'SimpleTileMap',
_('Tile map'),
_('Grid-based map built from reusable tiles.'),
'JsPlatform/Extensions/tile_map.svg',
objectSimpleTileMap
)
.setCategory('General')
.setOpenFullEditorLabel(_('Edit tileset and collisions'))
.addDefaultBehavior('ResizableCapability::ResizableBehavior')
.addDefaultBehavior('ScalableCapability::ScalableBehavior')
.addDefaultBehavior('OpacityCapability::OpacityBehavior')
.setIncludeFile('Extensions/TileMap/simpletilemapruntimeobject.js')
.addIncludeFile('Extensions/TileMap/TileMapRuntimeManager.js')
.addIncludeFile('Extensions/TileMap/tilemapruntimeobject-pixi-renderer.js')
.addIncludeFile('Extensions/TileMap/pixi-tilemap/dist/pixi-tilemap.umd.js')
.addIncludeFile('Extensions/TileMap/collision/TransformedTileMap.js')
.addIncludeFile('Extensions/TileMap/pako/dist/pako.min.js')
.addIncludeFile('Extensions/TileMap/helper/TileMapHelper.js');
object
.addExpression(
'TilesetColumnCount',
_('Tileset column count'),
_('Get the number of columns in the tileset.'),
'',
'JsPlatform/Extensions/tile_map.svg'
)
.addParameter('object', _('Tile map'), 'SimpleTileMap', false)
.setFunctionName('getTilesetColumnCount');
object
.addExpression(
'TilesetRowCount',
_('Tileset row count'),
_('Get the number of rows in the tileset.'),
'',
'JsPlatform/Extensions/tile_map.svg'
)
.addParameter('object', _('Tile map'), 'SimpleTileMap', false)
.setFunctionName('getTilesetRowCount');
object
.addExpression(
'TileCenterX',
_('Scene X coordinate of tile'),
_('Get the scene X position of the center of the tile.'),
'',
'JsPlatform/Extensions/tile_map.svg'
)
.addParameter('object', _('Tile map'), 'SimpleTileMap', false)
.addParameter('number', _('Grid X'), '', false)
.addParameter('number', _('Grid Y'), '', false)
.setFunctionName('getSceneXCoordinateOfTileCenter');
object
.addExpression(
'TileCenterY',
_('Scene Y coordinate of tile'),
_('Get the scene Y position of the center of the tile.'),
'',
'JsPlatform/Extensions/tile_map.svg'
)
.addParameter('object', _('Tile map'), 'SimpleTileMap', false)
.addParameter('number', _('Grid X'), '', false)
.addParameter('number', _('Grid Y'), '', false)
.setFunctionName('getSceneYCoordinateOfTileCenter');
object
.addExpression(
'GridX',
_('Tile map grid column coordinate'),
_(
'Get the grid column coordinates in the tile map corresponding to the scene coordinates.'
),
'',
'JsPlatform/Extensions/tile_map.svg'
)
.addParameter('object', _('Tile map'), 'SimpleTileMap', false)
.addParameter('number', _('Position X'), '', false)
.addParameter('number', _('Position Y'), '', false)
.setFunctionName('getColumnIndexAtPosition');
object
.addExpression(
'GridY',
_('Tile map grid row coordinate'),
_(
'Get the grid row coordinates in the tile map corresponding to the scene coordinates.'
),
'',
'JsPlatform/Extensions/tile_map.svg'
)
.addParameter('object', _('Tile map'), 'SimpleTileMap', false)
.addParameter('number', _('Position X'), '', false)
.addParameter('number', _('Position Y'), '', false)
.setFunctionName('getRowIndexAtPosition');
object
.addExpressionAndConditionAndAction(
'number',
'TileIdAtPosition',
_('Tile (at position)'),
_('the id of the tile at the scene coordinates'),
_('the tile id in _PARAM0_ at scene coordinates _PARAM3_ ; _PARAM4_'),
'',
'JsPlatform/Extensions/tile_map.svg'
)
.addParameter('object', _('Tile map'), 'SimpleTileMap', false)
.useStandardParameters('number', gd.ParameterOptions.makeNewOptions())
.addParameter('number', _('Position X'), '', false)
.addParameter('number', _('Position Y'), '', false)
.setFunctionName('setTileAtPosition')
.setGetter('getTileAtPosition');
object
.addAction(
'FlipTileOnYAtPosition',
_('Flip tile vertically (at position)'),
_('Flip tile vertically at scene coordinates.'),
_(
'Flip tile vertically in _PARAM0_ at scene coordinates _PARAM1_ ; _PARAM2_: _PARAM3_'
),
_('Effects'),
'res/actions/flipY24.png',
'res/actions/flipY.png'
)
.addParameter('object', _('Tile map'), 'SimpleTileMap', false)
.addParameter('number', _('Position X'), '', false)
.addParameter('number', _('Position Y'), '', false)
.addParameter('yesorno', _('Flip vertically'), '', false)
.setDefaultValue('false')
.setFunctionName('flipTileOnYAtPosition');
object
.addAction(
'FlipTileOnXAtPosition',
_('Flip tile horizontally (at position)'),
_('Flip tile horizontally at scene coordinates.'),
_(
'Flip tile horizontally in _PARAM0_ at scene coordinates _PARAM1_ ; _PARAM2_: _PARAM3_'
),
_('Effects'),
'res/actions/flipX24.png',
'res/actions/flipX.png'
)
.addParameter('object', _('Tile map'), 'SimpleTileMap', false)
.addParameter('number', _('Position X'), '', false)
.addParameter('number', _('Position Y'), '', false)
.addParameter('yesorno', _('Flip horizontally'), '', false)
.setDefaultValue('false')
.setFunctionName('flipTileOnXAtPosition');
object
.addAction(
'RemoveTileAtPosition',
_('Remove tile (at position)'),
_('Remove the tile at the scene coordinates.'),
_('Remove tile in _PARAM0_ at scene coordinates _PARAM1_ ; _PARAM2_'),
'',
'JsPlatform/Extensions/tile_map.svg',
'JsPlatform/Extensions/tile_map.svg'
)
.addParameter('object', _('Tile map'), 'SimpleTileMap', false)
.addParameter('number', _('Position X'), '', false)
.addParameter('number', _('Position Y'), '', false)
.getCodeExtraInformation()
.setFunctionName('removeTileAtPosition');
object
.addExpressionAndConditionAndAction(
'number',
'TileIdAtGrid',
_('Tile (on the grid)'),
_('the id of the tile at the grid coordinates'),
_('the tile id at grid coordinates _PARAM3_ ; _PARAM4_'),
'',
'JsPlatform/Extensions/tile_map.svg'
)
.addParameter('object', _('Tile map'), 'SimpleTileMap', false)
.useStandardParameters('number', gd.ParameterOptions.makeNewOptions())
.addParameter('number', _('Grid X'), '', false)
.addParameter('number', _('Grid Y'), '', false)
.setFunctionName('setTileAtGridCoordinates')
.setGetter('getTileAtGridCoordinates');
object
.addAction(
'FlipTileOnYAtGridCoordinates',
_('Flip tile vertically (on the grid)'),
_('Flip tile vertically at grid coordinates.'),
_(
'Flip tile vertically in _PARAM0_ at grid coordinates _PARAM1_ ; _PARAM2_: _PARAM3_'
),
_('Effects'),
'res/actions/flipY24.png',
'res/actions/flipY.png'
)
.addParameter('object', _('Tile map'), 'SimpleTileMap', false)
.addParameter('number', _('Grid X'), '', false)
.addParameter('number', _('Grid Y'), '', false)
.addParameter('yesorno', _('Flip vertically'), '', false)
.setDefaultValue('false')
.setFunctionName('flipTileOnYAtGridCoordinates');
object
.addAction(
'FlipTileOnXAtGridCoordinates',
_('Flip tile horizontally (on the grid)'),
_('Flip tile horizontally at grid coordinates.'),
_(
'Flip tile horizontally in _PARAM0_ at grid coordinates _PARAM1_ ; _PARAM2_: _PARAM3_'
),
_('Effects'),
'res/actions/flipX24.png',
'res/actions/flipX.png'
)
.addParameter('object', _('Tile map'), 'SimpleTileMap', false)
.addParameter('number', _('Grid X'), '', false)
.addParameter('number', _('Grid Y'), '', false)
.addParameter('yesorno', _('Flip horizontally'), '', false)
.setDefaultValue('false')
.setFunctionName('flipTileOnXAtGridCoordinates');
object
.addAction(
'RemoveTileAtGridCoordinates',
_('Remove tile (on the grid)'),
_('Remove the tile at the grid coordinates.'),
_('Remove tile in _PARAM0_ at grid coordinates _PARAM1_ ; _PARAM2_'),
'',
'JsPlatform/Extensions/tile_map.svg',
'JsPlatform/Extensions/tile_map.svg'
)
.addParameter('object', _('Tile map'), 'SimpleTileMap', false)
.addParameter('number', _('Grid X'), '', false)
.addParameter('number', _('Grid Y'), '', false)
.getCodeExtraInformation()
.setFunctionName('removeTileAtGridCoordinates');
object
.addCondition(
'IsTileFlippedOnXAtPosition',
_('Tile flipped horizontally (at position)'),
_('Check if tile at scene coordinates is flipped horizontally.'),
_(
'The tile in _PARAM0_ at scene coordinates _PARAM1_ ; _PARAM2_ is flipped horizontally'
),
_('Effects'),
'res/actions/flipX24.png',
'res/actions/flipX.png'
)
.addParameter('object', _('Tile map'), 'SimpleTileMap', false)
.addParameter('number', _('Position X'), '', false)
.addParameter('number', _('Position Y'), '', false)
.getCodeExtraInformation()
.setFunctionName('isTileFlippedOnXAtPosition');
object
.addCondition(
'IsTileFlippedOnYAtPosition',
_('Tile flipped vertically (at position)'),
_('Check if tile at scene coordinates is flipped vertically.'),
_(
'The tile in _PARAM0_ at scene coordinates _PARAM1_ ; _PARAM2_ is flipped vertically'
),
_('Effects'),
'res/actions/flipY24.png',
'res/actions/flipY.png'
)
.addParameter('object', _('Tile map'), 'SimpleTileMap', false)
.addParameter('number', _('Position X'), '', false)
.addParameter('number', _('Position Y'), '', false)
.getCodeExtraInformation()
.setFunctionName('isTileFlippedOnYAtPosition');
object
.addCondition(
'IsTileFlippedOnXAtGridCoordinates',
_('Tile flipped horizontally (on the grid)'),
_('Check if tile at grid coordinates is flipped horizontally.'),
_(
'The tile in _PARAM0_ at grid coordinates _PARAM1_ ; _PARAM2_ is flipped horizontally'
),
_('Effects'),
'res/actions/flipX24.png',
'res/actions/flipX.png'
)
.addParameter('object', _('Tile map'), 'SimpleTileMap', false)
.addParameter('number', _('Grid X'), '', false)
.addParameter('number', _('Grid Y'), '', false)
.getCodeExtraInformation()
.setFunctionName('isTileFlippedOnXAtGridCoordinates');
object
.addCondition(
'IsTileFlippedOnYAtGridCoordinates',
_('Tile flipped vertically (on the grid)'),
_('Check if tile at grid coordinates is flipped vertically.'),
_(
'The tile in _PARAM0_ at grid coordinates _PARAM1_ ; _PARAM2_ is flipped vertically'
),
_('Effects'),
'res/actions/flipY24.png',
'res/actions/flipY.png'
)
.addParameter('object', _('Tile map'), 'SimpleTileMap', false)
.addParameter('number', _('Grid X'), '', false)
.addParameter('number', _('Grid Y'), '', false)
.getCodeExtraInformation()
.setFunctionName('isTileFlippedOnYAtGridCoordinates');
object
.addExpressionAndConditionAndAction(
'number',
'GridRowCount',
_('Grid row count'),
_('the grid row count in the tile map'),
_('the grid row count'),
_('Size'),
'res/actions/scaleHeight24_black.png'
)
.addParameter('object', _('Tile map'), 'SimpleTileMap', false)
.useStandardParameters('number', gd.ParameterOptions.makeNewOptions())
.setFunctionName('setGridRowCount')
.setGetter('getGridRowCount');
object
.addExpressionAndConditionAndAction(
'number',
'GridColumnCount',
_('Grid column count'),
_('the grid column count in the tile map'),
_('the grid column count'),
_('Size'),
'res/actions/scaleWidth24_black.png'
)
.addParameter('object', _('Tile map'), 'SimpleTileMap', false)
.useStandardParameters('number', gd.ParameterOptions.makeNewOptions())
.setFunctionName('setGridColumnCount')
.setGetter('getGridColumnCount');
};
/**
* @param {gd.PlatformExtension} extension
* @param {(translationSource: string) => string} _
* @param {GDNamespace} gd
*/
const defineCollisionMask = function (extension, _, gd) {
var collisionMaskObject = new gd.ObjectJsImplementation();
collisionMaskObject.updateProperty = function (propertyName, newValue) {
const objectContent = this.content;
if (propertyName === 'tilemapJsonFile') {
objectContent.tilemapJsonFile = newValue;
return true;
}
if (propertyName === 'tilesetJsonFile') {
objectContent.tilesetJsonFile = newValue;
return true;
}
if (propertyName === 'collisionMaskTag') {
objectContent.collisionMaskTag = newValue;
return true;
}
if (propertyName === 'layerIndex') {
objectContent.layerIndex = parseFloat(newValue);
return true;
}
if (propertyName === 'useAllLayers') {
objectContent.useAllLayers = newValue === '1';
return true;
}
if (propertyName === 'debugMode') {
objectContent.debugMode = newValue === '1';
return true;
}
if (propertyName === 'fillColor') {
objectContent.fillColor = newValue;
return true;
}
if (propertyName === 'outlineColor') {
objectContent.outlineColor = newValue;
return true;
}
if (propertyName === 'fillOpacity') {
objectContent.fillOpacity = parseFloat(newValue);
return true;
}
if (propertyName === 'outlineOpacity') {
objectContent.outlineOpacity = parseFloat(newValue);
return true;
}
if (propertyName === 'outlineSize') {
objectContent.outlineSize = parseFloat(newValue);
return true;
}
return false;
};
collisionMaskObject.getProperties = function () {
const objectProperties = new gd.MapStringPropertyDescriptor();
const objectContent = this.content;
objectProperties.set(
'tilemapJsonFile',
new gd.PropertyDescriptor(objectContent.tilemapJsonFile)
.setType('resource')
.addExtraInfo('tilemap')
.addExtraInfo('json')
.setLabel(_('Tilemap JSON file'))
.setDescription(
_(
'This is the JSON file that was saved or exported from Tiled. LDtk is not supported yet for collisions.'
)
)
);
objectProperties.set(
'tilesetJsonFile',
new gd.PropertyDescriptor(objectContent.tilesetJsonFile || '')
.setType('resource')
.addExtraInfo('tileset')
.addExtraInfo('json')
.setLabel(_('Tileset JSON file (optional)'))
.setDescription(
_(
"Optional, don't specify it if you've not saved the tileset in a different file."
)
)
);
objectProperties.set(
'collisionMaskTag',
new gd.PropertyDescriptor(objectContent.collisionMaskTag)
.setType('string')
.setLabel(_('Class filter'))
.setDescription(
_(
'Only the tiles with the given class (set in Tiled 1.9+) will have hitboxes created.'
)
)
);
objectProperties.set(
'layerIndex',
new gd.PropertyDescriptor((objectContent.layerIndex || 1).toString())
.setType('number')
.setLabel(_('Layer index'))
.setGroup(_('Layers'))
.setAdvanced(true)
);
objectProperties.set(
'useAllLayers',
new gd.PropertyDescriptor(
objectContent.useAllLayers ||
objectContent.useAllLayers === undefined ||
objectContent.useAllLayers === null
? 'true'
: 'false'
)
.setType('boolean')
.setLabel(_('Use all layers'))
.setGroup(_('Layers'))
.setAdvanced(true)
);
objectProperties.set(
'debugMode',
new gd.PropertyDescriptor(objectContent.debugMode ? 'true' : 'false')
.setType('boolean')
.setLabel(_('Debug mode'))
.setDescription(
_('When activated, it displays the hitboxes in the given color.')
)
.setGroup(_('Appearance'))
);
objectProperties.set(
'outlineColor',
new gd.PropertyDescriptor(objectContent.outlineColor)
.setType('color')
.setLabel(_('Outline color'))
.setGroup(_('Appearance'))
);
objectProperties.set(
'outlineOpacity',
new gd.PropertyDescriptor(
objectContent.outlineOpacity === undefined
? '64'
: objectContent.outlineOpacity.toString()
)
.setType('number')
.setLabel(_('Outline opacity (0-255)'))
.setGroup(_('Appearance'))
);
objectProperties.set(
'outlineSize',
new gd.PropertyDescriptor(
objectContent.outlineSize === undefined
? '1'
: objectContent.outlineSize.toString()
)
.setType('number')
.setLabel(_('Outline size (in pixels)'))
.setGroup(_('Appearance'))
);
objectProperties.set(
'fillColor',
new gd.PropertyDescriptor(objectContent.fillColor)
.setType('color')
.setLabel(_('Fill color'))
.setGroup(_('Appearance'))
);
objectProperties.set(
'fillOpacity',
new gd.PropertyDescriptor(
objectContent.fillOpacity === undefined
? '32'
: objectContent.fillOpacity.toString()
)
.setType('number')
.setLabel(_('Fill opacity (0-255)'))
.setGroup(_('Appearance'))
);
return objectProperties;
};
collisionMaskObject.content = {
tilemapJsonFile: '',
tilesetJsonFile: '',
collisionMaskTag: '',
layerIndex: 1,
useAllLayers: true,
debugMode: false,
fillColor: '255;255;255',
outlineColor: '255;255;255',
fillOpacity: 64,
outlineOpacity: 128,
outlineSize: 1,
};
collisionMaskObject.updateInitialInstanceProperty = function (
instance,
propertyName,
newValue
) {
return false;
};
collisionMaskObject.getInitialInstanceProperties = function (instance) {
var instanceProperties = new gd.MapStringPropertyDescriptor();
return instanceProperties;
};
const object = extension
.addObject(
'CollisionMask',
_('External Tilemap (Tiled/LDtk) collision mask'),
_('Invisible object handling collisions with parts of a tilemap.'),
'JsPlatform/Extensions/tile_map_collision_mask32.svg',
collisionMaskObject
)
.setCategory('Advanced')
.addDefaultBehavior('ResizableCapability::ResizableBehavior')
.addDefaultBehavior('ScalableCapability::ScalableBehavior')
.setIncludeFile('Extensions/TileMap/tilemapcollisionmaskruntimeobject.js')
.addIncludeFile('Extensions/TileMap/TileMapRuntimeManager.js')
.addIncludeFile('Extensions/TileMap/pako/dist/pako.min.js')
.addIncludeFile('Extensions/TileMap/helper/TileMapHelper.js')
.addIncludeFile('Extensions/TileMap/collision/TransformedTileMap.js')
.addIncludeFile(
'Extensions/TileMap/collision/TileMapCollisionMaskRenderer.js'
);
object
.addScopedCondition(
'TilemapJsonFile',
_('Tilemap JSON file'),
_('Check the Tilemap JSON file being used.'),
_('The Tilemap JSON file of _PARAM0_ is _PARAM1_'),
'',
'JsPlatform/Extensions/tile_map_collision_mask24.svg',
'JsPlatform/Extensions/tile_map_collision_mask32.svg'
)
.addParameter(
'object',
_('Tile map collision mask'),
'CollisionMask',
false
)
.addParameter('jsonResource', _('Tilemap JSON file'), '', false)
.getCodeExtraInformation()
.setFunctionName('isTilemapJsonFile');
object
.addScopedAction(
'SetTilemapJsonFile',
_('Tilemap JSON file'),
_(
'Set the JSON file containing the Tilemap data to display. This is usually the JSON file exported from Tiled.'
),
_('Set the Tilemap JSON file of _PARAM0_ to _PARAM1_'),
'',
'JsPlatform/Extensions/tile_map_collision_mask24.svg',
'JsPlatform/Extensions/tile_map_collision_mask32.svg'
)
.addParameter(
'object',
_('Tile map collision mask'),
'CollisionMask',
false
)
.addParameter('jsonResource', _('Tilemap JSON file'), '', false)
.getCodeExtraInformation()
.setFunctionName('setTilemapJsonFile');
object
.addScopedCondition(
'TilesetJsonFile',
_('Tileset JSON file'),
_('Check the tileset JSON file being used.'),
_('The tileset JSON file of _PARAM0_ is _PARAM1_'),
'',
'JsPlatform/Extensions/tile_map_collision_mask24.svg',
'JsPlatform/Extensions/tile_map_collision_mask32.svg'
)
.addParameter(
'object',
_('Tile map collision mask'),
'CollisionMask',
false
)
.addParameter('jsonResource', _('Tileset JSON file'), '', false)
.getCodeExtraInformation()
.setFunctionName('isTilesetJsonFile');
object
.addScopedAction(
'SetTilesetJsonFile',
_('Tileset JSON file'),
_(
'Set the JSON file with the tileset data (sometimes that is embedded in the Tilemap, so not needed)'
),
_('Set the tileset JSON file of _PARAM0_ to _PARAM1_'),
'',
'JsPlatform/Extensions/tile_map_collision_mask24.svg',
'JsPlatform/Extensions/tile_map_collision_mask32.svg'
)
.addParameter(
'object',
_('Tile map collision mask'),
'CollisionMask',
false
)
.addParameter('jsonResource', _('Tileset JSON file'), '', false)
.getCodeExtraInformation()
.setFunctionName('setTilesetJsonFile');
// Deprecated
object
.addScopedAction(
'Scale',
_('Scale'),
_('Modify the scale of the specified object.'),
_('the scale'),
_('Size'),
'res/actions/scale24_black.png',
'res/actions/scale_black.png'
)
.addParameter(
'object',
_('Tile map collision mask'),
'CollisionMask',
false
)
.useStandardOperatorParameters(
'number',
gd.ParameterOptions.makeNewOptions().setDescription(
_('Scale (1 by default)')
)
)
.markAsAdvanced()
.setHidden()
.getCodeExtraInformation()
.setFunctionName('setScale')
.setGetter('getScale');
// Deprecated
object
.addExpressionAndConditionAndAction(
'number',
'ScaleX',
_('Scale on X axis'),
_("the width's scale of an object"),
_("the width's scale"),
_('Size'),
'res/actions/scaleWidth24_black.png'
)
.addParameter(
'object',
_('Tile map collision mask'),
'CollisionMask',
false
)
.useStandardParameters(
'number',
gd.ParameterOptions.makeNewOptions().setDescription(
_('Scale (1 by default)')
)
)
.markAsAdvanced()
.setHidden()
.setFunctionName('setScaleX')
.setGetter('getScaleX');
// Deprecated
object
.addExpressionAndConditionAndAction(
'number',
'ScaleY',
_('Scale on Y axis'),
_("the height's scale of an object"),
_("the height's scale"),
_('Size'),
'res/actions/scaleHeight24_black.png'
)
.addParameter(
'object',
_('Tile map collision mask'),
'CollisionMask',
false
)
.useStandardParameters(
'number',
gd.ParameterOptions.makeNewOptions().setDescription(
_('Scale (1 by default)')
)
)
.markAsAdvanced()
.setHidden()
.setFunctionName('setScaleY')
.setGetter('getScaleY');
// Deprecated
object
.addScopedAction(
'Width',
_('Width'),
_('Change the width of an object.'),
_('the width'),
_('Size'),
'res/actions/scaleWidth24_black.png',
'res/actions/scaleWidth_black.png'
)
.addParameter(
'object',
_('Tile map collision mask'),
'CollisionMask',
false
)
.useStandardOperatorParameters(
'number',
gd.ParameterOptions.makeNewOptions()
)
.markAsAdvanced()
.setHidden()
.getCodeExtraInformation()
.setFunctionName('setWidth');
// Deprecated
object
.addScopedAction(
'Height',
_('Height'),
_('Change the height of an object.'),
_('the height'),
_('Size'),
'res/actions/scaleHeight24_black.png',
'res/actions/scaleHeight_black.png'
)
.addParameter(
'object',
_('Tile map collision mask'),
'CollisionMask',
false
)
.useStandardOperatorParameters(
'number',
gd.ParameterOptions.makeNewOptions()
)
.markAsAdvanced()
.setHidden()
.getCodeExtraInformation()
.setFunctionName('setHeight');
};
/** @type {ExtensionModule} */
module.exports = {
createExtension: function (
_ /*: (string) => string */,
gd /*: libGDevelop */
) {
const extension = new gd.PlatformExtension();
extension
.setExtensionInformation(
'TileMap',
_('Tile map'),
_(
"The Tilemap object can be used to display tile-based objects. It's a good way to create maps for RPG, strategy games or create objects by assembling tiles, useful for platformer, retro-looking games, etc... External tilemaps are also supported - but it's recommended to use the built-in, simple Tilemap object for most use cases."
),
'Todor Imreorov',
'Open source (MIT License)'
)
.setShortDescription(
'Tile-based map object for RPG, platformers, strategy. Built-in editor. External Tiled/LDtk support.'
)
.setDimension('2D')
.setCategory('Advanced')
.setExtensionHelpPath('/objects/tilemap');
extension
.addInstructionOrExpressionGroupMetadata(_('Tilemap'))
.setIcon('JsPlatform/Extensions/tile_map.svg');
defineTileMap(extension, _, gd);
defineSimpleTileMap(extension, _, gd);
defineCollisionMask(extension, _, gd);
return extension;
},
registerClearCache: function (
objectsRenderingService /*: ObjectsRenderingService */
) {
const TilemapHelper = objectsRenderingService.requireModule(
__dirname,
'helper/TileMapHelper'
);
const clearCaches = (
project /* InstanceHolder - gdProject in the editor */
) => {
/** @type {TileMapHelper.TileMapManager} */
const manager = TilemapHelper.TileMapManager.getManager(project);
manager.clearCaches();
};
objectsRenderingService.registerClearCache(clearCaches);
},
/**
* You can optionally add sanity tests that will check the basic working
* of your extension behaviors/objects by instantiating behaviors/objects
* and setting the property to a given value.
*
* If you don't have any tests, you can simply return an empty array like this:
* `runExtensionSanityTests: function(gd, extension) { return []; }`
*
* But it is recommended to create tests for the behaviors/objects properties you created
* to avoid mistakes.
*/
runExtensionSanityTests: function (gd, extension) {
return [];
},
/**
* Register editors for objects.
*
* ℹ️ Run `node import-GDJS-Runtime.js` (in newIDE/app/scripts) if you make any change.
*/
registerEditorConfigurations: function (objectsEditorService) {
objectsEditorService.registerEditorConfiguration(
'TileMap::TileMap',
objectsEditorService.getDefaultObjectJsImplementationPropertiesEditor({
helpPagePath: '/objects/tilemap',
})
);
objectsEditorService.registerEditorConfiguration(
'TileMap::CollisionMask',
objectsEditorService.getDefaultObjectJsImplementationPropertiesEditor({
helpPagePath: '/objects/tilemap',
})
);
},
/**
* Register renderers for instance of objects on the scene editor.
*
* ℹ️ Run `node import-GDJS-Runtime.js` (in newIDE/app/scripts) if you make any change.
*/
registerInstanceRenderers: function (objectsRenderingService) {
const RenderedInstance = objectsRenderingService.RenderedInstance;
const PIXI = objectsRenderingService.PIXI;
const Tilemap = objectsRenderingService.requireModule(
__dirname,
'pixi-tilemap/dist/pixi-tilemap.umd'
);
const TilemapHelper = objectsRenderingService.requireModule(
__dirname,
'helper/TileMapHelper'
);
// required for decoding tiled zlib compressed layer data
const pako = objectsRenderingService.requireModule(
__dirname,
'pako/dist/pako.min'
);
// When on the webapp, and using webpack, the extension does not seem to
// be able to register itself properly. So we do it manually.
// (This should be done here https://github.com/pixijs/tilemap/blob/master/src/index.ts#L43-L47)
PIXI.extensions.add({
name: 'tilemap',
type: PIXI.ExtensionType.RendererPlugin,
ref: Tilemap.TileRenderer,
});
/**
* Renderer for instances of TileMap inside the IDE.
*/
class RenderedTileMapInstance extends RenderedInstance {
constructor(
project,
instance,
associatedObjectConfiguration,
pixiContainer,
pixiResourcesLoader
) {
super(
project,
instance,
associatedObjectConfiguration,
pixiContainer,
pixiResourcesLoader
);
// This setting allows tile maps with more than 16K tiles.
Tilemap.settings.use32bitIndex = true;
this.tileMapPixiObject = new Tilemap.CompositeTilemap();
this._pixiObject = this.tileMapPixiObject;
this._editableTileMap = null;
// Implement `containsPoint` so that we can set `interactive` to true and
// the Tilemap will properly emit events when hovered/clicked.
// By default, this is not implemented in pixi-tilemap.
this._pixiObject.containsPoint = (position) => {
if (!this._pixiObject) {
// Ease debugging by throwing now rather than waiting for an exception later.
throw new Error(
'containsPoint called on a destroyed PIXI object - this object was not properly removed from the PIXI container.'
);
return;
}
// Turns the world position to the local object coordinates
const localPosition = new PIXI.Point();
this._pixiObject.worldTransform.applyInverse(position, localPosition);
return (
localPosition.x >= 0 &&
localPosition.x < this.width &&
localPosition.y >= 0 &&
localPosition.y < this.height
);
};
this._pixiContainer.addChild(this._pixiObject);
this.width = 48;
this.height = 48;
this.update();
}
onRemovedFromScene() {
super.onRemovedFromScene();
// Keep textures because they are shared by all tile maps.
this._pixiObject.destroy(false);
// Not strictly necessary, but helps finding wrong
// handling of this._pixiObject in its container.
this._pixiObject = null;
}
_replacePixiObject(newPixiObject) {
if (this._pixiObject !== null)
this._pixiContainer.removeChild(this._pixiObject);
this._pixiObject = newPixiObject;
this._pixiContainer.addChild(this._pixiObject);
}
_onLoadingError() {
this.errorPixiObject =
this.errorPixiObject ||
new PIXI.Sprite(this._pixiResourcesLoader.getInvalidPIXITexture());
this._replacePixiObject(this.errorPixiObject);
}
_onLoadingSuccess() {
if (this.errorPixiObject) {
this._replacePixiObject(this.tileMapPixiObject);
this.errorPixiObject = null;
}
}
/**
* Return the path to the thumbnail of the specified object.
*/
static getThumbnail(project, resourcesLoader, objectConfiguration) {
return 'JsPlatform/Extensions/tile_map.svg';
}
/**
* This is used to reload the Tilemap
*/
updateTileMap() {
const tilemapAtlasImage = this._tilemapAtlasImage;
const tilemapJsonFile = this._tilemapJsonFile;
const tilesetJsonFile = this._tilesetJsonFile;
const layerIndex = this._layerIndex;
const levelIndex = this._levelIndex;
const displayMode = this._displayMode;
const tilemapResource = this._project
.getResourcesManager()
.getResource(tilemapJsonFile);
let metadata = {};
try {
const tilemapMetadataAsString = tilemapResource.getMetadata();
if (tilemapMetadataAsString)
metadata = JSON.parse(tilemapMetadataAsString);
} catch (error) {
console.warn('Malformed metadata in a tilemap object:', error);
}
const mapping = metadata.embeddedResourcesMapping || {};
const atlasTexture = this._pixiResourcesLoader.getPIXITexture(
this._project,
tilemapAtlasImage
);
const loadTileMap = () => {
/** @type {TileMapHelper.TileMapManager} */
const manager = TilemapHelper.TileMapManager.getManager(
this._project
);
manager.getOrLoadTileMap(
this._loadTileMapWithCallback.bind(this),
tilemapJsonFile,
tilesetJsonFile,
levelIndex,
pako,
(tileMap) => {
if (this._wasDestroyed) return;
if (!tileMap) {
this._onLoadingError();
// _loadTileMapWithCallback already log errors
return;
}
this._editableTileMap = tileMap;
/** @type {TileMapHelper.TileTextureCache} */
manager.getOrLoadTextureCache(
this._loadTileMapWithCallback.bind(this),
(textureName) =>
this._pixiResourcesLoader.getPIXITexture(
this._project,
mapping[textureName] || textureName
),
tilemapAtlasImage,
tilemapJsonFile,
tilesetJsonFile,
levelIndex,
(textureCache) => {
if (this._wasDestroyed) return;
if (!textureCache) {
this._onLoadingError();
// getOrLoadTextureCache already log warns and errors.
return;
}
this._onLoadingSuccess();
if (!this._editableTileMap) return;
this.width = this._editableTileMap.getWidth();
this.height = this._editableTileMap.getHeight();
TilemapHelper.PixiTileMapHelper.updatePixiTileMap(
this.tileMapPixiObject,
this._editableTileMap,
textureCache,
displayMode,
layerIndex,
0,
this._editableTileMap.getDimensionX(),
0,
this._editableTileMap.getDimensionY()
);
}
);
}
);
};
if (atlasTexture.valid) {
loadTileMap();
} else {
// Wait for the atlas image to load.
atlasTexture.once('update', () => {
if (this._wasDestroyed) return;
loadTileMap();
});
}
}
/**
* This is called to update the PIXI object on the scene editor, without reloading the tilemap.
*/
updatePixiTileMap() {
const tilemapAtlasImage = this._tilemapAtlasImage;
const tilemapJsonFile = this._tilemapJsonFile;
const tilesetJsonFile = this._tilesetJsonFile;
const layerIndex = this._layerIndex;
const levelIndex = this._levelIndex;
const displayMode = this._displayMode;
const tilemapResource = this._project
.getResourcesManager()
.getResource(tilemapJsonFile);
let metadata = {};
try {
const tilemapMetadataAsString = tilemapResource.getMetadata();
if (tilemapMetadataAsString)
metadata = JSON.parse(tilemapMetadataAsString);
} catch (error) {
console.warn('Malformed metadata in a tilemap object:', error);
}
const mapping = metadata.embeddedResourcesMapping || {};
/** @type {TileMapHelper.TileMapManager} */
const manager = TilemapHelper.TileMapManager.getManager(this._project);
/** @type {TileMapHelper.TileTextureCache} */
manager.getOrLoadTextureCache(
this._loadTileMapWithCallback.bind(this),
(textureName) =>
this._pixiResourcesLoader.getPIXITexture(
this._project,
mapping[textureName] || textureName
),
tilemapAtlasImage,
tilemapJsonFile,
tilesetJsonFile,
levelIndex,
(textureCache) => {
if (this._wasDestroyed) return;
if (!textureCache) {
this._onLoadingError();
// getOrLoadTextureCache already log warns and errors.
return;
}
this._onLoadingSuccess();
if (!this._editableTileMap) return;
this.width = this._editableTileMap.getWidth();
this.height = this._editableTileMap.getHeight();
TilemapHelper.PixiTileMapHelper.updatePixiTileMap(
this.tileMapPixiObject,
this._editableTileMap,
textureCache,
displayMode,
layerIndex,
0,
this._editableTileMap.getDimensionX(),
0,
this._editableTileMap.getDimensionY()
);
}
);
}
// GDJS doesn't use Promise to avoid allocation.
_loadTileMapWithCallback(tilemapJsonFile, tilesetJsonFile, callback) {
this._loadTileMap(tilemapJsonFile, tilesetJsonFile).then(callback);
}
async _loadTileMap(tilemapJsonFile, tilesetJsonFile) {
try {
const tileMapJsonData =
await this._pixiResourcesLoader.getResourceJsonData(
this._project,
tilemapJsonFile
);
const tileMap =
TilemapHelper.TileMapManager.identify(tileMapJsonData);
if (tileMap.kind === 'tiled') {
const tilesetJsonData = tilesetJsonFile
? await this._pixiResourcesLoader.getResourceJsonData(
this._project,
tilesetJsonFile
)
: null;
if (tilesetJsonData) {
tileMapJsonData.tilesets = [tilesetJsonData];
}
}
return tileMap;
} catch (err) {
console.error('Unable to load a Tilemap JSON data: ', err);
}
return null;
}
/**
* This is called to update the PIXI object on the scene editor
*/
update() {
const object = gd.castObject(
this._associatedObjectConfiguration,
gd.ObjectJsImplementation
);
const tilemapAtlasImage = object.content.tilemapAtlasImage;
const tilemapJsonFile = object.content.tilemapJsonFile;
const tilesetJsonFile = object.content.tilesetJsonFile;
const layerIndex = object.content.layerIndex;
const levelIndex = object.content.levelIndex;
const displayMode = object.content.displayMode;
if (
tilemapAtlasImage !== this._tilemapAtlasImage ||
tilemapJsonFile !== this._tilemapJsonFile ||
tilesetJsonFile !== this._tilesetJsonFile ||
layerIndex !== this._layerIndex ||
levelIndex !== this._levelIndex ||
displayMode !== this._displayMode
) {
this._tilemapAtlasImage = tilemapAtlasImage;
this._tilemapJsonFile = tilemapJsonFile;
this._tilesetJsonFile = tilesetJsonFile;
this._layerIndex = layerIndex;
this._levelIndex = levelIndex;
this._displayMode = displayMode;
this.updateTileMap();
}
if (this._instance.hasCustomSize()) {
this._pixiObject.scale.x = this.getCustomWidth() / this.width;
this._pixiObject.scale.y = this.getCustomHeight() / this.height;
} else {
this._pixiObject.scale.x = 1;
this._pixiObject.scale.y = 1;
}
// Place the center of rotation in the center of the object. Because pivot position in Pixi
// is in the **local coordinates of the object**, we need to find back the original width
// and height of the object before scaling (then divide by 2 to find the center)
const originalWidth = this.width;
const originalHeight = this.height;
this._pixiObject.pivot.x = originalWidth / 2;
this._pixiObject.pivot.y = originalHeight / 2;
// Modifying the pivot position also has an impact on the transform. The instance (X,Y) position
// of this object refers to the top-left point, but now in Pixi, as we changed the pivot, the Pixi
// object (X,Y) position refers to the center. So we add an offset to convert from top-left to center.
this._pixiObject.x =
this._instance.getX() +
this._pixiObject.pivot.x * this._pixiObject.scale.x;
this._pixiObject.y =
this._instance.getY() +
this._pixiObject.pivot.y * this._pixiObject.scale.y;
// Rotation works as intended because we put the pivot in the center
this._pixiObject.rotation = RenderedInstance.toRad(
this._instance.getAngle()
);
// Update the opacity, if needed.
// Do not hide completely an object so it can still be manipulated
const alphaForDisplay = Math.max(
this._instance.getOpacity() / 255,
0.5
);
if (
this._editableTileMap &&
this._pixiObject.alpha !== alphaForDisplay
) {
this._pixiObject.alpha = alphaForDisplay;
for (const layer of this._editableTileMap.getLayers()) {
const isLayerHidden =
(displayMode === 'index' && layerIndex !== layer.id) ||
(displayMode === 'visible' && !layer.isVisible());
// Only set alpha on editable layers that are not hidden,
// as others are not rendered.
if (isLayerHidden) continue;
// @ts-ignore - only this type of layer has setAlpha.
if (layer.setAlpha) {
const editableLayer =
/** @type {TileMapHelper.EditableTileMapLayer} */ (layer);
editableLayer.setAlpha(alphaForDisplay);
}
}
// Only update the tilemap if the alpha has changed.
this.updatePixiTileMap();
}
}
/**
* Return the width of the instance, when it's not resized.
*/
getDefaultWidth() {
return this.width;
}
/**
* Return the height of the instance, when it's not resized.
*/
getDefaultHeight() {
return this.height;
}
}
objectsRenderingService.registerInstanceRenderer(
'TileMap::TileMap',
RenderedTileMapInstance
);
/**
* Renderer for instances of SimpleTileMap inside the IDE.
*/
class RenderedSimpleTileMapInstance extends RenderedInstance {
_getStartedText = 'Select this instance\nto start painting';
_noAtlasText = 'Set up an atlas image\nin the tilemap object.';
_placeholderTextPixiObject = new PIXI.Text(
'',
new PIXI.TextStyle({
fontFamily: 'Arial',
fontSize: 16,
align: 'center',
padding: 5,
})
);
_placeholderImagePixiObject = new PIXI.Sprite(
PIXI.Texture.from(
'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgAgMAAAAOFJJnAAAADFBMVEUAAAAkMoYsfqH///8FP6xgAAAAAXRSTlMAQObYZgAAAAFiS0dEAxEMTPIAAAAjSURBVBjTpcYxAQAADIMwTGISlTsmoVcCQClzSmvNo2ueGnMajGpBwI5BnwAAAABJRU5ErkJggg=='
)
);
_placeholderPixiObject = new PIXI.Container();
constructor(
project,
instance,
associatedObjectConfiguration,
pixiContainer,
pixiResourcesLoader
) {
super(
project,
instance,
associatedObjectConfiguration,
pixiContainer,
pixiResourcesLoader
);
// This setting allows tile maps with more than 16K tiles.
Tilemap.settings.use32bitIndex = true;
this.tileMapPixiObject = new Tilemap.CompositeTilemap();
this._pixiObject = new PIXI.Container();
this._pixiObject.addChild(this.tileMapPixiObject);
this._editableTileMap = null;
// Implement `containsPoint` so that we can set `interactive` to true and
// the Tilemap will properly emit events when hovered/clicked.
// By default, this is not implemented in pixi-tilemap.
this._pixiObject.containsPoint = (position) => {
if (!this._pixiObject) {
// Ease debugging by throwing now rather than waiting for an exception later.
throw new Error(
'containsPoint called on a destroyed PIXI object - this object was not properly removed from the PIXI container.'
);
return;
}
// Turns the world position to the local object coordinates
const localPosition = new PIXI.Point();
if (this.tileMapPixiObject.visible) {
this.tileMapPixiObject.worldTransform.applyInverse(
position,
localPosition
);
} else {
this._placeholderImagePixiObject.worldTransform.applyInverse(
position,
localPosition
);
}
return (
localPosition.x >= 0 &&
localPosition.x < this.width &&
localPosition.y >= 0 &&
localPosition.y < this.height
);
};
this._placeholderTextPixiObject.eventMode = 'static';
this._placeholderImagePixiObject.eventMode = 'static';
this._placeholderTextPixiObject.anchor.x = 0.5;
this._placeholderTextPixiObject.anchor.y = 0.5;
this._placeholderTextPixiObject.y = 30;
this._placeholderImagePixiObject.y = -30;
this._placeholderImagePixiObject.x = -16;
this._placeholderPixiObject.addChild(this._placeholderTextPixiObject);
this._placeholderPixiObject.addChild(this._placeholderImagePixiObject);
this._pixiObject.addChild(this._placeholderPixiObject);
this._pixiContainer.addChild(this._pixiObject);
this.width = 48;
this.height = 48;
this._objectName = instance.getObjectName();
this.update();
this.updateTileMap();
}
onRemovedFromScene() {
super.onRemovedFromScene();
// Keep textures because they are shared by all tile maps.
this._pixiObject.destroy(false);
// Not strictly necessary, but helps finding wrong
// handling of this._pixiObject in its container.
this._pixiObject = null;
}
_replacePixiObject(newPixiObject) {
if (this._pixiObject !== null)
this._pixiContainer.removeChild(this._pixiObject);
this._pixiObject = newPixiObject;
this._pixiContainer.addChild(this._pixiObject);
}
_onLoadingError() {
this.errorPixiObject =
this.errorPixiObject ||
new PIXI.Sprite(this._pixiResourcesLoader.getInvalidPIXITexture());
this._replacePixiObject(this.errorPixiObject);
}
_onLoadingSuccess() {
if (this.errorPixiObject) {
this._replacePixiObject(this.tileMapPixiObject);
this.errorPixiObject = null;
}
}
/**
* Return the path to the thumbnail of the specified object.
*/
static getThumbnail(project, resourcesLoader, objectConfiguration) {
const object = gd.castObject(
objectConfiguration,
gd.ObjectJsImplementation
);
const atlasImageResourceName = object.content.atlasImage || '';
return resourcesLoader.getResourceFullUrl(
project,
atlasImageResourceName,
{}
);
}
getEditableTileMap() {
return this._editableTileMap;
}
/**
* This is used to reload the Tilemap
*/
updateTileMap() {
const object = gd.castObject(
this._associatedObjectConfiguration,
gd.ObjectJsImplementation
);
const atlasImageResourceName = object.content.atlasImage;
if (!atlasImageResourceName) return;
const tilemapAsJSObject = JSON.parse(
this._instance.getRawStringProperty('tilemap') || '{}'
);
const tileSize = object.content.tileSize;
const columnCount = object.content.columnCount;
const rowCount = object.content.rowCount;
const atlasTexture = this._pixiResourcesLoader.getPIXITexture(
this._project,
atlasImageResourceName
);
const loadTileMap = () => {
/** @type {TileMapHelper.TileMapManager} */
const manager = TilemapHelper.TileMapManager.getManager(
this._project
);
try {
manager.getOrLoadSimpleTileMap(
tilemapAsJSObject,
this._objectName,
tileSize,
columnCount,
rowCount,
(tileMap) => {
if (this._wasDestroyed) return;
if (!tileMap) {
this._onLoadingError();
console.error('Could not parse tilemap.');
return;
}
this._editableTileMap = tileMap;
manager.getOrLoadSimpleTileMapTextureCache(
(textureName) =>
this._pixiResourcesLoader.getPIXITexture(
this._project,
textureName
),
atlasImageResourceName,
tileSize,
columnCount,
rowCount,
(
/** @type {TileMapHelper.TileTextureCache | null} */
textureCache
) => {
if (this._wasDestroyed) return;
this._onLoadingSuccess();
if (!this._editableTileMap) return;
this.width = this._editableTileMap.getWidth();
this.height = this._editableTileMap.getHeight();
TilemapHelper.PixiTileMapHelper.updatePixiTileMap(
this.tileMapPixiObject,
this._editableTileMap,
textureCache,
'all', // No notion of visibility on simple tile maps.
0, // Only one layer is used on simple tile maps.
0,
this._editableTileMap.getDimensionX(),
0,
this._editableTileMap.getDimensionY()
);
}
);
}
);
} catch (error) {
this._onLoadingError();
console.error('Could not load tilemap:', error);
}
};
if (atlasTexture.valid) {
loadTileMap();
} else {
// Wait for the atlas image to load.
atlasTexture.once('update', () => {
if (this._wasDestroyed) return;
loadTileMap();
});
}
}
/**
* This is called to update the PIXI object on the scene editor, without reloading the tilemap.
*/
updatePixiTileMap() {
const object = gd.castObject(
this._associatedObjectConfiguration,
gd.ObjectJsImplementation
);
const atlasImageResourceName = object.content.atlasImage;
const tileSize = object.content.tileSize;
const columnCount = object.content.columnCount;
const rowCount = object.content.rowCount;
/** @type {TileMapHelper.TileMapManager} */
const manager = TilemapHelper.TileMapManager.getManager(this._project);
manager.getOrLoadSimpleTileMapTextureCache(
(textureName) =>
this._pixiResourcesLoader.getPIXITexture(
this._project,
textureName
),
atlasImageResourceName,
tileSize,
columnCount,
rowCount,
(
/** @type {TileMapHelper.TileTextureCache | null} */
textureCache
) => {
if (this._wasDestroyed) return;
this._onLoadingSuccess();
if (!this._editableTileMap) return;
this.width = this._editableTileMap.getWidth();
this.height = this._editableTileMap.getHeight();
TilemapHelper.PixiTileMapHelper.updatePixiTileMap(
this.tileMapPixiObject,
this._editableTileMap,
textureCache,
'all',
0,
0,
this._editableTileMap.getDimensionX(),
0,
this._editableTileMap.getDimensionY()
);
}
);
}
/**
* This is called to update the PIXI object on the scene editor
*/
update() {
const object = gd.castObject(
this._associatedObjectConfiguration,
gd.ObjectJsImplementation
);
const atlasImageResourceName = object.content.atlasImage;
const isTileMapEmpty = this._editableTileMap
? this._editableTileMap.isEmpty()
: false;
let objectToChange;
if (this.errorPixiObject) {
objectToChange = this.errorPixiObject;
} else if (isTileMapEmpty || !atlasImageResourceName) {
this.tileMapPixiObject.visible = false;
this._placeholderPixiObject.visible = true;
this._placeholderTextPixiObject.text = !atlasImageResourceName
? this._noAtlasText
: this._getStartedText;
objectToChange = this._placeholderPixiObject;
} else {
this._placeholderPixiObject.visible = false;
this.tileMapPixiObject.visible = true;
objectToChange = this.tileMapPixiObject;
}
if (!isTileMapEmpty) {
// Don't change size of placeholder object.
if (this._instance.hasCustomSize()) {
objectToChange.scale.x = this.getCustomWidth() / this.width;
objectToChange.scale.y = this.getCustomHeight() / this.height;
} else {
objectToChange.scale.x = 1;
objectToChange.scale.y = 1;
}
// Place the center of rotation in the center of the object. Because pivot position in Pixi
// is in the **local coordinates of the object**, we need to find back the original width
// and height of the object before scaling (then divide by 2 to find the center)
const originalWidth = this.width;
const originalHeight = this.height;
objectToChange.pivot.x = originalWidth / 2;
objectToChange.pivot.y = originalHeight / 2;
}
// Modifying the pivot position also has an impact on the transform. The instance (X,Y) position
// of this object refers to the top-left point, but now in Pixi, as we changed the pivot, the Pixi
// object (X,Y) position refers to the center. So we add an offset to convert from top-left to center.
objectToChange.x =
this._instance.getX() +
objectToChange.pivot.x * objectToChange.scale.x;
objectToChange.y =
this._instance.getY() +
objectToChange.pivot.y * objectToChange.scale.y;
// Rotation works as intended because we put the pivot in the center
objectToChange.rotation = RenderedInstance.toRad(
this._instance.getAngle()
);
// Update the opacity, if needed.
// Do not hide completely an object so it can still be manipulated
const alphaForDisplay = Math.max(
this._instance.getOpacity() / 255,
0.5
);
if (this._editableTileMap && objectToChange.alpha !== alphaForDisplay) {
objectToChange.alpha = alphaForDisplay;
for (const layer of this._editableTileMap.getLayers()) {
// Only update layers that are of type TileMapHelper.EditableTileMapLayer.
// @ts-ignore - only this type of layer has setAlpha.
if (layer.setAlpha) {
const editableLayer =
/** @type {TileMapHelper.EditableTileMapLayer} */ (layer);
editableLayer.setAlpha(alphaForDisplay);
}
}
this.updatePixiTileMap();
}
}
/**
* Return the width of the instance, when it's not resized.
*/
getDefaultWidth() {
return this.width;
}
/**
* Return the height of the instance, when it's not resized.
*/
getDefaultHeight() {
return this.height;
}
}
objectsRenderingService.registerInstanceRenderer(
'TileMap::SimpleTileMap',
RenderedSimpleTileMapInstance
);
/**
* Renderer for instances of TileMap collision mask inside the IDE.
*/
class RenderedCollisionMaskInstance extends RenderedInstance {
_tilemapJsonFile = '';
_tilesetJsonFile = '';
_collisionMaskTag = '';
_layerIndex = null;
_outlineColor = 0xffffff;
_fillColor = 0xffffff;
_outlineOpacity = 0;
_fillOpacity = 0;
_outlineSize = 1;
constructor(
project,
instance,
associatedObjectConfiguration,
pixiContainer,
pixiResourcesLoader
) {
super(
project,
instance,
associatedObjectConfiguration,
pixiContainer,
pixiResourcesLoader
);
this.tileMapPixiObject = new PIXI.Graphics();
this._pixiObject = this.tileMapPixiObject;
// Implement `containsPoint` so that we can set `interactive` to true and
// the Tilemap will properly emit events when hovered/clicked.
// By default, this is not implemented in pixi-tilemap.
this._pixiObject.containsPoint = (position) => {
if (!this._pixiObject) {
// Ease debugging by throwing now rather than waiting for an exception later.
throw new Error(
'containsPoint called on a destroyed PIXI object - this object was not properly removed from the PIXI container.'
);
return;
}
// Turns the world position to the local object coordinates
const localPosition = new PIXI.Point();
this._pixiObject.worldTransform.applyInverse(position, localPosition);
// Check if the point is inside the object bounds
return (
localPosition.x >= 0 &&
localPosition.x < this.width &&
localPosition.y >= 0 &&
localPosition.y < this.height
);
};
this._pixiContainer.addChild(this._pixiObject);
this.width = 48;
this.height = 48;
this.update();
}
onRemovedFromScene() {
super.onRemovedFromScene();
this._pixiObject.destroy();
// Not strictly necessary, but helps finding wrong
// handling of this._pixiObject in its container.
this._pixiObject = null;
}
_replacePixiObject(newPixiObject) {
if (this._pixiObject !== null)
this._pixiContainer.removeChild(this._pixiObject);
this._pixiObject = newPixiObject;
this._pixiContainer.addChild(this._pixiObject);
}
_onLoadingError() {
this.errorPixiObject =
this.errorPixiObject ||
new PIXI.Sprite(this._pixiResourcesLoader.getInvalidPIXITexture());
this._replacePixiObject(this.errorPixiObject);
}
_onLoadingSuccess() {
if (this.errorPixiObject) {
this._replacePixiObject(this.tileMapPixiObject);
this.errorPixiObject = null;
}
}
/**
* Return the path to the thumbnail of the specified object.
*/
static getThumbnail(project, resourcesLoader, objectConfiguration) {
return 'JsPlatform/Extensions/tile_map_collision_mask24.svg';
}
/**
* This is used to reload the Tilemap
*/
updateTileMap() {
const tilemapJsonFile = this._tilemapJsonFile;
const tilesetJsonFile = this._tilesetJsonFile;
const collisionMaskTag = this._collisionMaskTag;
const layerIndex = this._layerIndex;
const outlineColor = this._outlineColor;
const fillColor = this._fillColor;
const outlineOpacity = this._outlineOpacity;
const fillOpacity = this._fillOpacity;
const outlineSize = this._outlineSize;
/** @type {TileMapHelper.TileMapManager} */
const manager = TilemapHelper.TileMapManager.getManager(this._project);
manager.getOrLoadTileMap(
this._loadTiledMapWithCallback.bind(this),
tilemapJsonFile,
tilesetJsonFile,
0, // levelIndex
pako,
(tileMap) => {
if (this._wasDestroyed) return;
if (!tileMap) {
this._onLoadingError();
// _loadTiledMapWithCallback already log errors
return;
}
this._onLoadingSuccess();
this.width = tileMap.getWidth();
this.height = tileMap.getHeight();
TilemapHelper.PixiTileMapHelper.updatePixiCollisionMask(
this._pixiObject,
tileMap,
collisionMaskTag,
layerIndex,
outlineSize,
outlineColor,
outlineOpacity,
fillColor,
fillOpacity
);
}
);
}
// GDJS doesn't use Promise to avoid allocation.
_loadTiledMapWithCallback(tilemapJsonFile, tilesetJsonFile, callback) {
this._loadTileMap(tilemapJsonFile, tilesetJsonFile).then(callback);
}
async _loadTileMap(tilemapJsonFile, tilesetJsonFile) {
try {
const tileMapJsonData =
await this._pixiResourcesLoader.getResourceJsonData(
this._project,
tilemapJsonFile
);
const tileMap =
TilemapHelper.TileMapManager.identify(tileMapJsonData);
if (tileMap.kind === 'tiled') {
const tilesetJsonData = tilesetJsonFile
? await this._pixiResourcesLoader.getResourceJsonData(
this._project,
tilesetJsonFile
)
: null;
if (tilesetJsonData) {
tileMapJsonData.tilesets = [tilesetJsonData];
}
}
return tileMap;
} catch (err) {
console.error('Unable to load a Tilemap JSON data: ', err);
}
return null;
}
/**
* This is called to update the PIXI object on the scene editor
*/
update() {
const object = gd.castObject(
this._associatedObjectConfiguration,
gd.ObjectJsImplementation
);
const tilemapJsonFile = object.content.tilemapJsonFile;
const tilesetJsonFile = object.content.tilesetJsonFile;
const collisionMaskTag = object.content.collisionMaskTag;
const useAllLayers = object.content.useAllLayers;
const layerIndex = useAllLayers ? null : object.content.layerIndex;
const outlineColor = objectsRenderingService.rgbOrHexToHexNumber(
object.content.outlineColor
);
const fillColor = objectsRenderingService.rgbOrHexToHexNumber(
object.content.fillColor
);
const outlineOpacity = object.content.outlineOpacity / 255;
const fillOpacity = object.content.fillOpacity / 255;
const outlineSize = object.content.outlineSize || 0;
if (
tilemapJsonFile !== this._tilemapJsonFile ||
tilesetJsonFile !== this._tilesetJsonFile ||
collisionMaskTag !== this._collisionMaskTag ||
layerIndex !== this._layerIndex ||
outlineColor !== this._outlineColor ||
fillColor !== this._fillColor ||
outlineOpacity !== this._outlineOpacity ||
fillOpacity !== this._fillOpacity ||
outlineSize !== this._outlineSize
) {
this._tilemapJsonFile = tilemapJsonFile;
this._tilesetJsonFile = tilesetJsonFile;
this._collisionMaskTag = collisionMaskTag;
this._layerIndex = layerIndex;
this._outlineColor = outlineColor;
this._fillColor = fillColor;
this._outlineOpacity = outlineOpacity;
this._fillOpacity = fillOpacity;
this._outlineSize = outlineSize;
this.updateTileMap();
}
if (this._instance.hasCustomSize()) {
this._pixiObject.scale.x = this.getCustomWidth() / this.width;
this._pixiObject.scale.y = this.getCustomHeight() / this.height;
} else {
this._pixiObject.scale.x = 1;
this._pixiObject.scale.y = 1;
}
// Place the center of rotation in the center of the object. Because pivot position in Pixi
// is in the **local coordinates of the object**, we need to find back the original width
// and height of the object before scaling (then divide by 2 to find the center)
const originalWidth = this.width;
const originalHeight = this.height;
this._pixiObject.pivot.x = originalWidth / 2;
this._pixiObject.pivot.y = originalHeight / 2;
// Modifying the pivot position also has an impact on the transform. The instance (X,Y) position
// of this object refers to the top-left point, but now in Pixi, as we changed the pivot, the Pixi
// object (X,Y) position refers to the center. So we add an offset to convert from top-left to center.
this._pixiObject.x =
this._instance.getX() +
this._pixiObject.pivot.x * this._pixiObject.scale.x;
this._pixiObject.y =
this._instance.getY() +
this._pixiObject.pivot.y * this._pixiObject.scale.y;
// Rotation works as intended because we put the pivot in the center
this._pixiObject.rotation = RenderedInstance.toRad(
this._instance.getAngle()
);
}
/**
* Return the width of the instance, when it's not resized.
*/
getDefaultWidth() {
return this.width;
}
/**
* Return the height of the instance, when it's not resized.
*/
getDefaultHeight() {
return this.height;
}
}
objectsRenderingService.registerInstanceRenderer(
'TileMap::CollisionMask',
RenderedCollisionMaskInstance
);
},
};