HumanBreak/src/core/fx/canvas2d.ts
2025-02-27 16:03:14 +08:00

215 lines
6.2 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { EventEmitter } from 'eventemitter3';
import { logger } from '../common/logger';
interface OffscreenCanvasEvent {
/** 当被动触发resize时例如core.domStyle.scale变化、窗口大小变化时触发使用size函数并不会触发 */
resize: [];
}
export class MotaOffscreenCanvas2D extends EventEmitter<OffscreenCanvasEvent> {
static list: Set<MotaOffscreenCanvas2D> = new Set();
canvas: HTMLCanvasElement;
ctx: CanvasRenderingContext2D;
width: number;
height: number;
/** 是否自动跟随样板的core.domStyle.scale进行缩放 */
autoScale: boolean = false;
/** 是否是高清画布 */
highResolution: boolean = true;
/** 是否启用抗锯齿 */
antiAliasing: boolean = true;
scale: number = 1;
/** 更新标识符,如果发生变化则说明画布被动清空 */
symbol: number = 0;
private _freezed: boolean = false;
/** 当前画布是否被冻结 */
get freezed() {
return this._freezed;
}
private _active: boolean = true;
get active() {
return this._active;
}
/**
* 创建一个新的离屏画布\
* **注意**:如果你在自定义渲染元素中使用,请避免使用此构造函数,而应该使用 `RenderItem.requireCanvas`
* @param alpha 是否启用透明度通道
* @param canvas 指定画布,不指定时会自动创建一个新画布
*/
constructor(alpha: boolean = true, canvas?: HTMLCanvasElement) {
super();
// console.trace();
this.canvas = canvas ?? document.createElement('canvas');
this.ctx = this.canvas.getContext('2d', { alpha })!;
this.width = this.canvas.width / devicePixelRatio;
this.height = this.canvas.height / devicePixelRatio;
MotaOffscreenCanvas2D.list.add(this);
}
/**
* 设置画布的大小
*/
size(width: number, height: number) {
if (this._freezed) {
logger.warn(33);
return;
}
const w = Math.max(width, 1);
const h = Math.max(height, 1);
let ratio = this.highResolution ? devicePixelRatio : 1;
const scale = core.domStyle.scale;
if (this.autoScale) {
ratio *= scale;
}
this.scale = ratio;
this.canvas.width = w * ratio;
this.canvas.height = h * ratio;
this.width = w;
this.height = height;
this.ctx.setTransform(1, 0, 0, 1, 0, 0);
this.ctx.scale(ratio, ratio);
this.ctx.imageSmoothingEnabled = this.antiAliasing;
if (this.canvas.isConnected) {
this.canvas.style.width = `${w * scale}px`;
this.canvas.style.height = `${h * scale}px`;
}
}
/**
* 设置当前画布是否跟随样板的 core.domStyle.scale 一同进行缩放
*/
withGameScale(auto: boolean) {
if (this._freezed) {
logger.warn(33);
return;
}
this.autoScale = auto;
this.size(this.width, this.height);
}
/**
* 设置当前画布是否为高清画布
*/
setHD(hd: boolean) {
if (this._freezed) {
logger.warn(33);
return;
}
this.highResolution = hd;
this.size(this.width, this.height);
}
/**
* 设置当前画布的抗锯齿设置
*/
setAntiAliasing(anti: boolean) {
if (this._freezed) {
logger.warn(33);
return;
}
this.antiAliasing = anti;
this.ctx.imageSmoothingEnabled = anti;
}
/**
* 清空画布
*/
clear() {
if (this._freezed) {
logger.warn(33);
return;
}
this.ctx.save();
this.ctx.setTransform(1, 0, 0, 1, 0, 0);
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
this.ctx.restore();
}
/**
* 删除这个画布
*/
delete() {
this.canvas.remove();
this.ctx.reset();
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
this._freezed = true;
MotaOffscreenCanvas2D.list.delete(this);
}
/**
* 冻结这个画布的属性,之后便不能被修改,同时会从画布列表中删去。
*/
freeze() {
this._freezed = true;
MotaOffscreenCanvas2D.list.delete(this);
}
/**
* 使此画布生效,使用前请务必调用此函数,生效后会跟随游戏的放缩比例更改大小,但会导致不会被垃圾回收
*/
activate() {
if (this._active || this._freezed) return;
MotaOffscreenCanvas2D.list.add(this);
if (this.autoScale) {
this.size(this.width, this.height);
this.symbol++;
this.emit('resize');
}
}
/**
* 使此画布失效,当这个画布暂时不会被使用时请务必调用此函数,失效后若没有对此画布的引用,那么会自动垃圾回收
*/
deactivate() {
if (!this._active || this._freezed) return;
MotaOffscreenCanvas2D.list.delete(this);
}
/**
* 复制一个离屏Canvas2D对象一般用于缓存等操作
* @param canvas 被复制的MotaOffscreenCanvas2D对象
* @returns 复制结果,注意复制结果是被冻结的,无法进行大小等的修改,但是可以继续绘制
*/
static clone(canvas: MotaOffscreenCanvas2D): MotaOffscreenCanvas2D {
const newCanvas = new MotaOffscreenCanvas2D();
newCanvas.setHD(canvas.highResolution);
newCanvas.withGameScale(canvas.autoScale);
newCanvas.size(canvas.width, canvas.height);
newCanvas.ctx.drawImage(
canvas.canvas,
0,
0,
canvas.width,
canvas.height
);
newCanvas.freeze();
return newCanvas;
}
static refreshAll(force: boolean = false) {
this.list.forEach(v => {
if (force || v.autoScale) {
v.size(v.width, v.height);
v.symbol++;
v.emit('resize');
}
});
}
}
window.addEventListener('resize', () => {
requestAnimationFrame(() => {
MotaOffscreenCanvas2D.refreshAll();
});
});