diff --git a/packages-user/client-modules/src/render/commonIns.ts b/packages-user/client-modules/src/render/commonIns.ts index 9da3d48..acfd1c2 100644 --- a/packages-user/client-modules/src/render/commonIns.ts +++ b/packages-user/client-modules/src/render/commonIns.ts @@ -17,5 +17,6 @@ export async function createMainExtension() { const layer = state.layer.getLayerByAlias('event'); if (layer) { mainMapExtension.addHero(state.hero, layer); + mainMapExtension.addDoor(layer); } } diff --git a/packages-user/client-modules/src/render/map/extension/door.ts b/packages-user/client-modules/src/render/map/extension/door.ts new file mode 100644 index 0000000..17da5bb --- /dev/null +++ b/packages-user/client-modules/src/render/map/extension/door.ts @@ -0,0 +1,73 @@ +import { + IMapLayer, + IMapLayerHookController, + IMapLayerHooks +} from '@user/data-state'; +import { IMapDoorRenderer } from './types'; +import { IMapRenderer } from '../types'; +import { sleep } from 'mutate-animate'; +import { DOOR_ANIMATE_INTERVAL } from '../../shared'; + +export class MapDoorRenderer implements IMapDoorRenderer { + /** 钩子控制器 */ + readonly controller: IMapLayerHookController; + + /** 动画间隔 */ + private interval: number = DOOR_ANIMATE_INTERVAL; + + constructor( + readonly renderer: IMapRenderer, + readonly layer: IMapLayer + ) { + this.controller = layer.addHook(new MapDoorHook(this)); + this.controller.load(); + } + + setAnimateInterval(interval: number): void { + this.interval = interval; + } + + async openDoor(x: number, y: number): Promise { + const status = this.renderer.getBlockStatus(this.layer, x, y); + if (!status) return; + const array = this.layer.getMapRef().array; + const index = y * this.layer.width + x; + const num = array[index]; + const data = this.renderer.manager.getIfBigImage(num); + if (!data) return; + const frames = data.frames; + for (let i = 0; i < frames; i++) { + status.useSpecifiedFrame(i); + await sleep(this.interval); + } + } + + async closeDoor(num: number, x: number, y: number): Promise { + const data = this.renderer.manager.getIfBigImage(num); + if (!data) return; + const moving = this.renderer.addMovingBlock(this.layer, num, x, y); + const frames = data.frames; + + for (let i = frames - 1; i >= 0; i--) { + moving.useSpecifiedFrame(i); + await sleep(this.interval); + } + moving.destroy(); + } + + destroy(): void { + this.controller.unload(); + } +} + +class MapDoorHook implements Partial { + constructor(readonly renderer: MapDoorRenderer) {} + + onOpenDoor(x: number, y: number): Promise { + return this.renderer.openDoor(x, y); + } + + onCloseDoor(num: number, x: number, y: number): Promise { + return this.renderer.closeDoor(num, x, y); + } +} diff --git a/packages-user/client-modules/src/render/map/extension/hero.ts b/packages-user/client-modules/src/render/map/extension/hero.ts index 4ff7865..fefb7cc 100644 --- a/packages-user/client-modules/src/render/map/extension/hero.ts +++ b/packages-user/client-modules/src/render/map/extension/hero.ts @@ -156,7 +156,8 @@ export class MapHeroRenderer implements IMapHeroRenderer { offset: dirImage.width / 4, texture: dirImage, cls: BlockCls.Unknown, - frames: 4 + frames: 4, + defaultFrame: 0 }; this.textureMap.set(v, data); }); diff --git a/packages-user/client-modules/src/render/map/extension/manager.ts b/packages-user/client-modules/src/render/map/extension/manager.ts index 70d628d..25f286e 100644 --- a/packages-user/client-modules/src/render/map/extension/manager.ts +++ b/packages-user/client-modules/src/render/map/extension/manager.ts @@ -1,22 +1,30 @@ import { IHeroState, IMapLayer } from '@user/data-state'; -import { IMapExtensionManager, IMapHeroRenderer } from './types'; +import { + IMapDoorRenderer, + IMapExtensionManager, + IMapHeroRenderer +} from './types'; import { IMapRenderer } from '../types'; import { MapHeroRenderer } from './hero'; import { logger } from '@motajs/common'; +import { MapDoorRenderer } from './door'; export class MapExtensionManager implements IMapExtensionManager { /** 勇士状态至勇士渲染器的映射 */ - readonly heroMap: WeakMap = new WeakMap(); + readonly heroMap: Map = new Map(); + /** 地图图层到门渲染器的映射 */ + readonly doorMap: Map = new Map(); constructor(readonly renderer: IMapRenderer) {} - addHero(state: IHeroState, layer: IMapLayer): void { + addHero(state: IHeroState, layer: IMapLayer): IMapHeroRenderer | null { if (this.heroMap.has(state)) { - logger.error(45); - return; + logger.error(45, 'hero renderer'); + return null; } const heroRenderer = new MapHeroRenderer(this.renderer, layer, state); this.heroMap.set(state, heroRenderer); + return heroRenderer; } removeHero(state: IHeroState): void { @@ -25,4 +33,28 @@ export class MapExtensionManager implements IMapExtensionManager { renderer.destroy(); this.heroMap.delete(state); } + + addDoor(layer: IMapLayer): IMapDoorRenderer | null { + if (this.doorMap.has(layer)) { + logger.error(45, 'door renderer'); + return null; + } + const doorRenderer = new MapDoorRenderer(this.renderer, layer); + this.doorMap.set(layer, doorRenderer); + return doorRenderer; + } + + removeDoor(layer: IMapLayer): void { + const renderer = this.doorMap.get(layer); + if (!renderer) return; + renderer.destroy(); + this.doorMap.delete(layer); + } + + destroy(): void { + this.heroMap.forEach(v => void v.destroy()); + this.doorMap.forEach(v => void v.destroy()); + this.heroMap.clear(); + this.doorMap.clear(); + } } diff --git a/packages-user/client-modules/src/render/map/extension/types.ts b/packages-user/client-modules/src/render/map/extension/types.ts index 5f5f83c..c3250fd 100644 --- a/packages-user/client-modules/src/render/map/extension/types.ts +++ b/packages-user/client-modules/src/render/map/extension/types.ts @@ -12,13 +12,28 @@ export interface IMapExtensionManager { * @param state 勇士状态 * @param layer 勇士所在图层 */ - addHero(state: IHeroState, layer: IMapLayer): void; + addHero(state: IHeroState, layer: IMapLayer): IMapHeroRenderer | null; /** * 移除勇士渲染拓展 * @param state 勇士状态 */ removeHero(state: IHeroState): void; + + /** + * 添加开门动画拓展 + */ + addDoor(layer: IMapLayer): IMapDoorRenderer | null; + + /** + * 移除开门动画拓展 + */ + removeDoor(layer: IMapLayer): void; + + /** + * 摧毁这个拓展管理对象,释放相关资源 + */ + destroy(): void; } export interface IMapHeroRenderer { @@ -119,3 +134,31 @@ export interface IMapHeroRenderer { */ destroy(): void; } + +export interface IMapDoorRenderer { + /** + * 开启指定位置的门,播放开门动画 + * @param x 门横坐标 + * @param y 门纵坐标 + */ + openDoor(x: number, y: number): Promise; + + /** + * 在指定位置执行关门动画 + * @param num 门图块数字 + * @param x 门横坐标 + * @param y 门纵坐标 + */ + closeDoor(num: number, x: number, y: number): Promise; + + /** + * 设置开关门动画两帧之间的间隔 + * @param interval 开门动画间隔 + */ + setAnimateInterval(interval: number): void; + + /** + * 摧毁这个门动画拓展,释放相关资源 + */ + destroy(): void; +} diff --git a/packages-user/client-modules/src/render/map/renderer.ts b/packages-user/client-modules/src/render/map/renderer.ts index 49872f8..1ffd930 100644 --- a/packages-user/client-modules/src/render/map/renderer.ts +++ b/packages-user/client-modules/src/render/map/renderer.ts @@ -1348,7 +1348,6 @@ export class MapRenderer } render(): HTMLCanvasElement { - // todo: 改为 FBO,最后把 FBO 画到画布上 const gl = this.gl; const data = this.contextData; if (!this.assetData) { @@ -1711,6 +1710,10 @@ export class MapRenderer this.needUpdateTransform = true; } + requestUpdate(): void { + this.updateRequired = true; + } + needUpdate(): boolean { return ( this.updateRequired || diff --git a/packages-user/client-modules/src/render/map/vertex.ts b/packages-user/client-modules/src/render/map/vertex.ts index 72822a9..0a93d8d 100644 --- a/packages-user/client-modules/src/render/map/vertex.ts +++ b/packages-user/client-modules/src/render/map/vertex.ts @@ -41,6 +41,11 @@ export interface IMapDataGetter { * @param moving 移动图块对象 */ hasMoving(moving: IMovingBlock): boolean; + + /** + * 申请更新渲染 + */ + requestUpdate(): void; } interface BlockMapPos { @@ -106,7 +111,13 @@ export class MapVertexGenerator /** 空顶点数组,因为空顶点很常用,所以直接定义一个全局常量 */ private static readonly EMPTY_VETREX: Float32Array = new Float32Array( - INSTANCED_COUNT + // prettier-ignore + [ + 0, 0, 0, 0, // 顶点坐标 + 0, 0, 0, 0, // 纹理坐标 + 0, 1, 0, 0, // a_tileData,不透明度需要设为 1 + -1, 0, 0, 0, // a_texData,当前帧数需要设为 -1 + ] ); readonly block: IBlockSplitter; @@ -1044,7 +1055,7 @@ class MapVertexBlock implements IMapVertexBlock { * @param blockHeight 分块高度 */ constructor( - readonly renderer: IMapRenderer, + readonly renderer: IMapRenderer & IMapDataGetter, originArray: IMapVertexData, startIndex: number, count: number, @@ -1074,6 +1085,7 @@ class MapVertexBlock implements IMapVertexBlock { */ markRenderDirty() { this.renderDirty = true; + this.renderer.requestUpdate(); } markDirty( @@ -1101,6 +1113,7 @@ class MapVertexBlock implements IMapVertexBlock { data.dirtyBottom = Math.max(db, data.dirtyBottom); } this.dirty = true; + this.renderer.requestUpdate(); } getDirtyArea(layer: IMapLayer): Readonly | null { diff --git a/packages-user/client-modules/src/render/shared.ts b/packages-user/client-modules/src/render/shared.ts index 2cbe2bc..294fa7c 100644 --- a/packages-user/client-modules/src/render/shared.ts +++ b/packages-user/client-modules/src/render/shared.ts @@ -33,6 +33,8 @@ export const DYNAMIC_RESERVE = 16; * 调整此值可以调整频率,值越大,越不容易因为数量小于预留数量而减小预留。 */ export const MOVING_TOLERANCE = 60; +/** 开关门动画的动画时长 */ +export const DOOR_ANIMATE_INTERVAL = 50; //#region 状态栏 diff --git a/packages-user/data-state/src/index.ts b/packages-user/data-state/src/index.ts index 621f681..775735c 100644 --- a/packages-user/data-state/src/index.ts +++ b/packages-user/data-state/src/index.ts @@ -24,33 +24,32 @@ function createCoreState() { //#region 图块部分 - loading.once('coreInit', () => { - const data = Object.entries(core.maps.blocksInfo); - for (const [key, block] of data) { - const num = Number(key); - state.idNumberMap.set(block.id, num); - state.numberIdMap.set(num, block.id); + const data = Object.entries(core.maps.blocksInfo); + for (const [key, block] of data) { + const num = Number(key); + state.idNumberMap.set(block.id, num); + state.numberIdMap.set(num, block.id); + } + + for (const [key, block] of data) { + if (!block.faceIds) continue; + const { down, up, left, right } = block.faceIds; + const downNum = state.idNumberMap.get(down); + if (downNum !== Number(key)) continue; + const upNum = state.idNumberMap.get(up); + const leftNum = state.idNumberMap.get(left); + const rightNum = state.idNumberMap.get(right); + state.roleFace.malloc(downNum, FaceDirection.Down); + if (!isNil(upNum)) { + state.roleFace.bind(upNum, downNum, FaceDirection.Up); } - for (const [key, block] of data) { - if (!block.faceIds) continue; - const { down, up, left, right } = block.faceIds; - const downNum = state.idNumberMap.get(down); - if (downNum !== Number(key)) continue; - const upNum = state.idNumberMap.get(up); - const leftNum = state.idNumberMap.get(left); - const rightNum = state.idNumberMap.get(right); - state.roleFace.malloc(downNum, FaceDirection.Down); - if (!isNil(upNum)) { - state.roleFace.bind(upNum, downNum, FaceDirection.Up); - } - if (!isNil(leftNum)) { - state.roleFace.bind(leftNum, downNum, FaceDirection.Left); - } - if (!isNil(rightNum)) { - state.roleFace.bind(rightNum, downNum, FaceDirection.Right); - } + if (!isNil(leftNum)) { + state.roleFace.bind(leftNum, downNum, FaceDirection.Left); } - }); + if (!isNil(rightNum)) { + state.roleFace.bind(rightNum, downNum, FaceDirection.Right); + } + } //#endregion } diff --git a/packages-user/data-state/src/map/layerState.ts b/packages-user/data-state/src/map/layerState.ts index c45e8a0..d6c8d24 100644 --- a/packages-user/data-state/src/map/layerState.ts +++ b/packages-user/data-state/src/map/layerState.ts @@ -36,7 +36,7 @@ export class LayerState this.forEachHook(hook => { hook.onUpdateLayer?.(this.layerList); }); - const controller = layer.addHook(new StateMapLayerHook(this)); + const controller = layer.addHook(new StateMapLayerHook(this, layer)); this.layerHookMap.set(layer, controller); controller.load(); return layer; @@ -114,38 +114,26 @@ export class LayerState } class StateMapLayerHook implements Partial { - constructor(readonly state: LayerState) {} + constructor( + readonly state: LayerState, + readonly layer: IMapLayer + ) {} - onUpdateArea( - controller: IMapLayerHookController, - x: number, - y: number, - width: number, - height: number - ): void { + onUpdateArea(x: number, y: number, width: number, height: number): void { this.state.forEachHook(hook => { - hook.onUpdateLayerArea?.(controller.layer, x, y, width, height); + hook.onUpdateLayerArea?.(this.layer, x, y, width, height); }); } - onUpdateBlock( - controller: IMapLayerHookController, - block: number, - x: number, - y: number - ): void { + onUpdateBlock(block: number, x: number, y: number): void { this.state.forEachHook(hook => { - hook.onUpdateLayerBlock?.(controller.layer, block, x, y); + hook.onUpdateLayerBlock?.(this.layer, block, x, y); }); } - onResize( - controller: IMapLayerHookController, - width: number, - height: number - ): void { + onResize(width: number, height: number): void { this.state.forEachHook(hook => { - hook.onResizeLayer?.(controller.layer, width, height); + hook.onResizeLayer?.(this.layer, width, height); }); } } diff --git a/packages-user/data-state/src/map/mapLayer.ts b/packages-user/data-state/src/map/mapLayer.ts index 82607fa..a1a7879 100644 --- a/packages-user/data-state/src/map/mapLayer.ts +++ b/packages-user/data-state/src/map/mapLayer.ts @@ -72,8 +72,8 @@ export class MapLayer expired: false, array: this.mapArray }; - this.forEachHook((hook, controller) => { - hook.onResize?.(controller, width, height); + this.forEachHook(hook => { + hook.onResize?.(width, height); }); } @@ -91,8 +91,8 @@ export class MapLayer array: this.mapArray }; this.empty = true; - this.forEachHook((hook, controller) => { - hook.onResize?.(controller, width, height); + this.forEachHook(hook => { + hook.onResize?.(width, height); }); } @@ -100,8 +100,8 @@ export class MapLayer const index = y * this.width + x; if (block === this.mapArray[index]) return; this.mapArray[index] = block; - this.forEachHook((hook, controller) => { - hook.onUpdateBlock?.(controller, block, x, y); + this.forEachHook(hook => { + hook.onUpdateBlock?.(block, x, y); }); if (block !== 0) { this.empty = false; @@ -123,8 +123,8 @@ export class MapLayer const height = Math.ceil(array.length / width); if (width === this.width && height === this.height) { this.mapArray.set(array); - this.forEachHook((hook, controller) => { - hook.onUpdateArea?.(controller, x, y, width, height); + this.forEachHook(hook => { + hook.onUpdateArea?.(x, y, width, height); }); return; } @@ -151,8 +151,8 @@ export class MapLayer } this.mapArray.set(array.subarray(start, start + nw), offset); } - this.forEachHook((hook, controller) => { - hook.onUpdateArea?.(controller, x, y, width, height); + this.forEachHook(hook => { + hook.onUpdateArea?.(x, y, width, height); }); this.empty &&= empty; } @@ -214,6 +214,33 @@ export class MapLayer setZIndex(zIndex: number): void { this.zIndex = zIndex; } + + async openDoor(x: number, y: number): Promise { + const index = y * this.width + x; + const num = this.mapArray[index]; + if (num === 0) return; + await Promise.all( + this.forEachHook(hook => { + return hook.onOpenDoor?.(x, y); + }) + ); + this.setBlock(0, x, y); + } + + async closeDoor(num: number, x: number, y: number): Promise { + const index = y * this.width + x; + const nowNum = this.mapArray[index]; + if (nowNum !== 0) { + logger.error(46, x.toString(), y.toString()); + return; + } + await Promise.all( + this.forEachHook(hook => { + return hook.onCloseDoor?.(num, x, y); + }) + ); + this.setBlock(num, x, y); + } } class MapLayerHookController diff --git a/packages-user/data-state/src/map/types.ts b/packages-user/data-state/src/map/types.ts index 7dbb127..a702edb 100644 --- a/packages-user/data-state/src/map/types.ts +++ b/packages-user/data-state/src/map/types.ts @@ -10,45 +10,42 @@ export interface IMapLayerData { export interface IMapLayerHooks extends IHookBase { /** * 当地图大小发生变化时执行,如果调用了地图的 `resize` 方法,但是地图大小没变,则不会触发 - * @param controller 拓展控制器 * @param width 地图宽度 * @param height 地图高度 */ - onResize( - controller: IMapLayerHookController, - width: number, - height: number - ): void; + onResize(width: number, height: number): void; /** * 当更新某个区域的图块时执行 - * @param controller 拓展控制器 * @param x 更新区域左上角横坐标 * @param y 更新区域左上角纵坐标 * @param width 更新区域宽度 * @param height 更新区域高度 */ - onUpdateArea( - controller: IMapLayerHookController, - x: number, - y: number, - width: number, - height: number - ): void; + onUpdateArea(x: number, y: number, width: number, height: number): void; /** * 当更新某个点的图块时执行,如果设置的图块与原先一样,则不会触发此方法 - * @param controller 拓展控制器 * @param block 更新为的图块数字 * @param x 更新点横坐标 * @param y 更新点纵坐标 */ - onUpdateBlock( - controller: IMapLayerHookController, - block: number, - x: number, - y: number - ): void; + onUpdateBlock(block: number, x: number, y: number): void; + + /** + * 当开门时触发,返回一个 `Promise`,当相关动画执行完毕后兑现 + * @param x 门横坐标 + * @param y 门纵坐标 + */ + onOpenDoor(x: number, y: number): Promise; + + /** + * 当关门时触发,返回一个 `Promise`,当相关动画执行完毕后兑现 + * @param num 门的图块数字 + * @param x 门横坐标 + * @param y 门纵坐标 + */ + onCloseDoor(num: number, x: number, y: number): Promise; } export interface IMapLayerHookController @@ -143,6 +140,21 @@ export interface IMapLayer * @param zIndex 纵深 */ setZIndex(zIndex: number): void; + + /** + * 开启指定位置的门 + * @param x 门横坐标 + * @param y 门纵坐标 + */ + openDoor(x: number, y: number): Promise; + + /** + * 在指定位置关门,门的图块数字由参数指定 + * @param num 门图块数字 + * @param x 门横坐标 + * @param y 门纵坐标 + */ + closeDoor(num: number, x: number, y: number): Promise; } export interface ILayerStateHooks extends IHookBase { diff --git a/packages-user/data-state/src/types.ts b/packages-user/data-state/src/types.ts index 653a237..a635bc3 100644 --- a/packages-user/data-state/src/types.ts +++ b/packages-user/data-state/src/types.ts @@ -19,7 +19,14 @@ export interface ICoreState { /** 图块数字到 id 的映射 */ readonly numberIdMap: Map; + /** + * 保存状态 + */ saveState(): IStateSaveData; + /** + * 加载状态 + * @param data 状态对象 + */ loadState(data: IStateSaveData): void; } diff --git a/packages-user/legacy-plugin-data/src/fallback.ts b/packages-user/legacy-plugin-data/src/fallback.ts index f1f6cef..5dd540a 100644 --- a/packages-user/legacy-plugin-data/src/fallback.ts +++ b/packages-user/legacy-plugin-data/src/fallback.ts @@ -341,9 +341,9 @@ export function initFallback() { const locked = core.status.lockControl; core.lockControl(); core.status.replay.animate = true; - core.removeBlock(x, y); const cb = () => { + core.removeBlock(x, y); core.maps._removeBlockFromMap( core.status.floorId, block @@ -359,7 +359,8 @@ export function initFallback() { callback?.(); }; - adapters['door-animate']?.all('openDoor', block).then(cb); + const layer = state.layer.getLayerByAlias('event')!; + layer.openDoor(x, y).then(cb); const animate = fallbackIds++; core.animateFrame.lastAsyncId = animate; @@ -406,11 +407,9 @@ export function initFallback() { if (core.status.replay.speed === 24) { cb(); } else { - adapters['door-animate'] - ?.all('closeDoor', block) - .then(() => { - cb(); - }); + const num = state.idNumberMap.get(id)!; + const layer = state.layer.getLayerByAlias('event')!; + layer.closeDoor(num, x, y).then(cb); const animate = fallbackIds++; core.animateFrame.lastAsyncId = animate; diff --git a/packages/common/src/logger.json b/packages/common/src/logger.json index 9eb2f33..afc04cb 100644 --- a/packages/common/src/logger.json +++ b/packages/common/src/logger.json @@ -44,7 +44,8 @@ "42": "The '$1' property of map-render element is required.", "43": "Cannot bind face direction to main block $1, please call malloc in advance.", "44": "Cannot bind face direction to main block $1, since main direction cannot be override.", - "45": "Cannot add hero renderer, sincehero renderer already exists for the given state.", + "45": "Cannot add $1 map renderer extension, since $1 already exists for the given state.", + "46": "Cannot execute close door action on $1,$2, since the given position is not empty.", "1101": "Shadow extension needs 'floor-hero' extension as dependency.", "1201": "Floor-damage extension needs 'floor-binder' extension as dependency.", "1301": "Portal extension need 'floor-binder' extension as dependency.", diff --git a/public/libs/maps.js b/public/libs/maps.js index 25c957a..bef0447 100644 --- a/public/libs/maps.js +++ b/public/libs/maps.js @@ -3197,8 +3197,8 @@ maps.prototype.hideBlockByIndex = function (index, floorId) { this._updateMapArray(floorId, block.x, block.y); Mota.require('@user/data-base').hook.emit( 'setBlock', - x, - y, + block.x, + block.y, floorId, 0, block?.id ?? 0 @@ -3206,7 +3206,7 @@ maps.prototype.hideBlockByIndex = function (index, floorId) { if (floorId === core.status.floorId) { const { layer } = Mota.require('@user/data-state').state; const event = layer.getLayerByAlias('event'); - event.setBlock(0, x, y); + event.setBlock(0, block.x, block.y); } }; @@ -3283,7 +3283,7 @@ maps.prototype.removeBlockByIndex = function (index, floorId) { if (floorId === core.status.floorId) { const { layer } = Mota.require('@user/data-state').state; const event = layer.getLayerByAlias('event'); - event.setBlock(0, x, y); + event.setBlock(0, block.x, block.y); } }; @@ -3639,8 +3639,8 @@ maps.prototype.replaceBlock = function (fromNumber, toNumber, floorId) { this._updateMapArray(floorId, block.x, block.y); Mota.require('@user/data-base').hook.emit( 'setBlock', - x, - y, + block.x, + block.y, floorId, fromNumber, toNumber @@ -3648,7 +3648,7 @@ maps.prototype.replaceBlock = function (fromNumber, toNumber, floorId) { if (floorId === core.status.floorId) { const { layer } = Mota.require('@user/data-state').state; const event = layer.getLayerByAlias('event'); - event.setBlock(toNumber, x, y); + event.setBlock(toNumber, block.x, block.y); } } }, this);