feat: 开关门动画

This commit is contained in:
AncTe committed 2025-11-26 17:48:56 +08:00
1 parent 3372337fdf
commit dce4cc5b7d
16 files changed
+305 -104

No files matched your search

@@ -17,5 +17,6 @@ export async function createMainExtension() {
const layer = state.layer.getLayerByAlias('event');
if (layer) {
mainMapExtension.addHero(state.hero, layer);
mainMapExtension.addDoor(layer);
}
}
@@ -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<void> {
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<void> {
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<IMapLayerHooks> {
constructor(readonly renderer: MapDoorRenderer) {}
onOpenDoor(x: number, y: number): Promise<void> {
return this.renderer.openDoor(x, y);
}
onCloseDoor(num: number, x: number, y: number): Promise<void> {
return this.renderer.closeDoor(num, x, y);
}
}
@@ -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);
});
@@ -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<IHeroState, IMapHeroRenderer> = new WeakMap();
readonly heroMap: Map<IHeroState, IMapHeroRenderer> = new Map();
/** 地图图层到门渲染器的映射 */
readonly doorMap: Map<IMapLayer, IMapDoorRenderer> = 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();
}
}
@@ -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<void>;
/**
* 在指定位置执行关门动画
* @param num 门图块数字
* @param x 门横坐标
* @param y 门纵坐标
*/
closeDoor(num: number, x: number, y: number): Promise<void>;
/**
* 设置开关门动画两帧之间的间隔
* @param interval 开门动画间隔
*/
setAnimateInterval(interval: number): void;
/**
* 摧毁这个门动画拓展,释放相关资源
*/
destroy(): void;
}
@@ -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 ||
@@ -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<MapVertexBlock>;
@@ -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<ILayerDirtyData> | null {
@@ -33,6 +33,8 @@ export const DYNAMIC_RESERVE = 16;
* 调整此值可以调整频率,值越大,越不容易因为数量小于预留数量而减小预留。
*/
export const MOVING_TOLERANCE = 60;
/** 开关门动画的动画时长 */
export const DOOR_ANIMATE_INTERVAL = 50;
//#region 状态栏