refactor: 勇士移动

This commit is contained in:
unanmed 2024-09-25 16:15:46 +08:00
parent b9ae439cd9
commit 3de6aac6a1
13 changed files with 856 additions and 118 deletions

View File

@ -72,6 +72,7 @@ import { RenderAdapter } from './render/adapter';
import { getMainRenderer } from './render'; import { getMainRenderer } from './render';
import { Layer } from './render/preset/layer'; import { Layer } from './render/preset/layer';
import { LayerGroupFloorBinder } from './render/preset/floor'; import { LayerGroupFloorBinder } from './render/preset/floor';
import { HeroKeyMover } from './main/action/move';
// ----- 类注册 // ----- 类注册
Mota.register('class', 'AudioPlayer', AudioPlayer); Mota.register('class', 'AudioPlayer', AudioPlayer);
@ -162,6 +163,9 @@ Mota.register('module', 'Render', {
Layer, Layer,
LayerGroupFloorBinder LayerGroupFloorBinder
}); });
Mota.register('module', 'Action', {
HeroKeyMover
});
main.renderLoaded = true; main.renderLoaded = true;
Mota.require('var', 'hook').emit('renderLoaded'); Mota.require('var', 'hook').emit('renderLoaded');

View File

@ -0,0 +1,159 @@
import { KeyCode } from '@/plugin/keyCodes';
import { Hotkey, HotkeyData } from '../custom/hotkey';
import type { HeroMover, IMoveController } from '@/game/state/move';
import { Ticker } from 'mutate-animate';
import { mainScope } from '../init/hotkey';
type MoveKey = Record<Dir, HotkeyData>;
type MoveKeyConfig = Record<Dir, string>;
export class HeroKeyMover {
/** 当前按下的键 */
private pressedKey: Set<Dir> = new Set();
/** 当前的移动方向 */
private moveDir: Dir = 'down';
/** 当前是否正在使用按键移动 */
private moving: boolean = false;
/** 当前移动的控制器 */
private controller?: IMoveController;
/** 按键接续ticker */
private ticker = new Ticker();
/** 当前移动实例绑定的热键 */
hotkey: Hotkey;
/** 当前热键的移动按键信息 */
hotkeyData: MoveKey;
/** 移动实例 */
mover: HeroMover;
/** 移动可触发的作用域 */
scope: symbol = mainScope;
constructor(hotkey: Hotkey, mover: HeroMover, config?: MoveKeyConfig) {
this.hotkey = hotkey;
this.mover = mover;
hotkey.on('press', this.onPressKey);
hotkey.on('release', this.onReleaseKey);
const data = hotkey.data;
this.hotkeyData = {
left: data[config?.left ?? 'moveLeft'],
right: data[config?.right ?? 'moveRight'],
up: data[config?.up ?? 'moveUp'],
down: data[config?.down ?? 'moveDown']
};
this.ticker.add(() => {
if (!this.moving) {
if (this.pressedKey.size > 0) {
const dir = [...this.pressedKey].at(-1);
if (!dir) return;
this.moveDir = dir;
this.tryStartMove();
}
}
});
}
private onPressKey = (code: KeyCode) => {
if (code === this.hotkeyData.left.key) this.press('left');
else if (code === this.hotkeyData.right.key) this.press('right');
else if (code === this.hotkeyData.up.key) this.press('up');
else if (code === this.hotkeyData.down.key) this.press('down');
};
private onReleaseKey = (code: KeyCode) => {
if (code === this.hotkeyData.left.key) this.release('left');
else if (code === this.hotkeyData.right.key) this.release('right');
else if (code === this.hotkeyData.up.key) this.release('up');
else if (code === this.hotkeyData.down.key) this.release('down');
};
/**
*
*/
setScope(scope: symbol) {
this.scope = scope;
}
/**
*
* @param dir
*/
press(dir: Dir) {
if (this.hotkey.scope !== this.scope || core.status.lockControl) return;
this.pressedKey.add(dir);
this.moveDir = dir;
if (!this.moving) {
this.tryStartMove();
}
}
/**
*
* @param dir
*/
release(dir: Dir) {
this.pressedKey.delete(dir);
if (this.pressedKey.size > 0) {
this.moveDir = [...this.pressedKey][0];
} else {
this.endMove();
}
}
/**
*
* @returns
*/
tryStartMove() {
if (this.moving || core.status.lockControl) return false;
this.mover.oneStep(this.moveDir);
const controller = this.mover.startMove();
if (!controller) return;
this.controller = controller;
controller.onEnd.then(() => {
this.moving = false;
this.controller = void 0;
this.mover.off('stepEnd', this.onStepEnd);
});
this.moving = true;
this.mover.on('stepEnd', this.onStepEnd);
return true;
}
/**
*
*/
endMove() {
this.controller?.stop();
}
private onStepEnd = () => {
const con = this.controller;
if (!con) return;
if (!this.moving) {
con.stop();
return;
}
if (this.pressedKey.size > 0) {
if (con.queue.length === 0) {
con.push({ type: 'dir', value: this.moveDir });
}
} else {
con.stop();
}
};
destroy() {
this.hotkey.off('press', this.onPressKey);
this.hotkey.off('release', this.onReleaseKey);
this.mover.off('stepEnd', this.onStepEnd);
this.ticker.destroy();
}
}

View File

@ -251,7 +251,7 @@ export class Danmaku extends EventEmitter<DanmakuEvent> {
} else { } else {
logger.error( logger.error(
8, 8,
`Post not allowed css danmaku. Allow info: ${allow.join(',')}` `Post danmaku with not allowed css. Info: ${allow.join(',')}`
); );
} }
} }

View File

