HumanBreak/src/core/render/preset/hero.ts

396 lines
12 KiB
TypeScript
Raw Normal View History

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';
interface HeroRenderEvent {
stepEnd: [];
}
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';
/** 勇士移动定时器id */
private moveId: number = -1;
/** 上一次帧数切换的时间 */
private lastFrameTime: number = 0;
/** 当前的移动方向 */
2024-08-21 13:21:56 +08:00
private moveDir: Move2 = 'down';
/** 上一步走到格子上的时间 */
private lastStepTime: number = 0;
2024-08-21 21:29:47 +08:00
/** 执行当前步移动的Promise */
private moveDetached?: Promise<void>;
2024-08-21 21:29:47 +08:00
/** endMove的Promise */
private moveEnding?: Promise<void>;
/**
* {@link moveDir}
* {@link moveDir}
*/
2024-08-21 13:21:56 +08:00
private stepDir: Dir2 = 'down';
/** 每步的格子增量 */
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';
/**
*
* @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][] {
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];
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 => {
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;
}
/**
*
* @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;
}
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
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;
}
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;
this.lastStepTime = Date.now();
2024-08-21 13:21:56 +08:00
this.stepDelta = core.utils.scan2[this.stepDir];
this.turn(this.stepDir);
}
/**
*
*/
2024-08-21 13:21:56 +08:00
move(dir: Move2): Promise<void> {
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();
});
}));
}
}
/**
*
*/
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-21 21:29:47 +08:00
return (this.moveEnding = promise);
}
}
/**
*
* @param dir
*/
2024-08-21 13:21:56 +08:00
turn(dir?: Dir2): void {
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]);
}
}
this.moveDir = dir;
2024-08-21 21:29:47 +08:00
this.stepDir = dir;
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;
}
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
);
}
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;
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
}
/**
*
*/
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');
adapter.recieve('readyMove', item => {
item.readyMove();
return Promise.resolve();
});
adapter.recieve('move', (item, dir: Dir) => {
return item.move(dir);
});
adapter.recieve('endMove', item => {
return item.endMove();
});
2024-08-20 00:01:59 +08:00
adapter.recieve(
'moveAs',
(item, x: number, y: number, time: number, fn: TimingFn<3>) => {
return item.moveAs(x, y, time, fn);
}
);
2024-08-21 13:21:56 +08:00
adapter.recieve('setMoveSpeed', (item, speed: number) => {
item.setMoveSpeed(speed);
return Promise.resolve();
});
2024-08-21 21:29:47 +08:00
adapter.recieve('setHeroLoc', (item, x?: number, y?: number) => {
item.setHeroLoc(x, y);
return Promise.resolve();
});
adapter.recieve('turn', (item, dir: Dir2) => {
item.turn(dir);
return Promise.resolve();
});
2024-08-26 21:10:56 +08:00
adapter.recieve('setImage', (item, image: SizedCanvasImageSource) => {
item.setImage(image);
return Promise.resolve();
});
2024-08-21 21:29:47 +08:00
// 同步fallback用于适配现在的样板之后会删除
adapter.recieveSync('setHeroLoc', (item, x?: number, y?: number) => {
item.setHeroLoc(x, y);
});
adapter.recieveSync('turn', (item, dir: Dir2) => {
item.turn(dir);
});