mirror of
https://github.com/unanmed/HumanBreak.git
synced 2025-03-01 01:57:05 +08:00
refactor: 勇士移动
This commit is contained in:
parent
b9ae439cd9
commit
3de6aac6a1
@ -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');
|
||||||
|
159
src/core/main/action/move.ts
Normal file
159
src/core/main/action/move.ts
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
@ -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(',')}`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
@ -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);
|
||||||
});
|
});
|
||||||
|
@ -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;
|
||||||
|
@ -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
397
src/game/state/move.ts
Normal 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
35
src/game/state/utils.ts
Normal 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];
|
||||||
|
}
|
@ -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;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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;
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -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';
|
||||||
|
2
src/types/status.d.ts
vendored
2
src/types/status.d.ts
vendored
@ -641,7 +641,7 @@ interface InitGameStatus {
|
|||||||
/**
|
/**
|
||||||
* 自动寻路状态
|
* 自动寻路状态
|
||||||
*/
|
*/
|
||||||
automaticRoute: DeepReadonly<AutomaticRouteStatus>;
|
automaticRoute: AutomaticRouteStatus;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 按键按下的时间,用于判定双击
|
* 按键按下的时间,用于判定双击
|
||||||
|
Loading…
Reference in New Issue
Block a user