@ -34,7 +34,7 @@ interface RegisterHotkeyData extends Partial<AssistHoykey> {
defaults: KeyCode; defaults: KeyCode;
} }
interface HotkeyData extends Required<RegisterHotkeyData> { export interface HotkeyData extends Required<RegisterHotkeyData> {
key: KeyCode; key: KeyCode;
emits: Map<symbol, HotkeyEmitData>; emits: Map<symbol, HotkeyEmitData>;
group?: string; group?: string;

View File

@ -5,10 +5,10 @@ import { logger } from '@/core/common/logger';
import EventEmitter from 'eventemitter3'; import EventEmitter from 'eventemitter3';
import { texture } from '../cache'; import { texture } from '../cache';
import { TimingFn } from 'mutate-animate'; import { TimingFn } from 'mutate-animate';
import { backDir } from '@/plugin/game/utils';
import { isNil } from 'lodash-es'; import { isNil } from 'lodash-es';
type HeroMovingStatus = 'stop' | 'moving' | 'moving-as'; // type HeroMovingStatus = 'stop' | 'moving' | 'moving-as';
// export const enum HeroMovingStatus {}
interface HeroRenderEvent { interface HeroRenderEvent {
stepEnd: []; stepEnd: [];
@ -31,25 +31,27 @@ export class HeroRenderer
renderable?: LayerMovingRenderable; renderable?: LayerMovingRenderable;
layer!: Layer; layer!: Layer;
// hero?: Hero;
/** 勇士移动状态 */
status: HeroMovingStatus = 'stop';
/** 当前移动帧数 */ /** 当前移动帧数 */
movingFrame: number = 0; movingFrame: number = 0;
/** 是否正在移动 */
moving: boolean = false;
/** 是否正在播放动画,与移动分开以实现原地踏步 */
animate: boolean = false;
/** 勇士移动速度 */ /** 勇士移动速度 */
speed: number = 100; speed: number = 100;
/** 当前勇士朝向 */
// todo: 删了这个属性 /** 当前的移动方向 */
dir: Dir = 'down'; moveDir: Dir2 = 'down';
/** 当前移动的勇士显示方向 */
showDir: Dir = 'down';
/** 帧动画是否反向播放例如后退时就应该设为true */
animateReverse: boolean = false;
/** 勇士移动定时器id */ /** 勇士移动定时器id */
private moveId: number = -1; private moveId: number = -1;
/** 上一次帧数切换的时间 */ /** 上一次帧数切换的时间 */
private lastFrameTime: number = 0; private lastFrameTime: number = 0;
/** 当前的移动方向 */
moveDir: Move2 = 'down';
/** 上一步走到格子上的时间 */ /** 上一步走到格子上的时间 */
private lastStepTime: number = 0; private lastStepTime: number = 0;
/** 执行当前步移动的Promise */ /** 执行当前步移动的Promise */
@ -63,8 +65,6 @@ export class HeroRenderer
stepDir: Dir2 = 'down'; stepDir: Dir2 = 'down';
/** 每步的格子增量 */ /** 每步的格子增量 */
private stepDelta: Loc = { x: 0, y: 1 }; private stepDelta: Loc = { x: 0, y: 1 };
/** 动画显示的方向,用于适配后退 */
// private animateDir: Dir = 'down';
/** /**
* *
@ -76,6 +76,10 @@ export class HeroRenderer
this.layer.update(this.layer); this.layer.update(this.layer);
} }
/**
*
* @param speed
*/
setMoveSpeed(speed: number) { setMoveSpeed(speed: number) {
this.speed = speed; this.speed = speed;
} }
@ -102,7 +106,7 @@ export class HeroRenderer
zIndex: core.status.hero.loc.y, zIndex: core.status.hero.loc.y,
autotile: false, autotile: false,
bigImage: true, bigImage: true,
render: this.getRenderFromDir(this.moveDir), render: this.getRenderFromDir(this.showDir),
animate: 0 animate: 0
}; };
} }
@ -111,53 +115,90 @@ export class HeroRenderer
* *
* @param dir * @param dir
*/ */
getRenderFromDir(dir: Move2): [number, number, number, number][] { getRenderFromDir(dir: Dir): [number, number, number, number][] {
if (!this.cellWidth || !this.cellHeight) return []; if (!this.cellWidth || !this.cellHeight) return [];
let resolved: Dir2; const index = texture.characterDirection[dir];
if (dir === 'forward') resolved = this.dir;
else if (dir === 'backward') resolved = backDir(this.dir);
else resolved = dir;
const index = texture.characterDirection[resolved];
const y = index * this.cellHeight; const y = index * this.cellHeight;
const res: [number, number, number, number][] = [0, 1, 2, 3].map(v => { const res: [number, number, number, number][] = [0, 1, 2, 3].map(v => {
return [v * this.cellWidth!, y, this.cellWidth!, this.cellHeight!]; return [v * this.cellWidth!, y, this.cellWidth!, this.cellHeight!];
}); });
if (dir === 'backward') return res.reverse(); if (this.animateReverse) return res.reverse();
else return res; else return res;
} }
/** /**
* *
* @param hero
*/ */
// setHero(hero: Hero) { startAnimate() {
// this.hero = hero; this.animate = true;
// } this.lastFrameTime = Date.now();
}
/** /**
* *
*/ */
readyMove() { endAnimate() {
if (this.status !== 'stop') return; this.animate = false;
this.status = 'moving'; this.resetRenderable(false);
this.lastFrameTime = Date.now(); }
/**
*
* @param reverse
*/
setAnimateReversed(reverse: boolean) {
this.animateReverse = reverse;
this.resetRenderable(true);
}
/**
*
* @param dir
*/
setAnimateDir(dir: Dir) {
if (dir !== this.showDir) {
this.showDir = dir;
this.resetRenderable(true);
}
}
/**
* renderable状态
* @param getInfo
*/
resetRenderable(getInfo: boolean) {
this.movingFrame = 0;
console.trace();
if (this.renderable) {
this.renderable.animate = 0;
if (getInfo) {
this.renderable.render = this.getRenderFromDir(this.showDir);
}
}
this.layer.update(this.layer);
}
/**
*
*/
private animateTick(time: number) {
if (!this.animate) return;
if (time - this.lastFrameTime > this.speed) {
this.lastFrameTime = time;
this.movingFrame++;
this.movingFrame %= 4;
if (this.renderable) this.renderable.animate = this.movingFrame;
}
this.layer.update(this.layer);
} }
/** /**
* *
*/ */
private moveTick(time: number) { private moveTick(time: number) {
if (this.status !== 'moving') return; if (!this.moving) return;
if (!this.renderable) { if (!this.renderable) return;
this.emit('stepEnd');
return;
}
if (time - this.lastFrameTime > this.speed) {
this.lastFrameTime = time;
this.movingFrame++;
this.movingFrame %= 4;
}
const progress = (time - this.lastStepTime) / this.speed; const progress = (time - this.lastStepTime) / this.speed;
@ -174,7 +215,6 @@ export class HeroRenderer
this.renderable.y = ry; this.renderable.y = ry;
} }
this.emit('moveTick', this.renderable.x, this.renderable.y); this.emit('moveTick', this.renderable.x, this.renderable.y);
this.renderable.animate = this.movingFrame;
this.layer.update(this.layer); this.layer.update(this.layer);
} }
@ -182,18 +222,23 @@ export class HeroRenderer
* *
*/ */
private step() { private step() {
if (this.moveDir === 'backward') this.stepDir = backDir(this.stepDir);
else if (this.moveDir !== 'forward') this.stepDir = this.moveDir;
this.lastStepTime = Date.now(); this.lastStepTime = Date.now();
this.stepDelta = core.utils.scan2[this.stepDir]; this.stepDelta = core.utils.scan2[this.stepDir];
this.turn(this.stepDir); this.turn(this.stepDir);
} }
/**
*
*/
readyMove() {
this.moving = true;
}
/** /**
* *
*/ */
move(dir: Move2): Promise<void> { move(dir: Dir2): Promise<void> {
if (this.status !== 'moving') { if (!this.moving) {
logger.error( logger.error(
12, 12,
`Cannot move while status is not 'moving'. Call 'readyMove' first.` `Cannot move while status is not 'moving'. Call 'readyMove' first.`
@ -202,16 +247,16 @@ export class HeroRenderer
} }
this.moveDir = dir; this.moveDir = dir;
if (this.moveDetached) return this.moveDetached; if (this.moveDetached) return this.moveDetached;
else { else {
this.step(); this.step();
return (this.moveDetached = new Promise<void>(resolve => { this.moveDetached = new Promise(res => {
this.once('stepEnd', () => { this.once('stepEnd', () => {
this.moveDetached = void 0; this.moveDetached = void 0;
resolve(); res();
}); });
})); });
return this.moveDetached;
} }
} }
@ -219,14 +264,13 @@ export class HeroRenderer
* *
*/ */
endMove(): Promise<void> { endMove(): Promise<void> {
if (this.status !== 'moving') return Promise.reject(); if (!this.moving) return Promise.reject();
if (this.moveEnding) return this.moveEnding; if (this.moveEnding) return this.moveEnding;
else { else {
const promise = new Promise<void>(resolve => { const promise = new Promise<void>(resolve => {
this.once('stepEnd', () => { this.once('stepEnd', () => {
this.moveEnding = void 0; this.moveEnding = void 0;
this.status = 'stop'; this.moving = false;
this.movingFrame = 0;
const { x, y } = core.status.hero.loc; const { x, y } = core.status.hero.loc;
this.setHeroLoc(x, y); this.setHeroLoc(x, y);
this.render(); this.render();
@ -259,7 +303,7 @@ export class HeroRenderer
this.stepDir = dir; this.stepDir = dir;
if (!this.renderable) return; if (!this.renderable) return;
this.renderable.render = this.getRenderFromDir(this.moveDir); this.renderable.render = this.getRenderFromDir(this.showDir);
this.layer.update(this.layer); this.layer.update(this.layer);
} }
@ -291,9 +335,8 @@ export class HeroRenderer
* *
*/ */
moveAs(x: number, y: number, time: number, fn: TimingFn<3>): Promise<void> { moveAs(x: number, y: number, time: number, fn: TimingFn<3>): Promise<void> {
if (this.status !== 'stop') return Promise.reject(); if (!this.moving) return Promise.reject();
if (!this.renderable) return Promise.reject(); if (!this.renderable) return Promise.reject();
this.status = 'moving-as';
let nowZIndex = fn(0)[2]; let nowZIndex = fn(0)[2];
let startTime = Date.now(); let startTime = Date.now();
return new Promise(res => { return new Promise(res => {
@ -307,18 +350,15 @@ export class HeroRenderer
this.renderable.y = ny; this.renderable.y = ny;
this.renderable.zIndex = nz; this.renderable.zIndex = nz;
if (nz !== nowZIndex) { if (nz !== nowZIndex) {
this.layer.movingRenderable.sort( this.layer.sortMovingRenderable();
(a, b) => a.zIndex - b.zIndex
);
} }
this.emit('moveTick', this.renderable.x, this.renderable.y); this.emit('moveTick', this.renderable.x, this.renderable.y);
this.layer.update(this.layer); this.layer.update(this.layer);
}, },
time, time,
() => { () => {
this.status = 'stop'; this.moving = false;
if (!this.renderable) return res(); if (!this.renderable) return res();
this.renderable.animate = 0;
this.renderable.x = x; this.renderable.x = x;
this.renderable.y = y; this.renderable.y = y;
this.emit('moveTick', this.renderable.x, this.renderable.y); this.emit('moveTick', this.renderable.x, this.renderable.y);
@ -334,7 +374,7 @@ export class HeroRenderer
*/ */
render() { render() {
if (!this.renderable) return; if (!this.renderable) return;
if (this.status === 'stop') { if (!this.animate) {
this.renderable.animate = -1; this.renderable.animate = -1;
} else { } else {
this.renderable.animate = this.movingFrame; this.renderable.animate = this.movingFrame;
@ -346,7 +386,9 @@ export class HeroRenderer
this.layer = layer; this.layer = layer;
adapter.add(this); adapter.add(this);
this.moveId = layer.delegateTicker(() => { this.moveId = layer.delegateTicker(() => {
this.moveTick(Date.now()); const time = Date.now();
this.animateTick(time);
this.moveTick(time);
}); });
} }
@ -380,10 +422,6 @@ adapter.receive(
return item.moveAs(x, y, time, fn); return item.moveAs(x, y, time, fn);
} }
); );
adapter.receive('setMoveSpeed', (item, speed: number) => {
item.setMoveSpeed(speed);
return Promise.resolve();
});
adapter.receive('setHeroLoc', (item, x?: number, y?: number) => { adapter.receive('setHeroLoc', (item, x?: number, y?: number) => {
item.setHeroLoc(x, y); item.setHeroLoc(x, y);
return Promise.resolve(); return Promise.resolve();
@ -392,11 +430,28 @@ adapter.receive('turn', (item, dir: Dir2) => {
item.turn(dir); item.turn(dir);
return Promise.resolve(); return Promise.resolve();
}); });
adapter.receive('setImage', (item, image: SizedCanvasImageSource) => {
// 同步适配函数,这些函数用于同步设置信息等
adapter.receiveSync('setImage', (item, image: SizedCanvasImageSource) => {
item.setImage(image); item.setImage(image);
return Promise.resolve();
}); });
// 同步fallback用于适配现在的样板之后会删除 adapter.receiveSync('setMoveSpeed', (item, speed: number) => {
item.setMoveSpeed(speed);
});
adapter.receiveSync('setAnimateReversed', (item, reverse: boolean) => {
item.setAnimateReversed(reverse);
});
adapter.receiveSync('startAnimate', item => {
item.startAnimate();
});
adapter.receiveSync('endAnimate', item => {
item.endAnimate();
});
adapter.receiveSync('setAnimateDir', (item, dir: Dir) => {
item.setAnimateDir(dir);
});
// 不分同步fallback用于适配现在的样板之后会删除
adapter.receiveSync('setHeroLoc', (item, x?: number, y?: number) => { adapter.receiveSync('setHeroLoc', (item, x?: number, y?: number) => {
item.setHeroLoc(x, y); item.setHeroLoc(x, y);
}); });

View File

@ -1123,6 +1123,13 @@ export class Layer extends Container {
for (const ex of this.extend.values()) { for (const ex of this.extend.values()) {
ex.onMovingUpdate?.(this, this.movingRenderable); ex.onMovingUpdate?.(this, this.movingRenderable);
} }
this.sortMovingRenderable();
}
/**
* z坐标排序
*/
sortMovingRenderable() {
this.movingRenderable.sort((a, b) => a.zIndex - b.zIndex); this.movingRenderable.sort((a, b) => a.zIndex - b.zIndex);
} }
@ -1277,7 +1284,7 @@ export class Layer extends Container {
* / * /
*/ */
protected renderMoving(transform: Transform) { protected renderMoving(transform: Transform) {
const frame = (RenderItem.animatedFrame % 4) + 1; const frame = RenderItem.animatedFrame;
const cell = this.cellSize; const cell = this.cellSize;
const halfCell = cell / 2; const halfCell = cell / 2;
const { ctx } = this.movingMap; const { ctx } = this.movingMap;

View File

@ -11,6 +11,7 @@ import * as miscMechanism from './mechanism/misc';
import * as study from './mechanism/study'; import * as study from './mechanism/study';
import { registerPresetState } from './state/preset'; import { registerPresetState } from './state/preset';
import { ItemState } from './state/item'; import { ItemState } from './state/item';
import { HeroMover, ObjectMoverBase } from './state/move';
// ----- 类注册 // ----- 类注册
Mota.register('class', 'DamageEnemy', damage.DamageEnemy); Mota.register('class', 'DamageEnemy', damage.DamageEnemy);
@ -34,7 +35,9 @@ Mota.register('module', 'Mechanism', {
Study: study Study: study
}); });
Mota.register('module', 'State', { Mota.register('module', 'State', {
ItemState ItemState,
HeroMover,
ObjectMoverBase
}); });
main.loading = loading; main.loading = loading;

397
src/game/state/move.ts Normal file
View File

@ -0,0 +1,397 @@
import EventEmitter from 'eventemitter3';
import { backDir, toDir } from './utils';
import { loading } from '../game';
import type { RenderAdapter } from '@/core/render/adapter';
import type { HeroRenderer } from '@/core/render/preset/hero';
import type { FloorViewport } from '@/core/render/preset/viewport';
import type { KeyCode } from '@/plugin/keyCodes';
interface MoveStepDir {
type: 'dir';
value: Move2;
}
interface MoveStepSpeed {
type: 'speed';
value: number;
}
type MoveStep = MoveStepDir | MoveStepSpeed;
export interface IMoveController {
/** 本次移动是否完成 */
done: boolean;
/** 当前移动队列 */
queue: MoveStep[];
/** 当本次移动完成时兑现 */
onEnd: Promise<void>;
/**
*
*/
stop(): Promise<void>;
/**
*
* @param step
*/
push(...step: MoveStep[]): void;
}
interface EObjectMovingEvent {
stepEnd: [step: MoveStep];
moveEnd: [];
moveStart: [queue: MoveStep[]];
}
export abstract class ObjectMoverBase extends EventEmitter<EObjectMovingEvent> {
/** 移动队列 */
private moveQueue: MoveStep[] = [];
/** 当前的移动速度 */
moveSpeed: number = 100;
/** 当前的移动方向 */
moveDir: Dir2 = 'down';
/** 当前是否正在移动 */
moving: boolean = false;
/**
*
* @param controller
*/
protected abstract onMoveStart(controller: IMoveController): Promise<void>;
/**
*
* @param controller
*/
protected abstract onMoveEnd(controller: IMoveController): Promise<void>;
/**
*
* @param step
* @param controller
* @returns Promise{@link onStepEnd}
*/
protected abstract onStepStart(
step: MoveStepDir,
controller: IMoveController
): Promise<number>;
/**
* {@link onStepStart}
* @param step
* @param code onMoveStart的返回值
* @param controller
* @returns Promise
*/
protected abstract onStepEnd(
step: MoveStepDir,
code: number,
controller: IMoveController
): Promise<void>;
private getMoveDir(move: Move2): Dir2 {
if (move === 'forward') return this.moveDir;
if (move === 'backward') return backDir(this.moveDir);
return move;
}
/**
* {@link insertMove}
* {@link IMoveController.push}
*/
startMove(): IMoveController | null {
if (this.moving) return null;
const queue = this.moveQueue.slice();
this.clearMoveQueue();
this.moving = true;
let stopMove = false;
const start = async () => {
// 等待宏任务执行完成不然controller会在首次调用中未定义
await new Promise<void>(res => res());
this.emit('moveStart', queue);
await this.onMoveStart(controller);
while (queue.length > 0) {
if (stopMove || !this.moving) break;
const step = queue.shift();
if (!step) break;
if (step.type === 'dir') {
this.moveDir = this.getMoveDir(step.value);
const code = await this.onStepStart(step, controller);
await this.onStepEnd(step, code, controller);
} else {
this.moveSpeed = step.value;
}
this.emit('stepEnd', step);
}
this.moving = false;
this.emit('moveEnd');
await this.onMoveEnd(controller);
};
const onEnd = start();
const controller: IMoveController = {
done: false,
onEnd,
queue,
push(...step) {
queue.push(...step);
},
stop() {
stopMove = true;
return onEnd;
}
};
return controller;
}
/**
*
* @param move
*/
insertMove(...move: MoveStep[]) {
this.moveQueue.push(...move);
}
/**
*
*/
clearMoveQueue() {
this.moveQueue = [];
}
/**
*
* @param step
*/
oneStep(step: Move2) {
this.moveQueue.push({ type: 'dir', value: step });
}
/**
*
* @param steps
*/
moveAs(steps: MoveStep[]) {
this.moveQueue.push(...steps);
}
}
interface CanMoveStatus {
/** 由CannotIn和CannotOut计算出的信息不可移动时不会触发触发器 */
canMove: boolean;
/** 由图块的noPass计算出的信息不可移动时会触发触发器 */
noPass: boolean;
}
const enum HeroMoveCode {
Step,
Stop,
/** 不能移动,并撞击前面一格的图块,触发其触发器 */
Hit,
/** 不能移动同时当前格有CannotOut或目标格有CannotIn不会触发前面一格的触发器 */
CannotMove
}
export class HeroMover extends ObjectMoverBase {
/** 勇士渲染适配器,用于等待动画等操作 */
static adapter?: RenderAdapter<HeroRenderer>;
/** 视角适配器 */
static viewport?: RenderAdapter<FloorViewport>;
/** 当前移动是否忽略地形 */
private ignoreTerrain: boolean = false;
/** 当前移动是否不计入录像 */
private noRoute: boolean = false;
/** 当前移动是否是在lockControl条件下开始的 */
private inLockControl: boolean = false;
override startMove(
ignoreTerrain: boolean = false,
noRoute: boolean = false,
inLockControl: boolean = false
): IMoveController | null {
this.ignoreTerrain = ignoreTerrain;
this.noRoute = noRoute;
this.inLockControl = inLockControl;
return super.startMove();
}
protected async onMoveStart(controller: IMoveController): Promise<void> {
const adapter = HeroMover.adapter;
if (!adapter) return;
await adapter.all('readyMove');
adapter.sync('startAnimate');
}
protected async onMoveEnd(controller: IMoveController): Promise<void> {
const adapter = HeroMover.adapter;
if (!adapter) return;
await adapter.all('endMove');
adapter.sync('endAnimate');
}
protected async onStepStart(
step: MoveStepDir,
controller: IMoveController
): Promise<HeroMoveCode> {
const showDir = toDir(this.moveDir);
core.setHeroLoc('direction', showDir);
const adapter = HeroMover.adapter;
adapter?.sync('setAnimateDir', showDir);
const { x, y } = core.status.hero.loc;
const { x: nx, y: ny } = this.nextLoc(x, y, this.moveDir);
if (!this.inLockControl && core.status.lockControl) {
controller.stop();
return HeroMoveCode.Stop;
}
if (!this.ignoreTerrain || !this.noRoute) {
this.moveDir = showDir;
}
const dir = this.moveDir;
if (!this.ignoreTerrain) {
const { noPass, canMove } = this.checkCanMove(x, y, showDir);
// 不能移动
if (noPass || !canMove) {
if (!canMove) return HeroMoveCode.CannotMove;
else return HeroMoveCode.Hit;
}
}
// 可以移动,显示移动动画
await this.moveAnimate(nx, ny, showDir, dir);
return HeroMoveCode.Step;
}
protected async onStepEnd(
step: MoveStepDir,
code: HeroMoveCode,
controller: IMoveController
): Promise<void> {
const { x, y } = core.status.hero.loc;
const { x: nx, y: ny } = this.nextLoc(x, y, this.moveDir);
const showDir = toDir(this.moveDir);
// 前方不能移动
if (code === HeroMoveCode.CannotMove || code === HeroMoveCode.Hit) {
controller.stop();
this.onCannotMove(showDir);
if (code === HeroMoveCode.Hit) {
core.trigger(nx, ny);
}
return;
}
// 本次移动停止
if (code === HeroMoveCode.Stop) {
controller.stop();
return;
}
// 本次移动正常完成
if (code === HeroMoveCode.Step) {
core.setHeroLoc('x', nx, true);
core.setHeroLoc('y', ny, true);
const direction = core.getHeroLoc('direction');
core.control._moveAction_popAutomaticRoute();
if (!this.noRoute) core.status.route.push(direction);
core.moveOneStep();
core.checkRouteFolding();
}
}
/**
*
* @param x
* @param y
* @param showDir
* @param moveDir
*/
private async moveAnimate(
x: number,
y: number,
showDir: Dir,
moveDir: Dir2
) {
const adapter = HeroMover.adapter;
const viewport = HeroMover.viewport;
if (!adapter || !viewport) return;
viewport.all('moveTo', x, y);
await adapter.all('move', moveDir);
}
/**
*
* @param dir
*/
private onCannotMove(dir: Dir) {
if (!this.noRoute) core.status.route.push(dir);
core.status.automaticRoute.moveStepBeforeStop = [];
core.status.automaticRoute.lastDirection = dir;
if (core.status.automaticRoute.moveStepBeforeStop.length == 0) {
core.clearContinueAutomaticRoute();
core.stopAutomaticRoute();
}
}
/**
*
* @param x
* @param y
* @param dir
*/
private checkCanMove(x: number, y: number, dir: Dir): CanMoveStatus {
const { x: nx, y: ny } = this.nextLoc(x, y, dir);
const noPass = core.noPass(nx, ny);
const canMove = core.canMoveHero(x, y, dir);
return { noPass, canMove };
}
/**
*
* @param x
* @param y
* @param dir
*/
private nextLoc(x: number, y: number, dir: Dir2): Loc {
const { x: dx, y: dy } = core.utils.scan2[dir];
const nx = x + dx;
const ny = y + dy;
return { x: nx, y: ny };
}
}
// 初始化基础勇士移动实例
export const heroMover = new HeroMover();
loading.once('coreInit', () => {
// 注册按键操作
Mota.r(() => {
const { HeroKeyMover } = Mota.require('module', 'Action');
const gameKey = Mota.require('var', 'gameKey');
const keyMover = new HeroKeyMover(gameKey, heroMover);
});
});
// Adapter初始化
loading.once('coreInit', () => {
if (main.replayChecking || main.mode === 'editor') return;
const Adapter = Mota.require('module', 'Render').RenderAdapter;
const adapter = Adapter.get<HeroRenderer>('hero-adapter');
const viewport = Adapter.get<FloorViewport>('viewport');
HeroMover.adapter = adapter;
HeroMover.viewport = viewport;
});

35
src/game/state/utils.ts Normal file
View File

@ -0,0 +1,35 @@
const dirMap: Record<Dir2, Dir> = {
down: 'down',
left: 'left',
leftdown: 'left',
leftup: 'left',
right: 'right',
rightdown: 'right',
rightup: 'right',
up: 'up'
};
/**
*
* @param dir
*/
export function toDir(dir: Dir2): Dir {
return dirMap[dir];
}
const backDirMap: Record<Dir2, Dir2> = {
up: 'down',
down: 'up',
left: 'right',
right: 'left',
leftup: 'rightdown',
rightup: 'leftdown',
leftdown: 'rightup',
rightdown: 'leftup'
};
export function backDir(dir: Dir): Dir;
export function backDir(dir: Dir2): Dir2;
export function backDir(dir: Dir2): Dir2 {
return backDirMap[dir];
}

View File

@ -37,6 +37,8 @@ import type { RenderAdapter } from '@/core/render/adapter';
import type { ItemState } from './state/item'; import type { ItemState } from './state/item';
import type { Layer } from '@/core/render/preset/layer'; import type { Layer } from '@/core/render/preset/layer';
import type { LayerGroupFloorBinder } from '@/core/render/preset/floor'; import type { LayerGroupFloorBinder } from '@/core/render/preset/floor';
import type { HeroKeyMover } from '@/core/main/action/move';
import type { HeroMover, ObjectMoverBase } from './state/move';
interface ClassInterface { interface ClassInterface {
// 渲染进程与游戏进程通用 // 渲染进程与游戏进程通用
@ -121,6 +123,11 @@ interface ModuleInterface {
}; };
State: { State: {
ItemState: typeof ItemState; ItemState: typeof ItemState;
HeroMover: typeof HeroMover;
ObjectMoverBase: typeof ObjectMoverBase;
};
Action: {
HeroKeyMover: typeof HeroKeyMover;
}; };
} }

View File

@ -50,7 +50,7 @@ export function init() {
} }
let moving: boolean = false; let moving: boolean = false;
let stopChian: boolean = false; let stopChain: boolean = false;
let moveDir: Dir; let moveDir: Dir;
let stepDir: Dir; let stepDir: Dir;
let moveEnding: Promise<any[]> = Promise.resolve([]); let moveEnding: Promise<any[]> = Promise.resolve([]);
@ -65,24 +65,24 @@ export function init() {
let portal: boolean = false; let portal: boolean = false;
const pressedArrow: Set<Dir> = new Set(); const pressedArrow: Set<Dir> = new Set();
Mota.r(() => { // Mota.r(() => {
const gameKey = Mota.require('var', 'gameKey'); // const gameKey = Mota.require('var', 'gameKey');
const { moveUp, moveDown, moveLeft, moveRight } = gameKey.data; // const { moveUp, moveDown, moveLeft, moveRight } = gameKey.data;
const symbol = Symbol.for('@key_main'); // const symbol = Symbol.for('@key_main');
gameKey.on('press', code => { // gameKey.on('press', code => {
if (core.status.lockControl || gameKey.scope !== symbol) return; // if (core.status.lockControl || gameKey.scope !== symbol) return;
if (code === moveUp.key) onMoveKeyDown('up'); // if (code === moveUp.key) onMoveKeyDown('up');
if (code === moveDown.key) onMoveKeyDown('down'); // if (code === moveDown.key) onMoveKeyDown('down');
if (code === moveLeft.key) onMoveKeyDown('left'); // if (code === moveLeft.key) onMoveKeyDown('left');
if (code === moveRight.key) onMoveKeyDown('right'); // if (code === moveRight.key) onMoveKeyDown('right');
}); // });
gameKey.on('release', code => { // gameKey.on('release', code => {
if (code === moveUp.key) onMoveKeyUp('up'); // if (code === moveUp.key) onMoveKeyUp('up');
if (code === moveDown.key) onMoveKeyUp('down'); // if (code === moveDown.key) onMoveKeyUp('down');
if (code === moveLeft.key) onMoveKeyUp('left'); // if (code === moveLeft.key) onMoveKeyUp('left');
if (code === moveRight.key) onMoveKeyUp('right'); // if (code === moveRight.key) onMoveKeyUp('right');
}); // });
}); // });
function onMoveKeyDown(type: Dir) { function onMoveKeyDown(type: Dir) {
pressedArrow.add(type); pressedArrow.add(type);
@ -91,15 +91,15 @@ export function init() {
stepDir = moveDir; stepDir = moveDir;
readyMove(); readyMove();
} }
if (moving && stopChian) { if (moving && stopChain) {
stopChian = false; stopChain = false;
} }
} }
function onMoveKeyUp(type: Dir) { function onMoveKeyUp(type: Dir) {
pressedArrow.delete(type); pressedArrow.delete(type);
if (pressedArrow.size === 0) { if (pressedArrow.size === 0) {
stopChian = true; stopChain = true;
} else { } else {
const arr = [...pressedArrow]; const arr = [...pressedArrow];
moveDir = arr[0]; moveDir = arr[0];
@ -127,7 +127,7 @@ export function init() {
} }
await adapter.all('readyMove'); await adapter.all('readyMove');
moving = true; moving = true;
stopChian = false; stopChain = false;
startHeroMoveChain(ignoreTerrain, noRoute, inLockControl, callback); startHeroMoveChain(ignoreTerrain, noRoute, inLockControl, callback);
} }
@ -143,7 +143,7 @@ export function init() {
return Promise.resolve(); return Promise.resolve();
} else { } else {
if (moving) { if (moving) {
stopChian = true; stopChain = true;
moveEnding = adapter.all('endMove'); moveEnding = adapter.all('endMove');
await moveEnding; await moveEnding;
moveEmit.emit('moveEnd'); moveEmit.emit('moveEnd');
@ -161,7 +161,9 @@ export function init() {
function continueAfterEnd() { function continueAfterEnd() {
requestAnimationFrame(() => { requestAnimationFrame(() => {
if (pressedArrow.size === 0 || moving) { if (pressedArrow.size === 0 || moving) {
stopChian = true; // console.log(6);
stopChain = true;
return; return;
} }
if (checkCanMoveStatus()) { if (checkCanMoveStatus()) {
@ -185,8 +187,8 @@ export function init() {
if (!adapter) { if (!adapter) {
return Promise.resolve(); return Promise.resolve();
} else { } else {
while (moving || !stopChian) { while (moving || !stopChain) {
if (stopChian || (!inLockControl && core.status.lockControl)) { if (stopChain || (!inLockControl && core.status.lockControl)) {
break; break;
} }
@ -215,7 +217,7 @@ export function init() {
} }
endMove(); endMove();
stopChian = false; stopChain = false;
} }
} }
@ -297,6 +299,30 @@ export function init() {
callback?.(); callback?.();
} }
async function moveHeroAs(step: DiredLoc[]) {
if (moving) return;
// console.log(step);
let now = step.shift();
if (!now) return;
stepDir = now.direction;
await readyMove();
while (now) {
if (!moving) break;
stepDir = now.direction;
await new Promise<void>(res => {
moveEmit.once('stepEnd', () => {
now = step.shift();
if (!now) endMove();
console.log(now?.direction);
res();
});
});
}
}
// ----- 移动 - 传送门 // ----- 移动 - 传送门
function checkPortal() { function checkPortal() {
const map = BluePalace.portalMap.get(core.status.floorId); const map = BluePalace.portalMap.get(core.status.floorId);
@ -437,8 +463,8 @@ export function init() {
} }
}); });
const step0 = steps.find(v => !v.startsWith('speed')) as Move2; const step0 = moveSteps.find(v => !v.startsWith('speed')) as Move2;
return { steps, start: step0 }; return { steps: moveSteps, start: step0 };
} }
/** /**
@ -495,7 +521,7 @@ export function init() {
control.prototype.moveAction = async function (callback?: () => void) { control.prototype.moveAction = async function (callback?: () => void) {
// await endMove(false); // await endMove(false);
moveEmit.once('stepEnd', () => { moveEmit.once('stepEnd', () => {
stopChian = true; stopChain = true;
endMove(false); endMove(false);
}); });
readyMove(false, true, true, callback); readyMove(false, true, true, callback);
@ -570,14 +596,14 @@ export function init() {
readyMove(true, true, true); readyMove(true, true, true);
while (++pointer < len) { while (++pointer < len) {
const dir = moveSteps[pointer]; const dir = moveSteps[pointer];
if (dir === 'backward') moveDir = backDir(moveDir); if (dir === 'backward') stepDir = backDir(stepDir);
else if (dir.startsWith('speed')) { else if (dir.startsWith('speed')) {
const speed = parseInt(dir.slice(6)); const speed = parseInt(dir.slice(6));
list.forEach(v => v.setMoveSpeed(speed)); list.forEach(v => v.setMoveSpeed(speed));
} else if (dir !== 'forward') { } else if (dir !== 'forward') {
moveDir = dir as Dir; stepDir = dir as Dir;
} }
const { x, y } = core.utils.scan[moveDir]; const { x, y } = core.utils.scan[stepDir];
nx += x; nx += x;
ny += y; ny += y;
await new Promise<void>(res => { await new Promise<void>(res => {
@ -617,7 +643,9 @@ export function init() {
control.prototype.waitHeroToStop = function (callback?: () => void) { control.prototype.waitHeroToStop = function (callback?: () => void) {
core.stopAutomaticRoute(); core.stopAutomaticRoute();
core.clearContinueAutomaticRoute(); core.clearContinueAutomaticRoute();
moveEnding.then(() => {
stopChain = true;
stepEnding.then(() => {
callback?.(); callback?.();
}); });
}; };
@ -639,7 +667,8 @@ export function init() {
moveDir = direction; moveDir = direction;
stepDir = direction; stepDir = direction;
await readyMove(); await readyMove();
stopChian = true;
stopChain = true;
callback?.(); callback?.();
}; };
@ -647,13 +676,52 @@ export function init() {
events.prototype.setHeroIcon = function (name: ImageIds) { events.prototype.setHeroIcon = function (name: ImageIds) {
const img = core.material.images.images[name]; const img = core.material.images.images[name];
if (!img) return; if (!img) return;
adapters['hero-adapter']?.all('setImage', img); adapters['hero-adapter']?.sync('setImage', img);
}; };
control.prototype.isMoving = function () { control.prototype.isMoving = function () {
return moving; return moving;
}; };
////// 设置自动寻路路线 //////
control.prototype.setAutomaticRoute = function (
destX: number,
destY: number,
stepPostfix: DiredLoc[]
) {
if (!core.status.played || core.status.lockControl) return;
if (this._setAutomaticRoute_isMoving(destX, destY)) return;
if (this._setAutomaticRoute_isTurning(destX, destY, stepPostfix))
return;
if (
this._setAutomaticRoute_clickMoveDirectly(
destX,
destY,
stepPostfix
)
)
return;
// 找寻自动寻路路线
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)
)
return;
moveStep.push(...stepPostfix);
core.status.automaticRoute.destX = destX;
core.status.automaticRoute.destY = destY;
this._setAutomaticRoute_drawRoute(moveStep);
this._setAutomaticRoute_setAutoSteps(moveStep);
// 立刻移动
// core.setAutoHeroMove();
console.log(moveStep);
moveHeroAs(moveStep.slice());
};
hook.on('reset', () => { hook.on('reset', () => {
moveDir = core.status.hero.loc.direction; moveDir = core.status.hero.loc.direction;
stepDir = moveDir; stepDir = moveDir;
@ -868,10 +936,11 @@ export function init() {
for (const step of moveSteps) { for (const step of moveSteps) {
if (step === 'backward') stepDir = backDir(stepDir); if (step === 'backward') stepDir = backDir(stepDir);
if (step.startsWith('speed')) { else if (step.startsWith('speed')) {
time = parseInt(step.slice(6)); time = parseInt(step.slice(6));
continue; continue;
} } else stepDir = step as Dir2;
const { x, y } = core.utils.scan2[stepDir]; const { x, y } = core.utils.scan2[stepDir];
const tx = nx + x; const tx = nx + x;
const ty = ny + y; const ty = ny + y;
@ -983,8 +1052,10 @@ export function init() {
destY: number, destY: number,
ignoreSteps: number ignoreSteps: number
) { ) {
adapters.viewport?.all('mutateTo', destX, destY); const data = this.controldata;
return this.controldata.moveDirectly(destX, destY, ignoreSteps); const success = data.moveDirectly(destX, destY, ignoreSteps);
if (success) adapters.viewport?.all('mutateTo', destX, destY);
return success;
}; };
}); });

View File

@ -2,7 +2,7 @@ import { message } from 'ant-design-vue';
import { MessageApi } from 'ant-design-vue/lib/message'; import { MessageApi } from 'ant-design-vue/lib/message';
import { isNil } from 'lodash-es'; import { isNil } from 'lodash-es';
import { Animation, sleep, TimingFn } from 'mutate-animate'; import { Animation, sleep, TimingFn } from 'mutate-animate';
import { ref } from 'vue'; import { Ref, ref } from 'vue';
import { EVENT_KEY_CODE_MAP, KeyCode } from './keyCodes'; import { EVENT_KEY_CODE_MAP, KeyCode } from './keyCodes';
import axios from 'axios'; import axios from 'axios';
import { decompressFromBase64 } from 'lz-string'; import { decompressFromBase64 } from 'lz-string';

View File

@ -641,7 +641,7 @@ interface InitGameStatus {
/** /**
* *
*/ */
automaticRoute: DeepReadonly<AutomaticRouteStatus>; automaticRoute: AutomaticRouteStatus;
/** /**
* *