mirror of
https://github.com/unanmed/HumanBreak.git
synced 2025-04-18 17:48:52 +08:00
215 lines
6.2 KiB
TypeScript
215 lines
6.2 KiB
TypeScript
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();
|
||
});
|
||
});
|