HumanBreak/src/core/render/item.ts

493 lines
13 KiB
TypeScript
Raw Normal View History

2024-05-09 23:49:53 +08:00
import { isNil } from 'lodash-es';
2024-08-20 18:04:22 +08:00
import { EventEmitter } from 'eventemitter3';
2024-05-09 23:49:53 +08:00
import { MotaOffscreenCanvas2D } from '../fx/canvas2d';
import { Camera } from './camera';
import { Ticker, TickerFn } from 'mutate-animate';
2024-05-09 23:49:53 +08:00
export type RenderFunction = (
canvas: MotaOffscreenCanvas2D,
camera: Camera
) => void;
export type RenderItemPosition = 'absolute' | 'static';
interface IRenderCache {
/** 缓存列表 */
cacheList: Map<string, MotaOffscreenCanvas2D>;
/** 当前正在使用的缓存 */
using?: string;
/** 下次绘制需要写入的缓存 */
writing?: string;
/**
* 使使
* @param index 使
*/
useCache(index?: string): void;
/**
* 使
* @param index
*/
cache(index?: string): void;
/**
*
* @param index
*/
clearCache(index?: string): void;
}
export interface IRenderUpdater {
/**
*
* @param item
*/
update(item?: RenderItem): void;
}
export interface ICanvasCachedRenderItem {
/** 离屏画布首先渲染到它上面然后由Renderer渲染到最终画布上 */
canvas: MotaOffscreenCanvas2D;
}
interface IRenderAnchor {
anchorX: number;
anchorY: number;
/**
*
* @param x 01
* @param y 01
*/
setAnchor(x: number, y: number): void;
}
2024-05-16 22:43:24 +08:00
interface IRenderConfig {
/** 是否是高清画布 */
highResolution: boolean;
/** 是否启用抗锯齿 */
antiAliasing: boolean;
/**
* 使
* @param hd
*/
setHD(hd: boolean): void;
/**
2024-05-18 17:05:01 +08:00
* 齿
2024-05-16 22:43:24 +08:00
* @param anti 齿
*/
setAntiAliasing(anti: boolean): void;
}
export interface IRenderChildable {
children: RenderItem[];
2024-05-16 22:43:24 +08:00
/**
*
* @param child
2024-05-16 22:43:24 +08:00
*/
appendChild(...child: RenderItem[]): void;
/**
*
* @param child
*/
removeChild(...child: RenderItem[]): void;
/**
*
*/
sortChildren(): void;
2024-05-16 22:43:24 +08:00
}
2024-08-14 23:23:41 +08:00
interface IRenderFrame {
/**
*
* @param fn
*/
requestBeforeFrame(fn: () => void): void;
/**
*
* @param fn
*/
requestAfterFrame(fn: () => void): void;
/**
* {@link RenderItem.update}
* @param fn
*/
requestRenderFrame(fn: () => void): void;
}
interface IRenderTickerSupport {
/**
* ticker
* @param fn
* @param time
* @param end
* @returns id
*/
delegateTicker(fn: TickerFn, time?: number, end?: () => void): number;
/**
* ticker函数
* @param id id{@link IRenderTickerSupport.delegateTicker}
* @param callEnd {@link IRenderTickerSupport.delegateTicker}end参数
* @returns ticker不存在
*/
removeTicker(id: number, callEnd?: boolean): boolean;
}
interface RenderItemEvent {
2024-05-09 23:49:53 +08:00
beforeUpdate: (item?: RenderItem) => void;
afterUpdate: (item?: RenderItem) => void;
2024-05-16 22:43:24 +08:00
beforeRender: () => void;
afterRender: () => void;
2024-05-09 23:49:53 +08:00
}
interface TickerDelegation {
fn: TickerFn;
endFn?: () => void;
}
2024-08-14 23:23:41 +08:00
const beforeFrame: (() => void)[] = [];
const afterFrame: (() => void)[] = [];
const renderFrame: (() => void)[] = [];
// todo: 添加模型变换
2024-05-09 23:49:53 +08:00
export abstract class RenderItem
extends EventEmitter<RenderItemEvent>
2024-08-14 23:23:41 +08:00
implements
IRenderCache,
IRenderUpdater,
IRenderAnchor,
IRenderConfig,
IRenderFrame,
IRenderTickerSupport
2024-05-09 23:49:53 +08:00
{
2024-05-16 22:43:24 +08:00
/** 渲染的全局ticker */
static ticker: Ticker = new Ticker();
/** 包括但不限于怪物、npc、自动元件的动画帧数 */
static animatedFrame: number = 0;
/** ticker委托映射 */
static tickerMap: Map<number, TickerDelegation> = new Map();
/** ticker委托id */
static tickerId: number = 0;
2024-05-16 22:43:24 +08:00
2024-05-09 23:49:53 +08:00
zIndex: number = 0;
x: number = 0;
y: number = 0;
width: number = 200;
height: number = 200;
cacheList: Map<string, MotaOffscreenCanvas2D> = new Map();
using?: string;
writing?: string;
anchorX: number = 0;
anchorY: number = 0;
/** 渲染模式absolute表示绝对位置static表示跟随摄像机移动只对顶层元素有效 */
type: 'absolute' | 'static' = 'static';
2024-05-16 22:43:24 +08:00
/** 是否是高清画布 */
highResolution: boolean = true;
/** 是否抗锯齿 */
antiAliasing: boolean = true;
/** 是否被隐藏 */
hidden: boolean = false;
2024-05-09 23:49:53 +08:00
parent?: RenderItem & IRenderChildable;
2024-05-09 23:49:53 +08:00
2024-05-16 22:43:24 +08:00
protected needUpdate: boolean = false;
2024-05-09 23:49:53 +08:00
constructor() {
super();
2024-05-18 17:05:01 +08:00
// this.using = '@default';
2024-05-09 23:49:53 +08:00
}
/**
*
* @param canvas
* @param camera 使
*/
2024-08-17 23:11:20 +08:00
abstract render(canvas: MotaOffscreenCanvas2D, camera: Camera): void;
2024-05-09 23:49:53 +08:00
/**
*
*/
abstract size(width: number, height: number): void;
/**
*
*/
abstract pos(x: number, y: number): void;
useCache(index?: string): void {
if (isNil(index)) {
this.using = void 0;
return;
}
if (!this.cacheList.has(index)) {
this.writing = index;
}
this.using = index;
}
cache(index?: string): void {
this.writing = index;
this.using = index;
}
clearCache(index?: string): void {
if (isNil(index)) {
this.writing = void 0;
this.using = void 0;
this.cacheList.clear();
} else {
this.cacheList.delete(index);
}
}
setAnchor(x: number, y: number): void {
this.anchorX = x;
this.anchorY = y;
}
update(item?: RenderItem): void {
2024-05-16 22:43:24 +08:00
if (this.needUpdate) return;
this.needUpdate = true;
2024-05-18 17:05:01 +08:00
this.parent?.update(item);
2024-05-16 22:43:24 +08:00
}
setHD(hd: boolean): void {
this.highResolution = hd;
this.update(this);
}
setAntiAliasing(anti: boolean): void {
this.antiAliasing = anti;
this.update(this);
}
setZIndex(zIndex: number) {
this.zIndex = zIndex;
this.parent?.sortChildren?.();
}
2024-08-14 23:23:41 +08:00
requestBeforeFrame(fn: () => void): void {
beforeFrame.push(fn);
}
requestAfterFrame(fn: () => void): void {
afterFrame.push(fn);
}
requestRenderFrame(fn: () => void): void {
renderFrame.push(fn);
}
delegateTicker(fn: TickerFn, time?: number, end?: () => void): number {
const id = RenderItem.tickerId++;
if (typeof time === 'number' && time === 0) return id;
const delegation: TickerDelegation = {
fn,
endFn: end
};
RenderItem.tickerMap.set(id, delegation);
RenderItem.ticker.add(fn);
if (typeof time === 'number' && time < 2147438647 && time > 0) {
setTimeout(() => {
RenderItem.ticker.remove(fn);
end?.();
}, time);
}
return id;
}
removeTicker(id: number, callEnd: boolean = true): boolean {
const delegation = RenderItem.tickerMap.get(id);
if (!delegation) return false;
RenderItem.ticker.remove(delegation.fn);
if (callEnd) delegation.endFn?.();
return true;
}
/**
*
*/
hide() {
if (this.hidden) return;
this.hidden = true;
this.update(this);
}
/**
*
*/
show() {
if (!this.hidden) return;
this.hidden = false;
this.update(this);
}
2024-08-17 23:11:20 +08:00
/**
*
* @param parent
*/
append(parent: IRenderChildable) {
parent.appendChild(this);
}
/**
*
*/
remove() {
this.parent?.removeChild(this);
this.parent = void 0;
}
/**
* 使
*/
destroy(): void {
this.remove();
2024-05-09 23:49:53 +08:00
}
}
2024-08-14 23:23:41 +08:00
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());
}
});
2024-08-20 18:04:22 +08:00
export interface IAnimateFrame {
updateFrameAnimate(frame: number, time: number): void;
}
interface RenderEvent {
2024-08-20 18:04:22 +08:00
animateFrame: [frame: number, time: number];
}
class RenderEmits extends EventEmitter<RenderEvent> {
private framer: Set<IAnimateFrame> = new Set();
/**
*
*/
addFramer(framer: IAnimateFrame) {
this.framer.add(framer);
}
/**
*
*/
removeFramer(framer: IAnimateFrame) {
this.framer.delete(framer);
}
/**
*
* @param frame
* @param time
*/
emitAnimateFrame(frame: number, time: number) {
this.framer.forEach(v => v.updateFrameAnimate(frame, time));
this.emit('animateFrame', frame, time);
}
}
2024-08-20 18:04:22 +08:00
export const renderEmits = new RenderEmits();
2024-05-16 22:43:24 +08:00
Mota.require('var', 'hook').once('reset', () => {
let lastTime = 0;
RenderItem.ticker.add(time => {
if (!core.isPlaying()) return;
if (time - lastTime > core.values.animateSpeed) {
RenderItem.animatedFrame++;
lastTime = time;
2024-08-20 18:04:22 +08:00
renderEmits.emitAnimateFrame(RenderItem.animatedFrame, time);
2024-05-16 22:43:24 +08:00
}
});
});
2024-05-09 23:49:53 +08:00
export function withCacheRender(
item: RenderItem & ICanvasCachedRenderItem,
_canvas: HTMLCanvasElement,
2024-05-09 23:49:53 +08:00
ctx: CanvasRenderingContext2D,
camera: Camera,
fn: RenderFunction
) {
const { width, height } = item;
const ax = width * item.anchorX;
const ay = height * item.anchorY;
let write = item.writing;
2024-05-10 17:33:27 +08:00
if (!isNil(item.using) && isNil(item.writing)) {
2024-05-09 23:49:53 +08:00
const cache = item.cacheList.get(item.using);
if (cache) {
ctx.drawImage(
cache.canvas,
item.x - ax,
item.y - ay,
item.width,
item.height
);
return;
}
write = item.using;
}
const { canvas: c, ctx: ct } = item.canvas;
ct.clearRect(0, 0, c.width, c.height);
fn(item.canvas, camera);
if (!isNil(write)) {
const cache = item.cacheList.get(write);
if (cache) {
const { canvas, ctx } = cache;
2024-05-10 17:33:27 +08:00
ctx.drawImage(c, 0, 0, canvas.width, canvas.height);
2024-05-09 23:49:53 +08:00
} else {
item.cacheList.set(write, MotaOffscreenCanvas2D.clone(item.canvas));
}
}
ctx.drawImage(c, item.x - ax, item.y - ay, item.width, item.height);
}
export function transformCanvas(
canvas: MotaOffscreenCanvas2D,
camera: Camera,
clear: boolean = false
) {
const { canvas: ca, ctx, scale } = canvas;
const mat = camera.mat;
const a = mat[0] * scale;
const b = mat[1] * scale;
const c = mat[3] * scale;
const d = mat[4] * scale;
const e = mat[6] * scale;
const f = mat[7] * scale;
ctx.setTransform(1, 0, 0, 1, 0, 0);
if (clear) {
ctx.clearRect(0, 0, ca.width, ca.height);
}
ctx.translate(ca.width / 2, ca.height / 2);
ctx.transform(a, b, c, d, e, f);
}