refactor: 地图渲染与伤害渲染

This commit is contained in:
unanmed 2024-08-14 23:23:41 +08:00
parent bb656f8169
commit c7660e93c7
11 changed files with 1724 additions and 503 deletions

View File

@ -116,3 +116,4 @@ dam4.png ---- 存档 59
[] 自定义状态栏,通过申请空间进行布局 [] 自定义状态栏,通过申请空间进行布局
[x] 复写 apirewrite() [x] 复写 apirewrite()
[x] 对 vnode 进行简单的包装,提供出显示文字、显示图片等 api 以及修改 css 的 api [x] 对 vnode 进行简单的包装,提供出显示文字、显示图片等 api 以及修改 css 的 api
[] mapDamage 注册

View File

@ -1233,7 +1233,7 @@ var items_296f5d02_12fd_4166_a7c1_b5e830c9ee3a =
"cls": "constants", "cls": "constants",
"name": "成就", "name": "成就",
"canUseItemEffect": "true", "canUseItemEffect": "true",
"useItemEffect": "Mota.require('var', 'mainUi')open('achievement');", "useItemEffect": "Mota.require('var', 'mainUi').open('achievement');",
"text": "可以查看成就" "text": "可以查看成就"
}, },
"I662": { "I662": {

View File

@ -103,6 +103,26 @@ export interface IRenderChildable {
sortChildren(): void; sortChildren(): void;
} }
interface IRenderFrame {
/**
*
* @param fn
*/
requestBeforeFrame(fn: () => void): void;
/**
*
* @param fn
*/
requestAfterFrame(fn: () => void): void;
/**
* {@link RenderItem.update}
* @param fn
*/
requestRenderFrame(fn: () => void): void;
}
interface RenderItemEvent { interface RenderItemEvent {
beforeUpdate: (item?: RenderItem) => void; beforeUpdate: (item?: RenderItem) => void;
afterUpdate: (item?: RenderItem) => void; afterUpdate: (item?: RenderItem) => void;
@ -110,9 +130,18 @@ interface RenderItemEvent {
afterRender: () => void; afterRender: () => void;
} }
const beforeFrame: (() => void)[] = [];
const afterFrame: (() => void)[] = [];
const renderFrame: (() => void)[] = [];
export abstract class RenderItem export abstract class RenderItem
extends EventEmitter<RenderItemEvent> extends EventEmitter<RenderItemEvent>
implements IRenderCache, IRenderUpdater, IRenderAnchor, IRenderConfig implements
IRenderCache,
IRenderUpdater,
IRenderAnchor,
IRenderConfig,
IRenderFrame
{ {
/** 渲染的全局ticker */ /** 渲染的全局ticker */
static ticker: Ticker = new Ticker(); static ticker: Ticker = new Ticker();
@ -227,6 +256,18 @@ export abstract class RenderItem
this.parent?.sortChildren?.(); this.parent?.sortChildren?.();
} }
requestBeforeFrame(fn: () => void): void {
beforeFrame.push(fn);
}
requestAfterFrame(fn: () => void): void {
afterFrame.push(fn);
}
requestRenderFrame(fn: () => void): void {
renderFrame.push(fn);
}
/** /**
* *
*/ */
@ -261,6 +302,24 @@ export abstract class RenderItem
} }
} }
RenderItem.ticker.add(() => {
if (beforeFrame.length > 0) {
const toEmit = beforeFrame.slice();
beforeFrame.splice(0);
toEmit.forEach(v => v());
}
if (renderFrame.length > 0) {
const toEmit = renderFrame.slice();
renderFrame.splice(0);
toEmit.forEach(v => v());
}
if (afterFrame.length > 0) {
const toEmit = afterFrame.slice();
afterFrame.splice(0);
toEmit.forEach(v => v());
}
});
interface RenderEvent { interface RenderEvent {
animateFrame: (frame: number, time: number) => void; animateFrame: (frame: number, time: number) => void;
} }

View File

