From 037746999dcc501178c2fc8f1ed240279e375d81 Mon Sep 17 00:00:00 2001 From: unanmed <1319491857@qq.com> Date: Sun, 23 Nov 2025 23:07:24 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=8B=87=E5=A3=AB=E6=B8=B2=E6=9F=93?= =?UTF-8?q?=E5=85=BC=E5=AE=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../client-modules/src/render/index.tsx | 1 + .../src/render/map/extension/hero.ts | 29 ++- .../src/render/map/extension/types.ts | 6 + packages-user/data-state/src/common/utils.ts | 130 ++++++++++++++ packages-user/data-state/src/hero/state.ts | 13 +- packages-user/data-state/src/hero/types.ts | 12 ++ .../legacy-plugin-data/src/fallback.ts | 166 ++++++++---------- 7 files changed, 256 insertions(+), 101 deletions(-) diff --git a/packages-user/client-modules/src/render/index.tsx b/packages-user/client-modules/src/render/index.tsx index 3ce8c32..816c02f 100644 --- a/packages-user/client-modules/src/render/index.tsx +++ b/packages-user/client-modules/src/render/index.tsx @@ -46,6 +46,7 @@ export function createRender() { Font.setDefaults(DEFAULT_FONT); } +export * from './commonIns'; export * from './components'; export * from './elements'; export * from './fx'; 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 fc7928f..b017ead 100644 --- a/packages-user/client-modules/src/render/map/extension/hero.ts +++ b/packages-user/client-modules/src/render/map/extension/hero.ts @@ -6,6 +6,7 @@ import { IHeroState, IHeroStateHooks, IMapLayer, + nextFaceDirection, state } from '@user/data-state'; import { IMapRenderer, IMapRendererTicker, IMovingBlock } from '../types'; @@ -59,14 +60,14 @@ export class MapHeroRenderer implements IMapHeroRenderer { /** 勇士钩子 */ readonly controller: IHookController; - /** 每个朝向的贴图对象 */ + /** 勇士每个朝向的贴图对象 */ readonly textureMap: Map = new Map(); /** 勇士渲染实体,与 `entities[0]` 同引用 */ readonly heroEntity: HeroRenderEntity; /** * 渲染实体,索引 0 表示勇士,后续索引依次表示跟随的跟随者。 - * 整体是一个状态机,而且下一个跟随者只与上一个跟随者有关,下一个跟随者移动的方向就是上一个跟随者移动前指向的方向。 + * 整体是一个状态机,而且下一个跟随者只与上一个跟随者有关,下一个跟随者移动的方向就是上一个跟随者上一步移动后指向的方向。 */ readonly entities: HeroRenderEntity[] = []; @@ -194,7 +195,10 @@ export class MapHeroRenderer implements IMapHeroRenderer { } setPosition(x: number, y: number): void { - this.heroEntity.block.setPos(x, y); + this.entities.forEach(v => { + v.block.setPos(x, y); + v.nextDirection = FaceDirection.Unknown; + }); } /** @@ -220,12 +224,12 @@ export class MapHeroRenderer implements IMapHeroRenderer { entity.promise = entity.promise.then(async () => { entity.moving = true; entity.animating = true; - entity.nextDirection = entity.direction; entity.direction = direction; if (nextTex) block.setTexture(nextTex); await block.lineTo(tx, ty, time); entity.moving = false; entity.animating = false; + entity.nextDirection = entity.direction; }); } @@ -406,8 +410,8 @@ export class MapHeroRenderer implements IMapHeroRenderer { ); if (!tile) continue; moving.block.setTexture(tile); - moving.nextDirection = moving.direction; moving.direction = last.nextDirection; + moving.nextDirection = moving.direction; } this.entities.splice(index, 1); } @@ -426,6 +430,17 @@ export class MapHeroRenderer implements IMapHeroRenderer { this.heroEntity.animateDirection = direction; } + turn(direction?: FaceDirection): void { + const next = isNil(direction) + ? nextFaceDirection(this.heroEntity.direction) + : direction; + const tex = this.textureMap.get(next); + if (tex) { + this.heroEntity.block.setTexture(tex); + this.heroEntity.direction = next; + } + } + destroy() { this.controller.unload(); } @@ -447,6 +462,10 @@ class MapHeroHook implements Partial { this.hero.setPosition(x, y); } + onTurnHero(direction: FaceDirection): void { + this.hero.turn(direction); + } + onStartMove(): void { this.hero.startMove(); } 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 feeaa0c..c393fa6 100644 --- a/packages-user/client-modules/src/render/map/extension/types.ts +++ b/packages-user/client-modules/src/render/map/extension/types.ts @@ -88,6 +88,12 @@ export interface IMapHeroRenderer { */ setHeroAnimateDirection(direction: HeroAnimateDirection): void; + /** + * 设置勇士朝向 + * @param direction 勇士朝向,不填表示顺时针旋转 + */ + turn(direction?: FaceDirection): void; + /** * 摧毁这个勇士渲染拓展,释放相关资源 */ diff --git a/packages-user/data-state/src/common/utils.ts b/packages-user/data-state/src/common/utils.ts index 60b9a6f..9502841 100644 --- a/packages-user/data-state/src/common/utils.ts +++ b/packages-user/data-state/src/common/utils.ts @@ -50,3 +50,133 @@ export function degradeFace( } return dir; } + +/** + * 获取指定朝向旋转后的朝向 + * @param dir 当前朝向 + * @param anticlockwise 是否逆时针旋转,默认顺时针 + * @param face8 是否使用八朝向。为 `false` 时,旋转为九十度旋转,即 上->右->下->左,左上->右上->右下->左下。 + * 为 `true` 时,旋转为四十五度旋转,即 上->右上->右->右下->下->左下->左->左上。逆时针反过来旋转。 + */ +export function nextFaceDirection( + dir: FaceDirection, + anticlockwise: boolean = false, + face8: boolean = false +): FaceDirection { + if (face8) { + if (anticlockwise) { + switch (dir) { + case FaceDirection.Left: + return FaceDirection.LeftDown; + case FaceDirection.LeftDown: + return FaceDirection.Down; + case FaceDirection.Down: + return FaceDirection.RightDown; + case FaceDirection.RightDown: + return FaceDirection.Right; + case FaceDirection.Right: + return FaceDirection.RightUp; + case FaceDirection.RightUp: + return FaceDirection.Up; + case FaceDirection.Up: + return FaceDirection.LeftUp; + case FaceDirection.LeftUp: + return FaceDirection.Left; + case FaceDirection.Unknown: + return FaceDirection.Unknown; + } + } else { + switch (dir) { + case FaceDirection.Left: + return FaceDirection.LeftUp; + case FaceDirection.LeftUp: + return FaceDirection.Up; + case FaceDirection.Up: + return FaceDirection.RightUp; + case FaceDirection.RightUp: + return FaceDirection.Right; + case FaceDirection.Right: + return FaceDirection.RightDown; + case FaceDirection.RightDown: + return FaceDirection.Down; + case FaceDirection.Down: + return FaceDirection.LeftDown; + case FaceDirection.LeftDown: + return FaceDirection.Left; + case FaceDirection.Unknown: + return FaceDirection.Unknown; + } + } + } else { + if (anticlockwise) { + switch (dir) { + case FaceDirection.Left: + return FaceDirection.Down; + case FaceDirection.Down: + return FaceDirection.Right; + case FaceDirection.Right: + return FaceDirection.Up; + case FaceDirection.Up: + return FaceDirection.Left; + case FaceDirection.LeftUp: + return FaceDirection.LeftDown; + case FaceDirection.LeftDown: + return FaceDirection.RightDown; + case FaceDirection.RightDown: + return FaceDirection.RightUp; + case FaceDirection.RightUp: + return FaceDirection.LeftUp; + case FaceDirection.Unknown: + return FaceDirection.Unknown; + } + } else { + switch (dir) { + case FaceDirection.Left: + return FaceDirection.Up; + case FaceDirection.Up: + return FaceDirection.Right; + case FaceDirection.Right: + return FaceDirection.Down; + case FaceDirection.Down: + return FaceDirection.Left; + case FaceDirection.LeftUp: + return FaceDirection.RightUp; + case FaceDirection.RightUp: + return FaceDirection.RightDown; + case FaceDirection.RightDown: + return FaceDirection.LeftDown; + case FaceDirection.LeftDown: + return FaceDirection.LeftUp; + case FaceDirection.Unknown: + return FaceDirection.Unknown; + } + } + } +} + +/** + * 根据朝向字符串获取朝向枚举值 + * @param dir 朝向字符串 + */ +export function fromDirectionString(dir: Dir2): FaceDirection { + switch (dir) { + case 'left': + return FaceDirection.Left; + case 'right': + return FaceDirection.Right; + case 'up': + return FaceDirection.Up; + case 'down': + return FaceDirection.Down; + case 'leftup': + return FaceDirection.LeftUp; + case 'rightup': + return FaceDirection.RightUp; + case 'leftdown': + return FaceDirection.LeftDown; + case 'rightdown': + return FaceDirection.RightDown; + default: + return FaceDirection.Unknown; + } +} diff --git a/packages-user/data-state/src/hero/state.ts b/packages-user/data-state/src/hero/state.ts index 26f3e9a..bfc31e4 100644 --- a/packages-user/data-state/src/hero/state.ts +++ b/packages-user/data-state/src/hero/state.ts @@ -1,6 +1,7 @@ import { Hookable, HookController, IHookController } from '@motajs/common'; import { IHeroFollower, IHeroState, IHeroStateHooks } from './types'; -import { FaceDirection, getFaceMovement } from '../common'; +import { FaceDirection, getFaceMovement, nextFaceDirection } from '../common'; +import { isNil } from 'lodash-es'; export class HeroState extends Hookable implements IHeroState { x: number = 0; @@ -28,6 +29,16 @@ export class HeroState extends Hookable implements IHeroState { }); } + turn(direction?: FaceDirection): void { + const next = isNil(direction) + ? nextFaceDirection(this.direction) + : direction; + this.direction = next; + this.forEachHook(hook => { + hook.onTurnHero?.(next); + }); + } + startMove(): void { this.moving = true; this.forEachHook(hook => { diff --git a/packages-user/data-state/src/hero/types.ts b/packages-user/data-state/src/hero/types.ts index 4062b43..d34a0b1 100644 --- a/packages-user/data-state/src/hero/types.ts +++ b/packages-user/data-state/src/hero/types.ts @@ -26,6 +26,12 @@ export interface IHeroStateHooks extends IHookBase { */ onSetPosition(x: number, y: number): void; + /** + * 当设置勇士朝向时触发 + * @param direction 勇士朝向 + */ + onTurnHero(direction: FaceDirection): void; + /** * 当勇士开始移动时触发 */ @@ -119,6 +125,12 @@ export interface IHeroState extends IHookable { */ setPosition(x: number, y: number): void; + /** + * 设置勇士朝向 + * @param direction 勇士朝向,不填表示顺时针旋转 + */ + turn(direction?: FaceDirection): void; + /** * 开始勇士移动,在移动前必须先调用此方法将勇士切换为移动状态 */ diff --git a/packages-user/legacy-plugin-data/src/fallback.ts b/packages-user/legacy-plugin-data/src/fallback.ts index ae36dce..2ac4712 100644 --- a/packages-user/legacy-plugin-data/src/fallback.ts +++ b/packages-user/legacy-plugin-data/src/fallback.ts @@ -1,25 +1,27 @@ import type { RenderAdapter } from '@motajs/render'; import type { TimingFn } from 'mutate-animate'; -import { BlockMover, heroMoveCollection, MoveStep } from '@user/data-state'; +import { + BlockMover, + fromDirectionString, + heroMoveCollection, + MoveStep, + state +} from '@user/data-state'; import { hook, loading } from '@user/data-base'; import { Patch, PatchClass } from '@motajs/legacy-common'; import type { - HeroRenderer, LayerDoorAnimate, LayerGroupAnimate, - Layer, FloorViewport, - LayerFloorBinder, LayerGroup } from '@user/client-modules'; +import { isNil } from 'lodash-es'; // 向后兼容用,会充当两个版本间过渡的作用 interface Adapters { - 'hero-adapter'?: RenderAdapter; 'door-animate'?: RenderAdapter; animate?: RenderAdapter; - layer?: RenderAdapter; viewport?: RenderAdapter; } @@ -30,16 +32,12 @@ export function initFallback() { if (!main.replayChecking && main.mode === 'play') { const Adapter = Mota.require('@motajs/render').RenderAdapter; - const hero = Adapter.get('hero-adapter'); const doorAnimate = Adapter.get('door-animate'); const animate = Adapter.get('animate'); - const layer = Adapter.get('layer'); const viewport = Adapter.get('viewport'); - adapters['hero-adapter'] = hero; adapters['door-animate'] = doorAnimate; adapters['animate'] = animate; - adapters['layer'] = layer; adapters['viewport'] = viewport; } @@ -74,7 +72,7 @@ export function initFallback() { /** * 生成跳跃函数 */ - function generateJumpFn(dx: number, dy: number): TimingFn<3> { + function generateJumpFn(dx: number, dy: number): TimingFn<2> { const distance = Math.hypot(dx, dy); const peak = 3 + distance; @@ -82,7 +80,7 @@ export function initFallback() { const x = dx * progress; const y = progress * dy + (progress ** 2 - progress) * peak; - return [x, y, Math.ceil(y)]; + return [x, y]; }; } @@ -112,31 +110,28 @@ export function initFallback() { patch.add('_moveAction_moving', () => {}); - patch2.add( - '_action_moveAction', - function (data: any, x: number, y: number, prefix: any) { - if (core.canMoveHero()) { - const nx = core.nextX(), - ny = core.nextY(); - // 检查noPass决定是撞击还是移动 - if (core.noPass(nx, ny)) { - core.insertAction([{ type: 'trigger', loc: [nx, ny] }]); - } else { - // 先移动一格,然后尝试触发事件 - core.insertAction([ - { - type: 'function', - function: - 'function() { core.moveAction(core.doAction); }', - async: true - }, - { type: '_label' } - ]); - } + patch2.add('_action_moveAction', function () { + if (core.canMoveHero()) { + const nx = core.nextX(), + ny = core.nextY(); + // 检查noPass决定是撞击还是移动 + if (core.noPass(nx, ny)) { + core.insertAction([{ type: 'trigger', loc: [nx, ny] }]); + } else { + // 先移动一格,然后尝试触发事件 + core.insertAction([ + { + type: 'function', + function: + 'function() { core.moveAction(core.doAction); }', + async: true + }, + { type: '_label' } + ]); } - core.doAction(); } - ); + core.doAction(); + }); patch2.add( 'eventMoveHero', @@ -176,29 +171,28 @@ export function initFallback() { patch.add( 'setHeroLoc', - function ( - name: 'x' | 'y' | 'direction', - value: number | Dir, - noGather?: boolean - ) { + function (name: 'x' | 'y' | 'direction', value: number | Dir) { if (!core.status.hero) return; // @ts-ignore core.status.hero.loc[name] = value; - if ((name === 'x' || name === 'y') && !noGather) { - core.control.gatherFollowers(); - } if (name === 'direction') { - adapters['hero-adapter']?.sync('turn', value); - adapters['hero-adapter']?.sync('setAnimateDir', value); + const dir = fromDirectionString(value as Dir); + state.hero.turn(dir); setHeroDirection(value as Dir); } else if (name === 'x') { // 为了防止逆天样板出问题 core.bigmap.posX = value as number; - adapters['hero-adapter']?.sync('setHeroLoc', value); + state.hero.setPosition( + value as number, + core.status.hero.loc.y + ); } else { // 为了防止逆天样板出问题 core.bigmap.posY = value as number; - adapters['hero-adapter']?.sync('setHeroLoc', void 0, value); + state.hero.setPosition( + core.status.hero.loc.x, + value as number + ); } } ); @@ -243,10 +237,8 @@ export function initFallback() { ); patch2.add('setHeroIcon', function (name: ImageIds) { - const img = core.material.images.images[name]; - if (!img) return; core.status.hero.image = name; - adapters['hero-adapter']?.sync('setImage', img); + state.hero.setImage(name); }); patch.add('isMoving', function () { @@ -279,10 +271,10 @@ export function initFallback() { // 找寻自动寻路路线 const moveStep = core.automaticRoute(destX, destY); if ( - moveStep.length == 0 && - (destX != core.status.hero.loc.x || - destY != core.status.hero.loc.y || - stepPostfix.length == 0) + moveStep.length === 0 && + (destX !== core.status.hero.loc.x || + destY !== core.status.hero.loc.y || + stepPostfix.length === 0) ) return; moveStep.push(...stepPostfix); @@ -375,10 +367,10 @@ export function initFallback() { id = id || ''; if ( // @ts-ignore - (core.material.icons.animates[id] == null && + (isNil(core.material.icons.animates[id]) && // @ts-ignore - core.material.icons.npc48[id] == null) || - core.getBlock(x, y) != null + isNil(core.material.icons.npc48[id])) || + !isNil(core.getBlock(x, y)) ) { if (callback) callback(); return; @@ -438,10 +430,10 @@ export function initFallback() { if ( core.isReplaying() || !core.material.animates[name] || - x == null || - y == null + isNil(x) || + isNil(y) ) { - if (callback) callback(); + callback?.(); return -1; } @@ -547,26 +539,25 @@ export function initFallback() { const dy = ey - sy; const fn = generateJumpFn(dx, dy); - - const list = adapters.layer?.items ?? []; - const items = [...list].filter(v => { - if (v.layer !== 'event') return false; - const ex = v.getExtends('floor-binder') as LayerFloorBinder; - if (!ex) return false; - return ex.getFloor() === core.status.floorId; - }); - const width = core.status.thisMap.width; - const index = sx + sy * width; - - const promise = Promise.all( - items.map(v => { - return v.moveAs(index, ex, ey, fn, time, keep); - }) + // 先使用 mainMapRenderer 妥协 + const { mainMapRenderer: renderer } = Mota.require( + '@user/client-modules' ); - - core.updateStatusBar(); + if (renderer.layerState !== state.layer) { + callback?.(); + return; + } + const layer = state.layer.getLayerByAlias('event'); + if (!layer) { + callback?.(); + return; + } core.removeBlock(sx, sy); - await promise; + const moving = renderer.addMovingBlock(layer, block.id, sx, sy); + core.updateStatusBar(); + await moving.moveRelative(fn, time); + moving.destroy(); + if (keep) { core.setBlock(block.id, ex, ey); } @@ -586,30 +577,15 @@ export function initFallback() { ) { if (heroMover.moving) return; - const sx = core.getHeroLoc('x'); - const sy = core.getHeroLoc('y'); adapters.viewport?.all('mutateTo', ex, ey, time); const locked = core.status.lockControl; core.lockControl(); - const list = adapters['hero-adapter']?.items ?? []; - const items = [...list]; time /= core.status.replay.speed; if (core.status.replay.speed === 24) time = 1; - const fn = generateJumpFn(ex - sx, ey - sy); - await Promise.all( - items.map(v => { - if (!v.renderable) return Promise.reject(); - return v.layer.moveRenderable( - v.renderable, - sx, - sy, - fn, - time - ); - }) - ); + + await state.hero.jumpHero(ex, ey, time); if (!locked) core.unlockControl(); core.setHeroLoc('x', ex);