HumanBreak/src/core/fx/canvas2d.ts

260 lines
6.9 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 { parseCss } from '@/plugin/utils';
import { EventEmitter } from '../common/eventEmitter';
import { CSSObj } from '../interface';
interface OffscreenCanvasEvent {
/** 当被动触发resize时例如core.domStyle.scale变化、窗口大小变化时触发使用size函数并不会触发 */
resize: () => void;
}
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;
constructor() {
super();
this.canvas = document.createElement('canvas');
this.ctx = this.canvas.getContext('2d')!;
this.width = this.canvas.width / devicePixelRatio;
this.height = this.canvas.height / devicePixelRatio;
this.canvas.style.position = 'absolute';
MotaOffscreenCanvas2D.list.add(this);
}
/**
* 设置画布的大小
*/
size(width: number, height: number) {
let ratio = this.highResolution ? devicePixelRatio : 1;
if (this.autoScale && this.highResolution) {
ratio *= core.domStyle.scale;
}
this.scale = ratio;
this.canvas.width = width * ratio;
this.canvas.height = height * ratio;
this.width = width;
this.height = height;
this.ctx.setTransform(1, 0, 0, 1, 0, 0);
this.ctx.scale(ratio, ratio);
this.ctx.imageSmoothingEnabled = this.antiAliasing;
}
/**
* 设置当前画布是否跟随样板的 core.domStyle.scale 一同进行缩放
*/
withGameScale(auto: boolean) {
this.autoScale = auto;
this.size(this.width, this.height);
}
/**
* 设置当前画布是否为高清画布
*/
setHD(hd: boolean) {
this.highResolution = hd;
this.size(this.width, this.height);
}
/**
* 设置当前画布的抗锯齿设置
*/
setAntiAliasing(anti: boolean) {
this.antiAliasing = anti;
this.ctx.imageSmoothingEnabled = anti;
}
/**
* 清空画布
*/
clear() {
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() {
MotaCanvas2D.list.delete(this);
}
/**
* 复制一个离屏Canvas2D对象或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
);
return newCanvas;
}
}
export class MotaCanvas2D extends MotaOffscreenCanvas2D {
static map: Map<string, MotaCanvas2D> = new Map();
id: string = '';
x: number = 0;
y: number = 0;
private mounted: boolean = false;
private target!: HTMLElement;
/** 是否自动跟随样板的core.domStyle.scale进行缩放 */
autoScale: boolean = false;
/** 是否是高清画布 */
highResolution: boolean = true;
constructor(id: string = '', setTarget: boolean = true) {
super();
this.id = id;
if (setTarget) this.target = core.dom.gameDraw;
this.canvas = document.createElement('canvas');
this.canvas.id = id;
this.ctx = this.canvas.getContext('2d')!;
this.width = this.canvas.width / devicePixelRatio;
this.height = this.canvas.height / devicePixelRatio;
this.canvas.style.position = 'absolute';
MotaCanvas2D.map.set(this.id, this);
}
/**
* 修改画布的挂载目标,如果已经被挂载,那么会被重新挂载至新的目标元素
* @param target 画布将被挂载的目标
*/
setTarget(target: HTMLElement) {
this.target = target;
if (this.mounted) {
this.unmount();
this.mount();
}
}
/**
* 设置画布的大小
*/
size(width: number, height: number) {
let ratio = this.highResolution ? devicePixelRatio : 1;
if (this.autoScale) {
const scale = core.domStyle.scale;
if (this.highResolution) ratio *= scale;
this.canvas.style.width = `${width * scale}px`;
this.canvas.style.height = `${height * scale}px`;
} else {
this.canvas.style.width = `${width}px`;
this.canvas.style.height = `${height}px`;
}
this.scale = ratio;
this.canvas.width = width * ratio;
this.canvas.height = height * ratio;
this.width = width;
this.height = height;
this.ctx.setTransform(1, 0, 0, 1, 0, 0);
this.ctx.scale(ratio, ratio);
}
/**
* 设置画布的位置
*/
pos(x: number, y: number) {
this.canvas.style.left = `${x}px`;
this.canvas.style.top = `${y}px`;
this.x = x;
this.y = y;
}
/**
* 设置画布的css
* @param css 要设置成的css
*/
css(css: string | CSSObj) {
const s = typeof css === 'string' ? parseCss(css) : css;
for (const [key, value] of Object.entries(s)) {
this.canvas.style[key as CanParseCss] = value;
}
}
/**
* 删除这个画布
*/
delete() {
super.delete();
this.unmount();
MotaCanvas2D.map.delete(this.id);
}
/**
* 将这个画布添加至游戏画布
*/
mount() {
if (!this.mounted) {
this.mounted = true;
this.target.appendChild(this.canvas);
}
}
/**
* 将这个画布从页面上移除
*/
unmount() {
if (this.mounted) {
this.mounted = false;
this.canvas.remove();
}
}
/**
* 类似于 Symbol.for
*/
static for(id: string, setTarget?: boolean) {
const canvas = this.map.get(id);
return canvas ?? new MotaCanvas2D(id, setTarget);
}
}
window.addEventListener('resize', () => {
requestAnimationFrame(() => {
MotaOffscreenCanvas2D.list.forEach(v => {
if (v.autoScale) {
v.size(v.width, v.height);
v.symbol++;
v.emit('resize');
}
});
});
});