@ -16,6 +16,12 @@ interface BlockData {
restHeight: number; restHeight: number;
} }
/**
*
* 13x13划分缓存13x13的缓存分块
* 便`xx -> yy`
* xx说明传入的数据是元素还是分块的数据yy表示其返回值或转换为的值
*/
export class BlockCacher<T> extends EventEmitter<BlockCacherEvent> { export class BlockCacher<T> extends EventEmitter<BlockCacherEvent> {
/** 区域宽度 */ /** 区域宽度 */
width: number; width: number;
@ -47,6 +53,7 @@ export class BlockCacher<T> extends EventEmitter<BlockCacherEvent> {
this.height = height; this.height = height;
this.blockSize = size; this.blockSize = size;
this.cacheDepth = depth; this.cacheDepth = depth;
this.split();
} }
/** /**
@ -103,7 +110,7 @@ export class BlockCacher<T> extends EventEmitter<BlockCacherEvent> {
} }
/** /**
* * ->void
* @param index * @param index
* @param deep 310b111就是清除前三层的索引 * @param deep 310b111就是清除前三层的索引
*/ */
@ -117,7 +124,7 @@ export class BlockCacher<T> extends EventEmitter<BlockCacherEvent> {
} }
/** /**
* {@link clearCache} * {@link clearCache} ->void
*/ */
clearCacheByIndex(index: number) { clearCacheByIndex(index: number) {
this.cache.delete(index); this.cache.delete(index);
@ -131,14 +138,14 @@ export class BlockCacher<T> extends EventEmitter<BlockCacherEvent> {
} }
/** /**
* * ->
*/ */
getIndex(x: number, y: number) { getIndex(x: number, y: number) {
return x + y * this.blockData.width; return x + y * this.blockData.width;
} }
/** /**
* * ->
*/ */
getIndexByLoc(x: number, y: number) { getIndexByLoc(x: number, y: number) {
return this.getIndex( return this.getIndex(
@ -148,7 +155,7 @@ export class BlockCacher<T> extends EventEmitter<BlockCacherEvent> {
} }
/** /**
* * ->
*/ */
getBlockXYByIndex(index: number): LocArr { getBlockXYByIndex(index: number): LocArr {
const width = this.blockData.width; const width = this.blockData.width;
@ -156,29 +163,47 @@ export class BlockCacher<T> extends EventEmitter<BlockCacherEvent> {
} }
/** /**
* 使 * 使->
*/ */
getBlockXY(x: number, y: number): LocArr { getBlockXY(x: number, y: number): LocArr {
return [Math.floor(x / this.blockSize), Math.floor(y / this.blockSize)]; return [Math.floor(x / this.blockSize), Math.floor(y / this.blockSize)];
} }
/** /**
* deep获取一个分块的精确索引 * deep获取一个分块的精确索引->
*/ */
getPreciseIndex(x: number, y: number, deep: number) { getPreciseIndex(x: number, y: number, deep: number) {
return (x + y * this.blockSize) * this.cacheDepth + deep; return (x + y * this.blockSize) * this.cacheDepth + deep;
} }
/** /**
* deep获取元素所在块的精确索引 * deep获取元素所在块的精确索引->
*/ */
getPreciseIndexByLoc(x: number, y: number, deep: number) { getPreciseIndexByLoc(x: number, y: number, deep: number) {
return this.getPreciseIndex(...this.getBlockXY(x, y), deep); return this.getPreciseIndex(...this.getBlockXY(x, y), deep);
} }
/** /**
* * ->
* @param deep * @param deep
* @returns
*/
updateElementArea(
x: number,
y: number,
width: number,
height: number,
deep: number = 2 ** 31 - 1
) {
const [bx, by] = this.getBlockXY(x, y);
const [ex, ey] = this.getBlockXY(x + width, y + height);
return this.updateArea(bx, by, ex - bx, ey - by, deep);
}
/**
* ->
* @param deep
* @returns
*/ */
updateArea( updateArea(
x: number, x: number,
@ -187,13 +212,15 @@ export class BlockCacher<T> extends EventEmitter<BlockCacherEvent> {
height: number, height: number,
deep: number = 2 ** 31 - 1 deep: number = 2 ** 31 - 1
) { ) {
this.getIndexOf(x, y, width, height).forEach(v => { const blocks = this.getIndexOf(x, y, width, height);
blocks.forEach(v => {
this.clearCache(v, deep); this.clearCache(v, deep);
}); });
return blocks;
} }
/** /**
* * ->
*/ */
getIndexOf(x: number, y: number, width: number, height: number) { getIndexOf(x: number, y: number, width: number, height: number) {
const res = new Set<number>(); const res = new Set<number>();
@ -204,12 +231,43 @@ export class BlockCacher<T> extends EventEmitter<BlockCacherEvent> {
for (let nx = sx; nx < ex; nx++) { for (let nx = sx; nx < ex; nx++) {
for (let ny = sy; ny < ey; ny++) { for (let ny = sy; ny < ey; ny++) {
const index = this.getIndexByLoc(nx, ny); const index = this.getIndex(nx, ny);
if (res.has(index)) continue;
res.add(index); res.add(index);
} }
} }
return res; return res;
} }
/**
* ->
*/
getIndexOfElement(x: number, y: number, width: number, height: number) {
const [bx, by] = this.getBlockXY(x, y);
const [ex, ey] = this.getBlockXY(x + width, y + height);
return this.getIndexOf(bx, by, ex - bx, ey - by);
}
/**
* ->
* @param block
*/
getRectOfIndex(block: number) {
const [x, y] = this.getBlockXYByIndex(block);
return this.getRectOfBlockXY(x, y);
}
/**
* ->
* @param x
* @param y
*/
getRectOfBlockXY(x: number, y: number) {
return [
x * this.blockSize,
y * this.blockSize,
(x + 1) * this.blockSize,
(y + 1) * this.blockSize
];
}
} }

View File

@ -0,0 +1,443 @@
import { logger } from '@/core/common/logger';
import { LayerGroupFloorBinder } from './floor';
import {
calNeedRenderOf,
ILayerGroupRenderExtends,
Layer,
LayerGroup
} from './layer';
import { Sprite } from '../sprite';
import { BlockCacher } from './block';
import type {
DamageEnemy,
EnemyCollection,
MapDamage
} from '@/game/enemy/damage';
import { MotaOffscreenCanvas2D } from '@/core/fx/canvas2d';
import { Camera } from '../camera';
import { isNil } from 'lodash-es';
import { getDamageColor } from '@/plugin/utils';
import { transformCanvas } from '../item';
const ensureFloorDamage = Mota.require('fn', 'ensureFloorDamage');
export class FloorDamageExtends implements ILayerGroupRenderExtends {
id: string = 'floor-damage';
floorBinder!: LayerGroupFloorBinder;
group!: LayerGroup;
sprite!: Damage;
/**
*
*/
update(floor: FloorIds) {
if (!this.sprite) return;
const map = core.status.maps[floor];
this.sprite.setMapSize(map.width, map.height);
ensureFloorDamage(floor);
const enemy = core.status.maps[floor].enemy;
console.log(enemy);
this.sprite.updateCollection(enemy);
}
/**
*
*/
private create() {
if (this.sprite) return;
const sprite = new Damage();
this.group.appendChild(sprite);
this.sprite = sprite;
}
private onUpdate = (floor: FloorIds) => {
this.update(floor);
};
private onSetBlock = (x: number, y: number, floor: FloorIds) => {
if (floor !== this.sprite.enemy?.floorId) return;
this.sprite.updateEnemyOn(x, y);
};
/**
*
*/
private listen() {
this.floorBinder.on('update', this.onUpdate);
this.floorBinder.on('setBlock', this.onSetBlock);
}
awake(group: LayerGroup): void {
group.requestBeforeFrame(() => {
const ex = group.getExtends('floor-binder');
if (ex instanceof LayerGroupFloorBinder) {
this.floorBinder = ex;
this.group = group;
this.create();
this.listen();
} else {
logger.warn(
17,
`Floor-damage extends needs 'floor-binder' extends as dependency.`
);
group.removeExtends('floor-damage');
}
});
}
onDestroy(group: LayerGroup): void {
this.floorBinder.off('update', this.onUpdate);
this.floorBinder.off('setBlock', this.onSetBlock);
}
}
interface DamageRenderable {
x: number;
y: number;
align: CanvasTextAlign;
baseline: CanvasTextBaseline;
text: string;
color: CanvasStyle;
font?: string;
stroke?: CanvasStyle;
strokeWidth?: number;
}
export class Damage extends Sprite {
mapWidth: number = 0;
mapHeight: number = 0;
block: BlockCacher<HTMLCanvasElement>;
/** 键表示分块索引,值表示在这个分块上的渲染信息(当然实际渲染位置可以不在这个分块上) */
renderable: Map<number, Set<DamageRenderable>> = new Map();
/** 当前渲染怪物列表 */
enemy?: EnemyCollection;
/** 每个分块中包含的怪物集合 */
blockData: Map<number, Map<number, DamageEnemy>> = new Map();
/** 单元格大小 */
cellSize: number = 32;
/** 伤害渲染层 */
damageMap: MotaOffscreenCanvas2D = new MotaOffscreenCanvas2D();
/** 默认伤害字体 */
font: string = "14px 'normal'";
/** 默认描边样式,当伤害文字不存在描边属性时会使用此属性 */
strokeStyle: CanvasStyle = '#000';
/** 默认描边宽度 */
strokeWidth: number = 2;
/** 单个点的懒更新 */
private needUpdateBlock: boolean = false;
/** 要懒更新的所有分块 */
private needUpdateBlocks: Set<number> = new Set();
constructor() {
super();
this.block = new BlockCacher(0, 0, core._WIDTH_, 1);
this.type = 'absolute';
this.size(core._PX_, core._PY_);
this.damageMap.withGameScale(true);
this.damageMap.setHD(true);
this.damageMap.setAntiAliasing(true);
this.damageMap.size(core._PX_, core._PY_);
this.setRenderFn((canvas, camera) => {
const { ctx } = canvas;
const { width, height } = canvas.canvas;
ctx.save();
ctx.imageSmoothingEnabled = false;
this.renderDamage(camera);
ctx.setTransform(1, 0, 0, 1, 0, 0);
ctx.drawImage(this.damageMap.canvas, 0, 0, width, height);
ctx.restore();
});
}
/**
*
*/
setMapSize(width: number, height: number) {
this.mapWidth = width;
this.mapHeight = height;
this.enemy = void 0;
this.blockData.clear();
this.renderable.clear();
this.block.size(width, height);
// 预留blockData
const w = this.block.blockData.width;
const h = this.block.blockData.height;
const num = w * h;
for (let i = 0; i < num; i++) {
this.blockData.set(i, new Map());
this.renderable.set(i, new Set());
}
}
/**
* {@link Damage.enemy}
* @param enemy
*/
updateCollection(enemy: EnemyCollection) {
if (this.enemy === enemy) return;
this.enemy = enemy;
this.blockData.forEach(v => v.clear());
this.renderable.forEach(v => v.clear());
this.block.clearAllCache();
enemy.list.forEach(v => {
if (isNil(v.x) || isNil(v.y)) return;
const index = this.block.getIndexByLoc(v.x, v.y);
this.blockData.get(index)?.set(v.y * this.mapWidth + v.x, v);
});
this.updateBlocks();
this.update(this);
}
/**
*
* @param x
* @param y
* @param width
* @param height
*/
updateRenderable(x: number, y: number, width: number, height: number) {
this.updateBlocks(this.block.updateElementArea(x, y, width, height));
}
/**
*
* @param blocks
*/
updateBlocks(blocks?: Set<number>) {
if (blocks) {
blocks.forEach(v => this.updateBlock(v));
} else {
this.blockData.forEach((_, k) => this.updateBlock(k, false));
this.extractAllMapDamage();
}
this.update(this);
}
/**
*
*/
updateEnemyOn(x: number, y: number) {
const enemy = this.enemy?.get(x, y);
const block = this.block.getIndexByLoc(x, y);
const data = this.blockData.get(block);
const index = x + y * this.mapWidth;
if (!data) return;
if (!enemy) {
data.delete(index);
} else {
data.set(index, enemy);
}
this.update(this);
// 渲染懒更新,优化性能表现
if (!this.needUpdateBlock) {
this.requestBeforeFrame(() => {
this.needUpdateBlock = false;
this.needUpdateBlocks.forEach(v => this.updateBlock(v, false));
// todo: 阻击夹域等地图伤害检测是否必要更新,例如不包含阻击夹域的怪就不必要更新这个怪物信息
this.extractAllMapDamage();
});
this.needUpdateBlock = true;
}
}
/**
*
* @param block
* @param map
*/
private updateBlock(block: number, map: boolean = true) {
const data = this.blockData.get(block);
if (!data) return;
this.block.clearCache(block, 1);
const renderable = this.renderable.get(block)!;
data.forEach(v => this.extract(v, renderable));
if (map) this.extractMapDamage(block, renderable);
}
/**
* renderable的伤害
* @param enemy
* @param block
*/
private extract(enemy: DamageEnemy, block: Set<DamageRenderable>) {
if (enemy.progress !== 4) return;
const x = enemy.x!;
const y = enemy.y!;
const { damage } = enemy.calDamage();
const cri = enemy.calCritical(1)[0]?.atkDelta ?? Infinity;
const dam1: DamageRenderable = {
align: 'left',
baseline: 'alphabetic',
text: isFinite(damage) ? core.formatBigNumber(damage, true) : '???',
color: getDamageColor(damage),
x: x * this.cellSize + 1,
y: y * this.cellSize + this.cellSize - 1
};
const dam2: DamageRenderable = {
align: 'left',
baseline: 'alphabetic',
text: isFinite(cri) ? core.formatBigNumber(cri, true) : '?',
color: '#fff',
x: x * this.cellSize + 1,
y: y * this.cellSize + this.cellSize - 11
};
block.add(dam1).add(dam2);
}
/**
*
* @param block
*/
private extractMapDamage(block: number, renderable: Set<DamageRenderable>) {
if (!this.enemy) return;
const damage = this.enemy.mapDamage;
const [sx, sy, ex, ey] = this.block.getRectOfIndex(block);
for (let x = sx; x < ex; x++) {
for (let y = sy; y < ey; y++) {
const loc = `${x},${y}`;
const dam = damage[loc];
if (!dam) continue;
this.pushMapDamage(x, y, renderable, dam);
}
}
}
/**
*
*/
private extractAllMapDamage() {
// todo: 测试性能,这样真的会更快吗?或许能更好的优化?或者是根本不需要这个函数?
if (!this.enemy) return;
for (const [loc, enemy] of Object.entries(this.enemy.mapDamage)) {
const [sx, sy] = loc.split(',');
const x = Number(sx);
const y = Number(sy);
const block = this.renderable.get(this.block.getIndexByLoc(x, y))!;
this.pushMapDamage(x, y, block, enemy);
}
}
private pushMapDamage(
x: number,
y: number,
block: Set<DamageRenderable>,
dam: MapDamage
) {
// todo: 这个应当可以自定义,通过地图伤害注册实现
let text = '';
let color = '#fa3';
if (dam.damage > 0) {
text = core.formatBigNumber(dam.damage, true);
} else if (dam.mockery) {
dam.mockery.sort((a, b) =>
a[0] === b[0] ? a[1] - b[1] : a[0] - b[0]
);
const [tx, ty] = dam.mockery[0];
const dir = x > tx ? '←' : x < tx ? '→' : y > ty ? '↑' : '↓';
text = '嘲' + dir;
color = '#fd4';
} else if (dam.hunt) {
text = '猎';
color = '#fd4';
} else {
return;
}
const mapDam: DamageRenderable = {
align: 'center',
baseline: 'middle',
text,
color,
x: x * this.cellSize + this.cellSize / 2,
y: y * this.cellSize + this.cellSize / 2
};
block.add(mapDam);
}
/**
*
*/
calNeedRender(camera: Camera) {
if (this.parent instanceof LayerGroup) {
// 如果处于地图组中,每个地图的渲染区域应该是一样的,因此可以缓存优化
return this.parent.cacheNeedRender(camera, this.block);
} else if (this.parent instanceof Layer) {
// 如果是地图的子元素直接调用Layer的计算函数
return this.parent.calNeedRender(camera);
} else {
return calNeedRenderOf(camera, this.cellSize, this.block);
}
}
/**
*
* @param camera
*/
renderDamage(camera: Camera) {
const { ctx } = this.damageMap;
ctx.save();
transformCanvas(this.damageMap, camera, true);
const { res: render } = this.calNeedRender(camera);
const block = this.block;
const cell = this.cellSize;
const size = cell * block.blockSize;
render.forEach(v => {
const [x, y] = block.getBlockXYByIndex(v);
const bx = x * block.blockSize;
const by = y * block.blockSize;
const px = bx * cell;
const py = by * cell;
// 检查有没有缓存
const cache = block.cache.get(v * block.cacheDepth);
if (cache) {
ctx.drawImage(cache, px, py, size, size);
return;
}
console.time('damage1');
// 否则依次渲染并写入缓存
const temp = new MotaOffscreenCanvas2D();
temp.setHD(true);
temp.setAntiAliasing(true);
temp.withGameScale(true);
temp.size(size, size);
const { ctx: ct } = temp;
console.timeEnd('damage1');
console.time('damage2');
const render = this.renderable.get(v);
render?.forEach(v => {
if (!v) return;
ct.fillStyle = v.color;
ct.textAlign = v.align;
ct.textBaseline = v.baseline;
ct.font = v.font ?? this.font;
ct.strokeStyle = v.stroke ?? this.strokeStyle;
ct.lineWidth = v.strokeWidth ?? this.strokeWidth;
ct.strokeText(v.text, v.x, v.y);
ct.fillText(v.text, v.x, v.y);
});
console.timeEnd('damage2');
console.time('damage3');
ctx.drawImage(temp.canvas, px, py, size, size);
block.cache.set(v, temp.canvas);
console.timeEnd('damage3');
});
ctx.restore();
}
}

View File

@ -0,0 +1,269 @@
import EventEmitter from 'eventemitter3';
import {
FloorLayer,
ILayerGroupRenderExtends,
ILayerRenderExtends,
Layer,
LayerGroup
} from './layer';
import { texture } from '../cache';
const hook = Mota.require('var', 'hook');
hook.on('setBlock', (x, y, floor, block) => {
const isNow = floor === core.status.floorId;
LayerGroupFloorBinder.activedBinder.forEach(v => {
if (floor === v.floor || (isNow && v.bindThisFloor)) {
v.setBlock('event', block, x, y);
}
});
LayerFloorBinder.listenedBinder.forEach(v => {
if (v.layer.layer === 'event') {
if (v.floor === floor || (isNow && v.bindThisFloor)) {
v.setBlock(block, x, y);
}
}
});
});
hook.on('changingFloor', floor => {
// 潜在隐患如果putRenderData改成异步那么会变成两帧后才能真正刷新并渲染
// 考虑到楼层转换一般不会同时执行很多次,因此这里改为立刻更新
LayerGroupFloorBinder.activedBinder.forEach(v => {
if (v.bindThisFloor) v.updateBindData();
});
LayerFloorBinder.listenedBinder.forEach(v => {
if (v.bindThisFloor) v.updateBindData();
});
});
interface LayerGroupBinderEvent {
update: [floor: FloorIds];
setBlock: [x: number, y: number, floor: FloorIds, block: AllNumbers];
}
/**
* LayerGroup
* LayerGroup包含的子Layer上添加LayerFloorBinder拓展
*
*/
export class LayerGroupFloorBinder
extends EventEmitter<LayerGroupBinderEvent>
implements ILayerGroupRenderExtends
{
id: string = 'floor-binder';
bindThisFloor: boolean = true;
floor?: FloorIds;
group!: LayerGroup;
/** 附属的子LayerFloorBinder拓展 */
layerBinders: Set<LayerFloorBinder> = new Set();
private needUpdate: boolean = false;
static activedBinder: Set<LayerGroupFloorBinder> = new Set();
/**
*
*/
bindThis() {
this.floor = void 0;
this.bindThisFloor = true;
this.updateBind();
}
/**
*
* @param floorId id
*/
bindFloor(floorId: FloorIds) {
this.bindThisFloor = false;
this.floor = floorId;
this.updateBind();
}
/**
*
*/
updateBind() {
if (this.needUpdate) return;
this.needUpdate = true;
this.group.requestBeforeFrame(() => {
this.needUpdate = false;
this.updateBindData();
});
}
/**
*
*/
updateBindData() {
this.layerBinders.forEach(v => {
v.updateBindData();
});
const floor = this.bindThisFloor ? core.status.floorId : this.floor!;
this.emit('update', floor);
}
/**
*
*/
setBlock(layer: FloorLayer, block: AllNumbers, x: number, y: number) {
const ex = this.group
.getLayer(layer)
?.getExtends('floor-binder') as LayerFloorBinder;
if (!ex) return;
ex.setBlock(block, x, y);
const floor = this.bindThisFloor ? core.status.floorId : this.floor!;
this.emit('setBlock', x, y, floor, block);
}
private checkLayerExtends(layer: Layer) {
const ex = layer.getExtends('floor-binder');
if (!ex) {
const extend = new LayerFloorBinder(this);
layer.extends(extend);
this.layerBinders.add(extend);
} else {
if (ex instanceof LayerFloorBinder) {
ex.setParent(this);
this.layerBinders.add(ex);
}
}
}
awake(group: LayerGroup) {
this.group = group;
for (const layer of group.layers.values()) {
this.checkLayerExtends(layer);
}
LayerGroupFloorBinder.activedBinder.add(this);
}
onLayerAdd(group: LayerGroup, layer: Layer): void {
this.checkLayerExtends(layer);
}
onDestroy(group: LayerGroup) {
LayerGroupFloorBinder.activedBinder.delete(this);
group.layers.forEach(v => {
v.removeExtends('floor-binder');
});
this.removeAllListeners();
}
}
/**
* Layer的楼层渲染
* Layer是LayerGroup的子元素LayerGroupFloorBinder拓展
*
*/
export class LayerFloorBinder implements ILayerRenderExtends {
id: string = 'floor-binder';
parent?: LayerGroupFloorBinder;
layer!: Layer;
bindThisFloor: boolean = true;
floor?: FloorIds;
static listenedBinder: Set<LayerFloorBinder> = new Set();
private needUpdate: boolean = false;
constructor(parent?: LayerGroupFloorBinder) {
this.parent = parent;
}
/**
*
*/
bindThis() {
this.floor = void 0;
this.bindThisFloor = true;
}
/**
*
* @param floorId id
*/
bindFloor(floorId: FloorIds) {
this.bindThisFloor = false;
this.floor = floorId;
}
/**
* LayerGroupFloorBinder拓展
* @param parent
*/
setParent(parent?: LayerGroupFloorBinder) {
this.parent = parent;
this.checkListen();
}
private checkListen() {
if (this.parent) LayerFloorBinder.listenedBinder.delete(this);
else LayerFloorBinder.listenedBinder.add(this);
}
/**
*
*/
updateBind() {
if (this.needUpdate) return;
this.needUpdate = true;
this.layer.requestBeforeFrame(() => {
this.needUpdate = false;
this.updateBindData();
});
}
/**
*
*/
setBlock(block: AllNumbers, x: number, y: number) {
this.layer.putRenderData([block], 1, x, y);
}
/**
*
*/
updateBindData() {
const floor = this.bindThisFloor ? core.status.floorId : this.floor;
if (!floor) return;
core.extractBlocks(floor);
const map = core.status.maps[floor];
this.layer.setMapSize(map.width, map.height);
if (this.layer.layer === 'event') {
const m = map.map;
this.layer.putRenderData(m.flat(), map.width, 0, 0);
} else {
const m = core.maps._getBgFgMapArray(this.layer.layer!, floor);
this.layer.putRenderData(m.flat(), map.width, 0, 0);
}
if (this.layer.layer === 'bg') {
// 别忘了背景图块
this.layer.setBackground(texture.idNumberMap[map.defaultGround]);
}
}
awake(layer: Layer) {
this.layer = layer;
if (!this.parent) {
const group = layer.parent;
if (group instanceof LayerGroup) {
const ex = group.getExtends('floor-binder');
if (ex instanceof LayerGroupFloorBinder) {
this.parent = ex;
}
}
}
this.checkListen();
}
onDestroy(layer: Layer) {
LayerFloorBinder.listenedBinder.delete(this);
this.parent?.layerBinders.delete(this);
}
}

View File

@ -0,0 +1,3 @@
import { ILayerRenderExtends } from './layer';
export class HeroRenderer implements ILayerRenderExtends {}

File diff suppressed because it is too large Load Diff

View File

@ -3,7 +3,9 @@ import { MotaCanvas2D, MotaOffscreenCanvas2D } from '../fx/canvas2d';
import { Camera } from './camera'; import { Camera } from './camera';
import { Container } from './container'; import { Container } from './container';
import { RenderItem, transformCanvas, withCacheRender } from './item'; import { RenderItem, transformCanvas, withCacheRender } from './item';
import { Layer, LayerGroup } from './preset/layer'; import { FloorLayer, Layer, LayerGroup } from './preset/layer';
import { LayerGroupFloorBinder } from './preset/floor';
import { FloorDamageExtends } from './preset/damage';
export class MotaRenderer extends Container { export class MotaRenderer extends Container {
static list: Set<MotaRenderer> = new Set(); static list: Set<MotaRenderer> = new Set();
@ -65,6 +67,7 @@ export class MotaRenderer extends Container {
* *
*/ */
render() { render() {
console.time();
const { canvas, ctx } = this.target; const { canvas, ctx } = this.target;
const camera = this.camera; const camera = this.camera;
this.emit('beforeRender'); this.emit('beforeRender');
@ -91,12 +94,13 @@ export class MotaRenderer extends Container {
}); });
}); });
this.emit('afterRender'); this.emit('afterRender');
console.timeEnd();
} }
update(item?: RenderItem) { update(item?: RenderItem) {
if (this.needUpdate) return; if (this.needUpdate) return;
this.needUpdate = true; this.needUpdate = true;
requestAnimationFrame(() => { this.requestRenderFrame(() => {
this.cache(this.using); this.cache(this.using);
this.needUpdate = false; this.needUpdate = false;
this.refresh(item); this.refresh(item);
@ -141,7 +145,16 @@ Mota.require('var', 'hook').once('reset', () => {
render.mount(); render.mount();
const layer = new LayerGroup(); const layer = new LayerGroup();
layer.addDamage();
['bg', 'bg2', 'event', 'fg', 'fg2'].forEach(v => {
layer.addLayer(v as FloorLayer);
});
const binder = new LayerGroupFloorBinder();
const damage = new FloorDamageExtends();
layer.extends(binder);
layer.extends(damage);
binder.bindThis();
render.appendChild(layer); render.appendChild(layer);
camera.move(240, 240); camera.move(240, 240);

View File

@ -1,4 +1,4 @@
import { getHeroStatusOf, getHeroStatusOn } from '@/game/hero'; import { getHeroStatusOf, getHeroStatusOn } from '@/game/state/hero';
import { Range, RangeCollection } from '@/plugin/game/range'; import { Range, RangeCollection } from '@/plugin/game/range';
import { import {
checkV2, checkV2,
@ -44,7 +44,7 @@ interface DamageInfo {
skill?: number; skill?: number;
} }
interface MapDamage { export interface MapDamage {
damage: number; damage: number;
type: Set<string>; type: Set<string>;
mockery?: LocArr[]; mockery?: LocArr[];
@ -100,6 +100,7 @@ export class EnemyCollection implements RangeCollection<DamageEnemy> {
list: DamageEnemy[] = []; list: DamageEnemy[] = [];
range: Range<DamageEnemy> = new Range(this); range: Range<DamageEnemy> = new Range(this);
// todo: 改成Map<number, MapDamage>
mapDamage: Record<string, MapDamage> = {}; mapDamage: Record<string, MapDamage> = {};
haloList: HaloData[] = []; haloList: HaloData[] = [];
@ -353,7 +354,7 @@ export class DamageEnemy<T extends EnemyIds = EnemyIds> {
* 0 -> -> 1 -> -> 2 -> provide inject * 0 -> -> 1 -> -> 2 -> provide inject
* -> 3 -> -> 4 -> * -> 3 -> -> 4 ->
*/ */
private progress: number = 0; progress: number = 0;
constructor( constructor(
enemy: Enemy<T>, enemy: Enemy<T>,
@ -656,6 +657,7 @@ export class DamageEnemy<T extends EnemyIds = EnemyIds> {
* *
*/ */
calDamage(hero: Partial<HeroStatus> = core.status.hero) { calDamage(hero: Partial<HeroStatus> = core.status.hero) {
// todo: 缓存怪物伤害
const enemy = this.getRealInfo(); const enemy = this.getRealInfo();
return this.calEnemyDamageOf(hero, enemy); return this.calEnemyDamageOf(hero, enemy);
} }
@ -831,6 +833,7 @@ export class DamageEnemy<T extends EnemyIds = EnemyIds> {
num: number = 1, num: number = 1,
hero: Partial<HeroStatus> = core.status.hero hero: Partial<HeroStatus> = core.status.hero
): CriticalDamageDelta[] { ): CriticalDamageDelta[] {
// todo: 缓存临界
const origin = this.calDamage(hero); const origin = this.calDamage(hero);
const seckill = this.getSeckillAtk(); const seckill = this.getSeckillAtk();
return this.calCriticalWith(num, seckill, origin, hero); return this.calCriticalWith(num, seckill, origin, hero);

View File

@ -1,4 +1,4 @@
import { getHeroStatusOn } from '@/game/hero'; import { getHeroStatusOn } from '@/game/state/hero';
import { EnemyInfo } from './damage'; import { EnemyInfo } from './damage';
export interface SpecialDeclaration { export interface SpecialDeclaration {