mirror of
				https://github.com/unanmed/HumanBreak.git
				synced 2025-11-04 07:02:58 +08:00 
			
		
		
		
	refactor: 地图渲染与伤害渲染
This commit is contained in:
		
							parent
							
								
									bb656f8169
								
							
						
					
					
						commit
						c7660e93c7
					
				
							
								
								
									
										1
									
								
								idea.md
									
									
									
									
									
								
							
							
						
						
									
										1
									
								
								idea.md
									
									
									
									
									
								
							@ -116,3 +116,4 @@ dam4.png ---- 存档 59
 | 
			
		||||
[] 自定义状态栏,通过申请空间进行布局
 | 
			
		||||
[x] 复写 api,rewrite()
 | 
			
		||||
[x] 对 vnode 进行简单的包装,提供出显示文字、显示图片等 api 以及修改 css 的 api
 | 
			
		||||
[] mapDamage 注册
 | 
			
		||||
 | 
			
		||||
@ -1233,7 +1233,7 @@ var items_296f5d02_12fd_4166_a7c1_b5e830c9ee3a =
 | 
			
		||||
		"cls": "constants",
 | 
			
		||||
		"name": "成就",
 | 
			
		||||
		"canUseItemEffect": "true",
 | 
			
		||||
		"useItemEffect": "Mota.require('var', 'mainUi')open('achievement');",
 | 
			
		||||
		"useItemEffect": "Mota.require('var', 'mainUi').open('achievement');",
 | 
			
		||||
		"text": "可以查看成就"
 | 
			
		||||
	},
 | 
			
		||||
	"I662": {
 | 
			
		||||
 | 
			
		||||
@ -103,6 +103,26 @@ export interface IRenderChildable {
 | 
			
		||||
    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 {
 | 
			
		||||
    beforeUpdate: (item?: RenderItem) => void;
 | 
			
		||||
    afterUpdate: (item?: RenderItem) => void;
 | 
			
		||||
@ -110,9 +130,18 @@ interface RenderItemEvent {
 | 
			
		||||
    afterRender: () => void;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const beforeFrame: (() => void)[] = [];
 | 
			
		||||
const afterFrame: (() => void)[] = [];
 | 
			
		||||
const renderFrame: (() => void)[] = [];
 | 
			
		||||
 | 
			
		||||
export abstract class RenderItem
 | 
			
		||||
    extends EventEmitter<RenderItemEvent>
 | 
			
		||||
    implements IRenderCache, IRenderUpdater, IRenderAnchor, IRenderConfig
 | 
			
		||||
    implements
 | 
			
		||||
        IRenderCache,
 | 
			
		||||
        IRenderUpdater,
 | 
			
		||||
        IRenderAnchor,
 | 
			
		||||
        IRenderConfig,
 | 
			
		||||
        IRenderFrame
 | 
			
		||||
{
 | 
			
		||||
    /** 渲染的全局ticker */
 | 
			
		||||
    static ticker: Ticker = new Ticker();
 | 
			
		||||
@ -227,6 +256,18 @@ export abstract class RenderItem
 | 
			
		||||
        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 {
 | 
			
		||||
    animateFrame: (frame: number, time: number) => void;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -16,6 +16,12 @@ interface BlockData {
 | 
			
		||||
    restHeight: number;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 简单分块缓存类,内容包含元素与分块两种,其中元素是最小单元,分块是缓存单元。
 | 
			
		||||
 * 拿楼层举例,假如我将楼层按照13x13划分缓存,那么元素就是每个图块,而分块就是这13x13的缓存分块。
 | 
			
		||||
 * 为方便区分,在相关函数的注释最后,都会有`xx -> yy`的说明,
 | 
			
		||||
 * 其中xx说明传入的数据是元素还是分块的数据,而yy表示其返回值或转换为的值
 | 
			
		||||
 */
 | 
			
		||||
export class BlockCacher<T> extends EventEmitter<BlockCacherEvent> {
 | 
			
		||||
    /** 区域宽度 */
 | 
			
		||||
    width: number;
 | 
			
		||||
@ -47,6 +53,7 @@ export class BlockCacher<T> extends EventEmitter<BlockCacherEvent> {
 | 
			
		||||
        this.height = height;
 | 
			
		||||
        this.blockSize = size;
 | 
			
		||||
        this.cacheDepth = depth;
 | 
			
		||||
        this.split();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@ -103,7 +110,7 @@ export class BlockCacher<T> extends EventEmitter<BlockCacherEvent> {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 清除指定块的索引
 | 
			
		||||
     * 清除指定块的索引(分块->void)
 | 
			
		||||
     * @param index 要清除的缓存索引
 | 
			
		||||
     * @param deep 清除哪些深度的缓存,至多31位二进制数,例如填0b111就是清除前三层的索引
 | 
			
		||||
     */
 | 
			
		||||
@ -117,7 +124,7 @@ export class BlockCacher<T> extends EventEmitter<BlockCacherEvent> {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 清空指定索引的缓存,与 {@link clearCache} 不同的是,这里会直接清空对应索引的缓存,而不是指定分块的缓存
 | 
			
		||||
     * 清空指定索引的缓存,与 {@link clearCache} 不同的是,这里会直接清空对应索引的缓存,而不是指定分块的缓存(分块->void)
 | 
			
		||||
     */
 | 
			
		||||
    clearCacheByIndex(index: number) {
 | 
			
		||||
        this.cache.delete(index);
 | 
			
		||||
@ -131,14 +138,14 @@ export class BlockCacher<T> extends EventEmitter<BlockCacherEvent> {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 根据分块的横纵坐标获取其索引
 | 
			
		||||
     * 根据分块的横纵坐标获取其索引(分块->分块)
 | 
			
		||||
     */
 | 
			
		||||
    getIndex(x: number, y: number) {
 | 
			
		||||
        return x + y * this.blockData.width;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 根据元素位置获取分块索引(注意是单个元素的位置,而不是分块的位置)
 | 
			
		||||
     * 根据元素位置获取分块索引(注意是单个元素的位置,而不是分块的位置)(元素->分块)
 | 
			
		||||
     */
 | 
			
		||||
    getIndexByLoc(x: number, y: number) {
 | 
			
		||||
        return this.getIndex(
 | 
			
		||||
@ -148,7 +155,7 @@ export class BlockCacher<T> extends EventEmitter<BlockCacherEvent> {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 根据块的索引获取其位置
 | 
			
		||||
     * 根据块的索引获取其位置(分块->分块)
 | 
			
		||||
     */
 | 
			
		||||
    getBlockXYByIndex(index: number): LocArr {
 | 
			
		||||
        const width = this.blockData.width;
 | 
			
		||||
@ -156,29 +163,47 @@ export class BlockCacher<T> extends EventEmitter<BlockCacherEvent> {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 获取一个元素位置所在的分块位置(即使它不在任何分块内)
 | 
			
		||||
     * 获取一个元素位置所在的分块位置(即使它不在任何分块内)(元素->分块)
 | 
			
		||||
     */
 | 
			
		||||
    getBlockXY(x: number, y: number): LocArr {
 | 
			
		||||
        return [Math.floor(x / this.blockSize), Math.floor(y / this.blockSize)];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 根据分块坐标与deep获取一个分块的精确索引
 | 
			
		||||
     * 根据分块坐标与deep获取一个分块的精确索引(分块->分块)
 | 
			
		||||
     */
 | 
			
		||||
    getPreciseIndex(x: number, y: number, deep: number) {
 | 
			
		||||
        return (x + y * this.blockSize) * this.cacheDepth + deep;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 根据元素坐标及deep获取元素所在块的精确索引
 | 
			
		||||
     * 根据元素坐标及deep获取元素所在块的精确索引(元素->分块)
 | 
			
		||||
     */
 | 
			
		||||
    getPreciseIndexByLoc(x: number, y: number, deep: number) {
 | 
			
		||||
        return this.getPreciseIndex(...this.getBlockXY(x, y), 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(
 | 
			
		||||
        x: number,
 | 
			
		||||
@ -187,13 +212,15 @@ export class BlockCacher<T> extends EventEmitter<BlockCacherEvent> {
 | 
			
		||||
        height: number,
 | 
			
		||||
        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);
 | 
			
		||||
        });
 | 
			
		||||
        return blocks;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 获取一个区域内包含的所有分块索引
 | 
			
		||||
     * 传入分块坐标与范围,获取该区域内包含的所有分块索引(分块->分块)
 | 
			
		||||
     */
 | 
			
		||||
    getIndexOf(x: number, y: number, width: number, height: 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 ny = sy; ny < ey; ny++) {
 | 
			
		||||
                const index = this.getIndexByLoc(nx, ny);
 | 
			
		||||
                if (res.has(index)) continue;
 | 
			
		||||
                const index = this.getIndex(nx, ny);
 | 
			
		||||
                res.add(index);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        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
 | 
			
		||||
        ];
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										443
									
								
								src/core/render/preset/damage.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										443
									
								
								src/core/render/preset/damage.ts
									
									
									
									
									
										Normal 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();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										269
									
								
								src/core/render/preset/floor.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										269
									
								
								src/core/render/preset/floor.ts
									
									
									
									
									
										Normal 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);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										3
									
								
								src/core/render/preset/hero.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								src/core/render/preset/hero.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,3 @@
 | 
			
		||||
import { ILayerRenderExtends } from './layer';
 | 
			
		||||
 | 
			
		||||
export class HeroRenderer implements ILayerRenderExtends {}
 | 
			
		||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							@ -3,7 +3,9 @@ import { MotaCanvas2D, MotaOffscreenCanvas2D } from '../fx/canvas2d';
 | 
			
		||||
import { Camera } from './camera';
 | 
			
		||||
import { Container } from './container';
 | 
			
		||||
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 {
 | 
			
		||||
    static list: Set<MotaRenderer> = new Set();
 | 
			
		||||
@ -65,6 +67,7 @@ export class MotaRenderer extends Container {
 | 
			
		||||
     * 渲染游戏画面
 | 
			
		||||
     */
 | 
			
		||||
    render() {
 | 
			
		||||
        console.time();
 | 
			
		||||
        const { canvas, ctx } = this.target;
 | 
			
		||||
        const camera = this.camera;
 | 
			
		||||
        this.emit('beforeRender');
 | 
			
		||||
@ -91,12 +94,13 @@ export class MotaRenderer extends Container {
 | 
			
		||||
            });
 | 
			
		||||
        });
 | 
			
		||||
        this.emit('afterRender');
 | 
			
		||||
        console.timeEnd();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    update(item?: RenderItem) {
 | 
			
		||||
        if (this.needUpdate) return;
 | 
			
		||||
        this.needUpdate = true;
 | 
			
		||||
        requestAnimationFrame(() => {
 | 
			
		||||
        this.requestRenderFrame(() => {
 | 
			
		||||
            this.cache(this.using);
 | 
			
		||||
            this.needUpdate = false;
 | 
			
		||||
            this.refresh(item);
 | 
			
		||||
@ -141,7 +145,16 @@ Mota.require('var', 'hook').once('reset', () => {
 | 
			
		||||
    render.mount();
 | 
			
		||||
 | 
			
		||||
    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);
 | 
			
		||||
 | 
			
		||||
    camera.move(240, 240);
 | 
			
		||||
 | 
			
		||||
@ -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 {
 | 
			
		||||
    checkV2,
 | 
			
		||||
@ -44,7 +44,7 @@ interface DamageInfo {
 | 
			
		||||
    skill?: number;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
interface MapDamage {
 | 
			
		||||
export interface MapDamage {
 | 
			
		||||
    damage: number;
 | 
			
		||||
    type: Set<string>;
 | 
			
		||||
    mockery?: LocArr[];
 | 
			
		||||
@ -100,6 +100,7 @@ export class EnemyCollection implements RangeCollection<DamageEnemy> {
 | 
			
		||||
    list: DamageEnemy[] = [];
 | 
			
		||||
 | 
			
		||||
    range: Range<DamageEnemy> = new Range(this);
 | 
			
		||||
    // todo: 改成Map<number, MapDamage>
 | 
			
		||||
    mapDamage: Record<string, MapDamage> = {};
 | 
			
		||||
    haloList: HaloData[] = [];
 | 
			
		||||
 | 
			
		||||
@ -353,7 +354,7 @@ export class DamageEnemy<T extends EnemyIds = EnemyIds> {
 | 
			
		||||
     * 伤害计算进度,0 -> 预平衡光环 -> 1 -> 计算没有光环的属性 -> 2 -> provide inject 光环
 | 
			
		||||
     * -> 3 -> 计算光环加成 -> 4 -> 计算完毕
 | 
			
		||||
     */
 | 
			
		||||
    private progress: number = 0;
 | 
			
		||||
    progress: number = 0;
 | 
			
		||||
 | 
			
		||||
    constructor(
 | 
			
		||||
        enemy: Enemy<T>,
 | 
			
		||||
@ -656,6 +657,7 @@ export class DamageEnemy<T extends EnemyIds = EnemyIds> {
 | 
			
		||||
     * 计算怪物伤害
 | 
			
		||||
     */
 | 
			
		||||
    calDamage(hero: Partial<HeroStatus> = core.status.hero) {
 | 
			
		||||
        // todo: 缓存怪物伤害
 | 
			
		||||
        const enemy = this.getRealInfo();
 | 
			
		||||
        return this.calEnemyDamageOf(hero, enemy);
 | 
			
		||||
    }
 | 
			
		||||
@ -831,6 +833,7 @@ export class DamageEnemy<T extends EnemyIds = EnemyIds> {
 | 
			
		||||
        num: number = 1,
 | 
			
		||||
        hero: Partial<HeroStatus> = core.status.hero
 | 
			
		||||
    ): CriticalDamageDelta[] {
 | 
			
		||||
        // todo: 缓存临界
 | 
			
		||||
        const origin = this.calDamage(hero);
 | 
			
		||||
        const seckill = this.getSeckillAtk();
 | 
			
		||||
        return this.calCriticalWith(num, seckill, origin, hero);
 | 
			
		||||
 | 
			
		||||
@ -1,4 +1,4 @@
 | 
			
		||||
import { getHeroStatusOn } from '@/game/hero';
 | 
			
		||||
import { getHeroStatusOn } from '@/game/state/hero';
 | 
			
		||||
import { EnemyInfo } from './damage';
 | 
			
		||||
 | 
			
		||||
export interface SpecialDeclaration {
 | 
			
		||||
 | 
			
		||||
		Loading…
	
		Reference in New Issue
	
	Block a user