2024-08-19 22:21:36 +08:00
|
|
|
|
import { ILayerRenderExtends, Layer, LayerMovingRenderable } from './layer';
|
|
|
|
|
import { SizedCanvasImageSource } from './misc';
|
|
|
|
|
import { RenderAdapter } from '../adapter';
|
|
|
|
|
import { logger } from '@/core/common/logger';
|
|
|
|
|
import EventEmitter from 'eventemitter3';
|
|
|
|
|
import { texture } from '../cache';
|
2024-08-19 22:50:06 +08:00
|
|
|
|
import { TimingFn } from 'mutate-animate';
|
2024-08-20 18:29:55 +08:00
|
|
|
|
import { backDir } from '@/plugin/game/utils';
|
2024-08-21 21:29:47 +08:00
|
|
|
|
import { isNil } from 'lodash-es';
|
2024-08-14 23:23:41 +08:00
|
|
|
|
|
2024-08-19 22:50:06 +08:00
|
|
|
|
type HeroMovingStatus = 'stop' | 'moving' | 'moving-as';
|
2024-08-19 22:21:36 +08:00
|
|
|
|
|
|
|
|
|
interface HeroRenderEvent {
|
|
|
|
|
stepEnd: [];
|
2024-08-26 23:59:58 +08:00
|
|
|
|
moveTick: [x: number, y: number];
|
2024-08-19 22:21:36 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export class HeroRenderer
|
|
|
|
|
extends EventEmitter<HeroRenderEvent>
|
|
|
|
|
implements ILayerRenderExtends
|
|
|
|
|
{
|
|
|
|
|
id: string = 'floor-hero';
|
|
|
|
|
|
|
|
|
|
/** 勇士的图片资源 */
|
|
|
|
|
image?: SizedCanvasImageSource;
|
|
|
|
|
cellWidth?: number;
|
|
|
|
|
cellHeight?: number;
|
|
|
|
|
|
|
|
|
|
/** 勇士的渲染信息 */
|
|
|
|
|
renderable?: LayerMovingRenderable;
|
|
|
|
|
layer!: Layer;
|
|
|
|
|
|
|
|
|
|
// hero?: Hero;
|
|
|
|
|
|
|
|
|
|
/** 勇士移动状态 */
|
|
|
|
|
status: HeroMovingStatus = 'stop';
|
|
|
|
|
/** 当前移动帧数 */
|
|
|
|
|
movingFrame: number = 0;
|
|
|
|
|
|
|
|
|
|
/** 勇士移动速度 */
|
|
|
|
|
speed: number = 100;
|
2024-08-20 18:29:55 +08:00
|
|
|
|
/** 当前勇士朝向 */
|
|
|
|
|
dir: Dir = 'down';
|
2024-08-19 22:21:36 +08:00
|
|
|
|
|
|
|
|
|
/** 勇士移动定时器id */
|
|
|
|
|
private moveId: number = -1;
|
|
|
|
|
/** 上一次帧数切换的时间 */
|
|
|
|
|
private lastFrameTime: number = 0;
|
|
|
|
|
/** 当前的移动方向 */
|
2024-08-21 13:21:56 +08:00
|
|
|
|
private moveDir: Move2 = 'down';
|
2024-08-19 22:21:36 +08:00
|
|
|
|
/** 上一步走到格子上的时间 */
|
|
|
|
|
private lastStepTime: number = 0;
|
2024-08-21 21:29:47 +08:00
|
|
|
|
/** 执行当前步移动的Promise */
|
2024-08-19 22:21:36 +08:00
|
|
|
|
private moveDetached?: Promise<void>;
|
2024-08-21 21:29:47 +08:00
|
|
|
|
/** endMove的Promise */
|
|
|
|
|
private moveEnding?: Promise<void>;
|
2024-08-19 22:21:36 +08:00
|
|
|
|
/**
|
|
|
|
|
* 这一步的移动方向,与{@link moveDir}不同的是,在这一步走完之前,它都不会变,
|
|
|
|
|
* 当这一步走完之后,才会将其设置为{@link moveDir}的值
|
|
|
|
|
*/
|
2024-08-21 13:21:56 +08:00
|
|
|
|
private stepDir: Dir2 = 'down';
|
2024-08-19 22:21:36 +08:00
|
|
|
|
/** 每步的格子增量 */
|
|
|
|
|
private stepDelta: Loc = { x: 0, y: 1 };
|
2024-08-20 18:29:55 +08:00
|
|
|
|
/** 动画显示的方向,用于适配后退 */
|
2024-08-21 13:21:56 +08:00
|
|
|
|
// private animateDir: Dir = 'down';
|
2024-08-19 22:21:36 +08:00
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 设置勇士所用的图片资源
|
|
|
|
|
* @param image 图片资源
|
|
|
|
|
*/
|
|
|
|
|
setImage(image: SizedCanvasImageSource) {
|
|
|
|
|
this.image = image;
|
|
|
|
|
this.split();
|
|
|
|
|
this.layer.update(this.layer);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
setMoveSpeed(speed: number) {
|
|
|
|
|
this.speed = speed;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 分割勇士图像,生成renderable信息
|
|
|
|
|
*/
|
|
|
|
|
split() {
|
|
|
|
|
this.cellWidth = this.image!.width / 4;
|
|
|
|
|
this.cellHeight = this.image!.height / 4;
|
|
|
|
|
this.generateRenderable();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 生成渲染信息
|
|
|
|
|
*/
|
|
|
|
|
generateRenderable() {
|
|
|
|
|
if (!this.image) return;
|
|
|
|
|
this.renderable = {
|
|
|
|
|
image: this.image,
|
|
|
|
|
frame: 4,
|
|
|
|
|
x: core.status.hero.loc.x,
|
|
|
|
|
y: core.status.hero.loc.y,
|
|
|
|
|
zIndex: core.status.hero.loc.y,
|
|
|
|
|
autotile: false,
|
|
|
|
|
bigImage: true,
|
|
|
|
|
render: this.getRenderFromDir(this.moveDir),
|
|
|
|
|
animate: 0
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 根据方向获取勇士的裁切信息
|
|
|
|
|
* @param dir 方向
|
|
|
|
|
*/
|
2024-08-21 13:21:56 +08:00
|
|
|
|
getRenderFromDir(dir: Move2): [number, number, number, number][] {
|
2024-08-19 22:21:36 +08:00
|
|
|
|
if (!this.cellWidth || !this.cellHeight) return [];
|
2024-08-21 13:21:56 +08:00
|
|
|
|
let resolved: Dir2;
|
2024-08-20 18:29:55 +08:00
|
|
|
|
if (dir === 'forward') resolved = this.dir;
|
|
|
|
|
else if (dir === 'backward') resolved = backDir(this.dir);
|
|
|
|
|
else resolved = dir;
|
|
|
|
|
const index = texture.characterDirection[resolved];
|
2024-08-19 22:21:36 +08:00
|
|
|
|
const y = index * this.cellHeight;
|
2024-08-20 18:29:55 +08:00
|
|
|
|
const res: [number, number, number, number][] = [0, 1, 2, 3].map(v => {
|
2024-08-19 22:21:36 +08:00
|
|
|
|
return [v * this.cellWidth!, y, this.cellWidth!, this.cellHeight!];
|
|
|
|
|
});
|
2024-08-20 18:29:55 +08:00
|
|
|
|
if (dir === 'backward') return res.reverse();
|
|
|
|
|
else return res;
|
2024-08-19 22:21:36 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 设置当前勇士
|
|
|
|
|
* @param hero 绑定的勇士
|
|
|
|
|
*/
|
|
|
|
|
// setHero(hero: Hero) {
|
|
|
|
|
// this.hero = hero;
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 准备开始移动
|
|
|
|
|
*/
|
|
|
|
|
readyMove() {
|
|
|
|
|
if (this.status !== 'stop') return;
|
|
|
|
|
this.status = 'moving';
|
|
|
|
|
this.lastFrameTime = Date.now();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 勇士移动定时器
|
|
|
|
|
*/
|
|
|
|
|
private moveTick(time: number) {
|
|
|
|
|
if (this.status !== 'moving') return;
|
2024-08-26 21:10:56 +08:00
|
|
|
|
if (!this.renderable) {
|
|
|
|
|
this.emit('stepEnd');
|
|
|
|
|
return;
|
|
|
|
|
}
|
2024-08-19 22:21:36 +08:00
|
|
|
|
|
|
|
|
|
if (time - this.lastFrameTime > this.speed) {
|
|
|
|
|
this.lastFrameTime = time;
|
|
|
|
|
this.movingFrame++;
|
|
|
|
|
this.movingFrame %= 4;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const progress = (time - this.lastStepTime) / this.speed;
|
2024-08-20 18:04:22 +08:00
|
|
|
|
|
2024-08-19 22:21:36 +08:00
|
|
|
|
const { x: dx, y: dy } = this.stepDelta;
|
|
|
|
|
const { x, y } = core.status.hero.loc;
|
|
|
|
|
if (progress >= 1) {
|
|
|
|
|
this.renderable.x = x + dx;
|
|
|
|
|
this.renderable.y = y + dy;
|
|
|
|
|
this.emit('stepEnd');
|
|
|
|
|
} else {
|
|
|
|
|
const rx = dx * progress + x;
|
|
|
|
|
const ry = dy * progress + y;
|
|
|
|
|
this.renderable.x = rx;
|
|
|
|
|
this.renderable.y = ry;
|
|
|
|
|
}
|
2024-08-26 23:59:58 +08:00
|
|
|
|
this.emit('moveTick', this.renderable.x, this.renderable.y);
|
2024-08-19 22:21:36 +08:00
|
|
|
|
this.renderable.animate = this.movingFrame;
|
|
|
|
|
this.layer.update(this.layer);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 进行下一步的移动准备,设置移动信息
|
|
|
|
|
*/
|
|
|
|
|
private step() {
|
2024-08-20 18:29:55 +08:00
|
|
|
|
if (this.moveDir === 'backward') this.stepDir = backDir(this.stepDir);
|
|
|
|
|
else if (this.moveDir !== 'forward') this.stepDir = this.moveDir;
|
2024-08-19 22:21:36 +08:00
|
|
|
|
this.lastStepTime = Date.now();
|
2024-08-21 13:21:56 +08:00
|
|
|
|
this.stepDelta = core.utils.scan2[this.stepDir];
|
2024-08-19 22:21:36 +08:00
|
|
|
|
this.turn(this.stepDir);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 移动勇士
|
|
|
|
|
*/
|
2024-08-21 13:21:56 +08:00
|
|
|
|
move(dir: Move2): Promise<void> {
|
2024-08-19 22:21:36 +08:00
|
|
|
|
if (this.status !== 'moving') {
|
|
|
|
|
logger.error(
|
|
|
|
|
12,
|
|
|
|
|
`Cannot move while status is not 'moving'. Call 'readyMove' first.`
|
|
|
|
|
);
|
|
|
|
|
return Promise.reject();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
this.moveDir = dir;
|
|
|
|
|
|
|
|
|
|
if (this.moveDetached) return this.moveDetached;
|
|
|
|
|
else {
|
|
|
|
|
this.step();
|
|
|
|
|
return (this.moveDetached = new Promise<void>(resolve => {
|
2024-08-20 18:04:22 +08:00
|
|
|
|
this.once('stepEnd', () => {
|
|
|
|
|
this.moveDetached = void 0;
|
|
|
|
|
resolve();
|
|
|
|
|
});
|
2024-08-19 22:21:36 +08:00
|
|
|
|
}));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 结束勇士的移动过程
|
|
|
|
|
*/
|
|
|
|
|
endMove(): Promise<void> {
|
|
|
|
|
if (this.status !== 'moving') return Promise.reject();
|
2024-08-21 21:29:47 +08:00
|
|
|
|
if (this.moveEnding) return this.moveEnding;
|
|
|
|
|
else {
|
|
|
|
|
const promise = new Promise<void>(resolve => {
|
|
|
|
|
this.once('stepEnd', () => {
|
|
|
|
|
this.moveEnding = void 0;
|
|
|
|
|
this.status = 'stop';
|
|
|
|
|
this.movingFrame = 0;
|
|
|
|
|
const { x, y } = core.status.hero.loc;
|
|
|
|
|
this.setHeroLoc(x, y);
|
|
|
|
|
this.render();
|
|
|
|
|
resolve();
|
|
|
|
|
});
|
2024-08-19 22:21:36 +08:00
|
|
|
|
});
|
2024-08-21 21:29:47 +08:00
|
|
|
|
return (this.moveEnding = promise);
|
|
|
|
|
}
|
2024-08-19 22:21:36 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 勇士转向,不填表示顺时针转一个方向
|
|
|
|
|
* @param dir 移动方向
|
|
|
|
|
*/
|
2024-08-21 13:21:56 +08:00
|
|
|
|
turn(dir?: Dir2): void {
|
2024-08-19 22:21:36 +08:00
|
|
|
|
if (!dir) {
|
2024-08-21 13:21:56 +08:00
|
|
|
|
const index = texture.characterTurn2.indexOf(this.stepDir);
|
|
|
|
|
if (index === -1) {
|
|
|
|
|
const length = texture.characterTurn.length;
|
|
|
|
|
const index = texture.characterTurn.indexOf(
|
|
|
|
|
this.stepDir as Dir
|
|
|
|
|
);
|
|
|
|
|
const next = texture.characterTurn[index % length];
|
|
|
|
|
return this.turn(next);
|
|
|
|
|
} else {
|
|
|
|
|
return this.turn(texture.characterTurn[index]);
|
|
|
|
|
}
|
2024-08-19 22:21:36 +08:00
|
|
|
|
}
|
|
|
|
|
this.moveDir = dir;
|
2024-08-21 21:29:47 +08:00
|
|
|
|
this.stepDir = dir;
|
|
|
|
|
|
2024-08-19 22:21:36 +08:00
|
|
|
|
if (!this.renderable) return;
|
|
|
|
|
this.renderable.render = this.getRenderFromDir(this.moveDir);
|
|
|
|
|
this.layer.update(this.layer);
|
|
|
|
|
}
|
|
|
|
|
|
2024-08-21 21:29:47 +08:00
|
|
|
|
/**
|
|
|
|
|
* 设置勇士的坐标
|
|
|
|
|
* @param x 横坐标
|
|
|
|
|
* @param y 纵坐标
|
|
|
|
|
*/
|
|
|
|
|
setHeroLoc(x?: number, y?: number) {
|
|
|
|
|
if (!this.renderable) return;
|
|
|
|
|
if (!isNil(x)) {
|
|
|
|
|
this.renderable.x = x;
|
|
|
|
|
}
|
|
|
|
|
if (!isNil(y)) {
|
|
|
|
|
this.renderable.y = y;
|
|
|
|
|
}
|
2024-08-26 23:59:58 +08:00
|
|
|
|
this.emit('moveTick', this.renderable.x, this.renderable.y);
|
2024-08-21 21:29:47 +08:00
|
|
|
|
this.layer.update(this.layer);
|
|
|
|
|
}
|
|
|
|
|
|
2024-08-19 22:50:06 +08:00
|
|
|
|
/**
|
|
|
|
|
* 按照指定函数移动勇士
|
|
|
|
|
* @param x 目标横坐标
|
|
|
|
|
* @param y 目标纵坐标
|
|
|
|
|
* @param time 移动时间
|
|
|
|
|
* @param fn 移动函数,传入一个完成度(范围0-1),返回一个三元素数组,表示横纵格子坐标,可以是小数。
|
|
|
|
|
* 第三个元素表示图块纵深,一般图块的纵深就是其纵坐标,当地图上有大怪物时,此举可以辅助渲染,
|
|
|
|
|
* 否则可能会导致移动过程中与大怪物的层级关系不正确,比如全在大怪物身后。注意不建议频繁改动这个值,
|
|
|
|
|
* 因为此举会导致层级的重新排序,降低渲染性能。
|
|
|
|
|
*/
|
2024-08-20 00:01:59 +08:00
|
|
|
|
moveAs(x: number, y: number, time: number, fn: TimingFn<3>): Promise<void> {
|
|
|
|
|
if (this.status !== 'stop') return Promise.reject();
|
|
|
|
|
if (!this.renderable) return Promise.reject();
|
2024-08-19 22:50:06 +08:00
|
|
|
|
this.status = 'moving-as';
|
|
|
|
|
let nowZIndex = fn(0)[2];
|
|
|
|
|
let startTime = Date.now();
|
2024-08-20 00:01:59 +08:00
|
|
|
|
return new Promise(res => {
|
|
|
|
|
this.layer.delegateTicker(
|
|
|
|
|
() => {
|
|
|
|
|
if (!this.renderable) return;
|
|
|
|
|
const now = Date.now();
|
|
|
|
|
const progress = (now - startTime) / time;
|
|
|
|
|
const [nx, ny, nz] = fn(progress);
|
|
|
|
|
this.renderable.x = nx;
|
|
|
|
|
this.renderable.y = ny;
|
|
|
|
|
this.renderable.zIndex = nz;
|
|
|
|
|
if (nz !== nowZIndex) {
|
|
|
|
|
this.layer.movingRenderable.sort(
|
|
|
|
|
(a, b) => a.zIndex - b.zIndex
|
|
|
|
|
);
|
|
|
|
|
}
|
2024-08-26 23:59:58 +08:00
|
|
|
|
this.emit('moveTick', this.renderable.x, this.renderable.y);
|
2024-08-20 00:01:59 +08:00
|
|
|
|
this.layer.update(this.layer);
|
|
|
|
|
},
|
|
|
|
|
time,
|
|
|
|
|
() => {
|
|
|
|
|
this.status = 'stop';
|
|
|
|
|
if (!this.renderable) return res();
|
|
|
|
|
this.renderable.animate = 0;
|
|
|
|
|
this.renderable.x = x;
|
|
|
|
|
this.renderable.y = y;
|
2024-08-26 23:59:58 +08:00
|
|
|
|
this.emit('moveTick', this.renderable.x, this.renderable.y);
|
2024-08-20 00:01:59 +08:00
|
|
|
|
this.layer.update(this.layer);
|
|
|
|
|
res();
|
2024-08-19 22:50:06 +08:00
|
|
|
|
}
|
2024-08-20 00:01:59 +08:00
|
|
|
|
);
|
|
|
|
|
});
|
2024-08-19 22:50:06 +08:00
|
|
|
|
}
|
|
|
|
|
|
2024-08-19 22:21:36 +08:00
|
|
|
|
/**
|
|
|
|
|
* 渲染勇士
|
|
|
|
|
*/
|
|
|
|
|
render() {
|
|
|
|
|
if (!this.renderable) return;
|
|
|
|
|
if (this.status === 'stop') {
|
|
|
|
|
this.renderable.animate = -1;
|
|
|
|
|
} else {
|
|
|
|
|
this.renderable.animate = this.movingFrame;
|
|
|
|
|
}
|
|
|
|
|
this.layer.update(this.layer);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
awake(layer: Layer): void {
|
|
|
|
|
this.layer = layer;
|
|
|
|
|
adapter.add(this);
|
|
|
|
|
this.moveId = layer.delegateTicker(() => {
|
|
|
|
|
this.moveTick(Date.now());
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
onDestroy(layer: Layer): void {
|
|
|
|
|
adapter.remove(this);
|
|
|
|
|
layer.removeTicker(this.moveId);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
onMovingUpdate(layer: Layer, renderable: LayerMovingRenderable[]): void {
|
|
|
|
|
if (this.renderable) renderable.push(this.renderable);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const adapter = new RenderAdapter<HeroRenderer>('hero-adapter');
|
2024-08-27 18:34:03 +08:00
|
|
|
|
adapter.receive('readyMove', item => {
|
2024-08-19 22:21:36 +08:00
|
|
|
|
item.readyMove();
|
|
|
|
|
return Promise.resolve();
|
|
|
|
|
});
|
2024-08-27 18:34:03 +08:00
|
|
|
|
adapter.receive('move', (item, dir: Dir) => {
|
2024-08-19 22:21:36 +08:00
|
|
|
|
return item.move(dir);
|
|
|
|
|
});
|
2024-08-27 18:34:03 +08:00
|
|
|
|
adapter.receive('endMove', item => {
|
2024-08-19 22:21:36 +08:00
|
|
|
|
return item.endMove();
|
|
|
|
|
});
|
2024-08-27 18:34:03 +08:00
|
|
|
|
adapter.receive(
|
2024-08-20 00:01:59 +08:00
|
|
|
|
'moveAs',
|
|
|
|
|
(item, x: number, y: number, time: number, fn: TimingFn<3>) => {
|
|
|
|
|
return item.moveAs(x, y, time, fn);
|
|
|
|
|
}
|
|
|
|
|
);
|
2024-08-27 18:34:03 +08:00
|
|
|
|
adapter.receive('setMoveSpeed', (item, speed: number) => {
|
2024-08-21 13:21:56 +08:00
|
|
|
|
item.setMoveSpeed(speed);
|
|
|
|
|
return Promise.resolve();
|
|
|
|
|
});
|
2024-08-27 18:34:03 +08:00
|
|
|
|
adapter.receive('setHeroLoc', (item, x?: number, y?: number) => {
|
2024-08-21 21:29:47 +08:00
|
|
|
|
item.setHeroLoc(x, y);
|
|
|
|
|
return Promise.resolve();
|
|
|
|
|
});
|
2024-08-27 18:34:03 +08:00
|
|
|
|
adapter.receive('turn', (item, dir: Dir2) => {
|
2024-08-21 21:29:47 +08:00
|
|
|
|
item.turn(dir);
|
|
|
|
|
return Promise.resolve();
|
|
|
|
|
});
|
2024-08-27 18:34:03 +08:00
|
|
|
|
adapter.receive('setImage', (item, image: SizedCanvasImageSource) => {
|
2024-08-26 21:10:56 +08:00
|
|
|
|
item.setImage(image);
|
|
|
|
|
return Promise.resolve();
|
|
|
|
|
});
|
2024-08-21 21:29:47 +08:00
|
|
|
|
// 同步fallback,用于适配现在的样板,之后会删除
|
2024-08-27 18:34:03 +08:00
|
|
|
|
adapter.receiveSync('setHeroLoc', (item, x?: number, y?: number) => {
|
2024-08-21 21:29:47 +08:00
|
|
|
|
item.setHeroLoc(x, y);
|
|
|
|
|
});
|
2024-08-27 18:34:03 +08:00
|
|
|
|
adapter.receiveSync('turn', (item, dir: Dir2) => {
|
2024-08-21 21:29:47 +08:00
|
|
|
|
item.turn(dir);
|
|
|
|
|
});
|