mirror of
https://github.com/unanmed/HumanBreak.git
synced 2025-11-27 13:42:58 +08:00
feat: 勇士及跟随者渲染
This commit is contained in:
parent
37cd85cd1e
commit
8fe12634b0
@ -48,7 +48,7 @@
|
||||
- 不使用下划线命名法。
|
||||
- 注释:
|
||||
- 常用属性成员、方法、接口、类型必须添加 `jsDoc` 注释。
|
||||
- 长文件可使用 `#region` 分段,非必要则不写 `#endregion`。
|
||||
- 长文件可使用 `#region` 分段,可以写上 `#endretion` 允许折叠。
|
||||
- TODO 使用 `// TODO:` 或 `// todo:` 格式。
|
||||
- 单行注释的双斜杠与注释内容之间添加一个空格,多行注释只允许出现 `jsDoc` 注释,如果需要多行非 `jsDoc` 注释,使用多个单行注释。
|
||||
- 类型:
|
||||
|
||||
@ -633,7 +633,7 @@ export interface IWheelEvent extends IActionEvent {
|
||||
|
||||
1. 按下、抬起、点击**永远**保持为同一个 `identifier`
|
||||
2. 移动过程中,使用最后一个按下的按键的 `identifier` 作为移动事件的 `identifier`
|
||||
3. 如果移动过程中,最后一个按下的按键抬起,那么依然会维持**原先的** `identifer`,**不会**回退至上一个按下的按键
|
||||
3. 如果移动过程中,最后一个按下的按键抬起,那么依然会维持**原先的** `identifier`,**不会**回退至上一个按下的按键
|
||||
|
||||
除此之外,滚轮事件中的 `identifier` 永远为 -1。
|
||||
|
||||
|
||||
8
packages-user/client-modules/src/render/commonIns.ts
Normal file
8
packages-user/client-modules/src/render/commonIns.ts
Normal file
@ -0,0 +1,8 @@
|
||||
import { state } from '@user/data-state';
|
||||
import { MapRenderer } from './map/renderer';
|
||||
import { materials } from '@user/client-base';
|
||||
|
||||
/** 主地图渲染器,用于渲染游戏画面 */
|
||||
export const mainMapRenderer = new MapRenderer(materials, state.layer);
|
||||
/** 副地图渲染器,用于渲染缩略图、浏览地图等 */
|
||||
export const expandMapRenderer = new MapRenderer(materials, state.layer);
|
||||
@ -7,8 +7,9 @@ import { Icon, Winskin } from './misc';
|
||||
import { Animate } from './animate';
|
||||
import { createItemDetail } from './itemDetail';
|
||||
import { logger } from '@motajs/common';
|
||||
import { MapRender } from '../map/element';
|
||||
import { MapRender, MapRenderer } from '../map';
|
||||
import { state } from '@user/data-state';
|
||||
import { materials } from '@user/client-base';
|
||||
|
||||
export function createElements() {
|
||||
createCache();
|
||||
@ -72,14 +73,27 @@ export function createElements() {
|
||||
tagMap.register('map-render', (_0, _1, props) => {
|
||||
if (!props) {
|
||||
logger.error(42);
|
||||
return new MapRender(state.layer);
|
||||
return new MapRender(
|
||||
state.layer,
|
||||
new MapRenderer(materials, state.layer)
|
||||
);
|
||||
}
|
||||
const { layerState } = props;
|
||||
const { layerState, renderer } = props;
|
||||
if (!layerState) {
|
||||
logger.error(42);
|
||||
return new MapRender(state.layer);
|
||||
logger.error(42, 'layerState');
|
||||
return new MapRender(
|
||||
state.layer,
|
||||
new MapRenderer(materials, state.layer)
|
||||
);
|
||||
}
|
||||
return new MapRender(layerState);
|
||||
if (!renderer) {
|
||||
logger.error(42, 'renderer');
|
||||
return new MapRender(
|
||||
state.layer,
|
||||
new MapRenderer(materials, state.layer)
|
||||
);
|
||||
}
|
||||
return new MapRender(layerState, renderer);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@ -12,6 +12,7 @@ import { EAnimateEvent } from './animate';
|
||||
import { EIconEvent, EWinskinEvent } from './misc';
|
||||
import { IEnemyCollection } from '@motajs/types';
|
||||
import { ILayerState } from '@user/data-state';
|
||||
import { IMapRenderer } from '../map';
|
||||
|
||||
export interface AnimateProps extends BaseProps {}
|
||||
|
||||
@ -63,6 +64,7 @@ export interface LayerProps extends BaseProps {
|
||||
|
||||
export interface MapRenderProps extends BaseProps {
|
||||
layerState: ILayerState;
|
||||
renderer: IMapRenderer;
|
||||
}
|
||||
|
||||
declare module 'vue/jsx-runtime' {
|
||||
|
||||
@ -1,19 +1,21 @@
|
||||
import { MotaOffscreenCanvas2D, RenderItem } from '@motajs/render-core';
|
||||
import { ILayerState, state } from '@user/data-state';
|
||||
import { ILayerState } from '@user/data-state';
|
||||
import { IMapRenderer } from './types';
|
||||
import { MapRenderer } from './renderer';
|
||||
import { materials } from '@user/client-base';
|
||||
import { ElementNamespace, ComponentInternalInstance } from 'vue';
|
||||
import { CELL_HEIGHT, CELL_WIDTH, MAP_HEIGHT, MAP_WIDTH } from '../shared';
|
||||
|
||||
export class MapRender extends RenderItem {
|
||||
/** 地图渲染器 */
|
||||
readonly renderer: IMapRenderer;
|
||||
|
||||
constructor(readonly layerState: ILayerState) {
|
||||
/**
|
||||
* @param layerState 地图状态对象
|
||||
* @param renderer 地图渲染器对象
|
||||
*/
|
||||
constructor(
|
||||
readonly layerState: ILayerState,
|
||||
readonly renderer: IMapRenderer
|
||||
) {
|
||||
super('static');
|
||||
|
||||
this.renderer = new MapRenderer(materials, state.layer);
|
||||
this.renderer.setLayerState(layerState);
|
||||
this.renderer.useAsset(materials.trackedAsset);
|
||||
this.renderer.setCanvasSize(this.width, this.height);
|
||||
|
||||
473
packages-user/client-modules/src/render/map/extension/hero.ts
Normal file
473
packages-user/client-modules/src/render/map/extension/hero.ts
Normal file
@ -0,0 +1,473 @@
|
||||
import {
|
||||
degradeFace,
|
||||
FaceDirection,
|
||||
getFaceMovement,
|
||||
IHeroState,
|
||||
IHeroStateHooks,
|
||||
IMapLayer,
|
||||
state
|
||||
} from '@user/data-state';
|
||||
import { IMapRenderer, IMapRendererTicker, IMovingBlock } from '../types';
|
||||
import { isNil } from 'lodash-es';
|
||||
import { IHookController, logger } from '@motajs/common';
|
||||
import { BlockCls, IMaterialFramedData } from '@user/client-base';
|
||||
import {
|
||||
ITexture,
|
||||
ITextureSplitter,
|
||||
TextureRowSplitter
|
||||
} from '@motajs/render-assets';
|
||||
import { IMapHeroRenderer } from './types';
|
||||
import { TimingFn } from 'mutate-animate';
|
||||
|
||||
/** 默认的移动时长 */
|
||||
const DEFAULT_TIME = 100;
|
||||
|
||||
interface HeroRenderEntity {
|
||||
/** 移动图块对象 */
|
||||
readonly block: IMovingBlock;
|
||||
/** 标识符,用于判定跟随者 */
|
||||
readonly identifier: string;
|
||||
/** 目标横坐标,移动时有效 */
|
||||
targetX: number;
|
||||
/** 目标纵坐标,移动时有效 */
|
||||
targetY: number;
|
||||
/** 当前的移动朝向 */
|
||||
direction: FaceDirection;
|
||||
/** 下一个跟随者的移动方向 */
|
||||
nextDirection: FaceDirection;
|
||||
|
||||
/** 当前是否正在移动 */
|
||||
moving: boolean;
|
||||
/** 当前是否正在动画,移动跟动画要分开,有的操作比如跳跃就是在移动中但是没动画 */
|
||||
animating: boolean;
|
||||
/** 帧动画间隔 */
|
||||
animateInterval: number;
|
||||
/** 当一次动画时刻 */
|
||||
lastAnimateTime: number;
|
||||
/** 当前的动画帧数 */
|
||||
animateFrame: number;
|
||||
/** 移动的 `Promise`,移动完成时兑现,如果停止,则一直是兑现状态 */
|
||||
promise: Promise<void>;
|
||||
}
|
||||
|
||||
export class MapHeroRenderer implements IMapHeroRenderer {
|
||||
private static readonly splitter: ITextureSplitter<number> =
|
||||
new TextureRowSplitter();
|
||||
|
||||
/** 勇士钩子 */
|
||||
readonly controller: IHookController<IHeroStateHooks>;
|
||||
/** 每个朝向的贴图对象 */
|
||||
readonly textureMap: Map<FaceDirection, IMaterialFramedData> = new Map();
|
||||
/** 勇士渲染实体,与 `entities[0]` 同引用 */
|
||||
readonly heroEntity: HeroRenderEntity;
|
||||
|
||||
/**
|
||||
* 渲染实体,索引 0 表示勇士,后续索引依次表示跟随的跟随者。
|
||||
* 整体是一个状态机,而且下一个跟随者只与上一个跟随者有关,下一个跟随者移动的方向就是上一个跟随者移动前指向的方向。
|
||||
*/
|
||||
readonly entities: HeroRenderEntity[] = [];
|
||||
|
||||
/** 每帧执行的帧动画对象 */
|
||||
readonly ticker: IMapRendererTicker;
|
||||
|
||||
constructor(
|
||||
readonly renderer: IMapRenderer,
|
||||
readonly layer: IMapLayer,
|
||||
readonly hero: IHeroState
|
||||
) {
|
||||
this.controller = hero.addHook(new MapHeroHook(this));
|
||||
this.controller.load();
|
||||
const moving = this.addHeroMoving(renderer, layer, hero);
|
||||
const heroEntity: HeroRenderEntity = {
|
||||
block: moving,
|
||||
identifier: '',
|
||||
targetX: hero.x,
|
||||
targetY: hero.y,
|
||||
direction: hero.direction,
|
||||
nextDirection: FaceDirection.Unknown,
|
||||
moving: false,
|
||||
animating: false,
|
||||
animateInterval: 0,
|
||||
lastAnimateTime: 0,
|
||||
animateFrame: 0,
|
||||
promise: Promise.resolve()
|
||||
};
|
||||
this.heroEntity = heroEntity;
|
||||
this.entities.push(heroEntity);
|
||||
this.ticker = renderer.requestTicker(time => this.tick(time));
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加勇士对应的移动图块
|
||||
* @param renderer 渲染器
|
||||
* @param layer 图块所属图层
|
||||
* @param hero 勇士状态对象
|
||||
*/
|
||||
private addHeroMoving(
|
||||
renderer: IMapRenderer,
|
||||
layer: IMapLayer,
|
||||
hero: IHeroState
|
||||
) {
|
||||
if (isNil(hero.image)) {
|
||||
logger.warn(88);
|
||||
return renderer.addMovingBlock(layer, 0, hero.x, hero.y);
|
||||
}
|
||||
const image = this.renderer.manager.getImageByAlias(hero.image);
|
||||
if (!image) {
|
||||
logger.warn(89, hero.image);
|
||||
return renderer.addMovingBlock(layer, 0, hero.x, hero.y);
|
||||
}
|
||||
this.updateHeroTexture(image);
|
||||
const tex = this.textureMap.get(degradeFace(hero.direction));
|
||||
if (!tex) {
|
||||
return renderer.addMovingBlock(layer, 0, hero.x, hero.y);
|
||||
}
|
||||
return renderer.addMovingBlock(layer, tex, hero.x, hero.y);
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新勇士贴图
|
||||
* @param image 勇士使用的贴图,包含四个方向
|
||||
*/
|
||||
private updateHeroTexture(image: ITexture) {
|
||||
const textures = [
|
||||
...image.split(MapHeroRenderer.splitter, image.height / 4)
|
||||
];
|
||||
if (textures.length !== 4) {
|
||||
logger.warn(90, hero.image);
|
||||
return;
|
||||
}
|
||||
const faceList = [
|
||||
FaceDirection.Down,
|
||||
FaceDirection.Left,
|
||||
FaceDirection.Right,
|
||||
FaceDirection.Up
|
||||
];
|
||||
faceList.forEach((v, i) => {
|
||||
const dirImage = textures[i];
|
||||
const data: IMaterialFramedData = {
|
||||
offset: dirImage.width / 4,
|
||||
texture: dirImage,
|
||||
cls: BlockCls.Unknown,
|
||||
frames: 4
|
||||
};
|
||||
this.textureMap.set(v, data);
|
||||
});
|
||||
}
|
||||
|
||||
private tick(time: number) {
|
||||
this.entities.forEach(v => {
|
||||
if (!v.animating) {
|
||||
v.animateFrame = 0;
|
||||
return;
|
||||
}
|
||||
const dt = time - v.lastAnimateTime;
|
||||
if (dt > v.animateInterval) {
|
||||
v.animateFrame++;
|
||||
v.lastAnimateTime = time;
|
||||
v.block.useSpecifiedFrame(v.animateFrame);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
setImage(image: ITexture): void {
|
||||
this.updateHeroTexture(image);
|
||||
const tex = this.textureMap.get(degradeFace(this.hero.direction));
|
||||
if (!tex) return;
|
||||
this.heroEntity.block.setTexture(tex);
|
||||
}
|
||||
|
||||
setAlpha(alpha: number): void {
|
||||
this.heroEntity.block.setAlpha(alpha);
|
||||
}
|
||||
|
||||
setPosition(x: number, y: number): void {
|
||||
this.heroEntity.block.setPos(x, y);
|
||||
}
|
||||
|
||||
/**
|
||||
* 移动指定渲染实体,不会影响其他渲染实体。多次调用时会按顺序依次移动
|
||||
* @param entity 渲染实体
|
||||
* @param direction 移动方向
|
||||
* @param time 移动时长
|
||||
*/
|
||||
private moveEntity(
|
||||
entity: HeroRenderEntity,
|
||||
direction: FaceDirection,
|
||||
time: number
|
||||
) {
|
||||
const { x: dx, y: dy } = getFaceMovement(direction);
|
||||
if (dx === 0 && dy === 0) return;
|
||||
const block = entity.block;
|
||||
const tx = block.x + dx;
|
||||
const ty = block.y + dy;
|
||||
const nextTile = state.roleFace.getFaceOf(block.tile, direction);
|
||||
const nextTex = this.renderer.manager.getIfBigImage(
|
||||
nextTile?.identifier ?? block.tile
|
||||
);
|
||||
entity.promise = entity.promise.then(async () => {
|
||||
entity.moving = true;
|
||||
entity.animating = true;
|
||||
entity.nextDirection = entity.direction;
|
||||
entity.direction = direction;
|
||||
if (nextTex) block.setTexture(nextTex);
|
||||
await block.lineTo(tx, ty, time);
|
||||
entity.moving = false;
|
||||
entity.animating = false;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成跳跃曲线
|
||||
* @param dx 横向偏移量
|
||||
* @param dy 纵向偏移量
|
||||
*/
|
||||
private generateJumpFn(dx: number, dy: number): TimingFn<2> {
|
||||
const distance = Math.hypot(dx, dy);
|
||||
const peak = 3 + distance;
|
||||
|
||||
return (progress: number) => {
|
||||
const x = dx * progress;
|
||||
const y = progress * dy + (progress ** 2 - progress) * peak;
|
||||
|
||||
return [x, y];
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 将指定渲染实体跳跃至目标点,多次调用时会按顺序依次执行,可以与 `moveEntity` 混用
|
||||
* @param entity 渲染实体
|
||||
* @param x 目标横坐标
|
||||
* @param y 目标纵坐标
|
||||
* @param time 跳跃时长
|
||||
*/
|
||||
private jumpEntity(
|
||||
entity: HeroRenderEntity,
|
||||
x: number,
|
||||
y: number,
|
||||
time: number
|
||||
) {
|
||||
const block = entity.block;
|
||||
entity.promise = entity.promise.then(async () => {
|
||||
const dx = block.x - x;
|
||||
const dy = block.y - y;
|
||||
const fn = this.generateJumpFn(dx, dy);
|
||||
entity.moving = true;
|
||||
entity.animating = false;
|
||||
entity.animateFrame = 0;
|
||||
await block.moveRelative(fn, time);
|
||||
entity.moving = false;
|
||||
entity.animating = false;
|
||||
});
|
||||
}
|
||||
|
||||
startMove(): void {
|
||||
this.heroEntity.moving = true;
|
||||
this.heroEntity.animating = true;
|
||||
this.heroEntity.animateFrame = 1;
|
||||
this.heroEntity.lastAnimateTime = this.ticker.timestamp;
|
||||
this.heroEntity.block.useSpecifiedFrame(1);
|
||||
}
|
||||
|
||||
async waitMoveEnd(waitFollower: boolean): Promise<void> {
|
||||
if (waitFollower) {
|
||||
await Promise.all(this.entities.map(v => v.promise));
|
||||
return;
|
||||
}
|
||||
return this.heroEntity.promise;
|
||||
}
|
||||
|
||||
stopMove(stopFollower: boolean): void {
|
||||
if (stopFollower) {
|
||||
this.entities.forEach(v => {
|
||||
v.block.endMoving();
|
||||
});
|
||||
} else {
|
||||
this.heroEntity.block.endMoving();
|
||||
}
|
||||
}
|
||||
|
||||
async move(direction: FaceDirection, time: number): Promise<void> {
|
||||
this.moveEntity(this.heroEntity, direction, time);
|
||||
for (let i = 1; i < this.entities.length; i++) {
|
||||
const last = this.entities[i - 1];
|
||||
this.moveEntity(this.entities[i], last.nextDirection, time);
|
||||
}
|
||||
await Promise.all(this.entities.map(v => v.promise));
|
||||
}
|
||||
|
||||
async jumpTo(
|
||||
x: number,
|
||||
y: number,
|
||||
time: number,
|
||||
waitFollower: boolean
|
||||
): Promise<void> {
|
||||
// 首先要把所有的跟随者移动到勇士所在位置
|
||||
for (let i = 1; i < this.entities.length; i++) {
|
||||
// 对于每一个跟随者,需要向前遍历每一个跟随者,然后朝向移动,这样就可以聚集在一起了
|
||||
const now = this.entities[i];
|
||||
for (let j = i - 1; j >= 0; j--) {
|
||||
const last = this.entities[j];
|
||||
this.moveEntity(now, last.nextDirection, DEFAULT_TIME);
|
||||
}
|
||||
}
|
||||
this.entities.forEach(v => {
|
||||
this.jumpEntity(v, x, y, time);
|
||||
});
|
||||
if (waitFollower) {
|
||||
await Promise.all(this.entities.map(v => v.promise));
|
||||
} else {
|
||||
return this.heroEntity.promise;
|
||||
}
|
||||
}
|
||||
|
||||
addFollower(image: number, id: string): void {
|
||||
const last = this.entities[this.entities.length - 1];
|
||||
if (last.moving) {
|
||||
logger.warn(92);
|
||||
return;
|
||||
}
|
||||
const nowFace = degradeFace(last.nextDirection, FaceDirection.Down);
|
||||
const faced = state.roleFace.getFaceOf(image, nowFace);
|
||||
const tex = this.renderer.manager.getIfBigImage(faced?.face ?? image);
|
||||
if (!tex) {
|
||||
logger.warn(91, image.toString());
|
||||
return;
|
||||
}
|
||||
const { x: dxn, y: dyn } = getFaceMovement(last.nextDirection);
|
||||
const { x: dx, y: dy } = getFaceMovement(last.direction);
|
||||
const x = last.block.x - dxn;
|
||||
const y = last.block.y - dyn;
|
||||
const moving = this.renderer.addMovingBlock(this.layer, tex, x, y);
|
||||
const entity: HeroRenderEntity = {
|
||||
block: moving,
|
||||
identifier: id,
|
||||
targetX: last.targetX - dx,
|
||||
targetY: last.targetY - dy,
|
||||
direction: nowFace,
|
||||
nextDirection: FaceDirection.Unknown,
|
||||
moving: false,
|
||||
animating: false,
|
||||
animateInterval: 0,
|
||||
lastAnimateTime: 0,
|
||||
animateFrame: 0,
|
||||
promise: Promise.resolve()
|
||||
};
|
||||
this.entities.push(entity);
|
||||
}
|
||||
|
||||
async removeFollower(follower: string, animate: boolean): Promise<void> {
|
||||
const index = this.entities.findIndex(v => v.identifier === follower);
|
||||
if (index === -1) return;
|
||||
if (this.entities[index].moving) {
|
||||
logger.warn(93);
|
||||
return;
|
||||
}
|
||||
if (index === this.entities.length - 1) {
|
||||
this.entities.splice(index, 1);
|
||||
return;
|
||||
}
|
||||
// 展示动画
|
||||
if (animate) {
|
||||
for (let i = index + 1; i < this.entities.length; i++) {
|
||||
const last = this.entities[i - 1];
|
||||
const moving = this.entities[i];
|
||||
this.moveEntity(moving, last.nextDirection, DEFAULT_TIME);
|
||||
}
|
||||
this.entities.splice(index, 1);
|
||||
await Promise.all(this.entities.map(v => v.promise));
|
||||
return;
|
||||
}
|
||||
// 不展示动画
|
||||
for (let i = index + 1; i < this.entities.length; i++) {
|
||||
const last = this.entities[i - 1];
|
||||
const moving = this.entities[i];
|
||||
moving.block.setPos(last.block.x, last.block.y);
|
||||
const nextFace = state.roleFace.getFaceOf(
|
||||
moving.block.tile,
|
||||
last.nextDirection
|
||||
);
|
||||
if (!nextFace) continue;
|
||||
const tile = this.renderer.manager.getIfBigImage(
|
||||
nextFace.identifier
|
||||
);
|
||||
if (!tile) continue;
|
||||
moving.block.setTexture(tile);
|
||||
moving.nextDirection = moving.direction;
|
||||
moving.direction = last.nextDirection;
|
||||
}
|
||||
this.entities.splice(index, 1);
|
||||
}
|
||||
|
||||
removeAllFollowers(): void {
|
||||
this.entities.length = 1;
|
||||
}
|
||||
|
||||
setFollowerAlpha(identifier: string, alpha: number): void {
|
||||
const follower = this.entities.find(v => v.identifier === identifier);
|
||||
if (!follower) return;
|
||||
follower.block.setAlpha(alpha);
|
||||
}
|
||||
|
||||
destroy() {
|
||||
this.controller.unload();
|
||||
}
|
||||
}
|
||||
|
||||
class MapHeroHook implements Partial<IHeroStateHooks> {
|
||||
constructor(readonly hero: MapHeroRenderer) {}
|
||||
|
||||
onSetImage(image: ImageIds): void {
|
||||
const texture = this.hero.renderer.manager.getImageByAlias(image);
|
||||
if (!texture) {
|
||||
logger.warn(89, hero.image);
|
||||
return;
|
||||
}
|
||||
this.hero.setImage(texture);
|
||||
}
|
||||
|
||||
onSetPosition(x: number, y: number): void {
|
||||
this.hero.setPosition(x, y);
|
||||
}
|
||||
|
||||
onStartMove(): void {
|
||||
this.hero.startMove();
|
||||
}
|
||||
|
||||
onMoveHero(direction: FaceDirection, time: number): Promise<void> {
|
||||
return this.hero.move(direction, time);
|
||||
}
|
||||
|
||||
onEndMove(waitFollower: boolean): Promise<void> {
|
||||
return this.hero.waitMoveEnd(waitFollower);
|
||||
}
|
||||
|
||||
onJumpHero(
|
||||
x: number,
|
||||
y: number,
|
||||
time: number,
|
||||
waitFollower: boolean
|
||||
): Promise<void> {
|
||||
return this.hero.jumpTo(x, y, time, waitFollower);
|
||||
}
|
||||
|
||||
onSetAlpha(alpha: number): void {
|
||||
this.hero.setAlpha(alpha);
|
||||
}
|
||||
|
||||
onAddFollower(follower: number, identifier: string): void {
|
||||
this.hero.addFollower(follower, identifier);
|
||||
}
|
||||
|
||||
onRemoveFollower(identifier: string, animate: boolean): void {
|
||||
this.hero.removeFollower(identifier, animate);
|
||||
}
|
||||
|
||||
onRemoveAllFollowers(): void {
|
||||
this.hero.removeAllFollowers();
|
||||
}
|
||||
|
||||
onSetFollowerAlpha(identifier: string, alpha: number): void {
|
||||
this.hero.setFollowerAlpha(identifier, alpha);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,2 @@
|
||||
export * from './hero';
|
||||
export * from './types';
|
||||
@ -0,0 +1,89 @@
|
||||
import { ITexture } from '@motajs/render-assets';
|
||||
import { FaceDirection } from '@user/data-state';
|
||||
|
||||
export interface IMapHeroRenderer {
|
||||
/**
|
||||
* 设置勇士图片
|
||||
* @param image 勇士使用的图片
|
||||
*/
|
||||
setImage(image: ITexture): void;
|
||||
|
||||
/**
|
||||
* 添加跟随者
|
||||
* @param image 跟随者图块数字
|
||||
* @param id 跟随者的 id,用于删除操作
|
||||
*/
|
||||
addFollower(image: number, id: string): void;
|
||||
|
||||
/**
|
||||
* 取消跟随者
|
||||
* @param follower 跟随者的 id
|
||||
* @param animate 填 `true` 的话,如果删除了中间的跟随者,后续跟随者会使用移动动画移动到下一格,否则瞬移至下一格
|
||||
*/
|
||||
removeFollower(follower: string, animate: boolean): Promise<void>;
|
||||
|
||||
/**
|
||||
* 移除所有跟随者
|
||||
*/
|
||||
removeAllFollowers(): void;
|
||||
|
||||
/**
|
||||
* 设置勇士位置
|
||||
*/
|
||||
setPosition(x: number, y: number): void;
|
||||
|
||||
/**
|
||||
* 开始移动,在移动前需要调用此方法切换勇士状态
|
||||
*/
|
||||
startMove(): void;
|
||||
|
||||
/**
|
||||
* 等待勇士移动停止后,将移动状态切换为停止
|
||||
* @param waitFollower 是否也等待跟随者移动结束
|
||||
*/
|
||||
waitMoveEnd(waitFollower: boolean): Promise<void>;
|
||||
|
||||
/**
|
||||
* 立刻停止移动,勇士瞬移到目标点
|
||||
* @param stopFollower 是否也立刻停止跟随者的移动,此时跟随者也会瞬移到它们应该到达的地方
|
||||
*/
|
||||
stopMove(stopFollower: boolean): void;
|
||||
|
||||
/**
|
||||
* 勇士朝某个方向移动
|
||||
* @param direction 移动方向
|
||||
*/
|
||||
move(direction: FaceDirection, time: number): Promise<void>;
|
||||
|
||||
/**
|
||||
* 跳跃勇士至目标点
|
||||
* @param x 目标点横坐标
|
||||
* @param y 目标点纵坐标
|
||||
* @param time 跳跃时长
|
||||
* @param waitFollower 是否等待跟随者也跳跃完毕
|
||||
*/
|
||||
jumpTo(
|
||||
x: number,
|
||||
y: number,
|
||||
time: number,
|
||||
waitFollower: boolean
|
||||
): Promise<void>;
|
||||
|
||||
/**
|
||||
* 设置勇士不透明度
|
||||
* @param alpha 不透明度
|
||||
*/
|
||||
setAlpha(alpha: number): void;
|
||||
|
||||
/**
|
||||
* 设置跟随者的不透明度
|
||||
* @param identifier 跟随者标识符
|
||||
* @param alpha 跟随者不透明度
|
||||
*/
|
||||
setFollowerAlpha(identifier: string, alpha: number): void;
|
||||
|
||||
/**
|
||||
* 摧毁这个勇士渲染拓展,释放相关资源
|
||||
*/
|
||||
destroy(): void;
|
||||
}
|
||||
@ -1,3 +1,9 @@
|
||||
export * from './asset';
|
||||
export * from './block';
|
||||
export * from './constant';
|
||||
export * from './element';
|
||||
export * from './moving';
|
||||
export * from './renderer';
|
||||
export * from './status';
|
||||
export * from './types';
|
||||
export * from './vertex';
|
||||
export * from './viewport';
|
||||
|
||||
@ -24,11 +24,11 @@ export interface IMovingRenderer {
|
||||
}
|
||||
|
||||
export class MovingBlock extends DynamicBlockStatus implements IMovingBlock {
|
||||
readonly texture: IMaterialFramedData;
|
||||
readonly tile: number;
|
||||
readonly renderer: IMovingRenderer;
|
||||
readonly layer: IMapLayer;
|
||||
|
||||
texture: IMaterialFramedData;
|
||||
index: number;
|
||||
x: number = 0;
|
||||
y: number = 0;
|
||||
@ -100,12 +100,19 @@ export class MovingBlock extends DynamicBlockStatus implements IMovingBlock {
|
||||
this.posUpdated = true;
|
||||
}
|
||||
|
||||
setTexture(texture: IMaterialFramedData): void {
|
||||
if (texture === this.texture) return;
|
||||
this.texture = texture;
|
||||
this.renderer.vertex.updateMoving(this, true);
|
||||
}
|
||||
|
||||
lineTo(
|
||||
x: number,
|
||||
y: number,
|
||||
time: number,
|
||||
timing?: TimingFn
|
||||
): Promise<this> {
|
||||
if (!this.end) return Promise.resolve(this);
|
||||
this.startX = this.x;
|
||||
this.startY = this.y;
|
||||
this.targetX = x;
|
||||
@ -129,6 +136,7 @@ export class MovingBlock extends DynamicBlockStatus implements IMovingBlock {
|
||||
}
|
||||
|
||||
moveAs(curve: TimingFn<2>, time: number, timing?: TimingFn): Promise<this> {
|
||||
if (!this.end) return Promise.resolve(this);
|
||||
this.time = time;
|
||||
this.line = false;
|
||||
this.relative = false;
|
||||
@ -154,6 +162,7 @@ export class MovingBlock extends DynamicBlockStatus implements IMovingBlock {
|
||||
time: number,
|
||||
timing?: TimingFn
|
||||
): Promise<this> {
|
||||
if (!this.end) return Promise.resolve(this);
|
||||
this.time = time;
|
||||
this.line = false;
|
||||
this.relative = false;
|
||||
@ -225,6 +234,25 @@ export class MovingBlock extends DynamicBlockStatus implements IMovingBlock {
|
||||
return true;
|
||||
}
|
||||
|
||||
endMoving(): void {
|
||||
this.end = true;
|
||||
if (this.line) {
|
||||
this.x = this.targetX;
|
||||
this.y = this.targetY;
|
||||
} else {
|
||||
const [x, y] = this.curve(1);
|
||||
if (this.relative) {
|
||||
this.x = x + this.startX;
|
||||
this.y = y + this.startY;
|
||||
} else {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
}
|
||||
}
|
||||
this.promiseFunc();
|
||||
this.posUpdated = true;
|
||||
}
|
||||
|
||||
destroy(): void {
|
||||
this.renderer.deleteMoving(this);
|
||||
}
|
||||
|
||||
@ -18,6 +18,7 @@ import {
|
||||
IMapBackgroundConfig,
|
||||
IMapRenderConfig,
|
||||
IMapRenderer,
|
||||
IMapRendererTicker,
|
||||
IMapVertexGenerator,
|
||||
IMapViewportController,
|
||||
IMovingBlock,
|
||||
@ -161,6 +162,8 @@ export class MapRenderer
|
||||
private needUpdateFrameCounter: boolean = true;
|
||||
/** 帧动画速率 */
|
||||
private frameSpeed: number = 300;
|
||||
/** 帧动画列表 */
|
||||
private tickers: Set<MapRendererTicker> = new Set();
|
||||
|
||||
/** 画布元素 */
|
||||
readonly canvas: HTMLCanvasElement;
|
||||
@ -1494,6 +1497,16 @@ export class MapRenderer
|
||||
}
|
||||
}
|
||||
|
||||
requestTicker(fn: (timestamp: number) => void): IMapRendererTicker {
|
||||
const ticker = new MapRendererTicker(this, fn, this.timestamp);
|
||||
this.tickers.add(ticker);
|
||||
return ticker;
|
||||
}
|
||||
|
||||
removeTicker(ticker: MapRendererTicker): void {
|
||||
this.tickers.delete(ticker);
|
||||
}
|
||||
|
||||
updateTransform(): void {
|
||||
this.needUpdateTransform = true;
|
||||
}
|
||||
@ -1515,10 +1528,7 @@ export class MapRenderer
|
||||
class RendererLayerStateHook implements Partial<ILayerStateHooks> {
|
||||
constructor(readonly renderer: MapRenderer) {}
|
||||
|
||||
onChangeBackground(
|
||||
_: IHookController<ILayerStateHooks>,
|
||||
tile: number
|
||||
): void {
|
||||
onChangeBackground(tile: number): void {
|
||||
this.renderer.setTileBackground(tile);
|
||||
}
|
||||
|
||||
@ -1531,7 +1541,6 @@ class RendererLayerStateHook implements Partial<ILayerStateHooks> {
|
||||
}
|
||||
|
||||
onUpdateLayerArea(
|
||||
_: IHookController<ILayerStateHooks>,
|
||||
layer: IMapLayer,
|
||||
x: number,
|
||||
y: number,
|
||||
@ -1542,7 +1551,6 @@ class RendererLayerStateHook implements Partial<ILayerStateHooks> {
|
||||
}
|
||||
|
||||
onUpdateLayerBlock(
|
||||
_: IHookController<ILayerStateHooks>,
|
||||
layer: IMapLayer,
|
||||
block: number,
|
||||
x: number,
|
||||
@ -1551,3 +1559,20 @@ class RendererLayerStateHook implements Partial<ILayerStateHooks> {
|
||||
this.renderer.updateLayerBlock(layer, block, x, y);
|
||||
}
|
||||
}
|
||||
|
||||
class MapRendererTicker implements IMapRendererTicker {
|
||||
constructor(
|
||||
readonly renderer: MapRenderer,
|
||||
readonly fn: (timestamp: number) => void,
|
||||
public timestamp: number
|
||||
) {}
|
||||
|
||||
tick(timestamp: number) {
|
||||
this.timestamp = timestamp;
|
||||
this.fn(timestamp);
|
||||
}
|
||||
|
||||
remove(): void {
|
||||
this.renderer.removeTicker(this);
|
||||
}
|
||||
}
|
||||
|
||||
@ -198,6 +198,12 @@ export interface IMovingBlock extends IBlockStatus {
|
||||
*/
|
||||
setPos(x: number, y: number): void;
|
||||
|
||||
/**
|
||||
* 设置此移动图块使用的贴图,最好预先打包至图集中,否则动态重建图集会很耗时间
|
||||
* @param texture 贴图对象
|
||||
*/
|
||||
setTexture(texture: IMaterialFramedData): void;
|
||||
|
||||
/**
|
||||
* 沿直线移动到目标点
|
||||
* @param x 目标横坐标,可以填小数
|
||||
@ -236,12 +242,27 @@ export interface IMovingBlock extends IBlockStatus {
|
||||
*/
|
||||
stepMoving(timestamp: number): boolean;
|
||||
|
||||
/**
|
||||
* 立刻停止移动
|
||||
*/
|
||||
endMoving(): void;
|
||||
|
||||
/**
|
||||
* 摧毁这个移动图块对象,之后不会再显示到画面上
|
||||
*/
|
||||
destroy(): void;
|
||||
}
|
||||
|
||||
export interface IMapRendererTicker {
|
||||
/** 当前的时间戳 */
|
||||
readonly timestamp: number;
|
||||
|
||||
/**
|
||||
* 移除这个帧函数
|
||||
*/
|
||||
remove(): void;
|
||||
}
|
||||
|
||||
export interface IMapRenderer {
|
||||
/** 地图渲染器使用的资源管理器 */
|
||||
readonly manager: IMaterialManager;
|
||||
@ -456,6 +477,12 @@ export interface IMapRenderer {
|
||||
* 当前地图状态是否发生改变,需要更新
|
||||
*/
|
||||
needUpdate(): boolean;
|
||||
|
||||
/**
|
||||
* 添加一个每帧执行的函数
|
||||
* @param fn 每帧执行的函数
|
||||
*/
|
||||
requestTicker(fn: (timestamp: number) => void): IMapRendererTicker;
|
||||
}
|
||||
|
||||
export interface IMapVertexArray {
|
||||
|
||||
@ -48,6 +48,7 @@ import { mainUIController } from './controller';
|
||||
import { LayerGroup } from '../elements';
|
||||
import { isNil } from 'lodash-es';
|
||||
import { materials } from '@user/client-base';
|
||||
import { mainMapRenderer } from '../commonIns';
|
||||
|
||||
const MainScene = defineComponent(() => {
|
||||
//#region 基本定义
|
||||
@ -261,6 +262,7 @@ const MainScene = defineComponent(() => {
|
||||
return () => (
|
||||
<container id="main-scene" width={MAIN_WIDTH} height={MAIN_HEIGHT}>
|
||||
<sprite
|
||||
hidden
|
||||
render={testRender}
|
||||
loc={[180, 0, 480, 480]}
|
||||
zIndex={1000}
|
||||
@ -283,6 +285,7 @@ const MainScene = defineComponent(() => {
|
||||
onMove={moveMap}
|
||||
>
|
||||
<map-render
|
||||
renderer={mainMapRenderer}
|
||||
layerState={state.layer}
|
||||
loc={[0, 0, MAP_WIDTH, MAP_HEIGHT]}
|
||||
/>
|
||||
|
||||
63
packages-user/data-state/src/common/face.ts
Normal file
63
packages-user/data-state/src/common/face.ts
Normal file
@ -0,0 +1,63 @@
|
||||
import { logger } from '@motajs/common';
|
||||
import { IFaceData, IRoleFaceBinder } from './types';
|
||||
import { isNil } from 'lodash-es';
|
||||
import { FaceDirection } from '.';
|
||||
|
||||
interface FaceInfo {
|
||||
/** 此图块的朝向 */
|
||||
readonly face: FaceDirection;
|
||||
/** 此图块对应的映射 */
|
||||
readonly map: Map<FaceDirection, number>;
|
||||
}
|
||||
|
||||
export class RoleFaceBinder implements IRoleFaceBinder {
|
||||
/** 每个图块对应的朝向信息 */
|
||||
private faceMap: Map<number, FaceInfo> = new Map();
|
||||
/** 主要朝向映射 */
|
||||
private mainMap: Map<number, FaceDirection> = new Map();
|
||||
|
||||
malloc(identifier: number, main: FaceDirection): void {
|
||||
this.mainMap.set(identifier, main);
|
||||
const map = new Map<FaceDirection, number>();
|
||||
map.set(main, identifier);
|
||||
const info: FaceInfo = { face: main, map };
|
||||
this.faceMap.set(identifier, info);
|
||||
}
|
||||
|
||||
bind(identifier: number, main: number, face: FaceDirection): void {
|
||||
const mainFace = this.mainMap.get(main);
|
||||
if (isNil(mainFace)) {
|
||||
logger.error(43, main.toString());
|
||||
return;
|
||||
}
|
||||
if (mainFace === face) {
|
||||
logger.error(44, main.toString());
|
||||
return;
|
||||
}
|
||||
const { map } = this.faceMap.get(main)!;
|
||||
map.set(face, identifier);
|
||||
const info: FaceInfo = { face, map };
|
||||
this.faceMap.set(identifier, info);
|
||||
this.mainMap.set(identifier, mainFace);
|
||||
}
|
||||
|
||||
getFaceOf(identifier: number, face: FaceDirection): IFaceData | null {
|
||||
const info = this.faceMap.get(identifier);
|
||||
if (!info) return null;
|
||||
const target = info.map.get(face);
|
||||
if (isNil(target)) return null;
|
||||
const data: IFaceData = { identifier: target, face };
|
||||
return data;
|
||||
}
|
||||
|
||||
getFaceDirection(identifier: number): FaceDirection | undefined {
|
||||
return this.faceMap.get(identifier)?.face;
|
||||
}
|
||||
|
||||
getMainFace(identifier: number): IFaceData | null {
|
||||
const face = this.mainMap.get(identifier);
|
||||
if (isNil(face)) return null;
|
||||
const data: IFaceData = { identifier, face };
|
||||
return data;
|
||||
}
|
||||
}
|
||||
3
packages-user/data-state/src/common/index.ts
Normal file
3
packages-user/data-state/src/common/index.ts
Normal file
@ -0,0 +1,3 @@
|
||||
export * from './face';
|
||||
export * from './types';
|
||||
export * from './utils';
|
||||
54
packages-user/data-state/src/common/types.ts
Normal file
54
packages-user/data-state/src/common/types.ts
Normal file
@ -0,0 +1,54 @@
|
||||
export const enum FaceDirection {
|
||||
Unknown,
|
||||
Left,
|
||||
Up,
|
||||
Right,
|
||||
Down,
|
||||
LeftUp,
|
||||
RightUp,
|
||||
LeftDown,
|
||||
RightDown
|
||||
}
|
||||
|
||||
export interface IFaceData {
|
||||
/** 图块数字 */
|
||||
readonly identifier: number;
|
||||
/** 图块朝向 */
|
||||
readonly face: FaceDirection;
|
||||
}
|
||||
|
||||
export interface IRoleFaceBinder {
|
||||
/**
|
||||
* 给指定的图块分配朝向绑定
|
||||
* @param identifier 图块数字
|
||||
* @param main 主图块朝向,一般是朝下
|
||||
*/
|
||||
malloc(identifier: number, main: FaceDirection): void;
|
||||
|
||||
/**
|
||||
* 将一个图块与另一个图块绑定朝向,需要注意要先调用 {@link malloc} 分配朝向信息
|
||||
* @param identifier 当前图块数字
|
||||
* @param main 主图块数字,即当前图块与目标图块属于主图块的另一个朝向
|
||||
* @param face 当前图块的朝向方向
|
||||
*/
|
||||
bind(identifier: number, main: number, face: FaceDirection): void;
|
||||
|
||||
/**
|
||||
* 获取一个图块指定朝向的图块数字
|
||||
* @param identifier 图块数字,可以是任意朝向的图块数字
|
||||
* @param face 要获取的朝向
|
||||
*/
|
||||
getFaceOf(identifier: number, face: FaceDirection): IFaceData | null;
|
||||
|
||||
/**
|
||||
* 获取指定图块数字是哪个朝向
|
||||
* @param identifier 图块数字
|
||||
*/
|
||||
getFaceDirection(identifier: number): FaceDirection | undefined;
|
||||
|
||||
/**
|
||||
* 获取指定图块数字绑定至的主朝向
|
||||
* @param identifier 图块数字,可以是任意朝向的图块数字
|
||||
*/
|
||||
getMainFace(identifier: number): IFaceData | null;
|
||||
}
|
||||
52
packages-user/data-state/src/common/utils.ts
Normal file
52
packages-user/data-state/src/common/utils.ts
Normal file
@ -0,0 +1,52 @@
|
||||
import { FaceDirection } from './types';
|
||||
|
||||
/**
|
||||
* 获取指定朝向的坐标偏移量
|
||||
* @param dir 朝向
|
||||
*/
|
||||
export function getFaceMovement(dir: FaceDirection): Loc {
|
||||
switch (dir) {
|
||||
case FaceDirection.Left:
|
||||
return { x: -1, y: 0 };
|
||||
case FaceDirection.Right:
|
||||
return { x: 1, y: 0 };
|
||||
case FaceDirection.Up:
|
||||
return { x: 0, y: -1 };
|
||||
case FaceDirection.Down:
|
||||
return { x: 0, y: 1 };
|
||||
case FaceDirection.LeftUp:
|
||||
return { x: -1, y: -1 };
|
||||
case FaceDirection.RightUp:
|
||||
return { x: 1, y: -1 };
|
||||
case FaceDirection.LeftDown:
|
||||
return { x: -1, y: 1 };
|
||||
case FaceDirection.RightDown:
|
||||
return { x: 1, y: 1 };
|
||||
case FaceDirection.Unknown:
|
||||
return { x: 0, y: 0 };
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 将八方向朝向降级为四方向朝向
|
||||
* @param dir 朝向
|
||||
* @param unknown 如果朝向是 `FaceDirection.Unknown`,那么会返回什么,默认还是未知
|
||||
*/
|
||||
export function degradeFace(
|
||||
dir: FaceDirection,
|
||||
unknown: FaceDirection = FaceDirection.Unknown
|
||||
): FaceDirection {
|
||||
switch (dir) {
|
||||
case FaceDirection.LeftUp:
|
||||
return FaceDirection.Left;
|
||||
case FaceDirection.LeftDown:
|
||||
return FaceDirection.Left;
|
||||
case FaceDirection.RightUp:
|
||||
return FaceDirection.Right;
|
||||
case FaceDirection.RightDown:
|
||||
return FaceDirection.Right;
|
||||
case FaceDirection.Unknown:
|
||||
return unknown;
|
||||
}
|
||||
return dir;
|
||||
}
|
||||
33
packages-user/data-state/src/core.ts
Normal file
33
packages-user/data-state/src/core.ts
Normal file
@ -0,0 +1,33 @@
|
||||
import { ICoreState, IStateSaveData } from './types';
|
||||
import { IHeroState, HeroState } from './hero';
|
||||
import { ILayerState, LayerState } from './map';
|
||||
import { IRoleFaceBinder, RoleFaceBinder } from './common';
|
||||
|
||||
export class CoreState implements ICoreState {
|
||||
readonly layer: ILayerState;
|
||||
readonly hero: IHeroState;
|
||||
readonly roleFace: IRoleFaceBinder;
|
||||
readonly idNumberMap: Map<string, number>;
|
||||
readonly numberIdMap: Map<number, string>;
|
||||
|
||||
constructor() {
|
||||
this.layer = new LayerState();
|
||||
this.hero = new HeroState();
|
||||
this.roleFace = new RoleFaceBinder();
|
||||
this.idNumberMap = new Map();
|
||||
this.numberIdMap = new Map();
|
||||
}
|
||||
|
||||
saveState(): IStateSaveData {
|
||||
return structuredClone({
|
||||
followers: this.hero.followers
|
||||
});
|
||||
}
|
||||
|
||||
loadState(data: IStateSaveData): void {
|
||||
this.hero.removeAllFollowers();
|
||||
data.followers.forEach(v => {
|
||||
this.hero.addFollower(v.num, v.identifier);
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -1,10 +0,0 @@
|
||||
import { LayerState } from './layerState';
|
||||
import { ICoreState, ILayerState } from './types';
|
||||
|
||||
export class CoreState implements ICoreState {
|
||||
readonly layer: ILayerState;
|
||||
|
||||
constructor() {
|
||||
this.layer = new LayerState();
|
||||
}
|
||||
}
|
||||
@ -1,22 +0,0 @@
|
||||
import { CoreState } from './core';
|
||||
|
||||
export function createCoreState() {
|
||||
const width = core._WIDTH_;
|
||||
const height = core._HEIGHT_;
|
||||
const bg = state.layer.addLayer(width, height);
|
||||
const bg2 = state.layer.addLayer(width, height);
|
||||
const event = state.layer.addLayer(width, height);
|
||||
const fg = state.layer.addLayer(width, height);
|
||||
const fg2 = state.layer.addLayer(width, height);
|
||||
state.layer.setLayerAlias(bg, 'bg');
|
||||
state.layer.setLayerAlias(bg2, 'bg2');
|
||||
state.layer.setLayerAlias(event, 'event');
|
||||
state.layer.setLayerAlias(fg, 'fg');
|
||||
state.layer.setLayerAlias(fg2, 'fg2');
|
||||
}
|
||||
|
||||
export const state = new CoreState();
|
||||
|
||||
export * from './core';
|
||||
export * from './layerState';
|
||||
export * from './types';
|
||||
@ -1,222 +0,0 @@
|
||||
import { IHookable, IHookBase, IHookController } from '@motajs/common';
|
||||
import { IMapLayer } from '../map';
|
||||
|
||||
export interface ICoreState {
|
||||
/** 地图状态 */
|
||||
readonly layer: ILayerState;
|
||||
}
|
||||
|
||||
export interface ILayerStateHooks extends IHookBase {
|
||||
/**
|
||||
* 当设置背景图块时执行,如果设置的背景图块与原先一样,则不会执行
|
||||
* @param controller 钩子控制器
|
||||
* @param tile 背景图块
|
||||
*/
|
||||
onChangeBackground(controller: IHookController<this>, tile: number): void;
|
||||
|
||||
/**
|
||||
* 当地图列表发生变化时执行
|
||||
* @param controller 钩子控制器
|
||||
* @param layerList 地图图层列表
|
||||
*/
|
||||
onUpdateLayer(
|
||||
controller: IHookController<this>,
|
||||
layerList: Set<IMapLayer>
|
||||
): void;
|
||||
|
||||
/**
|
||||
* 当地图状态对象的某个图层发生区域更新时执行
|
||||
* @param controller 钩子控制器
|
||||
* @param layer 触发更新的地图图层对象
|
||||
* @param x 更新区域左上角横坐标
|
||||
* @param y 更新区域左上角纵坐标
|
||||
* @param width 更新区域宽度
|
||||
* @param height 更新区域高度
|
||||
*/
|
||||
onUpdateLayerArea(
|
||||
controller: IHookController<this>,
|
||||
layer: IMapLayer,
|
||||
x: number,
|
||||
y: number,
|
||||
width: number,
|
||||
height: number
|
||||
): void;
|
||||
|
||||
/**
|
||||
* 当地图状态对象的某个图层设置图块时执行,如果设置的图块与原先一样则不会触发
|
||||
* @param controller 钩子控制器
|
||||
* @param layer 触发更新的地图图层对象
|
||||
* @param block 设置为的图块
|
||||
* @param x 图块横坐标
|
||||
* @param y 图块纵坐标
|
||||
*/
|
||||
onUpdateLayerBlock(
|
||||
controller: IHookController<this>,
|
||||
layer: IMapLayer,
|
||||
block: number,
|
||||
x: number,
|
||||
y: number
|
||||
): void;
|
||||
|
||||
/**
|
||||
* 当地图状态对象的某个图层大小发生变化时执行
|
||||
* @param controller 钩子控制器
|
||||
* @param layer 触发更新的地图图层对象
|
||||
* @param width 地图的新宽度
|
||||
* @param height 地图的新高度
|
||||
*/
|
||||
onResizeLayer(
|
||||
controller: IHookController<this>,
|
||||
layer: IMapLayer,
|
||||
width: number,
|
||||
height: number
|
||||
): void;
|
||||
}
|
||||
|
||||
export interface ILayerState extends IHookable<ILayerStateHooks> {
|
||||
/** 地图列表 */
|
||||
readonly layerList: Set<IMapLayer>;
|
||||
|
||||
/**
|
||||
* 添加图层
|
||||
* @param width 地图宽度
|
||||
* @param height 地图高度
|
||||
*/
|
||||
addLayer(width: number, height: number): IMapLayer;
|
||||
|
||||
/**
|
||||
* 移除指定图层
|
||||
* @param layer 图层对象
|
||||
*/
|
||||
removeLayer(layer: IMapLayer): void;
|
||||
|
||||
/**
|
||||
* 当前地图状态对象是否包含指定图层对象
|
||||
* @param layer 图层对象
|
||||
*/
|
||||
hasLayer(layer: IMapLayer): boolean;
|
||||
|
||||
/**
|
||||
* 设置图层别名
|
||||
* @param layer 图层对象
|
||||
* @param alias 图层别名
|
||||
*/
|
||||
setLayerAlias(layer: IMapLayer, alias: string): void;
|
||||
|
||||
/**
|
||||
* 根据图层别名获取图层对象
|
||||
* @param alias 图层别名
|
||||
*/
|
||||
getLayerByAlias(alias: string): IMapLayer | null;
|
||||
|
||||
/**
|
||||
* 获取图层对象的别名
|
||||
* @param layer 图层对象
|
||||
*/
|
||||
getLayerAlias(layer: IMapLayer): string | undefined;
|
||||
|
||||
/**
|
||||
* 重新设置图层的大小
|
||||
* @param layer 图层对象
|
||||
* @param width 新的图层宽度
|
||||
* @param height 新的图层高度
|
||||
* @param keepBlock 是否保留原有图块,默认不保留
|
||||
*/
|
||||
resizeLayer(
|
||||
layer: IMapLayer,
|
||||
width: number,
|
||||
height: number,
|
||||
keepBlock?: boolean
|
||||
): void;
|
||||
|
||||
/**
|
||||
* 设置背景图块
|
||||
* @param tile 背景图块数字
|
||||
*/
|
||||
setBackground(tile: number): void;
|
||||
|
||||
/**
|
||||
* 获取背景图块数字,如果没有设置过,则返回 0
|
||||
*/
|
||||
getBackground(): number;
|
||||
}
|
||||
|
||||
export const enum HeroDirection {
|
||||
Left,
|
||||
Up,
|
||||
Right,
|
||||
Down
|
||||
}
|
||||
|
||||
export interface IHeroStateHooks extends IHookBase {
|
||||
/**
|
||||
* 当设置勇士的坐标时触发
|
||||
* @param controller 钩子控制器
|
||||
* @param x 勇士横坐标
|
||||
* @param y 勇士纵坐标
|
||||
*/
|
||||
onSetPosition(
|
||||
controller: IHookController<this>,
|
||||
x: number,
|
||||
y: number
|
||||
): void;
|
||||
|
||||
/**
|
||||
* 当移动勇士时触发
|
||||
* @param controller 钩子控制器
|
||||
* @param direction 移动方向
|
||||
* @param time 移动动画时长
|
||||
*/
|
||||
onMoveHero(
|
||||
controller: IHookController<this>,
|
||||
direction: HeroDirection,
|
||||
time: number
|
||||
): Promise<void>;
|
||||
|
||||
/**
|
||||
* 当勇士跳跃时触发
|
||||
* @param controller 钩子控制器
|
||||
* @param x 目标点横坐标
|
||||
* @param y 目标点纵坐标
|
||||
* @param time 跳跃动画时长
|
||||
*/
|
||||
onJumpHero(
|
||||
controller: IHookController<this>,
|
||||
x: number,
|
||||
y: number,
|
||||
time: number
|
||||
): Promise<void>;
|
||||
}
|
||||
|
||||
export interface IHeroState extends IHookable<IHeroStateHooks> {
|
||||
/** 勇士横坐标 */
|
||||
readonly x: number;
|
||||
/** 勇士纵坐标 */
|
||||
readonly y: number;
|
||||
/** 勇士朝向 */
|
||||
readonly direction: HeroDirection;
|
||||
|
||||
/**
|
||||
* 设置勇士位置
|
||||
* @param x 横坐标
|
||||
* @param y 纵坐标
|
||||
*/
|
||||
setPosition(x: number, y: number): void;
|
||||
|
||||
/**
|
||||
* 移动勇士
|
||||
* @param dir 移动方向
|
||||
* @param time 移动动画时长,默认 100ms
|
||||
* @returns 移动的 `Promise`,当相关的移动动画结束后兑现
|
||||
*/
|
||||
move(dir: HeroDirection, time?: number): Promise<void>;
|
||||
|
||||
/**
|
||||
* 跳跃勇士至目标点
|
||||
* @param x 目标点横坐标
|
||||
* @param y 目标点纵坐标
|
||||
* @param time 跳跃动画时长,默认 500ms
|
||||
* @returns 跳跃的 `Promise`,当相关的移动动画结束后兑现
|
||||
*/
|
||||
jumpHero(x: number, y: number, time?: number): Promise<void>;
|
||||
}
|
||||
@ -1,4 +1,4 @@
|
||||
import { getHeroStatusOf, getHeroStatusOn } from '../state/hero';
|
||||
import { getHeroStatusOf, getHeroStatusOn } from '../legacy/hero';
|
||||
import { Range, ensureArray, has, manhattan } from '@user/data-utils';
|
||||
import EventEmitter from 'eventemitter3';
|
||||
import { hook } from '@user/data-base';
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { getHeroStatusOn } from '../state/hero';
|
||||
import { getHeroStatusOn } from '../legacy/hero';
|
||||
import { UserEnemyInfo } from './damage';
|
||||
|
||||
export interface SpecialDeclaration {
|
||||
|
||||
2
packages-user/data-state/src/hero/index.ts
Normal file
2
packages-user/data-state/src/hero/index.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export * from './state';
|
||||
export * from './types';
|
||||
121
packages-user/data-state/src/hero/state.ts
Normal file
121
packages-user/data-state/src/hero/state.ts
Normal file
@ -0,0 +1,121 @@
|
||||
import { Hookable, HookController, IHookController } from '@motajs/common';
|
||||
import { IHeroFollower, IHeroState, IHeroStateHooks } from './types';
|
||||
import { FaceDirection, getFaceMovement } from '../common';
|
||||
|
||||
export class HeroState extends Hookable<IHeroStateHooks> implements IHeroState {
|
||||
x: number = 0;
|
||||
y: number = 0;
|
||||
direction: FaceDirection = FaceDirection.Down;
|
||||
image?: ImageIds;
|
||||
|
||||
/** 当前勇士是否正在移动 */
|
||||
moving: boolean = false;
|
||||
alpha: number = 1;
|
||||
|
||||
readonly followers: IHeroFollower[] = [];
|
||||
|
||||
protected createController(
|
||||
hook: Partial<IHeroStateHooks>
|
||||
): IHookController<IHeroStateHooks> {
|
||||
return new HookController(this, hook);
|
||||
}
|
||||
|
||||
setPosition(x: number, y: number): void {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.forEachHook(hook => {
|
||||
hook.onSetPosition?.(x, y);
|
||||
});
|
||||
}
|
||||
|
||||
startMove(): void {
|
||||
this.moving = true;
|
||||
this.forEachHook(hook => {
|
||||
hook.onStartMove?.();
|
||||
});
|
||||
}
|
||||
|
||||
async move(dir: FaceDirection, time: number = 100): Promise<void> {
|
||||
await Promise.all(
|
||||
this.forEachHook(hook => {
|
||||
return hook.onMoveHero?.(dir, time);
|
||||
})
|
||||
);
|
||||
const { x, y } = getFaceMovement(dir);
|
||||
this.x += x;
|
||||
this.y += y;
|
||||
}
|
||||
|
||||
async endMove(waitFollower: boolean = false): Promise<void> {
|
||||
if (!this.moving) return;
|
||||
await Promise.all(
|
||||
this.forEachHook(hook => {
|
||||
return hook.onEndMove?.(waitFollower);
|
||||
})
|
||||
);
|
||||
this.moving = false;
|
||||
}
|
||||
|
||||
async jumpHero(
|
||||
x: number,
|
||||
y: number,
|
||||
time: number = 500,
|
||||
waitFollower: boolean = false
|
||||
): Promise<void> {
|
||||
await Promise.all(
|
||||
this.forEachHook(hook => {
|
||||
return hook.onJumpHero?.(x, y, time, waitFollower);
|
||||
})
|
||||
);
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
}
|
||||
|
||||
setImage(image: ImageIds): void {
|
||||
this.image = image;
|
||||
this.forEachHook(hook => {
|
||||
hook.onSetImage?.(image);
|
||||
});
|
||||
}
|
||||
|
||||
setAlpha(alpha: number): void {
|
||||
this.alpha = alpha;
|
||||
this.forEachHook(hook => {
|
||||
hook.onSetAlpha?.(alpha);
|
||||
});
|
||||
}
|
||||
|
||||
setFollowerAlpha(identifier: string, alpha: number): void {
|
||||
const follower = this.followers.find(v => v.identifier === identifier);
|
||||
if (!follower) return;
|
||||
follower.alpha = alpha;
|
||||
this.forEachHook(hook => {
|
||||
hook.onSetFollowerAlpha?.(identifier, alpha);
|
||||
});
|
||||
}
|
||||
|
||||
addFollower(follower: number, identifier: string): void {
|
||||
this.followers.push({ num: follower, identifier, alpha: 1 });
|
||||
this.forEachHook(hook => {
|
||||
hook.onAddFollower?.(follower, identifier);
|
||||
});
|
||||
}
|
||||
|
||||
removeFollower(identifier: string, animate: boolean = false): void {
|
||||
const index = this.followers.findIndex(
|
||||
v => v.identifier === identifier
|
||||
);
|
||||
if (index === -1) return;
|
||||
this.followers.splice(index, 1);
|
||||
this.forEachHook(hook => {
|
||||
hook.onRemoveFollower?.(identifier, animate);
|
||||
});
|
||||
}
|
||||
|
||||
removeAllFollowers(): void {
|
||||
this.followers.length = 0;
|
||||
this.forEachHook(hook => {
|
||||
hook.onRemoveAllFollowers?.();
|
||||
});
|
||||
}
|
||||
}
|
||||
187
packages-user/data-state/src/hero/types.ts
Normal file
187
packages-user/data-state/src/hero/types.ts
Normal file
@ -0,0 +1,187 @@
|
||||
import { IHookBase, IHookable } from '@motajs/common';
|
||||
import { FaceDirection } from '../common/types';
|
||||
|
||||
export interface IHeroFollower {
|
||||
/** 跟随者的图块数字 */
|
||||
readonly num: number;
|
||||
/** 跟随者的标识符 */
|
||||
readonly identifier: string;
|
||||
/** 跟随者的不透明度 */
|
||||
alpha: number;
|
||||
}
|
||||
|
||||
export interface IHeroStateHooks extends IHookBase {
|
||||
/**
|
||||
* 当设置勇士的坐标时触发
|
||||
* @param controller 钩子控制器
|
||||
* @param x 勇士横坐标
|
||||
* @param y 勇士纵坐标
|
||||
*/
|
||||
onSetPosition(x: number, y: number): void;
|
||||
|
||||
/**
|
||||
* 当勇士开始移动时触发
|
||||
*/
|
||||
onStartMove(): void;
|
||||
|
||||
/**
|
||||
* 当移动勇士时触发
|
||||
* @param controller 钩子控制器
|
||||
* @param direction 移动方向
|
||||
* @param time 移动动画时长
|
||||
*/
|
||||
onMoveHero(direction: FaceDirection, time: number): Promise<void>;
|
||||
|
||||
/**
|
||||
* 当停止移动时触发
|
||||
* @param waitFollower 是否等待跟随者
|
||||
*/
|
||||
onEndMove(waitFollower: boolean): Promise<void>;
|
||||
|
||||
/**
|
||||
* 当勇士跳跃时触发
|
||||
* @param x 目标点横坐标
|
||||
* @param y 目标点纵坐标
|
||||
* @param time 跳跃动画时长
|
||||
* @param waitFollower 是否等待跟随者跳跃完毕
|
||||
*/
|
||||
onJumpHero(
|
||||
x: number,
|
||||
y: number,
|
||||
time: number,
|
||||
waitFollower: boolean
|
||||
): Promise<void>;
|
||||
|
||||
/**
|
||||
* 当设置勇士图片时触发
|
||||
* @param image 勇士图片 id
|
||||
*/
|
||||
onSetImage(image: ImageIds): void;
|
||||
|
||||
/**
|
||||
* 当设置勇士不透明度时执行
|
||||
* @param alpha 不透明度
|
||||
*/
|
||||
onSetAlpha(alpha: number): void;
|
||||
|
||||
/**
|
||||
* 添加跟随者时触发
|
||||
* @param follower 跟随者的图块数字
|
||||
* @param identifier 跟随者的标识符
|
||||
*/
|
||||
onAddFollower(follower: number, identifier: string): void;
|
||||
|
||||
/**
|
||||
* 当移除跟随者时触发
|
||||
* @param identifier 跟随者的标识符
|
||||
* @param animate 填 `true` 的话,如果删除了中间的跟随者,后续跟随者会使用移动动画移动到下一格,否则瞬移至下一格
|
||||
*/
|
||||
onRemoveFollower(identifier: string, animate: boolean): void;
|
||||
|
||||
/**
|
||||
* 当移除所有跟随者时触发
|
||||
*/
|
||||
onRemoveAllFollowers(): void;
|
||||
|
||||
/**
|
||||
* 设置跟随者的不透明度
|
||||
* @param identifier 跟随者标识符
|
||||
* @param alpha 跟随者不透明度
|
||||
*/
|
||||
onSetFollowerAlpha(identifier: string, alpha: number): void;
|
||||
}
|
||||
|
||||
export interface IHeroState extends IHookable<IHeroStateHooks> {
|
||||
/** 勇士横坐标 */
|
||||
readonly x: number;
|
||||
/** 勇士纵坐标 */
|
||||
readonly y: number;
|
||||
/** 勇士朝向 */
|
||||
readonly direction: FaceDirection;
|
||||
/** 勇士图片 */
|
||||
readonly image?: ImageIds;
|
||||
/** 跟随者列表 */
|
||||
readonly followers: readonly IHeroFollower[];
|
||||
/** 勇士当前的不透明度 */
|
||||
readonly alpha: number;
|
||||
|
||||
/**
|
||||
* 设置勇士位置
|
||||
* @param x 横坐标
|
||||
* @param y 纵坐标
|
||||
*/
|
||||
setPosition(x: number, y: number): void;
|
||||
|
||||
/**
|
||||
* 开始勇士移动,在移动前必须先调用此方法将勇士切换为移动状态
|
||||
*/
|
||||
startMove(): void;
|
||||
|
||||
/**
|
||||
* 移动勇士。能否移动的逻辑暂时不在这里,目前作为过渡作用,仅服务于渲染
|
||||
* @param dir 移动方向
|
||||
* @param time 移动动画时长,默认 100ms
|
||||
* @returns 移动的 `Promise`,当相关的移动动画结束后兑现
|
||||
*/
|
||||
move(dir: FaceDirection, time?: number): Promise<void>;
|
||||
|
||||
/**
|
||||
* 结束勇士移动
|
||||
* @param waitFollower 是否等待跟随者,默认不等待
|
||||
* @returns 当移动动画结束后兑现的 `Promise`
|
||||
*/
|
||||
endMove(waitFollower?: boolean): Promise<void>;
|
||||
|
||||
/**
|
||||
* 跳跃勇士至目标点
|
||||
* @param x 目标点横坐标
|
||||
* @param y 目标点纵坐标
|
||||
* @param time 跳跃动画时长,默认 500ms
|
||||
* @param waitFollower 是否等待跟随者跳跃完毕,默认不等待
|
||||
* @returns 跳跃的 `Promise`,当相关的移动动画结束后兑现
|
||||
*/
|
||||
jumpHero(
|
||||
x: number,
|
||||
y: number,
|
||||
time?: number,
|
||||
waitFollower?: boolean
|
||||
): Promise<void>;
|
||||
|
||||
/**
|
||||
* 设置勇士图片
|
||||
* @param image 图片 id
|
||||
*/
|
||||
setImage(image: ImageIds): void;
|
||||
|
||||
/**
|
||||
* 设置勇士的不透明度
|
||||
* @param alpha 不透明度
|
||||
*/
|
||||
setAlpha(alpha: number): void;
|
||||
|
||||
/**
|
||||
* 添加一个跟随者
|
||||
* @param follower 跟随者的图块数字
|
||||
* @param identifier 跟随者的标识符,可以用来移除
|
||||
*/
|
||||
addFollower(follower: number, identifier: string): void;
|
||||
|
||||
/**
|
||||
* 移除指定的跟随者
|
||||
* @param identifier 跟随者的标识符
|
||||
* @param animate 填 `true` 的话,如果删除了中间的跟随者,后续跟随者会使用移动动画移动到下一格,否则瞬移至下一格
|
||||
*/
|
||||
removeFollower(identifier: string, animate?: boolean): void;
|
||||
|
||||
/**
|
||||
* 移除所有跟随者
|
||||
*/
|
||||
removeAllFollowers(): void;
|
||||
|
||||
/**
|
||||
* 设置指定跟随者的不透明度
|
||||
* @param identifier 跟随者标识符
|
||||
* @param alpha 跟随者不透明度
|
||||
*/
|
||||
setFollowerAlpha(identifier: string, alpha: number): void;
|
||||
}
|
||||
@ -1,6 +1,59 @@
|
||||
import { loading } from '@user/data-base';
|
||||
import { createMechanism } from './mechanism';
|
||||
import { createCoreState } from './core';
|
||||
import { CoreState } from './core';
|
||||
import { isNil } from 'lodash-es';
|
||||
import { FaceDirection } from './common';
|
||||
|
||||
function createCoreState() {
|
||||
//#region 地图部分
|
||||
|
||||
const width = core._WIDTH_;
|
||||
const height = core._HEIGHT_;
|
||||
const bg = state.layer.addLayer(width, height);
|
||||
const bg2 = state.layer.addLayer(width, height);
|
||||
const event = state.layer.addLayer(width, height);
|
||||
const fg = state.layer.addLayer(width, height);
|
||||
const fg2 = state.layer.addLayer(width, height);
|
||||
state.layer.setLayerAlias(bg, 'bg');
|
||||
state.layer.setLayerAlias(bg2, 'bg2');
|
||||
state.layer.setLayerAlias(event, 'event');
|
||||
state.layer.setLayerAlias(fg, 'fg');
|
||||
state.layer.setLayerAlias(fg2, 'fg2');
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region 图块部分
|
||||
|
||||
loading.once('coreInit', () => {
|
||||
const data = Object.entries(core.maps.blocksInfo);
|
||||
for (const [key, block] of data) {
|
||||
const num = Number(key);
|
||||
state.idNumberMap.set(block.id, num);
|
||||
state.numberIdMap.set(num, block.id);
|
||||
}
|
||||
for (const [key, block] of data) {
|
||||
if (!block.faceIds) continue;
|
||||
const { down, up, left, right } = block.faceIds;
|
||||
const downNum = state.idNumberMap.get(down);
|
||||
if (downNum !== Number(key)) continue;
|
||||
const upNum = state.idNumberMap.get(up);
|
||||
const leftNum = state.idNumberMap.get(left);
|
||||
const rightNum = state.idNumberMap.get(right);
|
||||
state.roleFace.malloc(downNum, FaceDirection.Down);
|
||||
if (!isNil(upNum)) {
|
||||
state.roleFace.bind(upNum, downNum, FaceDirection.Up);
|
||||
}
|
||||
if (!isNil(leftNum)) {
|
||||
state.roleFace.bind(leftNum, downNum, FaceDirection.Left);
|
||||
}
|
||||
if (!isNil(rightNum)) {
|
||||
state.roleFace.bind(rightNum, downNum, FaceDirection.Right);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
//#endregion
|
||||
}
|
||||
|
||||
export function create() {
|
||||
createMechanism();
|
||||
@ -10,8 +63,17 @@ export function create() {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 数据端核心状态,目前处于过渡阶段,仅服务于渲染,不负责任何逻辑计算,会在后续把核心逻辑逐渐移动至此对象。
|
||||
* 此对象是数据端状态,本身不负责任何渲染操作,仅会向渲染端发送数据让渲染端渲染,不要把渲染操作直接放到此对象上,
|
||||
* 否则可能导致录像验证失败。
|
||||
*/
|
||||
export const state = new CoreState();
|
||||
|
||||
export * from './common';
|
||||
export * from './core';
|
||||
export * from './enemy';
|
||||
export * from './hero';
|
||||
export * from './map';
|
||||
export * from './mechanism';
|
||||
export * from './state';
|
||||
export * from './legacy';
|
||||
|
||||
@ -713,7 +713,7 @@ export class HeroMover extends ObjectMoverBase {
|
||||
core.status.automaticRoute.moveStepBeforeStop = [];
|
||||
core.status.automaticRoute.lastDirection = dir;
|
||||
|
||||
if (core.status.automaticRoute.moveStepBeforeStop.length == 0) {
|
||||
if (core.status.automaticRoute.moveStepBeforeStop.length === 0) {
|
||||
core.clearContinueAutomaticRoute();
|
||||
core.stopAutomaticRoute();
|
||||
}
|
||||
@ -1,2 +1,3 @@
|
||||
export * from './layerState';
|
||||
export * from './mapLayer';
|
||||
export * from './types';
|
||||
|
||||
@ -5,12 +5,13 @@ import {
|
||||
logger
|
||||
} from '@motajs/common';
|
||||
import {
|
||||
ILayerState,
|
||||
ILayerStateHooks,
|
||||
IMapLayer,
|
||||
IMapLayerHookController,
|
||||
IMapLayerHooks,
|
||||
MapLayer
|
||||
} from '../map';
|
||||
import { ILayerState, ILayerStateHooks } from './types';
|
||||
IMapLayerHooks
|
||||
} from './types';
|
||||
import { MapLayer } from './mapLayer';
|
||||
|
||||
export class LayerState
|
||||
extends Hookable<ILayerStateHooks>
|
||||
@ -32,8 +33,8 @@ export class LayerState
|
||||
const array = new Uint32Array(width * height);
|
||||
const layer = new MapLayer(array, width, height);
|
||||
this.layerList.add(layer);
|
||||
this.forEachHook((hook, controller) => {
|
||||
hook.onUpdateLayer?.(controller, this.layerList);
|
||||
this.forEachHook(hook => {
|
||||
hook.onUpdateLayer?.(this.layerList);
|
||||
});
|
||||
const controller = layer.addHook(new StateMapLayerHook(this));
|
||||
this.layerHookMap.set(layer, controller);
|
||||
@ -49,8 +50,8 @@ export class LayerState
|
||||
this.aliasLayerMap.delete(symbol);
|
||||
this.layerAliasMap.delete(layer);
|
||||
}
|
||||
this.forEachHook((hook, controller) => {
|
||||
hook.onUpdateLayer?.(controller, this.layerList);
|
||||
this.forEachHook(hook => {
|
||||
hook.onUpdateLayer?.(this.layerList);
|
||||
});
|
||||
const controller = this.layerHookMap.get(layer);
|
||||
if (!controller) return;
|
||||
@ -96,8 +97,8 @@ export class LayerState
|
||||
|
||||
setBackground(tile: number): void {
|
||||
this.backgroundTile = tile;
|
||||
this.forEachHook((hook, controller) => {
|
||||
hook.onChangeBackground?.(controller, tile);
|
||||
this.forEachHook(hook => {
|
||||
hook.onChangeBackground?.(tile);
|
||||
});
|
||||
}
|
||||
|
||||
@ -122,8 +123,8 @@ class StateMapLayerHook implements Partial<IMapLayerHooks> {
|
||||
width: number,
|
||||
height: number
|
||||
): void {
|
||||
this.state.forEachHook((hook, c) => {
|
||||
hook.onUpdateLayerArea?.(c, controller.layer, x, y, width, height);
|
||||
this.state.forEachHook(hook => {
|
||||
hook.onUpdateLayerArea?.(controller.layer, x, y, width, height);
|
||||
});
|
||||
}
|
||||
|
||||
@ -133,8 +134,8 @@ class StateMapLayerHook implements Partial<IMapLayerHooks> {
|
||||
x: number,
|
||||
y: number
|
||||
): void {
|
||||
this.state.forEachHook((hook, c) => {
|
||||
hook.onUpdateLayerBlock?.(c, controller.layer, block, x, y);
|
||||
this.state.forEachHook(hook => {
|
||||
hook.onUpdateLayerBlock?.(controller.layer, block, x, y);
|
||||
});
|
||||
}
|
||||
|
||||
@ -143,8 +144,8 @@ class StateMapLayerHook implements Partial<IMapLayerHooks> {
|
||||
width: number,
|
||||
height: number
|
||||
): void {
|
||||
this.state.forEachHook((hook, c) => {
|
||||
hook.onResizeLayer?.(c, controller.layer, width, height);
|
||||
this.state.forEachHook(hook => {
|
||||
hook.onResizeLayer?.(controller.layer, width, height);
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -144,3 +144,123 @@ export interface IMapLayer
|
||||
*/
|
||||
setZIndex(zIndex: number): void;
|
||||
}
|
||||
|
||||
export interface ILayerStateHooks extends IHookBase {
|
||||
/**
|
||||
* 当设置背景图块时执行,如果设置的背景图块与原先一样,则不会执行
|
||||
* @param tile 背景图块
|
||||
*/
|
||||
onChangeBackground(tile: number): void;
|
||||
|
||||
/**
|
||||
* 当地图列表发生变化时执行
|
||||
* @param layerList 地图图层列表
|
||||
*/
|
||||
onUpdateLayer(layerList: Set<IMapLayer>): void;
|
||||
|
||||
/**
|
||||
* 当地图状态对象的某个图层发生区域更新时执行
|
||||
* @param layer 触发更新的地图图层对象
|
||||
* @param x 更新区域左上角横坐标
|
||||
* @param y 更新区域左上角纵坐标
|
||||
* @param width 更新区域宽度
|
||||
* @param height 更新区域高度
|
||||
*/
|
||||
onUpdateLayerArea(
|
||||
layer: IMapLayer,
|
||||
x: number,
|
||||
y: number,
|
||||
width: number,
|
||||
height: number
|
||||
): void;
|
||||
|
||||
/**
|
||||
* 当地图状态对象的某个图层设置图块时执行,如果设置的图块与原先一样则不会触发
|
||||
* @param layer 触发更新的地图图层对象
|
||||
* @param block 设置为的图块
|
||||
* @param x 图块横坐标
|
||||
* @param y 图块纵坐标
|
||||
*/
|
||||
onUpdateLayerBlock(
|
||||
layer: IMapLayer,
|
||||
block: number,
|
||||
x: number,
|
||||
y: number
|
||||
): void;
|
||||
|
||||
/**
|
||||
* 当地图状态对象的某个图层大小发生变化时执行
|
||||
* @param layer 触发更新的地图图层对象
|
||||
* @param width 地图的新宽度
|
||||
* @param height 地图的新高度
|
||||
*/
|
||||
onResizeLayer(layer: IMapLayer, width: number, height: number): void;
|
||||
}
|
||||
|
||||
export interface ILayerState extends IHookable<ILayerStateHooks> {
|
||||
/** 地图列表 */
|
||||
readonly layerList: Set<IMapLayer>;
|
||||
|
||||
/**
|
||||
* 添加图层
|
||||
* @param width 地图宽度
|
||||
* @param height 地图高度
|
||||
*/
|
||||
addLayer(width: number, height: number): IMapLayer;
|
||||
|
||||
/**
|
||||
* 移除指定图层
|
||||
* @param layer 图层对象
|
||||
*/
|
||||
removeLayer(layer: IMapLayer): void;
|
||||
|
||||
/**
|
||||
* 当前地图状态对象是否包含指定图层对象
|
||||
* @param layer 图层对象
|
||||
*/
|
||||
hasLayer(layer: IMapLayer): boolean;
|
||||
|
||||
/**
|
||||
* 设置图层别名
|
||||
* @param layer 图层对象
|
||||
* @param alias 图层别名
|
||||
*/
|
||||
setLayerAlias(layer: IMapLayer, alias: string): void;
|
||||
|
||||
/**
|
||||
* 根据图层别名获取图层对象
|
||||
* @param alias 图层别名
|
||||
*/
|
||||
getLayerByAlias(alias: string): IMapLayer | null;
|
||||
|
||||
/**
|
||||
* 获取图层对象的别名
|
||||
* @param layer 图层对象
|
||||
*/
|
||||
getLayerAlias(layer: IMapLayer): string | undefined;
|
||||
|
||||
/**
|
||||
* 重新设置图层的大小
|
||||
* @param layer 图层对象
|
||||
* @param width 新的图层宽度
|
||||
* @param height 新的图层高度
|
||||
* @param keepBlock 是否保留原有图块,默认不保留
|
||||
*/
|
||||
resizeLayer(
|
||||
layer: IMapLayer,
|
||||
width: number,
|
||||
height: number,
|
||||
keepBlock?: boolean
|
||||
): void;
|
||||
|
||||
/**
|
||||
* 设置背景图块
|
||||
* @param tile 背景图块数字
|
||||
*/
|
||||
setBackground(tile: number): void;
|
||||
|
||||
/**
|
||||
* 获取背景图块数字,如果没有设置过,则返回 0
|
||||
*/
|
||||
getBackground(): number;
|
||||
}
|
||||
|
||||
25
packages-user/data-state/src/types.ts
Normal file
25
packages-user/data-state/src/types.ts
Normal file
@ -0,0 +1,25 @@
|
||||
import { ILayerState } from './map';
|
||||
import { IHeroFollower, IHeroState } from './hero';
|
||||
import { IRoleFaceBinder } from './common';
|
||||
|
||||
export interface IStateSaveData {
|
||||
/** 跟随者列表 */
|
||||
readonly followers: readonly IHeroFollower[];
|
||||
}
|
||||
|
||||
export interface ICoreState {
|
||||
/** 地图状态 */
|
||||
readonly layer: ILayerState;
|
||||
/** 勇士状态 */
|
||||
readonly hero: IHeroState;
|
||||
/** 朝向绑定 */
|
||||
readonly roleFace: IRoleFaceBinder;
|
||||
/** id 到图块数字的映射 */
|
||||
readonly idNumberMap: Map<string, number>;
|
||||
/** 图块数字到 id 的映射 */
|
||||
readonly numberIdMap: Map<number, string>;
|
||||
|
||||
saveState(): IStateSaveData;
|
||||
|
||||
loadState(data: IStateSaveData): void;
|
||||
}
|
||||
@ -61,8 +61,12 @@ export abstract class Hookable<
|
||||
this.hookMap.delete(obj.hook);
|
||||
}
|
||||
|
||||
forEachHook(fn: (hook: Partial<H>, controller: C) => void): void {
|
||||
this.loadedList.forEach(v => fn(v.hook, v.controller));
|
||||
forEachHook<T>(fn: (hook: Partial<H>, controller: C) => T): T[] {
|
||||
const arr: T[] = [];
|
||||
this.loadedList.forEach(v => {
|
||||
arr.push(fn(v.hook, v.controller));
|
||||
});
|
||||
return arr;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -41,7 +41,9 @@
|
||||
"39": "Offset pool size exceeds WebGL2 limitation, ensure size type of your big image is less than $1.",
|
||||
"40": "Material used by map block $1 is not found in built asset. Please ensure you have pack it into asset.",
|
||||
"41": "You are trying to use a texture on moving block whose offset is not in the offset pool, please build it into asset after loading.",
|
||||
"42": "The layerState property of map-render element is required.",
|
||||
"42": "The '$1' property of map-render element is required.",
|
||||
"43": "Cannot bind face direction to main block $1, please call malloc in advance.",
|
||||
"44": "Cannot bind face direction to main block $1, since main direction cannot be override.",
|
||||
"1101": "Shadow extension needs 'floor-hero' extension as dependency.",
|
||||
"1201": "Floor-damage extension needs 'floor-binder' extension as dependency.",
|
||||
"1301": "Portal extension need 'floor-binder' extension as dependency.",
|
||||
@ -135,6 +137,12 @@
|
||||
"85": "Hook to load does not belong to current hookable object.",
|
||||
"86": "Cannot restore vertex data since delivered state does not belong to current vertex generator instance.",
|
||||
"87": "Map texture asset cache not hit. This has no effect on rendering, but may extremely affect performance, please see the following link to find solution.",
|
||||
"88": "No hero image is specified, please set image in advance.",
|
||||
"89": "Cannot set hero image, since there is no image named '$1'.",
|
||||
"90": "Cannot set hero image, since delivered image has incorrect format or size.",
|
||||
"91": "Cannot add follower, since specified follower number $1 does not exist.",
|
||||
"92": "Followers can only be added when the last follower is not moving.",
|
||||
"93": "Followers can only be removed when the last follower is not moving.",
|
||||
"1001": "Item-detail extension needs 'floor-binder' and 'floor-damage' extension as dependency.",
|
||||
"1101": "Cannot add new effect to point effect instance, for there's no more reserve space for it. Please increase the max count of the instance."
|
||||
}
|
||||
|
||||
@ -107,10 +107,11 @@ export interface IHookable<
|
||||
removeHookByController(hook: C): void;
|
||||
|
||||
/**
|
||||
* 遍历每个钩子
|
||||
* 遍历每个钩子,执行顺序不固定
|
||||
* @param fn 对每个钩子执行的函数
|
||||
* @returns 每个钩子的返回值组成的数组,顺序不固定
|
||||
*/
|
||||
forEachHook(fn: (hook: Partial<H>, controller: C) => void): void;
|
||||
forEachHook<T>(fn: (hook: Partial<H>, controller: C) => T): T[];
|
||||
}
|
||||
|
||||
//#endregion
|
||||
|
||||
@ -384,7 +384,6 @@ var data_a1e2fb4a_e986_4524_b0da_9b7ba7c0874d =
|
||||
},
|
||||
"splitImages": [],
|
||||
"heroImages": [
|
||||
"hero.png",
|
||||
"hero1.png",
|
||||
"hero2.png"
|
||||
]
|
||||
|
||||
@ -310,7 +310,7 @@ var functions_d6ad677b_427a_4623_b50f_a445a3b0ef8a = {
|
||||
values[key] = core.clone(core.values[key]);
|
||||
}
|
||||
|
||||
const { NightSpecial, HeroSkill } =
|
||||
const { NightSpecial, HeroSkill, state } =
|
||||
Mota.require('@user/data-state');
|
||||
|
||||
// 要存档的内容
|
||||
@ -326,7 +326,8 @@ var functions_d6ad677b_427a_4623_b50f_a445a3b0ef8a = {
|
||||
time: new Date().getTime(),
|
||||
skills: Mota.require('@user/data-state').saveSkillTree(),
|
||||
night: [...NightSpecial.saveNight()],
|
||||
skill: HeroSkill.saveSkill()
|
||||
skill: HeroSkill.saveSkill(),
|
||||
coreState: state.saveState()
|
||||
};
|
||||
|
||||
return structuredClone(data);
|
||||
@ -371,9 +372,12 @@ var functions_d6ad677b_427a_4623_b50f_a445a3b0ef8a = {
|
||||
core.setFlag('__fromLoad__', true);
|
||||
|
||||
Mota.require('@user/data-state').loadSkillTree(data.skills);
|
||||
const { NightSpecial, HeroSkill } =
|
||||
const { NightSpecial, HeroSkill, state } =
|
||||
Mota.require('@user/data-state');
|
||||
|
||||
state.loadState(data.coreState);
|
||||
state.hero.setImage(core.status.hero.image);
|
||||
|
||||
if (!data.night) {
|
||||
// 兼容旧版
|
||||
NightSpecial.loadNight([]);
|
||||
|
||||
Loading…
Reference in New Issue
Block a user