diff --git a/Core/GDCore/Project/ResourcesManager.cpp b/Core/GDCore/Project/ResourcesManager.cpp index 49cd14b5c020..362890940c23 100644 --- a/Core/GDCore/Project/ResourcesManager.cpp +++ b/Core/GDCore/Project/ResourcesManager.cpp @@ -81,6 +81,8 @@ std::shared_ptr ResourcesManager::CreateResource( return std::make_shared(); else if (kind == "json") return std::make_shared(); + else if (kind == "tilemap") + return std::make_shared(); else if (kind == "bitmapFont") return std::make_shared(); @@ -674,6 +676,47 @@ bool JsonResource::UpdateProperty(const gd::String& name, #endif +//TilemapResource +void TilemapResource::SetFile(const gd::String& newFile) { + file = newFile; + + // Convert all backslash to slashs. + while (file.find('\\') != gd::String::npos) + file.replace(file.find('\\'), 1, "/"); +} + +void TilemapResource::UnserializeFrom(const SerializerElement& element) { + SetUserAdded(element.GetBoolAttribute("userAdded")); + SetFile(element.GetStringAttribute("file")); + DisablePreload(element.GetBoolAttribute("disablePreload", false)); +} + +#if defined(GD_IDE_ONLY) +void TilemapResource::SerializeTo(SerializerElement& element) const { + element.SetAttribute("userAdded", IsUserAdded()); + element.SetAttribute("file", GetFile()); + element.SetAttribute("disablePreload", IsPreloadDisabled()); +} + +std::map TilemapResource::GetProperties() + const { + std::map properties; + properties["disablePreload"] + .SetValue(disablePreload ? "true" : "false") + .SetType("Boolean") + .SetLabel(_("Disable preloading at game startup")); + + return properties; +} + +bool TilemapResource::UpdateProperty(const gd::String& name, + const gd::String& value) { + if (name == "disablePreload") disablePreload = value == "1"; + + return true; +} +#endif + void BitmapFontResource::SetFile(const gd::String& newFile) { file = newFile; diff --git a/Core/GDCore/Project/ResourcesManager.h b/Core/GDCore/Project/ResourcesManager.h index 2283fe55d8c5..7102dce2a29a 100644 --- a/Core/GDCore/Project/ResourcesManager.h +++ b/Core/GDCore/Project/ResourcesManager.h @@ -402,6 +402,48 @@ class GD_CORE_API BitmapFontResource : public Resource { gd::String file; }; +/** + * \brief Describe a tilemap file used by a project. + * + * \see Resource + * \ingroup ResourcesManagement + */ +class GD_CORE_API TilemapResource : public Resource { + public: + TilemapResource() : Resource(), disablePreload(false) { SetKind("tilemap"); }; + virtual ~TilemapResource(){}; + virtual TilemapResource* Clone() const override { + return new TilemapResource(*this); + } + + virtual const gd::String& GetFile() const override { return file; }; + virtual void SetFile(const gd::String& newFile) override; + +#if defined(GD_IDE_ONLY) + virtual bool UseFile() override { return true; } + + std::map GetProperties() const override; + bool UpdateProperty(const gd::String& name, const gd::String& value) override; + + void SerializeTo(SerializerElement& element) const override; +#endif + + void UnserializeFrom(const SerializerElement& element) override; + + /** + * \brief Return true if the loading at game startup must be disabled + */ + bool IsPreloadDisabled() const { return disablePreload; } + + /** + * \brief Set if the tilemap preload at game startup must be disabled + */ + void DisablePreload(bool disable = true) { disablePreload = disable; } + + private: + bool disablePreload; ///< If "true", don't load the Tilemap at game startup + gd::String file; +}; /** * \brief Inventory all resources used by a project * diff --git a/Extensions/TileMap/JsExtension.js b/Extensions/TileMap/JsExtension.js index 168c8a5a5ccd..c8b76eb3c4a9 100644 --- a/Extensions/TileMap/JsExtension.js +++ b/Extensions/TileMap/JsExtension.js @@ -62,6 +62,10 @@ module.exports = { objectContent.layerIndex = parseFloat(newValue); return true; } + if (propertyName === 'levelIndex') { + objectContent.levelIndex = parseFloat(newValue); + return true; + } if (propertyName === 'animationSpeedScale') { objectContent.animationSpeedScale = parseFloat(newValue); return true; @@ -81,10 +85,12 @@ module.exports = { 'tilemapJsonFile', new gd.PropertyDescriptor(objectContent.tilemapJsonFile) .setType('resource') - .addExtraInfo('json') - .setLabel(_('Tilemap JSON file')) + .addExtraInfo('tilemap') + .setLabel(_('Tilemap tiled JSON or Ldtk file')) .setDescription( - _('This is the JSON file that was saved or exported from Tiled.') + _( + 'This is the JSON/Ldtk file that was saved or exported from Tiled/Ldtk.' + ) ) ); objectProperties.set( @@ -117,7 +123,7 @@ module.exports = { ); objectProperties.set( 'layerIndex', - new gd.PropertyDescriptor(objectContent.layerIndex.toString()) + new gd.PropertyDescriptor((objectContent.layerIndex || 0).toString()) .setType('number') .setLabel(_('Layer index to display')) .setDescription( @@ -126,15 +132,26 @@ module.exports = { ) ) ); + 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)') + ) + ); objectProperties.set( 'animationSpeedScale', - new gd.PropertyDescriptor(objectContent.animationSpeedScale.toString()) + new gd.PropertyDescriptor( + (objectContent.animationSpeedScale || 0).toString() + ) .setType('number') .setLabel(_('Animation speed scale')) ); objectProperties.set( 'animationFps', - new gd.PropertyDescriptor(objectContent.animationFps.toString()) + new gd.PropertyDescriptor((objectContent.animationFps || 0).toString()) .setType('number') .setLabel(_('Animation FPS')) ); @@ -148,6 +165,7 @@ module.exports = { tilemapAtlasImage: '', displayMode: 'visible', layerIndex: 0, + levelIndex: 0, animationSpeedScale: 1, animationFps: 4, }) @@ -180,7 +198,7 @@ module.exports = { 'TileMap', _('Tilemap'), _( - 'Displays a tiled-based map, made with the Tiled editor (download it separately on https://www.mapeditor.org/).' + 'Displays a tiled-based map, made with the Tiled/Ldtk editor (download it separately on https://www.mapeditor.org/ or https://ldtk.io/).' ), 'JsPlatform/Extensions/tile_map32.png', objectTileMap @@ -206,7 +224,7 @@ module.exports = { 'JsPlatform/Extensions/tile_map32.png' ) .addParameter('object', 'TileMap', 'TileMap', false) - .addParameter('jsonResource', _('Tilemap JSON file'), '', false) + .addParameter('tilemapResource', _('Tilemap JSON/Ldtk file'), '', false) .getCodeExtraInformation() .setFunctionName('isTilemapJsonFile'); @@ -223,7 +241,7 @@ module.exports = { 'JsPlatform/Extensions/tile_map32.png' ) .addParameter('object', 'TileMap', 'TileMap', false) - .addParameter('jsonResource', _('Tilemap JSON file'), '', false) + .addParameter('tilemapResource', _('Tilemap JSON file'), '', false) .getCodeExtraInformation() .setFunctionName('setTilemapJsonFile'); @@ -238,7 +256,7 @@ module.exports = { 'JsPlatform/Extensions/tile_map32.png' ) .addParameter('object', 'TileMap', 'TileMap', false) - .addParameter('jsonResource', _('Tileset JSON file'), '', false) + .addParameter('tilemapResource', _('Tileset JSON file'), '', false) .getCodeExtraInformation() .setFunctionName('isTilesetJsonFile'); @@ -255,7 +273,7 @@ module.exports = { 'JsPlatform/Extensions/tile_map32.png' ) .addParameter('object', 'TileMap', 'TileMap', false) - .addParameter('jsonResource', _('Tileset JSON file'), '', false) + .addParameter('tilemapResource', _('Tileset JSON file'), '', false) .getCodeExtraInformation() .setFunctionName('setTilesetJsonFile'); @@ -300,134 +318,65 @@ module.exports = { .setFunctionName('setDisplayMode'); object - .addCondition( + .addExpressionAndConditionAndAction( + 'number', 'LayerIndex', _('Layer index'), - _('Compare the value of the layer index.'), - _('the layer index'), - '', - 'JsPlatform/Extensions/tile_map24.png', - 'JsPlatform/Extensions/tile_map32.png' - ) - .addParameter('object', 'TileMap', 'TileMap', false) - .useStandardRelationalOperatorParameters('number') - .getCodeExtraInformation() - .setFunctionName('getLayerIndex'); - - object - .addAction( - 'SetLayerIndex', - _('Layer index'), - _('Set the layer index of the Tilemap.'), + _('the layer index being displayed'), _('the layer index'), '', - 'JsPlatform/Extensions/tile_map24.png', 'JsPlatform/Extensions/tile_map32.png' ) .addParameter('object', 'TileMap', 'TileMap', false) - .useStandardOperatorParameters('number') - .getCodeExtraInformation() + .useStandardParameters('number') .setFunctionName('setLayerIndex') .setGetter('getLayerIndex'); object - .addExpression( - 'LayerIndex', - _('Layer index'), - _('Get the layer index being displayed'), + .addExpressionAndConditionAndAction( + 'number', + 'LevelIndex', + _('Level index'), + _('the level index being displayed'), + _('the level index'), '', 'JsPlatform/Extensions/tile_map32.png' ) .addParameter('object', 'TileMap', 'TileMap', false) - .getCodeExtraInformation() - .setFunctionName('getLayerIndex'); + .useStandardParameters('number') + .setFunctionName('setLevelIndex') + .setGetter('getLevelndex'); object - .addCondition( + .addExpressionAndConditionAndAction( + 'number', 'AnimationSpeedScale', _('Animation speed scale'), - _('Compare the animation speed scale.'), - _('the animation speed scale'), - '', - 'JsPlatform/Extensions/tile_map24.png', - 'JsPlatform/Extensions/tile_map32.png' - ) - .addParameter('object', 'TileMap', 'TileMap', false) - .useStandardRelationalOperatorParameters('number') - .getCodeExtraInformation() - .setFunctionName('getAnimationSpeedScale'); - - object - .addAction( - 'SetAnimationSpeedScale', - _('Animation speed scale'), - _('Set the animation speed scale of the Tilemap (1 by default).'), + _('the animation speed scale of the Tilemap (1 by default).'), _('the animation speed scale'), '', - 'JsPlatform/Extensions/tile_map24.png', 'JsPlatform/Extensions/tile_map32.png' ) .addParameter('object', 'TileMap', 'TileMap', false) - .useStandardOperatorParameters('number') - .getCodeExtraInformation() + .useStandardParameters('number') .setFunctionName('setAnimationSpeedScale') .setGetter('getAnimationSpeedScale'); object - .addExpression( - 'AnimationSpeedScale', - _('Animation speed scale'), - _('Get the Animation speed scale'), - '', - 'JsPlatform/Extensions/tile_map32.png' - ) - .addParameter('object', 'TileMap', 'TileMap', false) - .getCodeExtraInformation() - .setFunctionName('getAnimationSpeedScale'); - - object - .addCondition( + .addExpressionAndConditionAndAction( + 'number', 'AnimationFps', _('Animation speed (FPS)'), - _('Compare the animation speed (in frames per second).'), - _('the animation speed (FPS)'), - '', - 'JsPlatform/Extensions/tile_map24.png', - 'JsPlatform/Extensions/tile_map32.png' - ) - .addParameter('object', 'TileMap', 'TileMap', false) - .useStandardRelationalOperatorParameters('number') - .getCodeExtraInformation() - .setFunctionName('getAnimationFps'); - - object - .addAction( - 'SetAnimationFps', - _('Animation speed (FPS)'), - _('Set the animation speed (in frames per second) of the Tilemap.'), + _('the animation speed (in frames per second)'), _('the animation speed (FPS)'), '', - 'JsPlatform/Extensions/tile_map24.png', 'JsPlatform/Extensions/tile_map32.png' ) .addParameter('object', 'TileMap', 'TileMap', false) - .useStandardOperatorParameters('number') - .getCodeExtraInformation() + .useStandardParameters('number') .setFunctionName('setAnimationFps') .setGetter('getAnimationFps'); - object - .addExpression( - 'AnimationFps', - _('Animation speed (FPS)'), - _('Get the animation speed (in frames per second)'), - '', - 'JsPlatform/Extensions/tile_map32.png' - ) - .addParameter('object', 'TileMap', 'TileMap', false) - .getCodeExtraInformation() - .setFunctionName('getAnimationFps'); - return extension; }, @@ -501,7 +450,8 @@ module.exports = { instance, associatedObject, pixiContainer, - pixiResourcesLoader + pixiResourcesLoader, + pixiRenderer ) { RenderedInstance.call( this, @@ -510,10 +460,12 @@ module.exports = { instance, associatedObject, pixiContainer, - pixiResourcesLoader + pixiResourcesLoader, + pixiRenderer ); - this._pixiObject = new Tilemap.CompositeRectTileLayer(0); + pixiRenderer.plugins.tilemap = new Tilemap.TileRenderer(); + this._pixiObject = new Tilemap.CompositeTilemap(); // Implement `containsPoint` so that we can set `interactive` to true and // the Tilemap will properly emit events when hovered/clicked. @@ -577,6 +529,13 @@ module.exports = { .getValue(), 10 ); + const levelIndex = parseInt( + this._associatedObject + .getProperties(this.project) + .get('levelIndex') + .getValue(), + 10 + ); const displayMode = this._associatedObject .getProperties(this.project) .get('displayMode') @@ -587,14 +546,25 @@ module.exports = { .getValue(); const pixiTileMapData = PixiTilemapHelper.loadPixiTileMapData( - (textureName) => - this._pixiResourcesLoader.getPIXITexture(this._project, textureName), + (textureName, relativeToPath) => { + if (relativeToPath) + return this._pixiResourcesLoader.getPixiTextureRelativeToFile( + this._project, + textureName, + relativeToPath + ); + return this._pixiResourcesLoader.getPIXITexture( + this._project, + textureName + ); + }, tilesetJsonData ? { ...tileMapJsonData, tilesets: [tilesetJsonData] } : tileMapJsonData, tilemapAtlasImage, tilemapJsonFile, - tilesetJsonFile + tilesetJsonFile, + levelIndex ); if (pixiTileMapData) { @@ -620,10 +590,11 @@ module.exports = { .getValue(); try { - const tileMapJsonData = await this._pixiResourcesLoader.getResourceJsonData( - this._project, - tilemapJsonFile - ); + const tileMapJsonData = + await this._pixiResourcesLoader.getResourceJsonData( + this._project, + tilemapJsonFile + ); const tilesetJsonData = tilesetJsonFile ? await this._pixiResourcesLoader.getResourceJsonData( diff --git a/Extensions/TileMap/pixi-tilemap-helper.js b/Extensions/TileMap/pixi-tilemap-helper.js index ee340cfa89c2..28d1e4cf75ca 100644 --- a/Extensions/TileMap/pixi-tilemap-helper.js +++ b/Extensions/TileMap/pixi-tilemap-helper.js @@ -54,13 +54,20 @@ height: number, tileWidth: number, tileHeight: number, - atlasTexture: PIXI.BaseTexture, + atlasTexture: PIXI.BaseTexture | null, textureCache: Object, layers: Array, tiles: Array, }} GenericPixiTileMapData */ + // ldtk: f=0 (no flip), f=1 (X flip only), f=2 (Y flip only), f=3 (both flips) + const LdtkToPixiRotations = { + 1: 12, + 2: 8, + 3: 4, + 0: 0, + }; /** * The Tilesets that are ready to be used * with Pixi Tilemap, indexed by their id. @@ -79,14 +86,6 @@ * @returns {?GenericPixiTileMapData} */ const parseTiledData = (tiledData, atlasTexture, getTexture) => { - if (!tiledData.tiledversion) { - console.warn( - "The loaded Tiled map does not contain a 'tiledversion' key. Are you sure this file has been exported from Tiled (mapeditor.org)?" - ); - - return null; - } - // We only handle tileset embedded in the tilemap. Warn if it's not the case. if (!tiledData.tilesets.length || 'source' in tiledData.tilesets[0]) { console.warn( @@ -169,6 +168,152 @@ return tileMapData; }; + /** + * Parse a Tiled map Ldtk file, + * exported from Ldtk (https://ldtk.io/json/) + * into a generic tile map data (`GenericPixiTileMapData`). + * + * @param {Object} tiledData A JS object representing a map exported from Tiled. + * @param {?PIXI.BaseTexture} atlasTexture + * @param {(textureName: string, relativeToPath: string) => PIXI.BaseTexture} getTexture A getter to load a texture. Used if atlasTexture is not specified. + * @returns {?GenericPixiTileMapData} + */ + const parseLDtkData = ( + tiledData, + atlasTexture, + getTexture, + levelIndex, + tilemapResourceName + ) => { + const tileSetAtlases = {}; + tiledData.defs.tilesets.forEach((tileset) => { + const texture = tileset.relPath + ? getTexture(tileset.relPath, tilemapResourceName) + : null; + tileSetAtlases[tileset.uid] = { texture, ...tileset }; + }); + const selectedLevel = tiledData.levels[levelIndex > -1 ? levelIndex : 0]; + if (!selectedLevel) return; + + const layers = []; + const textureCache = {}; + selectedLevel.layerInstances.reverse().forEach((ldtkLayer, layerIndex) => { + const layerAtlasTextureRelPath = ldtkLayer['__tilesetRelPath']; + const gridSize = ldtkLayer['__gridSize']; + const type = ldtkLayer['__type']; + const tilesetUid = ldtkLayer['__tilesetDefUid']; + const autoLayerTiles = ldtkLayer.autoLayerTiles; + const gridTiles = ldtkLayer.gridTiles; + const ldtkTiles = [...autoLayerTiles, ...gridTiles]; + const entities = ldtkLayer.entityInstances || []; + const layer = { + type: type, + autoLayerTiles, + ldtkTiles, + gridTiles, + entityInstances: entities, + visible: ldtkLayer.visible, + opacity: ldtkLayer['__opacity'], + }; + textureCache[layerIndex] = {}; + const tileSet = tileSetAtlases[tilesetUid]; + + ldtkTiles.forEach((generatedTile) => { + if (generatedTile.t in textureCache[layerIndex]) return; + + try { + const [x, y] = generatedTile.src; + const rect = new PIXI.Rectangle(x, y, gridSize, gridSize); + + // @ts-ignore - atlasTexture is never null here. + const texture = new PIXI.Texture(tileSet.texture, rect); + + textureCache[layerIndex][generatedTile.t] = texture; + } catch (error) { + console.error( + 'An error occurred while creating a PIXI.Texture to be used in a TileMap:', + error + ); + textureCache[layerIndex] = null; + } + }); + + layers.push(layer); + }); + + const selectedLevelBg = selectedLevel.bgRelPath; + textureCache.levelBg = {}; + if (selectedLevelBg) { + if (selectedLevelBg in textureCache.levelBg) return; + try { + const levelBgTexture = getTexture(selectedLevelBg, tilemapResourceName); + const rect = new PIXI.Rectangle(0, 0, selectedLevel.pxWid, selectedLevel.pxHei); + // // @ts-ignore - atlasTexture is never null here. + const texture = new PIXI.Texture(levelBgTexture, rect); + textureCache.levelBg[selectedLevelBg] = texture; + } catch (error) { + console.error( + 'An error occurred while creating a PIXI.Texture to be used in a TileMap:', + error + ); + textureCache.levelBg[selectedLevelBg] = null; + } + } + /** @type {GenericPixiTileMapData} */ + const tileMapData = { + width: 0, + height: 0, + tileWidth: 0, //not needed offset + tileHeight: 0, // not needed + atlasTexture, // oops, every layer can have a different one,, cant use this + textureCache, + layers, + tiles: [], + selectedLevelBg, + }; + + return tileMapData; + }; + + /** + * Detects if the file was created in tiled or ldtk and creates a GenericPixiTileMapData with the appropriate method + * @param tiledData + * @param atlasTexture + * @param getTexture + * @returns {?GenericPixiTileMapData} + */ + const parseTilemapData = ( + tiledData, + atlasTexture, + getTexture, + levelIndex, + tilemapResourceName + ) => { + if (tiledData.tiledversion) { + console.info( + 'Detected the json file was created in Tiled, parsing the data...' + ); + return parseTiledData(tiledData, atlasTexture, getTexture); + } + if (tiledData['__header__'] && tiledData['__header__'].app === 'LDtk') { + console.info( + 'Detected the json/ldtk file was created in LDtk, parsing the data...' + ); + return parseLDtkData( + tiledData, + atlasTexture, + getTexture, + levelIndex, + tilemapResourceName + ); + } + + console.warn( + "The loaded Tiled map data does not contain a 'tiledversion' or '__header__' key. Are you sure this file has been exported from Tiled (mapeditor.org) or LDtk (ldtk.io)?" + ); + return null; + }; + /** * Decodes a layer data, which can sometimes be store as a compressed base64 string * by Tiled. @@ -226,11 +371,14 @@ }; /** - * Extract information about the rotation of a tile from the tile id. + * Returns the tileUid and the pixi rotation of tiled tiles + * information about rotation in bits 32, 31 and 30 + * (see https://doc.mapeditor.org/en/stable/reference/tmx-map-format/). + * * @param {number} globalTileUid - * @returns {[number, boolean, boolean, boolean]} + * @returns {[number, number]} */ - const extractTileUidFlippedStates = (globalTileUid) => { + const getTileUidWithRotation = (globalTileUid) => { const FLIPPED_HORIZONTALLY_FLAG = 0x80000000; const FLIPPED_VERTICALLY_FLAG = 0x40000000; const FLIPPED_DIAGONALLY_FLAG = 0x20000000; @@ -246,51 +394,6 @@ FLIPPED_DIAGONALLY_FLAG ); - return [tileUid, !!flippedHorizontally, !!flippedVertically, !!flippedDiagonally]; - }; - - /** - * Return the texture to use for the tile with the specified uid, which can contains - * information about rotation in bits 32, 31 and 30 - * (see https://doc.mapeditor.org/en/stable/reference/tmx-map-format/). - * - * @param {Object} textureCache - * @param {number} globalTileUid - * @returns {?PIXI.Texture} - */ - const findTileTextureInCache = (textureCache, globalTileUid) => { - if (globalTileUid === 0) return null; - - if (textureCache[globalTileUid]) { - return textureCache[globalTileUid]; - } - - // If the texture is not in the cache, it's potentially because its ID - // is a flipped/rotated version of another ID. - const flippedStates = extractTileUidFlippedStates(globalTileUid); - const tileUid = flippedStates[0]; - const flippedHorizontally = flippedStates[1]; - const flippedVertically = flippedStates[2]; - const flippedDiagonally = flippedStates[3]; - - if (tileUid === 0) return null; - - // If the tile still can't be found in the cache, it means the ID we got - // is invalid. - const unflippedTexture = textureCache[tileUid]; - if (!unflippedTexture) return null; - - // Clone the unflipped texture and save it in the cache - const frame = unflippedTexture.frame.clone(); - const orig = unflippedTexture.orig.clone(); - if (flippedDiagonally) { - const width = orig.width; - orig.width = orig.height; - orig.height = width; - } - const trim = orig.clone(); - - // Get the rotation "D8" number. // See https://pixijs.io/examples/#/textures/texture-rotate.js let rotate = 0; if (flippedDiagonally) { @@ -312,15 +415,7 @@ rotate = 4; } } - - const flippedTexture = new PIXI.Texture( - unflippedTexture.baseTexture, - frame, - orig, - trim, - rotate - ); - return (textureCache[globalTileUid] = flippedTexture); + return [tileUid, rotate]; }; /** @@ -342,19 +437,46 @@ if (!pixiTileMap || !genericTileMapData) return; pixiTileMap.clear(); + if (genericTileMapData.selectedLevelBg) { + const bgTexture = + genericTileMapData.textureCache.levelBg[ + genericTileMapData.selectedLevelBg + ]; + pixiTileMap.tile(bgTexture, 0, 0); + } genericTileMapData.layers.forEach(function (layer, index) { if (displayMode === 'index' && layerIndex !== index) return; else if (displayMode === 'visible' && !layer.visible) return; - if (layer.type === 'objectgroup') { + // Ldtk Types + if (layer.ldtkTiles) { + // @ts-ignore + layer.ldtkTiles.forEach(function (tile) { + var texture = genericTileMapData.textureCache[index]; + if (texture) { + const [x, y] = tile.px; + pixiTileMap.tile( + // @ts-ignore + texture[tile.t], + x, + y, + { alpha: layer.opacity, rotate: LdtkToPixiRotations[tile.f] } + ); + } + }); + } + + // Tiled types + else if (layer.type === 'objectgroup') { layer.objects.forEach(function (object) { const { gid, x, y, visible } = object; if (displayMode === 'visible' && !visible) return; if (genericTileMapData.textureCache[gid]) { - pixiTileMap.addFrame( + pixiTileMap.tile( genericTileMapData.textureCache[gid], x, - y - genericTileMapData.tileHeight + y - genericTileMapData.tileHeight, + { alpha: layer.opacity } ); } }); @@ -374,31 +496,27 @@ for (let i = 0; i < layer.height; i++) { for (let j = 0; j < layer.width; j++) { const xPos = genericTileMapData.tileWidth * j; - const yPos = genericTileMapData.tileHeight * i; + const yPos = genericTileMapData.tileHeight * i; // these are stored in Ldtk, so no need to compute their positions - // The "globalTileUid" is the tile UID with encoded - // bits about the flipping/rotation of the tile. /** @type {number} */ // @ts-ignore const globalTileUid = layerData[tileSlotIndex]; + const [tileUid, rotate] = getTileUidWithRotation(globalTileUid); + const tileTexture = + tileUid !== 0 ? genericTileMapData.textureCache[tileUid] : null; - // Extract the tile UID and the texture. - const tileUid = extractTileUidFlippedStates(globalTileUid)[0]; - const tileTexture = findTileTextureInCache( - genericTileMapData.textureCache, - globalTileUid - ); if (tileTexture) { const tileData = genericTileMapData.tiles && genericTileMapData.tiles.find(function (tile) { - return tile.id === tileUid - 1; + return tile.id === globalTileUid - 1; }); - const pixiTilemapFrame = pixiTileMap.addFrame( + const pixiTilemapFrame = pixiTileMap.tile( tileTexture, xPos, - yPos + yPos, + { alpha: layer.opacity, rotate } ); // Animated tiles have a limitation: @@ -438,16 +556,23 @@ tiledData, atlasImageResourceName, tilemapResourceName, - tilesetResourceName + tilesetResourceName, + levelIndex ) => { const requestedTileMapDataId = tilemapResourceName + '@' + tilesetResourceName + '@' + - atlasImageResourceName; + atlasImageResourceName + + '@' + + levelIndex; // If the tilemap data is already in the cache, use it directly. + // For LDtk we do not need to generate a tileset + + // TODO we will need to split the cache here - one for tilemaps and another for tilesets + // TODO this is because LDtk tilemaps can have different atlas per layer if (loadedGenericPixiTileMapData[requestedTileMapDataId]) { return loadedGenericPixiTileMapData[requestedTileMapDataId]; } @@ -455,16 +580,17 @@ const atlasTexture = atlasImageResourceName ? getTexture(atlasImageResourceName) : null; - const genericPixiTileMapData = parseTiledData( + const genericPixiTileMapData = parseTilemapData( tiledData, atlasTexture, - getTexture + getTexture, + levelIndex, + tilemapResourceName ); if (genericPixiTileMapData) - loadedGenericPixiTileMapData[ - requestedTileMapDataId - ] = genericPixiTileMapData; + loadedGenericPixiTileMapData[requestedTileMapDataId] = + genericPixiTileMapData; return genericPixiTileMapData; }; }); diff --git a/Extensions/TileMap/pixi-tilemap/dist/pixi-tilemap.umd.js b/Extensions/TileMap/pixi-tilemap/dist/pixi-tilemap.umd.js index fdb03f011f51..cf143e3596d1 100644 --- a/Extensions/TileMap/pixi-tilemap/dist/pixi-tilemap.umd.js +++ b/Extensions/TileMap/pixi-tilemap/dist/pixi-tilemap.umd.js @@ -1,10 +1,10 @@ /* eslint-disable */ /*! - * pixi-tilemap - v2.1.3 - * Compiled Sun, 18 Oct 2020 17:08:58 UTC + * @pixi/tilemap - v3.2.0 + * Compiled Sat, 24 Apr 2021 21:20:23 UTC * - * pixi-tilemap is licensed under the MIT License. + * @pixi/tilemap is licensed under the MIT License. * http://www.opensource.org/licenses/mit-license * * Copyright 2019-2020, Ivan Popelyshev, All Rights Reserved @@ -12,105 +12,361 @@ this.PIXI = this.PIXI || {}; this.PIXI.tilemap = this.PIXI.tilemap || {}; (function (global, factory) { - typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('@pixi/display'), require('@pixi/core'), require('@pixi/constants'), require('@pixi/math'), require('@pixi/graphics'), require('@pixi/sprite')) : - typeof define === 'function' && define.amd ? define(['exports', '@pixi/display', '@pixi/core', '@pixi/constants', '@pixi/math', '@pixi/graphics', '@pixi/sprite'], factory) : - (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.pixi_tilemap = {}, global.PIXI, global.PIXI, global.PIXI, global.PIXI, global.PIXI, global.PIXI)); -}(this, (function (exports, display, core, constants, math, graphics, sprite) { 'use strict'; + typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('@pixi/display'), require('@pixi/core'), require('@pixi/constants'), require('@pixi/math'), require('@pixi/utils')) : + typeof define === 'function' && define.amd ? define(['exports', '@pixi/display', '@pixi/core', '@pixi/constants', '@pixi/math', '@pixi/utils'], factory) : + (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global._pixi_tilemap = {}, global.PIXI, global.PIXI, global.PIXI, global.PIXI, global.PIXI.utils)); +}(this, (function (exports, display, core, constants, math, utils) { 'use strict'; - class CanvasTileRenderer { - constructor(renderer) { - this.tileAnim = [0, 0]; - this.dontUseTransform = false; + + /** + * The renderer plugin for canvas. It isn't registered by default. + * + * ``` + * import { CanvasTileRenderer } from '@pixi/tilemap'; + * import { CanvasRenderer } from '@pixi/canvas-core'; + * + * // You must register this yourself (optional). @pixi/tilemap doesn't do it to + * // prevent a hard dependency on @pixi/canvas-core. + * CanvasRenderer.registerPlugin('tilemap', CanvasTileRenderer); + * ``` + */ + // TODO: Move to @pixi/tilemap-canvas + class CanvasTileRenderer + { + /** The renderer */ + + + /** The global tile animation state */ + __init() {this.tileAnim = [0, 0];} + + /** @deprecated */ + __init2() {this.dontUseTransform = false;} + + /** @param renderer */ + constructor(renderer) + {;CanvasTileRenderer.prototype.__init.call(this);CanvasTileRenderer.prototype.__init2.call(this); this.renderer = renderer; this.tileAnim = [0, 0]; } } - const cr = PIXI.CanvasRenderer; - if (cr) { - cr.registerPlugin('tilemap', CanvasTileRenderer); - } - const Constant = { - maxTextures: 16, - bufferSize: 2048, - boundSize: 1024, - boundCountPerBuffer: 1, + /** + * These are additional @pixi/tilemap options. + * + * This settings should not be changed after the renderer has initialized; otherwise, the behavior + * is undefined. + */ + const settings = { + /** The default number of textures per tilemap in a tilemap composite. */ + TEXTURES_PER_TILEMAP: 16, + + /** + * The width/height of each texture tile in a {@link TEXTILE_DIMEN}. This is 1024px by default. + * + * This should fit all tile base-textures; otherwise, {@link TextileResource} may fail to correctly + * upload the textures togther in a tiled fashion. + */ + TEXTILE_DIMEN: 1024, + + /** + * The number of texture tiles per {@link TextileResource}. + * + * Texture tiling is disabled by default, and so this is set to `1` by default. If it is set to a + * higher value, textures will be uploaded together in a tiled fashion. + * + * Since {@link TextileResource} is a dual-column format, this should be even for packing + * efficiency. The optimal value is usually 4. + */ + TEXTILE_UNITS: 1, + + /** The scaling mode of the combined texture tiling. */ + TEXTILE_SCALE_MODE: constants.SCALE_MODES.LINEAR, + + /** This will enable 32-bit index buffers. It's useful when you have more than 16K tiles. */ use32bitIndex: false, - SCALE_MODE: constants.SCALE_MODES.LINEAR, - DO_CLEAR: true + + /** Flags whether textiles should be cleared when each tile is uploaded. */ + DO_CLEAR: true, + + // Backward compatibility + get maxTextures() { return this.MAX_TEXTURES; }, + set maxTextures(value) { this.MAX_TEXTURES = value; }, + + get boundSize() { return this.TEXTURE_TILE_DIMEN; }, + set boundSize(value) { this.TILE_TEXTURE_DIMEN = value; }, + + get boundCountPerBuffer() { return this.TEXTILE_UNITS; }, + set boundCountPerBuffer(value) { this.TEXTILE_UNITS = value; }, }; - const POINT_STRUCT_SIZE = 12; - class RectTileLayer extends display.Container { - constructor(zIndex, texture) { - super(); - this.zIndex = 0; - this.modificationMarker = 0; - this._$_localBounds = new display.Bounds(); - this.shadowColor = new Float32Array([0.0, 0.0, 0.0, 0.5]); - this._globalMat = null; - this.pointsBuf = []; - this.hasAnim = false; - this.offsetX = 0; - this.offsetY = 0; - this.compositeParent = false; - this.tileAnim = null; - this.vbId = 0; - this.vb = null; - this.vbBuffer = null; - this.vbArray = null; - this.vbInts = null; - this.initialize(zIndex, texture); - } - initialize(zIndex, textures) { - if (!textures) { - textures = []; - } - else if (!(textures instanceof Array) && textures.baseTexture) { - textures = [textures]; - } - this.textures = textures; - this.zIndex = zIndex; - } - clear() { + // @deprecated + const Constant = settings; + + function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } + + + + + + var POINT_STRUCT; (function (POINT_STRUCT) { + const U = 0; POINT_STRUCT[POINT_STRUCT["U"] = U] = "U"; + const V = U + 1; POINT_STRUCT[POINT_STRUCT["V"] = V] = "V"; + const X = V + 1; POINT_STRUCT[POINT_STRUCT["X"] = X] = "X"; + const Y = X + 1; POINT_STRUCT[POINT_STRUCT["Y"] = Y] = "Y"; + const TILE_WIDTH = Y + 1; POINT_STRUCT[POINT_STRUCT["TILE_WIDTH"] = TILE_WIDTH] = "TILE_WIDTH"; + const TILE_HEIGHT = TILE_WIDTH + 1; POINT_STRUCT[POINT_STRUCT["TILE_HEIGHT"] = TILE_HEIGHT] = "TILE_HEIGHT"; + const ROTATE = TILE_HEIGHT + 1; POINT_STRUCT[POINT_STRUCT["ROTATE"] = ROTATE] = "ROTATE"; + const ANIM_X = ROTATE + 1; POINT_STRUCT[POINT_STRUCT["ANIM_X"] = ANIM_X] = "ANIM_X"; + const ANIM_Y = ANIM_X + 1; POINT_STRUCT[POINT_STRUCT["ANIM_Y"] = ANIM_Y] = "ANIM_Y"; + const TEXTURE_INDEX = ANIM_Y + 1; POINT_STRUCT[POINT_STRUCT["TEXTURE_INDEX"] = TEXTURE_INDEX] = "TEXTURE_INDEX"; + const ANIM_COUNT_X = TEXTURE_INDEX + 1; POINT_STRUCT[POINT_STRUCT["ANIM_COUNT_X"] = ANIM_COUNT_X] = "ANIM_COUNT_X"; + const ANIM_COUNT_Y = ANIM_COUNT_X + 1; POINT_STRUCT[POINT_STRUCT["ANIM_COUNT_Y"] = ANIM_COUNT_Y] = "ANIM_COUNT_Y"; + const ANIM_DIVISOR = ANIM_COUNT_Y + 1; POINT_STRUCT[POINT_STRUCT["ANIM_DIVISOR"] = ANIM_DIVISOR] = "ANIM_DIVISOR"; + const ALPHA = ANIM_DIVISOR + 1; POINT_STRUCT[POINT_STRUCT["ALPHA"] = ALPHA] = "ALPHA"; + })(POINT_STRUCT || (POINT_STRUCT = {})); + + const POINT_STRUCT_SIZE = (Object.keys(POINT_STRUCT).length / 2); + + /** + * A rectangular tilemap implementation that renders a predefined set of tile textures. + * + * The {@link Tilemap.tileset tileset} of a tilemap defines the list of base-textures that can be painted in the + * tilemap. A texture is identified using its base-texture's index into the this list, i.e. changing the base-texture + * at a given index in the tileset modifies the paint of all tiles pointing to that index. + * + * The size of the tileset is limited by the texture units supported by the client device. The minimum supported + * value is 8, as defined by the WebGL 1 specification. `gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS`) can be used + * to extract this limit. {@link CompositeTilemap} can be used to get around this limit by layering multiple tilemap + * instances. + * + * @example + * import { Tilemap } from '@pixi/tilemap'; + * import { Loader } from '@pixi/loaders'; + * + * // Add the spritesheet into your loader! + * Loader.shared.add('atlas', 'assets/atlas.json'); + * + * // Make the tilemap once the tileset assets are available. + * Loader.shared.load(function onTilesetLoaded() + * { + * // The base-texture is shared between all the tile textures. + * const tilemap = new Tilemap([Texture.from('grass.png').baseTexture]) + * .tile('grass.png', 0, 0) + * .tile('grass.png', 100, 100) + * .tile('brick_wall.png', 0, 100); + * }); + */ + class Tilemap extends display.Container + { + __init() {this.shadowColor = new Float32Array([0.0, 0.0, 0.0, 0.5]);} + __init2() {this._globalMat = null;} + + /** + * The tile animation frame. + * + * @see CompositeTilemap.tileAnim + */ + __init3() {this.tileAnim = null;} + + /** + * This is the last uploaded size of the tilemap geometry. + * @ignore + */ + __init4() {this.modificationMarker = 0;} + + /** @ignore */ + __init5() {this.offsetX = 0;} + + /** @ignore */ + __init6() {this.offsetY = 0;} + + /** @ignore */ + __init7() {this.compositeParent = false;} + + /** + * The list of base-textures being used in the tilemap. + * + * This should not be shuffled after tiles have been added into this tilemap. Usually, only tile textures + * should be added after tiles have been added into the map. + */ + + + /** + * The local bounds of the tilemap itself. This does not include DisplayObject children. + */ + __init8() {this.tilemapBounds = new display.Bounds();} + + /** Flags whether any animated tile was added. */ + __init9() {this.hasAnimatedTile = false;} + + /** The interleaved geometry of the tilemap. */ + __init10() {this.pointsBuf = [];} + + /** + * @param tileset - The tileset to use for the tilemap. This can be reset later with {@link Tilemap.setTileset}. The + * base-textures in this array must not be duplicated. + */ + constructor(tileset) + { + super();Tilemap.prototype.__init.call(this);Tilemap.prototype.__init2.call(this);Tilemap.prototype.__init3.call(this);Tilemap.prototype.__init4.call(this);Tilemap.prototype.__init5.call(this);Tilemap.prototype.__init6.call(this);Tilemap.prototype.__init7.call(this);Tilemap.prototype.__init8.call(this);Tilemap.prototype.__init9.call(this);Tilemap.prototype.__init10.call(this);Tilemap.prototype.__init11.call(this);Tilemap.prototype.__init12.call(this);Tilemap.prototype.__init13.call(this);Tilemap.prototype.__init14.call(this);Tilemap.prototype.__init15.call(this);Tilemap.prototype.__init16.call(this);; + this.setTileset(tileset); + } + + /** + * @returns The tileset of this tilemap. + */ + getTileset() + { + return this.tileset; + } + + /** + * Define the tileset used by the tilemap. + * + * @param tileset - The list of textures to use in the tilemap. If a base-texture (not array) is passed, it will + * be wrapped into an array. This should not contain any duplicates. + */ + setTileset(tileset = []) + { + if (!Array.isArray(tileset)) + { + tileset = [tileset]; + } + for (let i = 0; i < tileset.length; i++) + { + if ((tileset[i] ).baseTexture) + { + tileset[i] = (tileset[i] ).baseTexture; + } + } + + this.tileset = tileset; + + return this; + } + + /** Clears all the tiles added into this tilemap. */ + clear() + { this.pointsBuf.length = 0; this.modificationMarker = 0; - this._$_localBounds.clear(); - this.hasAnim = false; - } - addFrame(texture_, x, y, animX, animY) { - let texture; - let textureIndex = 0; - if (typeof texture_ === "number") { - textureIndex = texture_; - texture = this.textures[textureIndex]; - } - else { - if (typeof texture_ === "string") { - texture = core.Texture.from(texture_); + this.tilemapBounds.clear(); + this.hasAnimatedTile = false; + + return this; + } + + /** + * Adds a tile that paints the given texture at (x, y). + * + * @param tileTexture - The tiling texture to render. + * @param x - The local x-coordinate of the tile's position. + * @param y - The local y-coordinate of the tile's position. + * @param options - Additional tile options. + * @param [options.u=texture.frame.x] - The x-coordinate of the texture in its base-texture's space. + * @param [options.v=texture.frame.y] - The y-coordinate of the texture in its base-texture's space. + * @param [options.tileWidth=texture.orig.width] - The local width of the tile. + * @param [options.tileHeight=texture.orig.height] - The local height of the tile. + * @param [options.animX=0] - For animated tiles, this is the "offset" along the x-axis for adjacent + * animation frame textures in the base-texture. + * @param [options.animY=0] - For animated tiles, this is the "offset" along the y-axis for adjacent + * animation frames textures in the base-texture. + * @param [options.rotate=0] + * @param [options.animCountX=1024] - For animated tiles, this is the number of animation frame textures + * per row. + * @param [options.animCountY=1024] - For animated tiles, this is the number of animation frame textures + * per column. + * @param [options.animDivisor=1] - For animated tiles, this is the animation duration of each frame + * @param [options.alpha=1] - Tile alpha + * @return This tilemap, good for chaining. + */ + tile( + tileTexture, + x, + y, + options + + + + + + + + + + + + = {} + ) + { + let baseTexture; + let textureIndex = -1; + + if (typeof tileTexture === 'number') + { + textureIndex = tileTexture; + baseTexture = this.tileset[textureIndex]; + } + else + { + let texture; + + if (typeof tileTexture === 'string') + { + texture = core.Texture.from(tileTexture); } - else { - texture = texture_; + else + { + texture = tileTexture; } - let found = false; - let textureList = this.textures; - for (let i = 0; i < textureList.length; i++) { - if (textureList[i].baseTexture === texture.baseTexture) { + + const textureList = this.tileset; + + for (let i = 0; i < textureList.length; i++) + { + if (textureList[i] === texture.castToBaseTexture()) + { textureIndex = i; - found = true; break; } } - if (!found) { - return false; + + if ('baseTexture' in texture) + { + options.u = _nullishCoalesce(options.u, () => ( texture.frame.x)); + options.v = _nullishCoalesce(options.v, () => ( texture.frame.y)); + options.tileWidth = _nullishCoalesce(options.tileWidth, () => ( texture.orig.width)); + options.tileHeight = _nullishCoalesce(options.tileHeight, () => ( texture.orig.height)); } + + baseTexture = texture.castToBaseTexture(); } - this.addRect(textureIndex, texture.frame.x, texture.frame.y, x, y, texture.orig.width, texture.orig.height, animX, animY, texture.rotate); - return true; - } - addRect(textureIndex, u, v, x, y, tileWidth, tileHeight, animX = 0, animY = 0, rotate = 0, animCountX = 1024, animCountY = 1024) { - let pb = this.pointsBuf; - this.hasAnim = this.hasAnim || animX > 0 || animY > 0; + + if (!baseTexture || textureIndex < 0) + { + console.error('The tile texture was not found in the tilemap tileset.'); + + return this; + } + + const { + u = 0, + v = 0, + tileWidth = baseTexture.realWidth, + tileHeight = baseTexture.realHeight, + animX = 0, + animY = 0, + rotate = 0, + animCountX = 1024, + animCountY = 1024, + animDivisor = 1, + alpha = 1, + } = options; + + const pb = this.pointsBuf; + + this.hasAnimatedTile = this.hasAnimatedTile || animX > 0 || animY > 0; + pb.push(u); pb.push(v); pb.push(x); @@ -123,103 +379,200 @@ this.PIXI.tilemap = this.PIXI.tilemap || {}; pb.push(textureIndex); pb.push(animCountX); pb.push(animCountY); - this._$_localBounds.addFramePad(x, y, x + tileWidth, y + tileHeight, 0, 0); + pb.push(animDivisor); + pb.push(alpha); + + this.tilemapBounds.addFramePad(x, y, x + tileWidth, y + tileHeight, 0, 0); + return this; } - tileRotate(rotate) { + + /** Changes the rotation of the last tile. */ + tileRotate(rotate) + { const pb = this.pointsBuf; - pb[pb.length - 3] = rotate; + + pb[pb.length - (POINT_STRUCT_SIZE - POINT_STRUCT.TEXTURE_INDEX)] = rotate; } - tileAnimX(offset, count) { + + /** Changes the `animX`, `animCountX` of the last tile. */ + tileAnimX(offset, count) + { const pb = this.pointsBuf; - pb[pb.length - 5] = offset; - pb[pb.length - 2] = count; + + pb[pb.length - (POINT_STRUCT_SIZE - POINT_STRUCT.ANIM_X)] = offset; + pb[pb.length - (POINT_STRUCT_SIZE - POINT_STRUCT.ANIM_COUNT_X)] = count; + // pb[pb.length - (POINT_STRUCT_SIZE - POINT_STRUCT.ANIM_DIVISOR)] = duration; } - tileAnimY(offset, count) { + + /** Changes the `animY`, `animCountY` of the last tile. */ + tileAnimY(offset, count) + { + const pb = this.pointsBuf; + + pb[pb.length - (POINT_STRUCT_SIZE - POINT_STRUCT.ANIM_Y)] = offset; + pb[pb.length - (POINT_STRUCT_SIZE - POINT_STRUCT.ANIM_COUNT_Y)] = count; + } + + /** Changes the `animDivisor` value of the last tile. */ + tileAnimDivisor(divisor) + { const pb = this.pointsBuf; - pb[pb.length - 4] = offset; - pb[pb.length - 1] = count; + + pb[pb.length - (POINT_STRUCT_SIZE - POINT_STRUCT.ANIM_DIVISOR)] = divisor; } - renderCanvas(renderer) { - let plugin = renderer.plugins.tilemap; - if (!plugin.dontUseTransform) { - let wt = this.worldTransform; - renderer.context.setTransform(wt.a, wt.b, wt.c, wt.d, wt.tx * renderer.resolution, wt.ty * renderer.resolution); + + tileAlpha(alpha) + { + const pb = this.pointsBuf; + + pb[pb.length - (POINT_STRUCT_SIZE - POINT_STRUCT.ALPHA)] = alpha; + } + + __init11() {this.renderCanvas = (renderer) => + { + const plugin = renderer.plugins.tilemap; + + if (plugin && !plugin.dontUseTransform) + { + const wt = this.worldTransform; + + renderer.context.setTransform( + wt.a, + wt.b, + wt.c, + wt.d, + wt.tx * renderer.resolution, + wt.ty * renderer.resolution + ); } + this.renderCanvasCore(renderer); - } - renderCanvasCore(renderer) { - if (this.textures.length === 0) - return; - let points = this.pointsBuf; - const tileAnim = this.tileAnim || renderer.plugins.tilemap.tileAnim; + };} + + renderCanvasCore(renderer) + { + if (this.tileset.length === 0) return; + const points = this.pointsBuf; + const tileAnim = this.tileAnim || (renderer.plugins.tilemap && renderer.plugins.tilemap.tileAnim); + renderer.context.fillStyle = '#000000'; - for (let i = 0, n = points.length; i < n; i += POINT_STRUCT_SIZE) { - let x1 = points[i], y1 = points[i + 1]; - let x2 = points[i + 2], y2 = points[i + 3]; - let w = points[i + 4]; - let h = points[i + 5]; - var rotate = points[i + 6]; - x1 += points[i + 7] * tileAnim[0]; - y1 += points[i + 8] * tileAnim[1]; - let textureIndex = points[i + 9]; - if (textureIndex >= 0) { - renderer.context.drawImage(this.textures[textureIndex].baseTexture.getDrawableSource(), x1, y1, w, h, x2, y2, w, h); + for (let i = 0, n = points.length; i < n; i += POINT_STRUCT_SIZE) + { + let x1 = points[i + POINT_STRUCT.U] * tileAnim[0]; + let y1 = points[i + POINT_STRUCT.V] * tileAnim[1]; + const x2 = points[i + POINT_STRUCT.X]; + const y2 = points[i + POINT_STRUCT.Y]; + const w = points[i + POINT_STRUCT.TILE_WIDTH]; + const h = points[i + POINT_STRUCT.TILE_HEIGHT]; + + x1 += points[i + POINT_STRUCT.ANIM_X] * renderer.plugins.tilemap.tileAnim[0]; + y1 += points[i + POINT_STRUCT.ANIM_Y] * renderer.plugins.tilemap.tileAnim[1]; + + const textureIndex = points[i + POINT_STRUCT.TEXTURE_INDEX]; + const alpha = points[i + POINT_STRUCT.ALPHA]; + + // canvas does not work with rotate yet + + if (textureIndex >= 0 && this.tileset[textureIndex]) + { + renderer.context.globalAlpha = alpha; + renderer.context.drawImage( + (this.tileset[textureIndex] ).getDrawableSource(), + x1, y1, w, h, x2, y2, w, h + ); } - else { + else + { renderer.context.globalAlpha = 0.5; renderer.context.fillRect(x2, y2, w, h); - renderer.context.globalAlpha = 1; } + renderer.context.globalAlpha = 1; } } - destroyVb() { - if (this.vb) { + + __init12() {this.vbId = 0;} + __init13() {this.vb = null;} + __init14() {this.vbBuffer = null;} + __init15() {this.vbArray = null;} + __init16() {this.vbInts = null;} + + destroyVb() + { + if (this.vb) + { this.vb.destroy(); this.vb = null; } } - render(renderer) { - let plugin = renderer.plugins['tilemap']; - let shader = plugin.getShader(); + + render(renderer) + { + const plugin = (renderer.plugins ).tilemap; + const shader = plugin.getShader(); + renderer.batch.setObjectRenderer(plugin); this._globalMat = shader.uniforms.projTransMatrix; - renderer.globalUniforms.uniforms.projectionMatrix.copyTo(this._globalMat).append(this.worldTransform); + renderer + .globalUniforms + .uniforms + .projectionMatrix + .copyTo(this._globalMat) + .append(this.worldTransform); + shader.uniforms.shadowColor = this.shadowColor; shader.uniforms.animationFrame = this.tileAnim || plugin.tileAnim; + this.renderWebGLCore(renderer, plugin); } - renderWebGLCore(renderer, plugin) { - let points = this.pointsBuf; - if (points.length === 0) - return; - let rectsCount = points.length / POINT_STRUCT_SIZE; - let shader = plugin.getShader(); - let textures = this.textures; - if (textures.length === 0) - return; - plugin.bindTextures(renderer, shader, textures); + + renderWebGLCore(renderer, plugin) + { + const points = this.pointsBuf; + + if (points.length === 0) return; + const rectsCount = points.length / POINT_STRUCT_SIZE; + + const shader = plugin.getShader(); + const textures = this.tileset; + + if (textures.length === 0) return; + + plugin.bindTileTextures(renderer, textures); renderer.shader.bind(shader, false); + + // lost context! recover! let vb = this.vb; - if (!vb) { + + if (!vb) + { vb = plugin.createVb(); this.vb = vb; - this.vbId = vb.id; + this.vbId = (vb ).id; this.vbBuffer = null; this.modificationMarker = 0; } + plugin.checkIndexBuffer(rectsCount, vb); - const boundCountPerBuffer = Constant.boundCountPerBuffer; - let vertexBuf = vb.getBuffer('aVertexPosition'); - let vertices = rectsCount * vb.vertPerQuad; - if (vertices === 0) - return; - if (this.modificationMarker !== vertices) { + const boundCountPerBuffer = settings.TEXTILE_UNITS; + + const vertexBuf = vb.getBuffer('aVertexPosition'); + // if layer was changed, re-upload vertices + const vertices = rectsCount * vb.vertPerQuad; + + if (vertices === 0) return; + if (this.modificationMarker !== vertices) + { this.modificationMarker = vertices; - let vs = vb.stride * vertices; - if (!this.vbBuffer || this.vbBuffer.byteLength < vs) { + const vs = vb.stride * vertices; + + if (!this.vbBuffer || this.vbBuffer.byteLength < vs) + { + // !@#$ happens, need resize let bk = vb.stride; - while (bk < vs) { + + while (bk < vs) + { bk *= 2; } this.vbBuffer = new ArrayBuffer(bk); @@ -227,36 +580,67 @@ this.PIXI.tilemap = this.PIXI.tilemap || {}; this.vbInts = new Uint32Array(this.vbBuffer); vertexBuf.update(this.vbBuffer); } - let arr = this.vbArray, ints = this.vbInts; + + const arr = this.vbArray; + // const ints = this.vbInts; + // upload vertices! let sz = 0; + // let tint = 0xffffffff; let textureId = 0; let shiftU = this.offsetX; let shiftV = this.offsetY; - let tint = -1; - for (let i = 0; i < points.length; i += POINT_STRUCT_SIZE) { - let eps = 0.5; - if (this.compositeParent) { - if (boundCountPerBuffer > 1) { - textureId = (points[i + 9] >> 2); - shiftU = this.offsetX * (points[i + 9] & 1); - shiftV = this.offsetY * ((points[i + 9] >> 1) & 1); + + // let tint = 0xffffffff; + // const tint = -1; + + for (let i = 0; i < points.length; i += POINT_STRUCT_SIZE) + { + const eps = 0.5; + + if (this.compositeParent) + { + const textureIndex = points[i + POINT_STRUCT.TEXTURE_INDEX]; + + if (boundCountPerBuffer > 1) + { + // TODO: what if its more than 4? + textureId = (textureIndex >> 2); + shiftU = this.offsetX * (textureIndex & 1); + shiftV = this.offsetY * ((textureIndex >> 1) & 1); } - else { - textureId = points[i + 9]; + else + { + textureId = textureIndex; shiftU = 0; shiftV = 0; } } - let x = points[i + 2], y = points[i + 3]; - let w = points[i + 4], h = points[i + 5]; - let u = points[i] + shiftU, v = points[i + 1] + shiftV; - let rotate = points[i + 6]; - const animX = points[i + 7], animY = points[i + 8]; - const animWidth = points[i + 10] || 1024, animHeight = points[i + 11] || 1024; + const x = points[i + POINT_STRUCT.X]; + const y = points[i + POINT_STRUCT.Y]; + const w = points[i + POINT_STRUCT.TILE_WIDTH]; + const h = points[i + POINT_STRUCT.TILE_HEIGHT]; + const u = points[i + POINT_STRUCT.U] + shiftU; + const v = points[i + POINT_STRUCT.V] + shiftV; + let rotate = points[i + POINT_STRUCT.ROTATE]; + + const animX = points[i + POINT_STRUCT.ANIM_X]; + const animY = points[i + POINT_STRUCT.ANIM_Y]; + const animWidth = points[i + POINT_STRUCT.ANIM_COUNT_X] || 1024; + const animHeight = points[i + POINT_STRUCT.ANIM_COUNT_Y] || 1024; + const animXEncoded = animX + (animWidth * 2048); const animYEncoded = animY + (animHeight * 2048); - let u0, v0, u1, v1, u2, v2, u3, v3; - if (rotate === 0) { + const animDivisor = points[i + POINT_STRUCT.ANIM_DIVISOR]; + const alpha = points[i + POINT_STRUCT.ALPHA]; + + let u0; + let v0; let u1; + let v1; let u2; + let v2; let u3; + let v3; + + if (rotate === 0) + { u0 = u; v0 = v; u1 = u + w; @@ -266,28 +650,36 @@ this.PIXI.tilemap = this.PIXI.tilemap || {}; u3 = u; v3 = v + h; } - else { + else + { let w2 = w / 2; let h2 = h / 2; - if (rotate % 4 !== 0) { + + if (rotate % 4 !== 0) + { w2 = h / 2; h2 = w / 2; } const cX = u + w2; const cY = v + h2; + rotate = math.groupD8.add(rotate, math.groupD8.NW); u0 = cX + (w2 * math.groupD8.uX(rotate)); v0 = cY + (h2 * math.groupD8.uY(rotate)); - rotate = math.groupD8.add(rotate, 2); + + rotate = math.groupD8.add(rotate, 2); // rotate 90 degrees clockwise u1 = cX + (w2 * math.groupD8.uX(rotate)); v1 = cY + (h2 * math.groupD8.uY(rotate)); + rotate = math.groupD8.add(rotate, 2); u2 = cX + (w2 * math.groupD8.uX(rotate)); v2 = cY + (h2 * math.groupD8.uY(rotate)); + rotate = math.groupD8.add(rotate, 2); u3 = cX + (w2 * math.groupD8.uX(rotate)); v3 = cY + (h2 * math.groupD8.uY(rotate)); } + arr[sz++] = x; arr[sz++] = y; arr[sz++] = u0; @@ -299,6 +691,9 @@ this.PIXI.tilemap = this.PIXI.tilemap || {}; arr[sz++] = animXEncoded; arr[sz++] = animYEncoded; arr[sz++] = textureId; + arr[sz++] = animDivisor; + arr[sz++] = alpha; + arr[sz++] = x + w; arr[sz++] = y; arr[sz++] = u1; @@ -310,6 +705,9 @@ this.PIXI.tilemap = this.PIXI.tilemap || {}; arr[sz++] = animXEncoded; arr[sz++] = animYEncoded; arr[sz++] = textureId; + arr[sz++] = animDivisor; + arr[sz++] = alpha; + arr[sz++] = x + w; arr[sz++] = y + h; arr[sz++] = u2; @@ -321,6 +719,9 @@ this.PIXI.tilemap = this.PIXI.tilemap || {}; arr[sz++] = animXEncoded; arr[sz++] = animYEncoded; arr[sz++] = textureId; + arr[sz++] = animDivisor; + arr[sz++] = alpha; + arr[sz++] = x; arr[sz++] = y + h; arr[sz++] = u3; @@ -332,641 +733,1170 @@ this.PIXI.tilemap = this.PIXI.tilemap || {}; arr[sz++] = animXEncoded; arr[sz++] = animYEncoded; arr[sz++] = textureId; + arr[sz++] = animDivisor; + arr[sz++] = alpha; } + vertexBuf.update(arr); } - renderer.geometry.bind(vb, shader); + + (renderer.geometry ).bind(vb, shader); renderer.geometry.draw(constants.DRAW_MODES.TRIANGLES, rectsCount * 6, 0); } - isModified(anim) { - if (this.modificationMarker !== this.pointsBuf.length || - anim && this.hasAnim) { + + /** + * @internal + * @ignore + */ + isModified(anim) + { + if (this.modificationMarker !== this.pointsBuf.length + || (anim && this.hasAnimatedTile)) + { return true; } + return false; } - clearModify() { + + /** + * This will pull forward the modification marker. + * + * @internal + * @ignore + */ + clearModify() + { this.modificationMarker = this.pointsBuf.length; } - _calculateBounds() { - const { minX, minY, maxX, maxY } = this._$_localBounds; + + /** @override */ + _calculateBounds() + { + const { minX, minY, maxX, maxY } = this.tilemapBounds; + this._bounds.addFrame(this.transform, minX, minY, maxX, maxY); } - getLocalBounds(rect) { - if (this.children.length === 0) { - return this._$_localBounds.getRectangle(rect); + + /** @override */ + getLocalBounds(rect) + { + // we can do a fast local bounds if the sprite has no children! + if (this.children.length === 0) + { + return this.tilemapBounds.getRectangle(rect); } + return super.getLocalBounds.call(this, rect); } - destroy(options) { + + /** @override */ + destroy(options) + { super.destroy(options); this.destroyVb(); } - } - class CompositeRectTileLayer extends display.Container { - constructor(zIndex, bitmaps, texPerChild) { - super(); - this.modificationMarker = 0; - this.shadowColor = new Float32Array([0.0, 0.0, 0.0, 0.5]); - this._globalMat = null; - this._lastLayer = null; - this.tileAnim = null; - this.initialize.apply(this, arguments); - } - initialize(zIndex, bitmaps, texPerChild) { - if (texPerChild === true) { - texPerChild = 0; - } - this.z = this.zIndex = zIndex; - this.texPerChild = texPerChild || Constant.boundCountPerBuffer * Constant.maxTextures; - if (bitmaps) { - this.setBitmaps(bitmaps); - } - } - setBitmaps(bitmaps) { - for (let i = 0; i < bitmaps.length; i++) { - if (bitmaps[i] && !bitmaps[i].baseTexture) { - throw new Error(`pixi-tilemap cannot use destroyed textures. ` + - `Probably, you passed resources['myAtlas'].texture in pixi > 5.2.1, it does not exist there.`); + /** + * Deprecated signature for {@link Tilemap.tile tile}. + * + * @deprecated Since @pixi/tilemap 3. + */ + addFrame(texture, x, y, animX, animY) + { + this.tile( + texture, + x, + y, + { + animX, + animY, } + ); + + return true; + } + + /** + * Deprecated signature for {@link Tilemap.tile tile}. + * + * @deprecated Since @pixi/tilemap 3. + */ + // eslint-disable-next-line max-params + addRect( + textureIndex, + u, + v, + x, + y, + tileWidth, + tileHeight, + animX = 0, + animY = 0, + rotate = 0, + animCountX = 1024, + animCountY = 1024, + animDivisor = 1, + alpha = 1, + ) + { + return this.tile( + textureIndex, + x, y, + { + u, v, tileWidth, tileHeight, animX, animY, rotate, animCountX, animCountY, animDivisor, alpha + } + ); + } + } + + /** + * A tilemap composite that lazily builds tilesets layered into multiple tilemaps. + * + * The composite tileset is the concatenatation of the individual tilesets used in the tilemaps. You can + * preinitialized it by passing a list of tile textures to the constructor. Otherwise, the composite tilemap + * is lazily built as you add more tiles with newer tile textures. A new tilemap is created once the last + * tilemap has reached its limit (as set by {@link CompositeTilemap.texturesPerTilemap texturesPerTilemap}). + * + * @example + * import { Application } from '@pixi/app'; + * import { CompositeTilemap } from '@pixi/tilemap'; + * import { Loader } from '@pixi/loaders'; + * + * // Setup view & stage. + * const app = new Application(); + * + * document.body.appendChild(app.renderer.view); + * app.stage.interactive = true; + * + * // Global reference to the tilemap. + * let globalTilemap: CompositeTilemap; + * + * // Load the tileset spritesheet! + * Loader.shared.load('atlas.json'); + * + * // Initialize the tilemap scene when the assets load. + * Loader.shared.load(function onTilesetLoaded() + * { + * const tilemap = new CompositeTilemap(); + * + * // Setup the game level with grass and dungeons! + * for (let x = 0; x < 10; x++) + * { + * for (let y = 0; y < 10; y++) + * { + * tilemap.tile( + * x % 2 === 0 && (x === y || x + y === 10) ? 'dungeon.png' : 'grass.png', + * x * 100, + * y * 100, + * ); + * } + * } + * + * globalTilemap = app.stage.addChild(tilemap); + * }); + * + * // Show a bomb at a random location whenever the user clicks! + * app.stage.on('click', function onClick() + * { + * if (!globalTilemap) return; + * + * const x = Math.floor(Math.random() * 10); + * const y = Math.floor(Math.random() * 10); + * + * globalTilemap.tile('bomb.png', x * 100, y * 100); + * }); + */ + class CompositeTilemap extends display.Container + { + /** The hard limit on the number of tile textures used in each tilemap. */ + + + /** + * The animation frame vector. + * + * Animated tiles have four parameters - `animX`, `animY`, `animCountX`, `animCountY`. The textures + * of adjacent animation frames are at offset `animX` or `animY` of each other, with `animCountX` per + * row and `animCountY` per column. + * + * The animation frame vector specifies which animation frame texture to use. If the x/y coordinate is + * larger than the `animCountX` or `animCountY` for a specific tile, the modulus is taken. + */ + __init() {this.tileAnim = null;} + + /** The last modified tilemap. */ + __init2() {this.lastModifiedTilemap = null;} + + __init3() {this.modificationMarker = 0;} + __init4() {this.shadowColor = new Float32Array([0.0, 0.0, 0.0, 0.5]);} + __init5() {this._globalMat = null;} + + /** + * @param tileset - A list of tile base-textures that will be used to eagerly initialized the layered + * tilemaps. This is only an performance optimization, and using {@link CompositeTilemap.tile tile} + * will work equivalently. + */ + constructor(tileset) + { + super();CompositeTilemap.prototype.__init.call(this);CompositeTilemap.prototype.__init2.call(this);CompositeTilemap.prototype.__init3.call(this);CompositeTilemap.prototype.__init4.call(this);CompositeTilemap.prototype.__init5.call(this);CompositeTilemap.prototype.__init6.call(this);CompositeTilemap.prototype.__init7.call(this);; + + this.tileset(tileset); + this.texturesPerTilemap = settings.TEXTURES_PER_TILEMAP; + } + + /** + * This will preinitialize the tilesets of the layered tilemaps. + * + * If used after a tilemap has been created (or a tile added), this will overwrite the tile textures of the + * existing tilemaps. Passing the tileset to the constructor instead is the best practice. + * + * @param tileTextures - The list of tile textures that make up the tileset. + */ + tileset(tileTextures) + { + if (!tileTextures) + { + tileTextures = []; } - let texPerChild = this.texPerChild; - let len1 = this.children.length; - let len2 = Math.ceil(bitmaps.length / texPerChild); - let i; - for (i = 0; i < len1; i++) { - this.children[i].textures = bitmaps.slice(i * texPerChild, (i + 1) * texPerChild); + + const texPerChild = this.texturesPerTilemap; + const len1 = this.children.length; + const len2 = Math.ceil(tileTextures.length / texPerChild); + + for (let i = 0; i < Math.min(len1, len2); i++) + { + (this.children[i] ).setTileset( + tileTextures.slice(i * texPerChild, (i + 1) * texPerChild) + ); } - for (i = len1; i < len2; i++) { - let layer = new RectTileLayer(this.zIndex, bitmaps.slice(i * texPerChild, (i + 1) * texPerChild)); - layer.compositeParent = true; - layer.offsetX = Constant.boundSize; - layer.offsetY = Constant.boundSize; - this.addChild(layer); + for (let i = len1; i < len2; i++) + { + const tilemap = new Tilemap(tileTextures.slice(i * texPerChild, (i + 1) * texPerChild)); + + tilemap.compositeParent = true; + tilemap.offsetX = settings.TEXTILE_DIMEN; + tilemap.offsetY = settings.TEXTILE_DIMEN; + + // TODO: Don't use children + this.addChild(tilemap); } + + return this; } - clear() { - for (let i = 0; i < this.children.length; i++) { - this.children[i].clear(); + + /** Clears the tilemap composite. */ + clear() + { + for (let i = 0; i < this.children.length; i++) + { + (this.children[i] ).clear(); } + this.modificationMarker = 0; + + return this; } - addRect(textureIndex, u, v, x, y, tileWidth, tileHeight, animX, animY, rotate, animWidth, animHeight) { - const childIndex = textureIndex / this.texPerChild >> 0; - const textureId = textureIndex % this.texPerChild; - if (this.children[childIndex] && this.children[childIndex].textures) { - this._lastLayer = this.children[childIndex]; - this._lastLayer.addRect(textureId, u, v, x, y, tileWidth, tileHeight, animX, animY, rotate, animWidth, animHeight); - } - else { - this._lastLayer = null; + + /** Changes the rotation of the last added tile. */ + tileRotate(rotate) + { + if (this.lastModifiedTilemap) + { + this.lastModifiedTilemap.tileRotate(rotate); } + return this; } - tileRotate(rotate) { - if (this._lastLayer) { - this._lastLayer.tileRotate(rotate); + + /** Changes `animX`, `animCountX` of the last added tile. */ + tileAnimX(offset, count) + { + if (this.lastModifiedTilemap) + { + this.lastModifiedTilemap.tileAnimX(offset, count); } + return this; } - tileAnimX(offset, count) { - if (this._lastLayer) { - this._lastLayer.tileAnimX(offset, count); + + /** Changes `animY`, `animCountY` of the last added tile. */ + tileAnimY(offset, count) + { + if (this.lastModifiedTilemap) + { + this.lastModifiedTilemap.tileAnimY(offset, count); } + return this; } - tileAnimY(offset, count) { - if (this._lastLayer) { - this._lastLayer.tileAnimY(offset, count); + + /** Changes `tileAnimDivisor` value of the last added tile. */ + tileAnimDivisor(divisor) + { + if (this.lastModifiedTilemap) + { + this.lastModifiedTilemap.tileAnimDivisor(divisor); } + return this; } - addFrame(texture_, x, y, animX, animY, animWidth, animHeight) { - let texture; - let layer = null; - let ind = 0; - let children = this.children; - this._lastLayer = null; - if (typeof texture_ === "number") { - let childIndex = texture_ / this.texPerChild >> 0; - layer = children[childIndex]; - if (!layer) { - layer = children[0]; - if (!layer) { - return this; - } - ind = 0; + + /** + * Adds a tile that paints the given tile texture at (x, y). + * + * @param tileTexture - The tile texture. You can pass an index into the composite tilemap as well. + * @param x - The local x-coordinate of the tile's location. + * @param y - The local y-coordinate of the tile's location. + * @param options - Additional options to pass to {@link Tilemap.tile}. + * @param [options.u=texture.frame.x] - The x-coordinate of the texture in its base-texture's space. + * @param [options.v=texture.frame.y] - The y-coordinate of the texture in its base-texture's space. + * @param [options.tileWidth=texture.orig.width] - The local width of the tile. + * @param [options.tileHeight=texture.orig.height] - The local height of the tile. + * @param [options.animX=0] - For animated tiles, this is the "offset" along the x-axis for adjacent + * animation frame textures in the base-texture. + * @param [options.animY=0] - For animated tiles, this is the "offset" along the y-axis for adjacent + * animation frames textures in the base-texture. + * @param [options.rotate=0] + * @param [options.animCountX=1024] - For animated tiles, this is the number of animation frame textures + * per row. + * @param [options.animCountY=1024] - For animated tiles, this is the number of animation frame textures + * per column. + * @param [options.animDivisor=1] - For animated tiles, this is the animation duration each frame + * @param [options.alpha=1] - Tile alpha + * @return This tilemap, good for chaining. + */ + tile( + tileTexture, + x, + y, + options + + + + + + + + + + + + = {} + ) + { + let tilemap = null; + const children = this.children; + + this.lastModifiedTilemap = null; + + if (typeof tileTexture === 'number') + { + const childIndex = tileTexture / this.texturesPerTilemap >> 0; + let tileIndex = 0; + + tilemap = children[childIndex] ; + + if (!tilemap) + { + tilemap = children[0] ; + + // Silently fail if the tilemap doesn't exist + if (!tilemap) return this; + + tileIndex = 0; } - else { - ind = texture_ % this.texPerChild; + else + { + tileIndex = tileTexture % this.texturesPerTilemap; } - texture = layer.textures[ind]; + + tilemap.tile( + tileIndex, + x, + y, + options, + ); } - else { - if (typeof texture_ === "string") { - texture = core.Texture.from(texture_); - } - else { - texture = texture_; + else + { + if (typeof tileTexture === 'string') + { + tileTexture = core.Texture.from(tileTexture); } - for (let i = 0; i < children.length; i++) { - let child = children[i]; - let tex = child.textures; - for (let j = 0; j < tex.length; j++) { - if (tex[j].baseTexture === texture.baseTexture) { - layer = child; - ind = j; + + // Probe all tilemaps to find which tileset contains the base-texture. + for (let i = 0; i < children.length; i++) + { + const child = children[i] ; + const tex = child.getTileset(); + + for (let j = 0; j < tex.length; j++) + { + if (tex[j] === tileTexture.baseTexture) + { + tilemap = child; break; } } - if (layer) { + + if (tilemap) + { break; } } - if (!layer) { - for (let i = 0; i < children.length; i++) { - let child = children[i]; - if (child.textures.length < this.texPerChild) { - layer = child; - ind = child.textures.length; - child.textures.push(texture); + + // If no tileset contains the base-texture, attempt to add it. + if (!tilemap) + { + // Probe the tilemaps to find one below capacity. If so, add the texture into that tilemap. + for (let i = children.length - 1; i >= 0; i--) + { + const child = children[i] ; + + if (child.getTileset().length < this.texturesPerTilemap) + { + tilemap = child; + child.getTileset().push(tileTexture.baseTexture); break; } } - if (!layer) { - layer = new RectTileLayer(this.zIndex, texture); - layer.compositeParent = true; - layer.offsetX = Constant.boundSize; - layer.offsetY = Constant.boundSize; - this.addChild(layer); - ind = 0; + + // Otherwise, create a new tilemap initialized with that tile texture. + if (!tilemap) + { + tilemap = new Tilemap(tileTexture.baseTexture); + tilemap.compositeParent = true; + tilemap.offsetX = settings.TEXTILE_DIMEN; + tilemap.offsetY = settings.TEXTILE_DIMEN; + + this.addChild(tilemap); } } + + tilemap.tile( + tileTexture, + x, + y, + options, + ); } - this._lastLayer = layer; - layer.addRect(ind, texture.frame.x, texture.frame.y, x, y, texture.orig.width, texture.orig.height, animX, animY, texture.rotate, animWidth, animHeight); + + this.lastModifiedTilemap = tilemap; + return this; } - renderCanvas(renderer) { - if (!this.visible || this.worldAlpha <= 0 || !this.renderable) { + + __init6() {this.renderCanvas = (renderer) => + { + if (!this.visible || this.worldAlpha <= 0 || !this.renderable) + { return; } - let plugin = renderer.plugins.tilemap; - if (!plugin.dontUseTransform) { - let wt = this.worldTransform; - renderer.context.setTransform(wt.a, wt.b, wt.c, wt.d, wt.tx * renderer.resolution, wt.ty * renderer.resolution); + + const tilemapPlugin = renderer.plugins.tilemap; + + if (tilemapPlugin && !tilemapPlugin.dontUseTransform) + { + const wt = this.worldTransform; + + renderer.context.setTransform( + wt.a, + wt.b, + wt.c, + wt.d, + wt.tx * renderer.resolution, + wt.ty * renderer.resolution + ); } - let layers = this.children; - for (let i = 0; i < layers.length; i++) { - const layer = layers[i]; + + const layers = this.children; + + for (let i = 0; i < layers.length; i++) + { + const layer = (layers[i] ); + layer.tileAnim = this.tileAnim; layer.renderCanvasCore(renderer); } - } - render(renderer) { - if (!this.visible || this.worldAlpha <= 0 || !this.renderable) { + };} + + render(renderer) + { + if (!this.visible || this.worldAlpha <= 0 || !this.renderable) + { return; } - let plugin = renderer.plugins['tilemap']; - let shader = plugin.getShader(); + + const plugin = renderer.plugins.tilemap ; + const shader = plugin.getShader(); + renderer.batch.setObjectRenderer(plugin); + + // TODO: dont create new array, please this._globalMat = shader.uniforms.projTransMatrix; renderer.globalUniforms.uniforms.projectionMatrix.copyTo(this._globalMat).append(this.worldTransform); shader.uniforms.shadowColor = this.shadowColor; shader.uniforms.animationFrame = this.tileAnim || plugin.tileAnim; + renderer.shader.bind(shader, false); - let layers = this.children; - for (let i = 0; i < layers.length; i++) { - const layer = layers[i]; - layer.renderWebGLCore(renderer, plugin); + + const layers = this.children; + + for (let i = 0; i < layers.length; i++) + { + (layers[i] ).renderWebGLCore(renderer, plugin); } } - isModified(anim) { - let layers = this.children; - if (this.modificationMarker !== layers.length) { + + /** + * @internal + * @ignore + */ + isModified(anim) + { + const layers = this.children; + + if (this.modificationMarker !== layers.length) + { return true; } - for (let i = 0; i < layers.length; i++) { - if (layers[i].isModified(anim)) { + for (let i = 0; i < layers.length; i++) + { + if ((layers[i] ).isModified(anim)) + { return true; } } + return false; } - clearModify() { - let layers = this.children; + + /** + * @internal + * @ignore + */ + clearModify() + { + const layers = this.children; + this.modificationMarker = layers.length; - for (let i = 0; i < layers.length; i++) { - layers[i].clearModify(); + for (let i = 0; i < layers.length; i++) + { + (layers[i] ).clearModify(); } } - } - class GraphicsLayer extends graphics.Graphics { - constructor(zIndex) { - super(); - this.zIndex = zIndex; - } - renderCanvas(renderer) { - let wt = null; - if (renderer.plugins.tilemap.dontUseTransform) { - wt = this.transform.worldTransform; - this.transform.worldTransform = math.Matrix.IDENTITY; - } - renderer.plugins.graphics.render(this); - if (renderer.plugins.tilemap.dontUseTransform) { - this.transform.worldTransform = wt; - } - renderer.context.globalAlpha = 1.0; - } - isModified(anim) { - return false; - } - clearModify() { + /** + * @deprecated Since @pixi/tilemap 3. + * @see CompositeTilemap.tile + */ + addFrame( + texture, + x, + y, + animX, + animY, + animWidth, + animHeight, + animDivisor, + alpha + ) + { + return this.tile( + texture, + x, y, + { + animX, + animY, + animCountX: animWidth, + animCountY: animHeight, + animDivisor, + alpha + } + ); } - } - class MultiTextureResource extends core.resources.Resource { - constructor(options) { - super(options.bufferSize, options.bufferSize); - this.DO_CLEAR = false; - this.boundSize = 0; - this._clearBuffer = null; - this.baseTex = null; - this.boundSprites = []; - this.dirties = []; - const bounds = this.boundSprites; - const dirties = this.dirties; - this.boundSize = options.boundSize; - for (let j = 0; j < options.boundCountPerBuffer; j++) { - const spr = new sprite.Sprite(); - spr.position.x = options.boundSize * (j & 1); - spr.position.y = options.boundSize * (j >> 1); - bounds.push(spr); - dirties.push(0); - } - this.DO_CLEAR = !!options.DO_CLEAR; - } - bind(baseTexture) { - if (this.baseTex) { - throw new Error('Only one baseTexture is allowed for this resource!'); - } - this.baseTex = baseTexture; - super.bind(baseTexture); - } - setTexture(ind, texture) { - const spr = this.boundSprites[ind]; - if (spr.texture.baseTexture === texture.baseTexture) { - return; + /** + * @deprecated @pixi/tilemap 3 + * @see CompositeTilemap.tile + */ + // eslint-disable-next-line max-params + addRect( + textureIndex, + u, + v, + x, + y, + tileWidth, + tileHeight, + animX, + animY, + rotate, + animWidth, + animHeight + ) + { + const childIndex = textureIndex / this.texturesPerTilemap >> 0; + const textureId = textureIndex % this.texturesPerTilemap; + + if (this.children[childIndex] && (this.children[childIndex] ).getTileset()) + { + this.lastModifiedTilemap = (this.children[childIndex] ); + this.lastModifiedTilemap.addRect( + textureId, u, v, x, y, tileWidth, tileHeight, animX, animY, rotate, animWidth, animHeight + ); } - spr.texture = texture; - this.baseTex.update(); - this.dirties[ind] = this.baseTex.dirtyId; - } - upload(renderer, texture, glTexture) { - const { gl } = renderer; - const { width, height } = this; - gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, texture.alphaMode === undefined || - texture.alphaMode === constants.ALPHA_MODES.UNPACK); - if (glTexture.dirtyId < 0) { - glTexture.width = width; - glTexture.height = height; - gl.texImage2D(texture.target, 0, texture.format, width, height, 0, texture.format, texture.type, null); - } - const doClear = this.DO_CLEAR; - if (doClear && !this._clearBuffer) { - this._clearBuffer = new Uint8Array(Constant.boundSize * Constant.boundSize * 4); - } - const bounds = this.boundSprites; - for (let i = 0; i < bounds.length; i++) { - const spr = bounds[i]; - const tex = spr.texture.baseTexture; - if (glTexture.dirtyId >= this.dirties[i]) { - continue; - } - const res = tex.resource; - if (!tex.valid || !res || !res.source) { - continue; - } - if (doClear && (tex.width < this.boundSize || tex.height < this.boundSize)) { - gl.texSubImage2D(texture.target, 0, spr.position.x, spr.position.y, this.boundSize, this.boundSize, texture.format, texture.type, this._clearBuffer); - } - gl.texSubImage2D(texture.target, 0, spr.position.x, spr.position.y, texture.format, texture.type, res.source); + else + { + this.lastModifiedTilemap = null; } - return true; - } - } - function fillSamplers(shader, maxTextures) { - let sampleValues = []; - for (let i = 0; i < maxTextures; i++) { - sampleValues[i] = i; - } - shader.uniforms.uSamplers = sampleValues; - let samplerSize = []; - for (let i = 0; i < maxTextures; i++) { - samplerSize.push(1.0 / Constant.bufferSize); - samplerSize.push(1.0 / Constant.bufferSize); + return this; } - shader.uniforms.uSamplerSize = samplerSize; + + /** + * Alias for {@link CompositeTilemap.tileset tileset}. + * + * @deprecated Since @pixi/tilemap 3. + */ + __init7() {this.setBitmaps = this.tileset;} + + /** + * @deprecated Since @pixi/tilemap 3. + * @readonly + * @see CompositeTilemap.texturesPerTilemap + */ + get texPerChild() { return this.texturesPerTilemap; } } - function generateFragmentSrc(maxTextures, fragmentSrc) { - return fragmentSrc.replace(/%count%/gi, maxTextures + "") - .replace(/%forloop%/gi, generateSampleSrc(maxTextures)); + + // For some reason ESLint goes mad with indendation in this file ^&^ + /* eslint-disable indent */ + + /** + * This texture tiling resource can be used to upload multiple base-textures together. + * + * This resource combines multiple base-textures into a "textile". They're laid out in + * a dual column format, placed in row-order order. The size of each tile is predefined, + * and defaults to {@link settings.TEXTILE_DIMEN}. This means that each input base-texture + * must is smaller than that along both its width and height. + * + * @see settings.TEXTILE_UNITS + */ + class TextileResource extends core.Resource + { + /** The base-texture that contains all the texture tiles. */ + __init() {this.baseTexture = null;} + + + + + + __init2() {this._clearBuffer = null;} + + /** + * @param options - This will default to the "settings" exported by @pixi/tilemap. + * @param options.TEXTILE_DIMEN - The dimensions of each tile. + * @param options.TEXTILE_UNITS - The number of texture tiles. + */ + constructor(options = settings) + { + super( + options.TEXTILE_DIMEN * 2, + options.TEXTILE_DIMEN * Math.ceil(options.TEXTILE_UNITS / 2), + );TextileResource.prototype.__init.call(this);TextileResource.prototype.__init2.call(this);; + + const tiles = this.tiles = new Array(options.TEXTILE_UNITS); + + this.doClear = !!options.DO_CLEAR; + this.tileDimen = options.TEXTILE_DIMEN; + + for (let j = 0; j < options.TEXTILE_UNITS; j++) + { + tiles[j] = { + dirtyId: 0, + x: options.TEXTILE_DIMEN * (j & 1), + y: options.TEXTILE_DIMEN * (j >> 1), + baseTexture: core.Texture.WHITE.baseTexture, + }; + } + } + + /** + * Sets the texture to be uploaded for the given tile. + * + * @param index - The index of the tile being set. + * @param texture - The texture with the base-texture to upload. + */ + tile(index, texture) + { + const tile = this.tiles[index]; + + if (tile.baseTexture === texture) + { + return; + } + + tile.baseTexture = texture; + this.baseTexture.update(); + + this.tiles[index].dirtyId = (this.baseTexture ).dirtyId; + } + + /** @override */ + bind(baseTexture) + { + if (this.baseTexture) + { + throw new Error('Only one baseTexture is allowed for this resource!'); + } + + this.baseTexture = baseTexture; + super.bind(baseTexture); + } + + /** @override */ + upload(renderer, texture, glTexture) + { + const { gl } = renderer; + const { width, height } = this; + + gl.pixelStorei( + gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, + texture.alphaMode === undefined || texture.alphaMode === constants.ALPHA_MODES.UNPACK + ); + + if (glTexture.dirtyId < 0) + { + (glTexture ).width = width; + (glTexture ).height = height; + + gl.texImage2D(texture.target, 0, + texture.format, + width, + height, + 0, + texture.format, + texture.type, + null); + } + + const doClear = this.doClear; + const tiles = this.tiles; + + if (doClear && !this._clearBuffer) + { + this._clearBuffer = new Uint8Array(settings.TEXTILE_DIMEN * settings.TEXTILE_DIMEN * 4); + } + + for (let i = 0; i < tiles.length; i++) + { + const spr = tiles[i]; + const tex = spr.baseTexture; + + if (glTexture.dirtyId >= this.tiles[i].dirtyId) + { + continue; + } + + const res = tex.resource ; + + if (!tex.valid || !res || !res.source) + { + continue; + } + if (doClear && (tex.width < this.tileDimen || tex.height < this.tileDimen)) + { + gl.texSubImage2D(texture.target, 0, + spr.x, + spr.y, + this.tileDimen, + this.tileDimen, + texture.format, + texture.type, + this._clearBuffer); + } + + gl.texSubImage2D(texture.target, 0, + spr.x, + spr.y, + texture.format, + texture.type, + res.source); + } + + return true; + } } - function generateSampleSrc(maxTextures) { + + /** + * This will generate fragment shader code that samples the correct texture into the "color" variable. + * + * @internal + * @ignore + * @param maxTextures - The texture array length in the shader's uniforms. + */ + function generateSampleSrc(maxTextures) + { let src = ''; + src += '\n'; src += '\n'; + src += 'if(vTextureId <= -1.0) {'; src += '\n\tcolor = shadowColor;'; src += '\n}'; - for (let i = 0; i < maxTextures; i++) { + + for (let i = 0; i < maxTextures; i++) + { src += '\nelse '; - if (i < maxTextures - 1) { - src += 'if(textureId == ' + i + '.0)'; + + if (i < maxTextures - 1) + { + src += `if(textureId == ${i}.0)`; } + src += '\n{'; - src += '\n\tcolor = texture2D(uSamplers[' + i + '], textureCoord * uSamplerSize[' + i + ']);'; + src += `\n\tcolor = texture2D(uSamplers[${i}], textureCoord * uSamplerSize[${i}]);`; src += '\n}'; } + src += '\n'; src += '\n'; + return src; } - let rectShaderFrag = ` -varying vec2 vTextureCoord; -varying vec4 vFrame; -varying float vTextureId; -uniform vec4 shadowColor; -uniform sampler2D uSamplers[%count%]; -uniform vec2 uSamplerSize[%count%]; - -void main(void){ - vec2 textureCoord = clamp(vTextureCoord, vFrame.xy, vFrame.zw); - float textureId = floor(vTextureId + 0.5); - - vec4 color; - %forloop% - gl_FragColor = color; -} -`; - let rectShaderVert = ` -attribute vec2 aVertexPosition; -attribute vec2 aTextureCoord; -attribute vec4 aFrame; -attribute vec2 aAnim; -attribute float aTextureId; - -uniform mat3 projTransMatrix; -uniform vec2 animationFrame; - -varying vec2 vTextureCoord; -varying float vTextureId; -varying vec4 vFrame; - -void main(void){ - gl_Position = vec4((projTransMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); - vec2 animCount = floor((aAnim + 0.5) / 2048.0); - vec2 animFrameOffset = aAnim - animCount * 2048.0; - vec2 animOffset = animFrameOffset * floor(mod(animationFrame + 0.5, animCount)); - - vTextureCoord = aTextureCoord + animOffset; - vFrame = aFrame + vec4(animOffset, animOffset); - vTextureId = aTextureId; -} -`; - class TilemapShader extends core.Shader { - constructor(maxTextures, shaderVert, shaderFrag) { - super(new core.Program(shaderVert, shaderFrag), { - animationFrame: new Float32Array(2), - uSamplers: [], - uSamplerSize: [], - projTransMatrix: new math.Matrix() - }); - this.maxTextures = 0; - this.maxTextures = maxTextures; - fillSamplers(this, this.maxTextures); + /** + * @internal + * @ignore + * @param shader + * @param maxTextures + */ + function fillSamplers(shader, maxTextures) + { + const sampleValues = []; + + for (let i = 0; i < maxTextures; i++) + { + sampleValues[i] = i; } - } - class RectTileShader extends TilemapShader { - constructor(maxTextures) { - super(maxTextures, rectShaderVert, generateFragmentSrc(maxTextures, rectShaderFrag)); - fillSamplers(this, this.maxTextures); + + shader.uniforms.uSamplers = sampleValues; + + const samplerSize = []; + + for (let i = 0; i < maxTextures; i++) + { + // These are overwritten by TileRenderer when textures actually bound. + samplerSize.push(1.0 / 2048); + samplerSize.push(1.0 / 2048); } + + shader.uniforms.uSamplerSize = samplerSize; } - class RectTileGeom extends core.Geometry { - constructor() { - super(); - this.vertSize = 11; - this.vertPerQuad = 4; - this.stride = this.vertSize * 4; - this.lastTimeAccess = 0; - const buf = this.buf = new core.Buffer(new Float32Array(2), true, false); - this.addAttribute('aVertexPosition', buf, 0, false, 0, this.stride, 0) - .addAttribute('aTextureCoord', buf, 0, false, 0, this.stride, 2 * 4) - .addAttribute('aFrame', buf, 0, false, 0, this.stride, 4 * 4) - .addAttribute('aAnim', buf, 0, false, 0, this.stride, 8 * 4) - .addAttribute('aTextureId', buf, 0, false, 0, this.stride, 10 * 4); - } + + /** + * @internal + * @ignore + * @param maxTextures + * @param fragmentSrc + * @returns + */ + function generateFragmentSrc(maxTextures, fragmentSrc) + { + return fragmentSrc.replace(/%count%/gi, `${maxTextures}`) + .replace(/%forloop%/gi, generateSampleSrc(maxTextures)); } - class TileRenderer extends core.ObjectRenderer { - constructor(renderer) { - super(renderer); - this.sn = -1; - this.indexBuffer = null; - this.ibLen = 0; - this.tileAnim = [0, 0]; - this.texLoc = []; - this.texResources = []; - this.rectShader = new RectTileShader(Constant.maxTextures); - this.indexBuffer = new core.Buffer(undefined, true, true); - this.checkIndexBuffer(2000); - this.initBounds(); - } - initBounds() { - if (Constant.boundCountPerBuffer <= 1) { - return; - } - const maxTextures = Constant.maxTextures; - for (let i = 0; i < maxTextures; i++) { - const resource = new MultiTextureResource(Constant); - const baseTex = new core.BaseTexture(resource); - baseTex.scaleMode = Constant.SCALE_MODE; - baseTex.wrapMode = constants.WRAP_MODES.CLAMP; - this.texResources.push(resource); - } - } - bindTexturesWithoutRT(renderer, shader, textures) { - let samplerSize = shader.uniforms.uSamplerSize; - this.texLoc.length = 0; - for (let i = 0; i < textures.length; i++) { - const texture = textures[i]; - if (!texture || !texture.valid) { - return; - } - renderer.texture.bind(textures[i], i); - samplerSize[i * 2] = 1.0 / textures[i].baseTexture.width; - samplerSize[i * 2 + 1] = 1.0 / textures[i].baseTexture.height; - } - shader.uniforms.uSamplerSize = samplerSize; - } - bindTextures(renderer, shader, textures) { - const len = textures.length; - const maxTextures = Constant.maxTextures; - if (len > Constant.boundCountPerBuffer * maxTextures) { - return; - } - if (Constant.boundCountPerBuffer <= 1) { - this.bindTexturesWithoutRT(renderer, shader, textures); - return; - } - let i = 0; - for (; i < len; i++) { - const texture = textures[i]; - if (!texture || !texture.valid) - continue; - const multi = this.texResources[i >> 2]; - multi.setTexture(i & 3, texture); - } - let gltsUsed = (i + 3) >> 2; - for (i = 0; i < gltsUsed; i++) { - renderer.texture.bind(this.texResources[i].baseTex, i); - } - } - start() { - } - createVb() { - const geom = new RectTileGeom(); - geom.addIndex(this.indexBuffer); - geom.lastTimeAccess = Date.now(); - return geom; - } - checkIndexBuffer(size, vb = null) { - const totalIndices = size * 6; - if (totalIndices <= this.ibLen) { - return; - } - let len = totalIndices; - while (len < totalIndices) { - len <<= 1; - } - this.ibLen = totalIndices; - this.indexBuffer.update(PIXI.utils.createIndicesForQuads(size, Constant.use32bitIndex ? new Uint32Array(size * 6) : undefined)); - } - getShader() { - return this.rectShader; - } - destroy() { - super.destroy(); - this.rectShader = null; - } + var tilemapVertexTemplateSrc = "attribute vec2 aVertexPosition;\nattribute vec2 aTextureCoord;\nattribute vec4 aFrame;\nattribute vec2 aAnim;\nattribute float aAnimDivisor;\nattribute float aTextureId;\nattribute float aAlpha;\n\nuniform mat3 projTransMatrix;\nuniform vec2 animationFrame;\n\nvarying vec2 vTextureCoord;\nvarying float vTextureId;\nvarying vec4 vFrame;\nvarying float vAlpha;\n\nvoid main(void)\n{\n gl_Position = vec4((projTransMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);\n vec2 animCount = floor((aAnim + 0.5) / 2048.0);\n vec2 animFrameOffset = aAnim - animCount * 2048.0;\n vec2 currentFrame = floor(animationFrame / aAnimDivisor);\n vec2 animOffset = animFrameOffset * floor(mod(currentFrame + 0.5, animCount));\n\n vTextureCoord = aTextureCoord + animOffset;\n vFrame = aFrame + vec4(animOffset, animOffset);\n vTextureId = aTextureId;\n vAlpha = aAlpha;\n}\n"; + + var tilemapFragmentTemplateSrc = "varying vec2 vTextureCoord;\nvarying vec4 vFrame;\nvarying float vTextureId;\nvarying float vAlpha;\nuniform vec4 shadowColor;\nuniform sampler2D uSamplers[%count%];\nuniform vec2 uSamplerSize[%count%];\n\nvoid main(void)\n{\n vec2 textureCoord = clamp(vTextureCoord, vFrame.xy, vFrame.zw);\n float textureId = floor(vTextureId + 0.5);\n\n vec4 color;\n %forloop%\n gl_FragColor = color * vAlpha;\n}\n"; + + // eslint-disable-next-line @typescript-eslint/triple-slash-reference, spaced-comment + + // For some reason ESLint goes mad with indendation in this file ^&^ + /* eslint-disable no-mixed-spaces-and-tabs, indent */ + + class TilemapShader extends core.Shader + { + __init() {this.maxTextures = 0;} + + constructor(maxTextures) + { + super( + new core.Program( + tilemapVertexTemplateSrc, + generateFragmentSrc(maxTextures, tilemapFragmentTemplateSrc) + ), + { + animationFrame: new Float32Array(2), + uSamplers: [], + uSamplerSize: [], + projTransMatrix: new math.Matrix() + } + );TilemapShader.prototype.__init.call(this);; + + this.maxTextures = maxTextures; + fillSamplers(this, this.maxTextures); + } } - core.Renderer.registerPlugin('tilemap', TileRenderer); - class ZLayer extends display.Container { - constructor(tilemap, zIndex) { - super(); - this._lastAnimationFrame = -1; - this.tilemap = tilemap; - this.z = zIndex; - } - clear() { - let layers = this.children; - for (let i = 0; i < layers.length; i++) - layers[i].clear(); - this._previousLayers = 0; - } - cacheIfDirty() { - let tilemap = this.tilemap; - let layers = this.children; - let modified = this._previousLayers !== layers.length; - this._previousLayers = layers.length; - let buf = this.canvasBuffer; - let tempRender = this._tempRender; - if (!buf) { - buf = this.canvasBuffer = document.createElement('canvas'); - tempRender = this._tempRender = new PIXI.CanvasRenderer({ width: 100, height: 100, view: buf }); - tempRender.context = tempRender.rootContext; - tempRender.plugins.tilemap.dontUseTransform = true; - } - if (buf.width !== tilemap._layerWidth || - buf.height !== tilemap._layerHeight) { - buf.width = tilemap._layerWidth; - buf.height = tilemap._layerHeight; - modified = true; - } - let i; - if (!modified) { - for (i = 0; i < layers.length; i++) { - if (layers[i].isModified(this._lastAnimationFrame !== tilemap.animationFrame)) { - modified = true; - break; - } - } - } - this._lastAnimationFrame = tilemap.animationFrame; - if (modified) { - if (tilemap._hackRenderer) { - tilemap._hackRenderer(tempRender); - } - tempRender.context.clearRect(0, 0, buf.width, buf.height); - for (i = 0; i < layers.length; i++) { - layers[i].clearModify(); - layers[i].renderCanvas(tempRender); - } - } - this.layerTransform = this.worldTransform; - for (i = 0; i < layers.length; i++) { - this.layerTransform = layers[i].worldTransform; - break; - } - } - renderCanvas(renderer) { - this.cacheIfDirty(); - let wt = this.layerTransform; - renderer.context.setTransform(wt.a, wt.b, wt.c, wt.d, wt.tx * renderer.resolution, wt.ty * renderer.resolution); - let tilemap = this.tilemap; - renderer.context.drawImage(this.canvasBuffer, 0, 0); - } + class TilemapGeometry extends core.Geometry + { + __init2() {this.vertSize = 13;} + __init3() {this.vertPerQuad = 4;} + __init4() {this.stride = this.vertSize * 4;} + __init5() {this.lastTimeAccess = 0;} + + constructor() + { + super();TilemapGeometry.prototype.__init2.call(this);TilemapGeometry.prototype.__init3.call(this);TilemapGeometry.prototype.__init4.call(this);TilemapGeometry.prototype.__init5.call(this);; + + const buf = this.buf = new core.Buffer(new Float32Array(2), true, false); + + this.addAttribute('aVertexPosition', buf, 0, false, 0, this.stride, 0) + .addAttribute('aTextureCoord', buf, 0, false, 0, this.stride, 2 * 4) + .addAttribute('aFrame', buf, 0, false, 0, this.stride, 4 * 4) + .addAttribute('aAnim', buf, 0, false, 0, this.stride, 8 * 4) + .addAttribute('aTextureId', buf, 0, false, 0, this.stride, 10 * 4) + .addAttribute('aAnimDivisor', buf, 0, false, 0, this.stride, 11 * 4) + .addAttribute('aAlpha', buf, 0, false, 0, this.stride, 12 * 4); + } + + + } + + // For some reason ESLint goes mad with indendation in this file ^&^ + /* eslint-disable no-mixed-spaces-and-tabs, indent */ + + /** + * Rendering helper pipeline for tilemaps. This plugin is registered automatically. + */ + class TileRenderer extends core.ObjectRenderer + { + /** The managing renderer */ + + + /** The tile animation frame */ + __init() {this.tileAnim = [0, 0];} + + __init2() {this.ibLen = 0;}// index buffer length + + /** The index buffer for the tilemaps to share. */ + __init3() {this.indexBuffer = null;} + + /** The shader used to render tilemaps. */ + + + /** + * {@link TextileResource} instances used to upload textures batched in tiled groups. This is + * used only if {@link settings.TEXTURES_PER_TILEMAP} is greater than 1. + */ + __init4() {this.textiles = [];} + + /** @param renderer - The managing renderer */ + constructor(renderer) + { + super(renderer);TileRenderer.prototype.__init.call(this);TileRenderer.prototype.__init2.call(this);TileRenderer.prototype.__init3.call(this);TileRenderer.prototype.__init4.call(this);; + + this.shader = new TilemapShader(settings.TEXTURES_PER_TILEMAP); + this.indexBuffer = new core.Buffer(undefined, true, true); + this.checkIndexBuffer(2000); + this.makeTextiles(); + } + + /** + * Binds the tile textures to the renderer, and updates the tilemap shader's `uSamplerSize` uniform. + * + * If {@link settings.TEXTILE_UNITS} + * + * @param renderer - The renderer to which the textures are to be bound. + * @param textures - The tile textures being bound. + */ + bindTileTextures(renderer, textures) + { + const len = textures.length; + const shader = this.shader; + const maxTextures = settings.TEXTURES_PER_TILEMAP; + const samplerSize = shader.uniforms.uSamplerSize; + + if (len > settings.TEXTILE_UNITS * maxTextures) + { + // TODO: Show error message instead of silently failing! + return; + } + + if (settings.TEXTILE_UNITS <= 1) + { + // Bind each texture directly & update samplerSize. + for (let i = 0; i < textures.length; i++) + { + const texture = textures[i]; + + if (!texture || !texture.valid) + { + return; + } + + renderer.texture.bind(textures[i], i); + + samplerSize[i * 2] = 1.0 / textures[i].realWidth; + samplerSize[(i * 2) + 1] = 1.0 / textures[i].realHeight; + } + } + else + { + // Ensure we have enough textiles, in case settings.TEXTILE_UNITS was modified. + this.makeTextiles(); + + const usedTextiles = Math.ceil(len / settings.TEXTILE_UNITS); + + // First ensure each textile has all tiles point to the right textures. + for (let i = 0; i < len; i++) + { + const texture = textures[i]; + + if (texture && texture.valid) + { + const resourceIndex = Math.floor(i / settings.TEXTILE_UNITS); + const tileIndex = i % settings.TEXTILE_UNITS; + + this.textiles[resourceIndex].tile(tileIndex, texture); + } + } + + // Then bind the textiles + update samplerSize. + for (let i = 0; i < usedTextiles; i++) + { + renderer.texture.bind(this.textiles[i].baseTexture, i); + + samplerSize[i * 2] = 1.0 / this.textiles[i].width; + samplerSize[(i * 2) + 1] = 1.0 / this.textiles[i].baseTexture.height; + } + } + + shader.uniforms.uSamplerSize = samplerSize; + } + + start() + { + // sorry, nothing + } + + /** + * @internal + * @ignore + */ + createVb() + { + const geom = new TilemapGeometry(); + + geom.addIndex(this.indexBuffer); + geom.lastTimeAccess = Date.now(); + + return geom; + } + + /** @return The {@link TilemapShader} shader that this rendering pipeline is using. */ + getShader() { return this.shader; } + + destroy() + { + super.destroy(); + // this.rectShader.destroy(); + this.shader = null; + } + + checkIndexBuffer(size, _vb = null) + { + const totalIndices = size * 6; + + if (totalIndices <= this.ibLen) + { + return; + } + + let len = totalIndices; + + while (len < totalIndices) + { + len <<= 1; + } + + this.ibLen = totalIndices; + const createIndicesForQuads = utils.createIndicesForQuads || utils.utils.createIndicesForQuads; + this.indexBuffer.update(createIndicesForQuads(size, + settings.use32bitIndex ? new Uint32Array(size * 6) : undefined)); + + // TODO: create new index buffer instead? + // if (vb) { + // const curIndex = vb.getIndex(); + // if (curIndex !== this.indexBuffer && (curIndex.data as any).length < totalIndices) { + // this.swapIndex(vb, this.indexBuffer); + // } + // } + } + + /** Makes textile resources and initializes {@link TileRenderer.textiles}. */ + makeTextiles() + { + if (settings.TEXTILE_UNITS <= 1) + { + return; + } + + for (let i = 0; i < settings.TEXTILE_UNITS; i++) + { + if (this.textiles[i]) continue; + + const resource = new TextileResource(); + const baseTex = new core.BaseTexture(resource); + + baseTex.scaleMode = settings.TEXTILE_SCALE_MODE; + baseTex.wrapMode = constants.WRAP_MODES.CLAMP; + + this.textiles[i] = resource; + } + } } + core.Renderer.registerPlugin('tilemap', TileRenderer ); + + // eslint-disable-next-line camelcase const pixi_tilemap = { CanvasTileRenderer, - CompositeRectTileLayer, + CompositeRectTileLayer: CompositeTilemap, + CompositeTilemap, Constant, - GraphicsLayer, - MultiTextureResource, - RectTileLayer, + TextileResource, + MultiTextureResource: TextileResource, + RectTileLayer: Tilemap, + Tilemap, TilemapShader, - RectTileShader, - RectTileGeom, + TilemapGeometry, + RectTileShader: TilemapShader, + RectTileGeom: TilemapGeometry, TileRenderer, - ZLayer, }; exports.CanvasTileRenderer = CanvasTileRenderer; - exports.CompositeRectTileLayer = CompositeRectTileLayer; + exports.CompositeRectTileLayer = CompositeTilemap; + exports.CompositeTilemap = CompositeTilemap; exports.Constant = Constant; - exports.GraphicsLayer = GraphicsLayer; - exports.MultiTextureResource = MultiTextureResource; exports.POINT_STRUCT_SIZE = POINT_STRUCT_SIZE; - exports.RectTileGeom = RectTileGeom; - exports.RectTileLayer = RectTileLayer; - exports.RectTileShader = RectTileShader; + exports.RectTileLayer = Tilemap; + exports.TextileResource = TextileResource; exports.TileRenderer = TileRenderer; + exports.Tilemap = Tilemap; + exports.TilemapGeometry = TilemapGeometry; exports.TilemapShader = TilemapShader; - exports.ZLayer = ZLayer; exports.fillSamplers = fillSamplers; exports.generateFragmentSrc = generateFragmentSrc; - exports.generateSampleSrc = generateSampleSrc; exports.pixi_tilemap = pixi_tilemap; + exports.settings = settings; Object.defineProperty(exports, '__esModule', { value: true }); }))); -if (typeof pixi_tilemap !== 'undefined') { Object.assign(this.PIXI.tilemap, pixi_tilemap); } -//# sourceMappingURL=pixi-tilemap.umd.js.map +if (typeof _pixi_tilemap !== 'undefined') { Object.assign(this.PIXI.tilemap, _pixi_tilemap); } +//# sourceMappingURL=pixi-tilemap.umd.js.map \ No newline at end of file diff --git a/Extensions/TileMap/tilemapruntimeobject-pixi-renderer.ts b/Extensions/TileMap/tilemapruntimeobject-pixi-renderer.ts index 6a1110ccad67..125a4aaaa3cd 100644 --- a/Extensions/TileMap/tilemapruntimeobject-pixi-renderer.ts +++ b/Extensions/TileMap/tilemapruntimeobject-pixi-renderer.ts @@ -26,8 +26,15 @@ namespace gdjs { // Load (or reset) if (this._pixiObject === undefined) { + const pixiRenderer = runtimeScene + .getGame() + .getRenderer() + .getPIXIRenderer(); + + // @ts-ignore - pixi-tilemap types to be added. + pixiRenderer.plugins.tilemap = new PIXI.tilemap.TileRenderer(); // @ts-ignore - pixi-tilemap types to be added. - this._pixiObject = new PIXI.tilemap.CompositeRectTileLayer(0); + this._pixiObject = new PIXI.tilemap.CompositeTilemap(); } this._pixiObject.tileAnim = [0, 0]; @@ -62,7 +69,8 @@ namespace gdjs { : tileMapJsonData, this._object._tilemapAtlasImage, this._object._tilemapJsonFile, - this._object._tilesetJsonFile + this._object._tilesetJsonFile, + this._object._levelIndex ); if (pixiTileMapData) { // @ts-ignore - TODO: Add typings for pixi-tilemap-helper. @@ -157,5 +165,6 @@ namespace gdjs { } export const TileMapRuntimeObjectRenderer = gdjs.TileMapRuntimeObjectPixiRenderer; - export type TileMapRuntimeObjectRenderer = gdjs.TileMapRuntimeObjectPixiRenderer; + export type TileMapRuntimeObjectRenderer = + gdjs.TileMapRuntimeObjectPixiRenderer; } diff --git a/Extensions/TileMap/tilemapruntimeobject.ts b/Extensions/TileMap/tilemapruntimeobject.ts index eb18ea54b4c2..f0178ead3ca2 100644 --- a/Extensions/TileMap/tilemapruntimeobject.ts +++ b/Extensions/TileMap/tilemapruntimeobject.ts @@ -13,6 +13,7 @@ namespace gdjs { _tilemapAtlasImage: string; _displayMode: string; _layerIndex: integer; + _levelIndex: integer; _animationSpeedScale: number; _animationFps: number; _renderer: any; @@ -25,6 +26,7 @@ namespace gdjs { this._tilemapAtlasImage = objectData.content.tilemapAtlasImage; this._displayMode = objectData.content.displayMode; this._layerIndex = objectData.content.layerIndex; + this._levelIndex = objectData.content.levelIndex; this._animationSpeedScale = objectData.content.animationSpeedScale; this._animationFps = objectData.content.animationFps; if (this._renderer) { @@ -86,6 +88,11 @@ namespace gdjs { ) { this.setLayerIndex(newObjectData.content.layerIndex); } + if ( + oldObjectData.content.levelIndex !== newObjectData.content.levelIndex + ) { + this.setLevelIndex(newObjectData.content.levelIndex); + } if ( oldObjectData.content.animationSpeedScale !== newObjectData.content.animationSpeedScale @@ -172,6 +179,15 @@ namespace gdjs { return this._layerIndex; } + setLevelIndex(levelIndex): void { + this._levelIndex = levelIndex; + this._renderer.updateTileMap(); + } + + getLevelIndex() { + return this._levelIndex; + } + setAnimationSpeedScale(animationSpeedScale): void { this._animationSpeedScale = animationSpeedScale; } diff --git a/GDJS/Runtime/jsonmanager.ts b/GDJS/Runtime/jsonmanager.ts index 2a85a60fe3b1..a92f87dd7679 100644 --- a/GDJS/Runtime/jsonmanager.ts +++ b/GDJS/Runtime/jsonmanager.ts @@ -61,7 +61,7 @@ namespace gdjs { ): void { const resources = this._resources; const jsonResources = resources.filter(function (resource) { - return resource.kind === 'json' && !resource.disablePreload; + return (resource.kind === 'json' || resource.kind === 'tilemap') && !resource.disablePreload; }); if (jsonResources.length === 0) { return onComplete(jsonResources.length); @@ -94,7 +94,7 @@ namespace gdjs { */ loadJson(resourceName: string, callback: JsonManagerRequestCallback): void { const resource = this._resources.find(function (resource) { - return resource.kind === 'json' && resource.name === resourceName; + return (resource.kind === 'json' || resource.kind === 'tilemap') && resource.name === resourceName; }); if (!resource) { callback( diff --git a/GDJS/Runtime/types/project-data.d.ts b/GDJS/Runtime/types/project-data.d.ts index 65e79516a72a..b5484cb2a731 100644 --- a/GDJS/Runtime/types/project-data.d.ts +++ b/GDJS/Runtime/types/project-data.d.ts @@ -228,4 +228,5 @@ declare type ResourceKind = | 'font' | 'video' | 'json' + | 'tilemap' | 'bitmapFont'; diff --git a/GDevelop.js/Bindings/Bindings.idl b/GDevelop.js/Bindings/Bindings.idl index 4aa59032443f..f0f01a342bd4 100644 --- a/GDevelop.js/Bindings/Bindings.idl +++ b/GDevelop.js/Bindings/Bindings.idl @@ -866,6 +866,11 @@ interface JsonResource { }; JsonResource implements Resource; +interface TilemapResource { + void TilemapResource(); +}; +TilemapResource implements Resource; + interface InitialInstance { void InitialInstance(); diff --git a/GDevelop.js/scripts/generate-types.js b/GDevelop.js/scripts/generate-types.js index 90db7ad5464d..191a2074c93a 100644 --- a/GDevelop.js/scripts/generate-types.js +++ b/GDevelop.js/scripts/generate-types.js @@ -308,13 +308,13 @@ type ParticleEmitterObject_RendererType = 0 | 1 | 2` shell.sed( '-i', /setKind\(kind: string\): void/, - "setKind(kind: 'image' | 'audio' | 'font' | 'video' | 'json'): void", + "setKind(kind: 'image' | 'audio' | 'font' | 'video' | 'json' | 'tilemap'): void", 'types/gdresource.js' ); shell.sed( '-i', /getKind\(\): string/, - "getKind(): 'image' | 'audio' | 'font' | 'video' | 'json'", + "getKind(): 'image' | 'audio' | 'font' | 'video' | 'json' | 'tilemap'", 'types/gdresource.js' ); diff --git a/GDevelop.js/types/gdresource.js b/GDevelop.js/types/gdresource.js index 7f67c871f268..a8f696e66e9a 100644 --- a/GDevelop.js/types/gdresource.js +++ b/GDevelop.js/types/gdresource.js @@ -4,8 +4,8 @@ declare class gdResource { clone(): gdResource; setName(name: string): void; getName(): string; - setKind(kind: 'image' | 'audio' | 'font' | 'video' | 'json'): void; - getKind(): 'image' | 'audio' | 'font' | 'video' | 'json'; + setKind(kind: 'image' | 'audio' | 'font' | 'video' | 'json' | 'tilemap'): void; + getKind(): 'image' | 'audio' | 'font' | 'video' | 'json' | 'tilemap'; isUserAdded(): boolean; setUserAdded(yes: boolean): void; useFile(): boolean; diff --git a/GDevelop.js/types/gdtilemapresource.js b/GDevelop.js/types/gdtilemapresource.js new file mode 100644 index 000000000000..401349545289 --- /dev/null +++ b/GDevelop.js/types/gdtilemapresource.js @@ -0,0 +1,6 @@ +// Automatically generated by GDevelop.js/scripts/generate-types.js +declare class gdTilemapResource extends gdResource { + constructor(): void; + delete(): void; + ptr: number; +}; \ No newline at end of file diff --git a/GDevelop.js/types/libgdevelop.js b/GDevelop.js/types/libgdevelop.js index 479e776394b2..e8e6b870964c 100644 --- a/GDevelop.js/types/libgdevelop.js +++ b/GDevelop.js/types/libgdevelop.js @@ -90,6 +90,7 @@ declare class libGDevelop { BitmapFontResource: Class; VideoResource: Class; JsonResource: Class; + TilemapResource: Class; InitialInstance: Class; InitialInstancesContainer: Class; HighestZOrderFinder: Class; diff --git a/newIDE/app/src/InstancesEditor/InstancesRenderer/LayerRenderer.js b/newIDE/app/src/InstancesEditor/InstancesRenderer/LayerRenderer.js index 125995318410..8f31fbaef49a 100644 --- a/newIDE/app/src/InstancesEditor/InstancesRenderer/LayerRenderer.js +++ b/newIDE/app/src/InstancesEditor/InstancesRenderer/LayerRenderer.js @@ -35,6 +35,7 @@ export default class LayerRenderer { renderedInstances: { [number]: RenderedInstance } = {}; pixiContainer: PIXI.Container; + pixiRenderer: PIXI.Renderer; /** Functor used to render an instance */ instancesRenderer: gdInitialInstanceJSFunctor; @@ -57,6 +58,7 @@ export default class LayerRenderer { onMoveInstance, onMoveInstanceEnd, onDownInstance, + pixiRenderer, }: { project: gdProject, instances: gdInitialInstancesContainer, @@ -70,7 +72,9 @@ export default class LayerRenderer { onMoveInstance: (gdInitialInstance, number, number) => void, onMoveInstanceEnd: void => void, onDownInstance: (gdInitialInstance, number, number) => void, + pixiRenderer: any, }) { + this.pixiRenderer = pixiRenderer; this.project = project; this.instances = instances; this.layout = layout; @@ -242,7 +246,8 @@ export default class LayerRenderer { this.layout, instance, associatedObject, - this.pixiContainer + this.pixiContainer, + this.pixiRenderer ); renderedInstance._pixiObject.interactive = true; diff --git a/newIDE/app/src/InstancesEditor/InstancesRenderer/index.js b/newIDE/app/src/InstancesEditor/InstancesRenderer/index.js index d0e6151400db..e67abad01d2c 100644 --- a/newIDE/app/src/InstancesEditor/InstancesRenderer/index.js +++ b/newIDE/app/src/InstancesEditor/InstancesRenderer/index.js @@ -20,6 +20,7 @@ export default class InstancesRenderer { layersRenderers: { [string]: LayerRenderer }; pixiContainer: PIXI.Container; + pixiRenderer: PIXI.Renderer; temporaryRectangle: Rectangle; instanceMeasurer: any; @@ -36,6 +37,7 @@ export default class InstancesRenderer { onMoveInstance, onMoveInstanceEnd, onDownInstance, + pixiRenderer, }: { project: gdProject, instances: gdInitialInstancesContainer, @@ -48,6 +50,7 @@ export default class InstancesRenderer { onMoveInstance: (gdInitialInstance, number, number) => void, onMoveInstanceEnd: void => void, onDownInstance: (gdInitialInstance, number, number) => void, + pixiRenderer: PIXI.Renderer, }) { this.project = project; this.instances = instances; @@ -60,6 +63,7 @@ export default class InstancesRenderer { this.onMoveInstance = onMoveInstance; this.onMoveInstanceEnd = onMoveInstanceEnd; this.onDownInstance = onDownInstance; + this.pixiRenderer = pixiRenderer; this.layersRenderers = {}; @@ -130,6 +134,7 @@ export default class InstancesRenderer { layout: this.layout, instances: this.instances, viewPosition: this.viewPosition, + pixiRenderer: this.pixiRenderer, layer: layer, onInstanceClicked: this.onInstanceClicked, onInstanceDoubleClicked: this.onInstanceDoubleClicked, diff --git a/newIDE/app/src/InstancesEditor/index.js b/newIDE/app/src/InstancesEditor/index.js index 6a38addd7c66..e7d81021ce2f 100644 --- a/newIDE/app/src/InstancesEditor/index.js +++ b/newIDE/app/src/InstancesEditor/index.js @@ -82,7 +82,7 @@ export default class InstancesEditor extends Component { lastCursorY = 0; fpsLimiter = new FpsLimiter(28); canvasArea: ?HTMLDivElement; - pixiRenderer: any; + pixiRenderer: PIXI.Renderer; keyboardShortcuts: DeprecatedKeyboardShortcuts; pinchHandler: PinchHandler; canvasCursor: CanvasCursor; @@ -322,6 +322,7 @@ export default class InstancesEditor extends Component { layout: props.layout, instances: props.initialInstances, viewPosition: this.viewPosition, + pixiRenderer: this.pixiRenderer, onOverInstance: this._onOverInstance, onMoveInstance: this._onMoveInstance, onMoveInstanceEnd: this._onMoveInstanceEnd, diff --git a/newIDE/app/src/ObjectsRendering/ObjectsRenderingService.js b/newIDE/app/src/ObjectsRendering/ObjectsRenderingService.js index 68b7ce2070e9..b17a5d648b96 100644 --- a/newIDE/app/src/ObjectsRendering/ObjectsRenderingService.js +++ b/newIDE/app/src/ObjectsRendering/ObjectsRenderingService.js @@ -62,7 +62,8 @@ export default { layout: gdLayout, instance: gdInitialInstance, associatedObject: gdObject, - pixiContainer: any + pixiContainer: any, + pixiRenderer: PIXI.Renderer ) { var objectType = associatedObject.getType(); if (this.renderers.hasOwnProperty(objectType)) @@ -72,7 +73,8 @@ export default { instance, associatedObject, pixiContainer, - PixiResourcesLoader + PixiResourcesLoader, + pixiRenderer ); else { console.warn( @@ -84,7 +86,8 @@ export default { instance, associatedObject, pixiContainer, - PixiResourcesLoader + PixiResourcesLoader, + pixiRenderer ); } }, diff --git a/newIDE/app/src/ObjectsRendering/PixiResourcesLoader.js b/newIDE/app/src/ObjectsRendering/PixiResourcesLoader.js index 172cd159612b..54f0f963ed66 100644 --- a/newIDE/app/src/ObjectsRendering/PixiResourcesLoader.js +++ b/newIDE/app/src/ObjectsRendering/PixiResourcesLoader.js @@ -4,7 +4,10 @@ import axios from 'axios'; import * as PIXI from 'pixi.js-legacy'; import ResourcesLoader from '../ResourcesLoader'; import { loadFontFace } from '../Utils/FontFaceLoader'; +import { getLocalResourceFullPath } from '../ResourcesList/ResourceUtils'; +import optionalRequire from '../Utils/OptionalRequire'; const gd: libGDevelop = global.gd; +const path = optionalRequire('path'); const loadedBitmapFonts = {}; const loadedFontFamilies = {}; @@ -96,8 +99,12 @@ export default class PixiResourcesLoader { * should listen to PIXI.Texture `update` event, and refresh your object * if this event is triggered. */ - static getPIXITexture(project: gdProject, resourceName: string) { - if (loadedTextures[resourceName]) { + static getPIXITexture( + project: gdProject, + resourceName: string, + isNew: boolean = false + ) { + if (!isNew && loadedTextures[resourceName]) { // TODO: we never consider textures as not valid anymore. When we // update the IDE to unload textures, we should handle loading them again // here (and also be careful to return the same texture if it's not valid @@ -124,6 +131,55 @@ export default class PixiResourcesLoader { return loadedTextures[resourceName]; } + /** + * Return the PIXI texture relative to a file. + * If not loaded, it will load it. + * @returns The PIXI.Texture to be used. It can be loading, so you + * should listen to PIXI.Texture `update` event, and refresh your object + * if this event is triggered. + * + * If the texture image resource is missing, GDevelop will try to add it + */ + static getPixiTextureRelativeToFile( + project: gdProject, + resourceName: string, + relativeToFile: string + ) { + if (!project.getResourcesManager().hasResource(resourceName)) { + const projectPath = path.dirname(project.getProjectFile()); + const fullPathRelativeFile = path.dirname( + path.join( + projectPath, + project + .getResourcesManager() + .getResource(relativeToFile) + .getFile() + ) + ); + const fullPathNewResource = path.resolve( + fullPathRelativeFile, + resourceName + ); + const resourceRelativePath = path.relative( + projectPath, + fullPathNewResource + ); + + const newResource = new gd.ImageResource(); + newResource.setName(resourceName); + newResource.setFile(resourceRelativePath); + project.getResourcesManager().addResource(newResource); + console.log( + relativeToFile, + 'file pulled a resource dependency:', + resourceName + ); + + return this.getPIXITexture(project, resourceName, true); + } + return this.getPIXITexture(project, resourceName); + } + /** * Return the PIXI video texture represented by the given resource. * If not loaded, it will load it. @@ -285,7 +341,7 @@ export default class PixiResourcesLoader { ); const resource = project.getResourcesManager().getResource(resourceName); - if (resource.getKind() !== 'json') + if (resource.getKind() !== 'json' && resource.getKind() !== 'tilemap') return Promise.reject( new Error(`The resource called ${resourceName} is not a json file.`) ); diff --git a/newIDE/app/src/ResourcesList/ResourcePreview/index.js b/newIDE/app/src/ResourcesList/ResourcePreview/index.js index 4ac1792ee1ec..91dffea6a6c8 100644 --- a/newIDE/app/src/ResourcesList/ResourcePreview/index.js +++ b/newIDE/app/src/ResourcesList/ResourcePreview/index.js @@ -73,6 +73,12 @@ export default class ResourcePreview extends React.PureComponent { renderIcon={props => } /> ); + case 'tilemap': + return ( + } + /> + ); case 'video': return ( { }, }; resourceExternalEditor.edit(externalEditorOptions); - } else if (resourceKind === 'json') { + } else if (resourceKind === 'json' || resourceKind === 'tilemap') { const externalEditorOptions = { project, resourcesLoader, diff --git a/newIDE/app/src/ResourcesList/ResourceSource.js b/newIDE/app/src/ResourcesList/ResourceSource.js index 08846371e845..8e71f0fbe276 100644 --- a/newIDE/app/src/ResourcesList/ResourceSource.js +++ b/newIDE/app/src/ResourcesList/ResourceSource.js @@ -47,6 +47,12 @@ export const allResourceKindsAndMetadata = [ fileExtensions: ['json'], createNewResource: () => new gd.JsonResource(), }, + { + kind: 'tilemap', + displayName: t`Tilemap`, + fileExtensions: ['json', 'ldtk'], + createNewResource: () => new gd.JsonResource(),//Should it be TilemapResource? Do we still need it in gd? + }, { kind: 'bitmapFont', displayName: t`Bitmap Font`,