diff --git a/src/core/index.ts b/src/core/index.ts index 881e0ee..e9f29f9 100644 --- a/src/core/index.ts +++ b/src/core/index.ts @@ -70,6 +70,7 @@ import { RenderItem } from './render/item'; import { texture } from './render/cache'; import { RenderAdapter } from './render/adapter'; import { getMainRenderer } from './render'; +import { Layer } from './render/preset/layer'; // ----- 类注册 Mota.register('class', 'AudioPlayer', AudioPlayer); @@ -156,7 +157,8 @@ Mota.register('module', 'Render', { Text, Image, RenderItem, - RenderAdapter + RenderAdapter, + Layer }); main.renderLoaded = true; diff --git a/src/core/render/preset/layer.ts b/src/core/render/preset/layer.ts index 7190805..61ae14d 100644 --- a/src/core/render/preset/layer.ts +++ b/src/core/render/preset/layer.ts @@ -9,6 +9,7 @@ import { glMatrix } from 'gl-matrix'; import { BlockCacher } from './block'; import { Transform } from '../transform'; import { LayerFloorBinder, LayerGroupFloorBinder } from './floor'; +import { RenderAdapter } from '../adapter'; export interface ILayerGroupRenderExtends { /** 拓展的唯一标识符 */ @@ -657,7 +658,7 @@ export class Layer extends Container { private extend: Map = new Map(); /** 正在移动的图块的渲染信息 */ - private moving: Set = new Set(); + moving: Set = new Set(); constructor() { super('absolute', false); @@ -694,6 +695,7 @@ export class Layer extends Container { }); this.extends(new LayerFloorBinder()); + layerAdapter.add(this); } /** @@ -1333,7 +1335,8 @@ export class Layer extends Container { /** * 让图块按照一个函数进行移动 * @param index 要移动的图块在渲染数据中的索引位置 - * @param type 函数式移动 + * @param x 目标位置横坐标 + * @param y 目标位置纵坐标 * @param fn 移动函数,传入一个完成度(范围0-1),返回一个三元素数组,表示横纵格子坐标,可以是小数。 * 第三个元素表示图块纵深,一般图块的纵深就是其纵坐标,当地图上有大怪物时,此举可以辅助渲染, * 否则可能会导致移动过程中与大怪物的层级关系不正确,比如全在大怪物身后。注意不建议频繁改动这个值, @@ -1352,24 +1355,9 @@ export class Layer extends Container { const block = this.renderData[index]; const fx = index % this.width; const fy = Math.floor(index / this.width); - const renderable = texture.getRenderable(block); - if (!renderable) return Promise.reject(); + const moving = Layer.getMovingRenderable(block, fx, fy); + if (!moving) return Promise.reject(); - const image = renderable.autotile - ? renderable.image[0] - : renderable.image; - - const moving: LayerMovingRenderable = { - x: fx, - y: fy, - zIndex: fy, - image: image, - autotile: false, - frame: renderable.frame, - bigImage: renderable.bigImage, - animate: -1, - render: renderable.render - }; this.moving.add(moving); // 删除原始位置的图块 @@ -1405,10 +1393,84 @@ export class Layer extends Container { }); } + /** + * 移动一个可移动的renderable + * @param data 移动renderable + * @param x 起始横坐标,注意与`moveAs`的`x`区分 + * @param y 起始纵坐标,注意与`moveAs`的`y`区分 + * @param fn 移动函数 + * @param time 移动时间 + * @param relative 是否是相对模式,默认相对模式 + */ + moveRenderable( + data: LayerMovingRenderable, + x: number, + y: number, + fn: TimingFn<3>, + time: number, + relative: boolean = true + ) { + let nowZ = y; + const startTime = Date.now(); + return new Promise(resolve => { + this.delegateTicker( + () => { + const now = Date.now(); + const progress = (now - startTime) / time; + const [nx, ny, nz] = fn(progress); + const tx = relative ? nx + x : nx; + const ty = relative ? ny + y : ny; + data.x = tx; + data.y = ty; + data.zIndex = nz; + if (nz !== nowZ) { + this.movingRenderable.sort( + (a, b) => a.zIndex - b.zIndex + ); + } + this.update(this); + }, + time, + () => { + resolve(); + } + ); + }); + } + destroy(): void { for (const ex of this.extend.values()) { ex.onDestroy?.(this); } super.destroy(); + layerAdapter.remove(this); + } + + /** + * 根据图块信息初始化移动信息 + * @param num 图块数字 + * @param x 横坐标 + * @param y 纵坐标 + */ + static getMovingRenderable(num: number, x: number, y: number) { + const renderable = texture.getRenderable(num); + if (!renderable) return null; + const image = renderable.autotile + ? renderable.image[0] + : renderable.image; + const moving: LayerMovingRenderable = { + x: x, + y: y, + zIndex: y, + image: image, + autotile: false, + frame: renderable.frame, + bigImage: renderable.bigImage, + animate: -1, + render: renderable.render + }; + return moving; } } + +const layerAdapter = new RenderAdapter('layer'); diff --git a/src/game/mechanism/misc.ts b/src/game/mechanism/misc.ts index 28e968f..91a6232 100644 --- a/src/game/mechanism/misc.ts +++ b/src/game/mechanism/misc.ts @@ -197,8 +197,6 @@ export namespace BluePalace { } function initPortals() { - // 主要是复写勇士绘制以及传送判定,还有自动寻路 generatePortalMap(); - console.log(portalMap); } } diff --git a/src/game/system.ts b/src/game/system.ts index a415f7b..7f132db 100644 --- a/src/game/system.ts +++ b/src/game/system.ts @@ -35,6 +35,7 @@ import type { Image, Text } from '@/core/render/preset/misc'; import type { RenderItem } from '@/core/render/item'; import type { RenderAdapter } from '@/core/render/adapter'; import type { ItemState } from './state/item'; +import type { Layer } from '@/core/render/preset/layer'; interface ClassInterface { // 渲染进程与游戏进程通用 @@ -114,6 +115,7 @@ interface ModuleInterface { Image: typeof Image; RenderItem: typeof RenderItem; RenderAdapter: typeof RenderAdapter; + Layer: typeof Layer; }; State: { ItemState: typeof ItemState; diff --git a/src/plugin/game/fallback.ts b/src/plugin/game/fallback.ts index 2372dbf..13addf8 100644 --- a/src/plugin/game/fallback.ts +++ b/src/plugin/game/fallback.ts @@ -1,14 +1,20 @@ import type { RenderAdapter } from '@/core/render/adapter'; import type { LayerGroupAnimate } from '@/core/render/preset/animate'; -import type { LayerDoorAnimate } from '@/core/render/preset/floor'; +import type { + LayerDoorAnimate, + LayerFloorBinder +} from '@/core/render/preset/floor'; import type { HeroRenderer } from '@/core/render/preset/hero'; -import type { LayerMovingRenderable } from '@/core/render/preset/layer'; +import type { Layer, LayerMovingRenderable } from '@/core/render/preset/layer'; import { BluePalace } from '@/game/mechanism/misc'; +import { backDir } from './utils'; +import type { TimingFn } from 'mutate-animate'; interface Adapters { 'hero-adapter'?: RenderAdapter; 'door-animate'?: RenderAdapter; animate?: RenderAdapter; + layer?: RenderAdapter; } const adapters: Adapters = {}; @@ -23,17 +29,19 @@ export function init() { const hero = Adapter.get('hero-adapter'); const doorAnimate = Adapter.get('door-animate'); const animate = Adapter.get('animate'); + const layer = Adapter.get('layer'); adapters['hero-adapter'] = hero; adapters['door-animate'] = doorAnimate; adapters['animate'] = animate; + adapters['layer'] = layer; } let moving: boolean = false; let stopChian: boolean = false; let moveDir: Dir; let stepDir: Dir; - let moveEnding: Promise; + let moveEnding: Promise = Promise.resolve([]); /** 传送门信息,下一步传送到哪 */ let portalData: BluePalace.PortalTo | undefined; @@ -288,11 +296,8 @@ export function init() { const originFrom = structuredClone(v.renderable.render); const originTo = structuredClone(renderable.render); + v.layer.moving.add(renderable); v.layer.requestUpdateMoving(); - const append = (r: LayerMovingRenderable[]) => { - r.push(renderable); - }; - v.on('append', append); v.on('moveTick', function func() { const progress = heroDir === 'left' || heroDir === 'right' @@ -301,7 +306,7 @@ export function init() { if (progress >= 1 || !portal) { v.renderable!.render = originFrom; v.off('moveTick', func); - v.off('append', append); + v.layer.moving.delete(renderable); v.layer.requestUpdateMoving(); return; } @@ -353,9 +358,79 @@ export function init() { }); } - // ----- 勇士移动相关 + // ----- 工具函数 + + /** + * 根据事件中给出的移动数组解析出全部的移动步骤 + */ + function getMoveSteps(steps: string[]) { + const moveSteps: string[] = []; + steps.forEach(v => { + const [type, number] = v.split(':'); + if (!number) moveSteps.push(type); + else { + if (type === 'speed') moveSteps.push(v); + else { + moveSteps.push(...Array(number).fill(type)); + } + } + }); + + const step0 = steps.find(v => !v.startsWith('speed')) as Dir2; + return { steps, start: step0 }; + } + + /** + * 移动一个LayerMovingRenderable + */ + function moveRenderable( + item: Layer, + data: LayerMovingRenderable, + time: number, + x: number, + y: number + ) { + const fx = data.x; + const fy = data.y; + const dx = x - fx; + const dy = y - fy; + const start = Date.now(); + return new Promise(res => { + item.delegateTicker( + () => { + const now = Date.now() - start; + const progress = now / time; + data.x = fx + dx * progress; + data.y = fy + dy * progress; + item.update(item); + }, + time, + () => { + data.x = x; + data.y = y; + res(); + } + ); + }); + } + + /** + * 生成跳跃函数 + */ + function generateJumpFn(dx: number, dy: number): TimingFn<3> { + const distance = Math.hypot(dx, dy); + const peak = 3 + distance; + const k = dy / dx; + + return (progress: number) => { + const x = dx * progress; + const y = k * x + (progress ** 2 - progress) * peak; + return [x, y, Math.ceil(y)]; + }; + } Mota.r(() => { + // ----- 勇士移动相关 control.prototype._moveAction_moving = function ( callback?: () => void ) {}; @@ -588,6 +663,157 @@ export function init() { callback?.(); }); }; + + // 移动跳跃图块 & 跳跃勇士 + maps.prototype.moveBlock = async function ( + x: number, + y: number, + steps: string[], + time: number = 500, + keep: boolean = false, + callback?: () => void + ) { + if (!steps || steps.length === 0) { + callback?.(); + return; + } + const speed = core.status.replay.speed; + if (speed === 24) time = 1; + const block = core.getBlock(x, y); + if (!block) { + callback?.(); + return; + } + core.removeBlock(x, y); + 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 { steps: moveSteps, start } = getMoveSteps(steps); + if (!start || items.length === 0) { + callback?.(); + return; + } + let stepDir: Dir2 = start; + let nx = x; + let ny = y; + + const { Layer } = Mota.require('module', 'Render'); + const moving = Layer.getMovingRenderable(block.id, x, y); + if (!moving) { + callback?.(); + return; + } + items.forEach(v => v.moving.add(moving)); + + for (const step of moveSteps) { + if (step === 'backward') stepDir = backDir(stepDir); + if (step.startsWith('speed')) { + time = parseInt(step.slice(6)); + continue; + } + const { x, y } = core.utils.scan2[stepDir]; + const tx = nx + x; + const ty = ny + y; + await moveRenderable(items[0], moving, time / speed, tx, ty); + nx = tx; + ny = ty; + moving.zIndex = ty; + } + + items.forEach(v => v.moving.delete(moving)); + if (keep) { + core.setBlock(block.id, nx, ny); + } + callback?.(); + }; + + ////// 显示跳跃某块的动画,达到{"type":"jump"}的效果 ////// + maps.prototype.jumpBlock = async function ( + sx: number, + sy: number, + ex: number, + ey: number, + time: number = 500, + keep: boolean = false, + callback?: () => void + ) { + const block = core.getBlock(sx, sy); + if (!block) { + callback?.(); + return; + } + time /= core.status.replay.speed; + if (core.status.replay.speed === 24) time = 1; + const dx = ex - sx; + 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); + }) + ); + + core.removeBlock(sx, sy); + await promise; + if (keep) { + core.setBlock(block.id, ex, ey); + } + + callback?.(); + }; + + events.prototype.jumpHero = async function ( + ex: number, + ey: number, + time: number = 500, + callback?: () => void + ) { + const sx = core.getHeroLoc('x'); + const sy = core.getHeroLoc('y'); + + 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 + ); + }) + ); + + if (!locked) core.unlockControl(); + core.setHeroLoc('x', ex); + core.setHeroLoc('y', ey); + callback?.(); + }; }); loading.once('loaded', () => { @@ -600,5 +826,35 @@ export function init() { } }); + // 复写录像的移动 + core.registerReplayAction('move', action => { + if ( + action === 'up' || + action === 'down' || + action === 'left' || + action === 'right' + ) { + stepDir = action; + const { noPass, canMove } = checkCanMove(); + const { nx, ny } = getNextLoc(); + if (noPass || !canMove) { + if (canMove) core.trigger(nx, ny); + } else { + core.setHeroLoc('x', nx); + core.setHeroLoc('y', ny); + core.setHeroLoc('direction', action); + } + if (!main.replayChecking) { + setTimeout(core.replay, 100); + } else { + core.replay(); + } + + return true; + } else { + return false; + } + }); + return { readyMove, endMove, move }; }