From 16abedf87becf16b4dbb408fefa11e727322ca33 Mon Sep 17 00:00:00 2001 From: Ben Richards Date: Mon, 19 Aug 2024 12:28:27 +1200 Subject: [PATCH] Add Tiled animation support to TilemapLayer. --- src/tilemaps/TilemapLayer.js | 94 +++++++++++++++++++++ src/tilemaps/TilemapLayerWebGLRenderer.js | 11 ++- src/tilemaps/Tileset.js | 69 +++++++++++++++ src/tilemaps/parsers/tiled/ParseTilesets.js | 16 ++++ 4 files changed, 189 insertions(+), 1 deletion(-) diff --git a/src/tilemaps/TilemapLayer.js b/src/tilemaps/TilemapLayer.js index ff51d2e2b..ffa188e12 100644 --- a/src/tilemaps/TilemapLayer.js +++ b/src/tilemaps/TilemapLayer.js @@ -329,6 +329,26 @@ var TilemapLayer = new Class({ * @since 3.0.0 */ + /** + * The time elapsed since timer initialization. + * This drives the animation of the texture. + * + * @name Phaser.Tilemaps.TilemapLayer#timeElapsed + * @type {number} + * @since 3.90.0 + */ + this.timeElapsed = 0; + + /** + * Whether the animation timer is paused. + * + * @name Phaser.Tilemaps.TilemapLayer#timePaused + * @type {boolean} + * @since 3.90.0 + * @default false + */ + this.timePaused = false; + this.setTilesets(tileset); this.setAlpha(this.layer.alpha); this.setPosition(x, y); @@ -339,6 +359,23 @@ var TilemapLayer = new Class({ this.initPostPipeline(false); }, + // Overrides Game Object method + addedToScene: function () + { + this.scene.sys.updateList.add(this); + }, + + // Overrides Game Object method + removedFromScene: function () + { + this.scene.sys.updateList.remove(this); + }, + + preUpdate: function (time, delta) + { + this.updateTimer(time, delta); + }, + /** * Populates the internal `tileset` array with the Tileset references this Layer requires for rendering. * @@ -1488,6 +1525,63 @@ var TilemapLayer = new Class({ return this.tilemap.worldToTileXY(worldX, worldY, snapToFloor, point, camera, this); }, + /** + * Pauses or resumes the animation timer for this game object. + * + * @method Phaser.Tilemaps.TilemapLayer#setTimerPaused + * @since 3.90.0 + * @param {boolean} [paused] - Pause state (`true` to pause, `false` to unpause). If not specified, the timer will unpause. + * @return {this} This game object. + */ + setTimerPaused: function (paused) + { + this.timePaused = !!paused; + + return this; + }, + + /** + * Reset the animation timer for this game object. + * + * @method Phaser.Tilemaps.TilemapLayer#resetTimer + * @since 3.90.0 + * @param {number} [ms=0] - The time to reset the timer to. + * @return {this} This game object. + */ + resetTimer: function (ms) + { + if (ms === undefined) { ms = 0; } + this.timeElapsed = ms; + + return this; + }, + + /** + * Update the timer for this game object. + * This is called automatically by the preUpdate method. + * The timer drives the animation of the texture. + * + * Override this method to create more advanced time management, + * or set it to a NOOP function to disable the timer update. + * If you want to control animations with a tween or input system, + * disabling the timer update could be useful. + * + * @method Phaser.Tilemaps.TilemapLayer#updateTimer + * @since 3.90.0 + * @param {number} time - The current time in milliseconds. + * @param {number} delta - The time since the last update, in milliseconds. + * @return {this} This game object. + */ + updateTimer: function (time, delta) + { + if (!this.timePaused) + { + this.timeElapsed += delta; + } + + return this; + }, + /** * Destroys this TilemapLayer and removes its link to the associated LayerData. * diff --git a/src/tilemaps/TilemapLayerWebGLRenderer.js b/src/tilemaps/TilemapLayerWebGLRenderer.js index 1674d4592..ab3f7cd02 100644 --- a/src/tilemaps/TilemapLayerWebGLRenderer.js +++ b/src/tilemaps/TilemapLayerWebGLRenderer.js @@ -63,6 +63,8 @@ var TilemapLayerWebGLRenderer = function (renderer, src, drawingContext) var submitterNode = src.customRenderNodes.Submitter || src.defaultRenderNodes.Submitter; var transformerNode = src.customRenderNodes.Transformer || src.defaultRenderNodes.Transformer; + var timeElapsed = src.timeElapsed; + for (var i = 0; i < tileCount; i++) { var tile = renderTiles[i]; @@ -74,7 +76,14 @@ var TilemapLayerWebGLRenderer = function (renderer, src, drawingContext) continue; } - var tileTexCoords = tileset.getTileTextureCoordinates(tile.index); + var tileIndex = tileset.getAnimatedTileId(tile.index, timeElapsed); + + if (tileIndex === null) + { + continue; + } + + var tileTexCoords = tileset.getTileTextureCoordinates(tileIndex); var tileWidth = tileset.tileWidth; var tileHeight = tileset.tileHeight; diff --git a/src/tilemaps/Tileset.js b/src/tilemaps/Tileset.js index 44f78fdac..c9b243702 100644 --- a/src/tilemaps/Tileset.js +++ b/src/tilemaps/Tileset.js @@ -194,6 +194,20 @@ var Tileset = new Class({ * @since 3.0.0 */ this.texCoordinates = []; + + /** + * The number of frames above which a tile is considered to have + * many animation frames. This is used to optimize rendering. + * If a tile has fewer frames than this, frames are searched using + * a linear search. If a tile has more, frames are searched using + * a binary search. + * + * @name Phaser.Tilemaps.Tileset#animationSearchThreshold + * @type {number} + * @since 3.90.0 + * @default 64 + */ + this.animationSearchThreshold = 64; }, /** @@ -269,6 +283,61 @@ var Tileset = new Class({ ); }, + /** + * Returns the ID of the tile to use, given a base tile and time, + * according to the tile's animation properties. + * + * If the tile is not animated, this method returns the base tile ID. + * + * @method Phaser.Tilemaps.Tileset#getAnimatedTileId + * @since 3.90.0 + * @param {number} tileIndex - The unique id of the tile across all tilesets in the map. + * @param {number} milliseconds - The current time in milliseconds. + * @return {?number} The tile ID to use, or null if the tile is not contained in this tileset. + */ + getAnimatedTileId: function (tileIndex, milliseconds) + { + if (!this.containsTileIndex(tileIndex)) { return null; } + + var animData = this.getTileData(tileIndex); + + if (!(animData && animData.animation)) { return tileIndex; } + + milliseconds = milliseconds % animData.animationDuration; + var anim = animData.animation; + var frame = null; + + // Binary search. + + var low = 0; + var high = anim.length - 1; + var mid = 0; + var startTime = 0; + + while (low <= high) + { + mid = (low + high) >>> 1; + frame = anim[mid]; + startTime = frame.startTime; + + if (startTime <= milliseconds && startTime + frame.duration > milliseconds) + { + return frame.tileid + this.firstgid; + } + + if (startTime < milliseconds) + { + low = mid + 1; + } + else + { + high = mid - 1; + } + } + + return null; + }, + /** * Returns the texture coordinates (UV in pixels) in the Tileset image for the given tile index. * Returns null if tile index is not contained in this Tileset. diff --git a/src/tilemaps/parsers/tiled/ParseTilesets.js b/src/tilemaps/parsers/tiled/ParseTilesets.js index 0c86564c3..1608c56d3 100644 --- a/src/tilemaps/parsers/tiled/ParseTilesets.js +++ b/src/tilemaps/parsers/tiled/ParseTilesets.js @@ -96,6 +96,22 @@ var ParseTilesets = function (json) (datas[tile.id] || (datas[tile.id] = {})).type = tile.type; } } + + // Sum up animation length. + for (var tileId in datas) + { + var animData = datas[tileId].animation; + if (animData) + { + var animTime = 0; + for (var j = 0; j < animData.length; j++) + { + animData[j].startTime = animTime; + animTime += animData[j].duration; + } + datas[tileId].animationDuration = animTime; + } + } } if (Array.isArray(set.wangsets))