refactor: 优化渲染缓存 & 添加每个元素的Transform

This commit is contained in:
unanmed 2024-08-22 23:29:15 +08:00
parent 3f500e35ca
commit fc05ddec9b
16 changed files with 317 additions and 483 deletions

View File

@ -1,10 +1,10 @@
import { parseCss } from '@/plugin/utils';
import { EventEmitter } from '../common/eventEmitter';
import { EventEmitter } from 'eventemitter3';
import { CSSObj } from '../interface';
interface OffscreenCanvasEvent {
/** 当被动触发resize时例如core.domStyle.scale变化、窗口大小变化时触发使用size函数并不会触发 */
resize: () => void;
resize: [];
}
export class MotaOffscreenCanvas2D extends EventEmitter<OffscreenCanvasEvent> {

View File

@ -65,7 +65,6 @@ import * as portal from './fx/portal';
import { MotaRenderer } from './render/render';
import { Container } from './render/container';
import { Sprite } from './render/sprite';
import { Camera } from './render/camera';
import { Image, Text } from './render/preset/misc';
import { RenderItem } from './render/item';
import { texture } from './render/cache';
@ -152,7 +151,6 @@ Mota.register('module', 'Render', {
MotaRenderer,
Container,
Sprite,
Camera,
Text,
Image,
RenderItem,

View File

@ -422,7 +422,9 @@ document.addEventListener('keyup', e => {
const code = keycode(e.keyCode);
if (gameKey.emitKey(code, assist, 'up', e)) {
e.preventDefault();
deleteWith(core.status.holdingKeys, e.keyCode);
if (core.status.holdingKeys) {
deleteWith(core.status.holdingKeys, e.keyCode);
}
} else {
// polyfill样板
if (

View File

@ -1,95 +1,51 @@
import { MotaOffscreenCanvas2D } from '../fx/canvas2d';
import { Camera } from './camera';
import {
ICanvasCachedRenderItem,
IRenderChildable,
RenderItem,
RenderItemPosition,
withCacheRender
} from './item';
import { IRenderChildable, RenderItem, RenderItemPosition } from './item';
import { Transform } from './transform';
export class Container
extends RenderItem
implements ICanvasCachedRenderItem, IRenderChildable
{
export class Container extends RenderItem implements IRenderChildable {
children: RenderItem[] = [];
sortedChildren: RenderItem[] = [];
canvas: MotaOffscreenCanvas2D;
/** 是否启用缓存机制,对于特殊场景,内部已经包含了缓存机制,这时就不需要启用了 */
private readonly enableCache: boolean;
/**
*
* @param type absolute表示绝对位置static表示跟随摄像机移动
* @param type absolute表示绝对位置static表示跟随摄像机移动
* @param cache
*/
constructor(type: RenderItemPosition = 'static', cache: boolean = true) {
super();
this.canvas = new MotaOffscreenCanvas2D();
super(cache);
this.type = type;
this.canvas.withGameScale(true);
this.enableCache = cache;
}
private renderTo(canvas: MotaOffscreenCanvas2D, camera: Camera) {
protected render(
canvas: MotaOffscreenCanvas2D,
transform: Transform
): void {
const { ctx } = canvas;
this.sortedChildren.forEach(v => {
if (v.hidden) return;
ctx.save();
if (!v.antiAliasing) {
ctx.imageSmoothingEnabled = false;
} else {
if (v.antiAliasing) {
ctx.imageSmoothingEnabled = true;
} else {
ctx.imageSmoothingEnabled = false;
}
v.render(canvas, camera);
v.renderContent(canvas, transform);
ctx.restore();
});
}
render(canvas: MotaOffscreenCanvas2D, camera: Camera): void {
this.emit('beforeRender');
if (this.needUpdate) {
this.cache(this.using);
this.needUpdate = false;
}
if (this.enableCache) {
withCacheRender(this, canvas.canvas, canvas.ctx, camera, c => {
this.renderTo(c, camera);
});
} else {
this.renderTo(canvas, camera);
}
this.writing = void 0;
this.emit('afterRender');
}
size(width: number, height: number) {
this.width = width;
this.height = height;
this.canvas.size(width, height);
this.writing = this.using;
this.update(this);
}
pos(x: number, y: number) {
this.x = x;
this.y = y;
}
/**
* tick执行更新
* @param children
*/
appendChild(...children: RenderItem[]) {
appendChild(...children: RenderItem<any>[]) {
children.forEach(v => (v.parent = this));
this.children.push(...children);
this.sortChildren();
this.update(this);
}
removeChild(...child: RenderItem[]): void {
removeChild(...child: RenderItem<any>[]): void {
child.forEach(v => {
const index = this.children.indexOf(v);
if (index === -1) return;
@ -105,18 +61,6 @@ export class Container
.sort((a, b) => a.zIndex - b.zIndex);
}
setHD(hd: boolean): void {
this.highResolution = hd;
this.canvas.setHD(hd);
this.update(this);
}
setAntiAliasing(anti: boolean): void {
this.antiAliasing = anti;
this.canvas.setAntiAliasing(anti);
this.update(this);
}
destroy(): void {
super.destroy();
this.children.forEach(v => {

View File

@ -1,43 +1,16 @@
import { isNil } from 'lodash-es';
import { EventEmitter } from 'eventemitter3';
import { MotaOffscreenCanvas2D } from '../fx/canvas2d';
import { Camera } from './camera';
import { Ticker, TickerFn } from 'mutate-animate';
import { Transform } from './transform';
export type RenderFunction = (
canvas: MotaOffscreenCanvas2D,
camera: Camera
transform: Transform
) => 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 {
/**
*
@ -46,11 +19,6 @@ export interface IRenderUpdater {
update(item?: RenderItem): void;
}
export interface ICanvasCachedRenderItem {
/** 离屏画布首先渲染到它上面然后由Renderer渲染到最终画布上 */
canvas: MotaOffscreenCanvas2D;
}
interface IRenderAnchor {
anchorX: number;
anchorY: number;
@ -89,13 +57,13 @@ export interface IRenderChildable {
*
* @param child
*/
appendChild(...child: RenderItem[]): void;
appendChild(...child: RenderItem<any>[]): void;
/**
*
* @param child
*/
removeChild(...child: RenderItem[]): void;
removeChild(...child: RenderItem<any>[]): void;
/**
*
@ -142,11 +110,11 @@ interface IRenderTickerSupport {
removeTicker(id: number, callEnd?: boolean): boolean;
}
interface RenderItemEvent {
beforeUpdate: (item?: RenderItem) => void;
afterUpdate: (item?: RenderItem) => void;
beforeRender: () => void;
afterRender: () => void;
export interface ERenderItemEvent {
beforeUpdate: [item?: RenderItem];
afterUpdate: [item?: RenderItem];
beforeRender: [transform: Transform];
afterRender: [transform: Transform];
}
interface TickerDelegation {
@ -158,11 +126,9 @@ const beforeFrame: (() => void)[] = [];
const afterFrame: (() => void)[] = [];
const renderFrame: (() => void)[] = [];
// todo: 添加模型变换
export abstract class RenderItem
extends EventEmitter<RenderItemEvent>
export abstract class RenderItem<E extends ERenderItemEvent = ERenderItemEvent>
extends EventEmitter<ERenderItemEvent | E>
implements
IRenderCache,
IRenderUpdater,
IRenderAnchor,
IRenderConfig,
@ -178,22 +144,17 @@ export abstract class RenderItem
/** ticker委托id */
static tickerId: number = 0;
/** 元素纵深,表示了遮挡关系 */
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;
// 渲染锚点,(0,0)表示左上角,(1,1)表示右下角
anchorX: number = 0;
anchorY: number = 0;
/** 渲染模式absolute表示绝对位置static表示跟随摄像机移动,只对顶层元素有效 */
/** 渲染模式absolute表示绝对位置static表示跟随摄像机移动 */
type: 'absolute' | 'static' = 'static';
/** 是否是高清画布 */
highResolution: boolean = true;
@ -202,57 +163,92 @@ export abstract class RenderItem
/** 是否被隐藏 */
hidden: boolean = false;
/** 当前元素的父元素 */
parent?: RenderItem & IRenderChildable;
protected needUpdate: boolean = false;
constructor() {
/** 该渲染元素的模型变换矩阵 */
transform: Transform = new Transform();
/** 渲染缓存信息 */
private cache: MotaOffscreenCanvas2D = new MotaOffscreenCanvas2D();
/** 是否需要更新缓存 */
private cacheDirty: boolean = true;
/** 是否启用缓存机制 */
readonly enableCache: boolean = true;
constructor(enableCache: boolean = true) {
super();
// this.using = '@default';
this.enableCache = enableCache;
this.cache.withGameScale(true);
}
/**
*
*
* @param canvas
* @param camera 使
* @param transform
*
* `absolute`
* `Layer``Damage`
*/
abstract render(canvas: MotaOffscreenCanvas2D, camera: Camera): void;
protected abstract render(
canvas: MotaOffscreenCanvas2D,
transform: Transform
): void;
/**
*
*/
abstract size(width: number, height: number): void;
size(width: number, height: number): void {
this.width = width;
this.height = height;
this.cache.size(width, height);
this.cacheDirty = true;
this.update(this);
}
/**
*
*
* @param canvas
* @param transform
*/
abstract pos(x: number, y: number): void;
renderContent(canvas: MotaOffscreenCanvas2D, transform: Transform) {
if (this.hidden) return;
this.emit('beforeRender', transform);
this.needUpdate = false;
const tran = transform.multiply(this.transform);
const ax = -this.anchorX * this.width;
const ay = -this.anchorY * this.height;
if (this.enableCache) {
if (this.cacheDirty) {
const cache = this.cache;
this.render(cache, tran);
this.cache = cache;
this.cacheDirty = false;
}
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();
canvas.ctx.save();
if (this.type === 'static') transformCanvas(canvas, tran);
canvas.ctx.drawImage(this.cache.canvas, ax, ay);
canvas.ctx.restore();
} else {
this.cacheList.delete(index);
canvas.ctx.save();
if (this.type === 'static') transformCanvas(canvas, tran);
this.render(canvas, tran);
canvas.ctx.restore();
}
this.emit('afterRender', transform);
}
/**
* `transform.setTranslate(x, y)`
* @param x
* @param y
*/
pos(x: number, y: number) {
this.transform.setTranslate(x, y);
}
setAnchor(x: number, y: number): void {
@ -260,19 +256,22 @@ export abstract class RenderItem
this.anchorY = y;
}
update(item?: RenderItem): void {
update(item?: RenderItem<any>): void {
if (this.needUpdate) return;
this.needUpdate = true;
this.cacheDirty = true;
this.parent?.update(item);
}
setHD(hd: boolean): void {
this.highResolution = hd;
this.cache.setHD(hd);
this.update(this);
}
setAntiAliasing(anti: boolean): void {
this.antiAliasing = anti;
this.cache.setAntiAliasing(anti);
this.update(this);
}
@ -429,54 +428,13 @@ Mota.require('var', 'hook').once('reset', () => {
});
});
export function withCacheRender(
item: RenderItem & ICanvasCachedRenderItem,
_canvas: HTMLCanvasElement,
ctx: CanvasRenderingContext2D,
camera: Camera,
fn: RenderFunction
) {
const { width, height } = item;
const ax = width * item.anchorX;
const ay = height * item.anchorY;
let write = item.writing;
if (!isNil(item.using) && isNil(item.writing)) {
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;
ctx.drawImage(c, 0, 0, canvas.width, canvas.height);
} 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,
transform: Transform,
clear: boolean = false
) {
const { canvas: ca, ctx, scale } = canvas;
const mat = camera.mat;
const mat = transform.mat;
const a = mat[0] * scale;
const b = mat[1] * scale;
const c = mat[3] * scale;
@ -487,6 +445,5 @@ export function transformCanvas(
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);
}

View File

@ -6,7 +6,7 @@ import {
Layer,
LayerGroup
} from './layer';
import { Sprite } from '../sprite';
import { ESpriteEvent, Sprite } from '../sprite';
import { BlockCacher } from './block';
import type {
DamageEnemy,
@ -14,22 +14,28 @@ import type {
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';
import EventEmitter from 'eventemitter3';
import { Transform } from '../transform';
const ensureFloorDamage = Mota.require('fn', 'ensureFloorDamage');
export class FloorDamageExtends implements ILayerGroupRenderExtends {
interface EFloorDamageEvent {
update: [floor: FloorIds];
}
export class FloorDamageExtends
extends EventEmitter<EFloorDamageEvent>
implements ILayerGroupRenderExtends
{
id: string = 'floor-damage';
floorBinder!: LayerGroupFloorBinder;
group!: LayerGroup;
sprite!: Damage;
static listenedDamage: Set<FloorDamageExtends> = new Set();
/**
*
*/
@ -41,6 +47,7 @@ export class FloorDamageExtends implements ILayerGroupRenderExtends {
const enemy = core.status.maps[floor].enemy;
this.sprite.updateCollection(enemy);
this.emit('update', floor);
}
/**
@ -89,18 +96,16 @@ export class FloorDamageExtends implements ILayerGroupRenderExtends {
);
group.removeExtends('floor-damage');
}
FloorDamageExtends.listenedDamage.add(this);
});
}
onDestroy(group: LayerGroup): void {
this.floorBinder.off('update', this.onUpdate);
this.floorBinder.off('setBlock', this.onSetBlock);
FloorDamageExtends.listenedDamage.delete(this);
}
}
interface DamageRenderable {
export interface DamageRenderable {
x: number;
y: number;
align: CanvasTextAlign;
@ -117,7 +122,12 @@ interface DamageCache {
symbol: number;
}
export class Damage extends Sprite {
interface EDamageEvent extends ESpriteEvent {
setMapSize: [width: number, height: number];
beforeDamageRender: [need: Set<number>, transform: Transform];
}
export class Damage extends Sprite<EDamageEvent> {
mapWidth: number = 0;
mapHeight: number = 0;
@ -188,6 +198,8 @@ export class Damage extends Sprite {
this.blockData.set(i, new Map());
this.renderable.set(i, new Set());
}
this.emit('setMapSize', width, height);
}
/**
@ -385,32 +397,34 @@ export class Damage extends Sprite {
/**
*
*/
calNeedRender(camera: Camera) {
calNeedRender(transform: Transform) {
if (this.parent instanceof LayerGroup) {
// 如果处于地图组中,每个地图的渲染区域应该是一样的,因此可以缓存优化
return this.parent.cacheNeedRender(camera, this.block);
return this.parent.cacheNeedRender(transform, this.block);
} else if (this.parent instanceof Layer) {
// 如果是地图的子元素直接调用Layer的计算函数
return this.parent.calNeedRender(camera);
return this.parent.calNeedRender(transform);
} else {
return calNeedRenderOf(camera, this.cellSize, this.block);
return calNeedRenderOf(transform, this.cellSize, this.block);
}
}
/**
*
* @param camera
* @param transform
*/
renderDamage(camera: Camera) {
renderDamage(transform: Transform) {
// console.time('damage');
const { ctx } = this.damageMap;
ctx.save();
transformCanvas(this.damageMap, camera, true);
transformCanvas(this.damageMap, transform, true);
const { res: render } = this.calNeedRender(camera);
const { res: render } = this.calNeedRender(transform);
const block = this.block;
const cell = this.cellSize;
const size = cell * block.blockSize;
this.emit('beforeDamageRender', render, transform);
render.forEach(v => {
const [x, y] = block.getBlockXYByIndex(v);
const bx = x * block.blockSize;

View File

@ -107,6 +107,10 @@ export class LayerGroupFloorBinder
this.emit('update', floor);
}
getFloor() {
return this.bindThisFloor ? core.status.floorId : this.floor!;
}
/**
*
*/

View File

@ -338,7 +338,6 @@ export class HeroRenderer
this.moveId = layer.delegateTicker(() => {
this.moveTick(Date.now());
});
console.log(this);
}
onDestroy(layer: Layer): void {

View File

@ -1,18 +1,13 @@
import { MotaOffscreenCanvas2D } from '@/core/fx/canvas2d';
import { Container } from '../container';
import { Sprite } from '../sprite';
import { Camera } from '../camera';
import { TimingFn } from 'mutate-animate';
import {
IAnimateFrame,
renderEmits,
RenderItem,
transformCanvas
} from '../item';
import { IAnimateFrame, renderEmits, RenderItem } from '../item';
import { logger } from '@/core/common/logger';
import { AutotileRenderable, RenderableData, texture } from '../cache';
import { RenderableData, texture } from '../cache';
import { glMatrix } from 'gl-matrix';
import { BlockCacher } from './block';
import { Transform } from '../transform';
export interface ILayerGroupRenderExtends {
/** 拓展的唯一标识符 */
@ -58,13 +53,6 @@ export interface ILayerGroupRenderExtends {
*/
onEmptyLayer?(group: LayerGroup): void;
/**
*
* @param group LayerGroup实例
* @param floor
*/
// onDataUpdate?: (group: LayerGroup, floor: FloorIds) => void;
/**
*
* @param group LayerGroup实例
@ -72,13 +60,6 @@ export interface ILayerGroupRenderExtends {
*/
onFrameUpdate?(group: LayerGroup, frame: number): void;
/**
*
* @param group LayerGroup实例
* @param floor id
*/
// onBindFloor?: (group: LayerGroup, floor?: FloorIds) => void;
/**
* LayerGroup被销毁
* @param group LayerGroup实例
@ -267,13 +248,13 @@ export class LayerGroup extends Container implements IAnimateFrame {
/**
*
* @param camera
* @param transform
* @param blockData
*/
cacheNeedRender(camera: Camera, block: BlockCacher<any>) {
cacheNeedRender(transform: Transform, block: BlockCacher<any>) {
return (
this.needRender ??
(this.needRender = calNeedRenderOf(camera, this.cellSize, block))
(this.needRender = calNeedRenderOf(transform, this.cellSize, block))
);
}
@ -288,9 +269,6 @@ export class LayerGroup extends Container implements IAnimateFrame {
*
*/
updateFrameAnimate() {
this.layers.forEach(v => {
v.cache(v.using);
});
this.update(this);
for (const ex of this.extend.values()) {
@ -308,7 +286,7 @@ export class LayerGroup extends Container implements IAnimateFrame {
}
export function calNeedRenderOf(
camera: Camera,
transform: Transform,
cell: number,
block: BlockCacher<any>
): NeedRenderData {
@ -317,10 +295,10 @@ export function calNeedRenderOf(
const size = block.blockSize;
// -1是因为宽度是core._PX_从0开始的话末尾索引就是core._PX_ - 1
const [x1, y1] = Camera.untransformed(camera, -w, -h);
const [x2, y2] = Camera.untransformed(camera, w - 1, -h);
const [x3, y3] = Camera.untransformed(camera, w - 1, h - 1);
const [x4, y4] = Camera.untransformed(camera, -w, h - 1);
const [x1, y1] = Transform.untransformed(transform, -w, -h);
const [x2, y2] = Transform.untransformed(transform, w - 1, -h);
const [x3, y3] = Transform.untransformed(transform, w - 1, h - 1);
const [x4, y4] = Transform.untransformed(transform, -w, h - 1);
const res: Set<number> = new Set();
/** 一个纵坐标对应的所有横坐标,用于填充 */
@ -469,8 +447,6 @@ export interface ILayerRenderExtends {
*/
onBackgroundSet?(layer: Layer, background: AllNumbers): void;
// onBackgroundBind?: (layer: Layer, floorId: FloorIds) => void;
/**
*
* @param layer Layer实例
@ -568,18 +544,26 @@ export interface ILayerRenderExtends {
/**
*
* @param layer Layer实例
* @param camera
* @param transform
* @param need
*/
onBeforeRender?(layer: Layer, camera: Camera, need: NeedRenderData): void;
onBeforeRender?(
layer: Layer,
transform: Transform,
need: NeedRenderData
): void;
/**
*
* @param layer Layer实例
* @param camera
* @param transform
* @param need
*/
onAfterRender?(layer: Layer, camera: Camera, need: NeedRenderData): void;
onAfterRender?(
layer: Layer,
transform: Transform,
need: NeedRenderData
): void;
/**
* Layer被销毁
@ -606,38 +590,6 @@ interface NeedRenderData {
back: [x: number, y: number][];
}
interface MovingStepLinearSwap {
/** 线性差值移动(也就是平移)或者是瞬移 */
type: 'linear' | 'swap';
x: number;
y: number;
/** 这次移动的总时长,不是每格时长 */
time?: number;
}
interface MovingStepFunction {
/** 自定义移动方式 */
type: 'fn';
/**
*
*
*/
fn: TimingFn<3>;
time?: number;
relative?: boolean;
}
interface MovingBlock {
/** 当前横坐标 */
x: number;
/** 当前纵坐标 */
y: number;
/** 渲染信息 */
render: RenderableData | AutotileRenderable;
/** 当前的纵深 */
nowZ: number;
}
export class Layer extends Container {
// 一些会用到的常量
static readonly FRAME_0 = 1;
@ -654,7 +606,7 @@ export class Layer extends Container {
protected backMap: MotaOffscreenCanvas2D = new MotaOffscreenCanvas2D();
/** 最终渲染至的Sprite */
main: Sprite = new Sprite();
main: Sprite = new Sprite('static', false);
/** 渲染的层 */
layer?: FloorLayer;
@ -714,13 +666,13 @@ export class Layer extends Container {
this.main.size(core._PX_, core._PY_);
this.appendChild(this.main);
this.main.setRenderFn((canvas, camera) => {
this.main.setRenderFn((canvas, transform) => {
const { ctx } = canvas;
const { width, height } = canvas.canvas;
ctx.save();
ctx.imageSmoothingEnabled = false;
const need = this.calNeedRender(camera);
this.renderMap(camera, need);
const need = this.calNeedRender(transform);
this.renderMap(transform, need);
ctx.setTransform(1, 0, 0, 1, 0, 0);
ctx.drawImage(this.backMap.canvas, 0, 0, width, height);
ctx.drawImage(this.staticMap.canvas, 0, 0, width, height);
@ -930,8 +882,6 @@ export class Layer extends Container {
this.updateBlocks(x, y, width, height);
this.updateBigImages(x, y, width, height);
// this.update(this);
for (const ex of this.extend.values()) {
ex.onDataPut?.(this, data, width, x, y, calAutotile);
}
@ -1109,15 +1059,15 @@ export class Layer extends Container {
}
/**
*
* @param camera
*
* @param transform
*/
calNeedRender(camera: Camera): NeedRenderData {
calNeedRender(transform: Transform): NeedRenderData {
if (this.parent instanceof LayerGroup) {
// 如果处于地图组中,每个地图的渲染区域应该是一样的,因此可以缓存优化
return this.parent.cacheNeedRender(camera, this.block);
return this.parent.cacheNeedRender(transform, this.block);
} else {
return calNeedRenderOf(camera, this.cellSize, this.block);
return calNeedRenderOf(transform, this.cellSize, this.block);
}
}
@ -1138,7 +1088,7 @@ export class Layer extends Container {
/**
*
*/
renderMap(camera: Camera, need: NeedRenderData) {
renderMap(transform: Transform, need: NeedRenderData) {
this.staticMap.clear();
this.movingMap.clear();
this.backMap.clear();
@ -1146,32 +1096,31 @@ export class Layer extends Container {
if (this.needUpdateMoving) this.updateMovingRenderable();
for (const ex of this.extend.values()) {
ex.onBeforeRender?.(this, camera, need);
ex.onBeforeRender?.(this, transform, need);
}
this.renderBack(camera, need);
this.renderStatic(camera, need);
this.renderMoving(camera);
this.renderBack(transform, need);
this.renderStatic(transform, need);
this.renderMoving(transform);
for (const ex of this.extend.values()) {
ex.onAfterRender?.(this, camera, need);
ex.onAfterRender?.(this, transform, need);
}
}
/**
*
* @param camera
* @param transform
* @param need
*/
protected renderBack(camera: Camera, need: NeedRenderData) {
protected renderBack(transform: Transform, need: NeedRenderData) {
const cell = this.cellSize;
const frame = (RenderItem.animatedFrame % 4) + 1;
const blockSize = this.block.blockSize;
const { back } = need;
const { ctx } = this.backMap;
const mat = camera.mat;
const mat = transform.mat;
const [a, b, , c, d, , e, f] = mat;
ctx.setTransform(1, 0, 0, 1, 0, 0);
ctx.translate(core._PX_ / 2, core._PY_ / 2);
ctx.transform(a, b, c, d, e, f);
if (this.background !== 0) {
@ -1195,7 +1144,7 @@ export class Layer extends Container {
/**
*
*/
protected renderStatic(camera: Camera, need: NeedRenderData) {
protected renderStatic(transform: Transform, need: NeedRenderData) {
const cell = this.cellSize;
const frame = RenderItem.animatedFrame % 4;
const { width } = this.block.blockData;
@ -1205,7 +1154,9 @@ export class Layer extends Container {
ctx.save();
const { res: render } = need;
transformCanvas(this.staticMap, camera, false);
const [a, b, , c, d, , e, f] = transform.mat;
ctx.setTransform(1, 0, 0, 1, 0, 0);
ctx.transform(a, b, c, d, e, f);
render.forEach(v => {
const x = v % width;
@ -1277,17 +1228,16 @@ export class Layer extends Container {
/**
* /
*/
protected renderMoving(camera: Camera) {
protected renderMoving(transform: Transform) {
const frame = (RenderItem.animatedFrame % 4) + 1;
const cell = this.cellSize;
const halfCell = cell / 2;
const { ctx } = this.movingMap;
ctx.save();
const mat = camera.mat;
const mat = transform.mat;
const [a, b, , c, d, , e, f] = mat;
ctx.setTransform(1, 0, 0, 1, 0, 0);
ctx.translate(core._PX_ / 2, core._PY_ / 2);
ctx.transform(a, b, c, d, e, f);
const max1 = Math.max(a, b, c, d) ** 2;
const max2 = Math.max(core._PX_, core._PY_) * 2;

View File

@ -1,3 +1,4 @@
import { MotaOffscreenCanvas2D } from '@/core/fx/canvas2d';
import { Sprite } from '../sprite';
type CanvasStyle = string | CanvasGradient | CanvasPattern;
@ -12,6 +13,8 @@ export class Text extends Sprite {
private length: number = 0;
private descent: number = 0;
private static measureCanvas = new MotaOffscreenCanvas2D();
constructor(text: string = '') {
super();
@ -39,11 +42,10 @@ export class Text extends Sprite {
*
*/
measure() {
this.canvas.ctx.save();
this.canvas.ctx.textBaseline = 'bottom';
this.canvas.ctx.font = this.font ?? '';
const res = this.canvas.ctx.measureText(this.text);
this.canvas.ctx.restore();
const ctx = Text.measureCanvas.ctx;
ctx.textBaseline = 'bottom';
ctx.font = this.font ?? '';
const res = ctx.measureText(this.text);
return res;
}
@ -53,8 +55,6 @@ export class Text extends Sprite {
*/
setText(text: string) {
this.text = text;
this.writing = this.using;
this.using = void 0;
this.calBox();
if (this.parent) this.update(this);
}
@ -101,8 +101,6 @@ export class Image extends Sprite {
constructor(image: SizedCanvasImageSource) {
super();
this.image = image;
this.canvas.withGameScale(false);
this.canvas.setHD(false);
this.size(image.width, image.height);
this.renderFn = ({ canvas, ctx }) => {
@ -117,8 +115,6 @@ export class Image extends Sprite {
setImage(image: SizedCanvasImageSource) {
this.image = image;
this.size(image.width, image.height);
this.writing = this.using;
this.using = void 0;
this.update(this);
}
}

View File

@ -1,8 +1,7 @@
import { Animation, hyper, sleep } from 'mutate-animate';
import { MotaCanvas2D, MotaOffscreenCanvas2D } from '../fx/canvas2d';
import { Camera } from './camera';
import { Container } from './container';
import { RenderItem, transformCanvas, withCacheRender } from './item';
import { RenderItem, transformCanvas } from './item';
import { FloorLayer, Layer, LayerGroup } from './preset/layer';
import { LayerGroupFloorBinder } from './preset/floor';
import { FloorDamageExtends } from './preset/damage';
@ -11,11 +10,6 @@ import { HeroRenderer } from './preset/hero';
export class MotaRenderer extends Container {
static list: Set<MotaRenderer> = new Set();
canvas: MotaOffscreenCanvas2D;
camera: Camera;
/** 摄像机缓存,如果是需要快速切换摄像机的场景,使用缓存可以大幅提升性能表现 */
cameraCache: Map<Camera, MotaOffscreenCanvas2D> = new Map();
target: MotaCanvas2D;
/** 这个渲染对象的id */
id: string;
@ -23,19 +17,14 @@ export class MotaRenderer extends Container {
protected needUpdate: boolean = false;
constructor(id: string = 'render-main') {
super();
super('static', false);
this.id = id;
this.canvas = new MotaOffscreenCanvas2D();
this.camera = new Camera();
this.target = new MotaCanvas2D(id);
this.width = core._PX_;
this.height = core._PY_;
this.size(core._PX_, core._PY_);
this.target.withGameScale(true);
this.target.size(core._PX_, core._PY_);
this.canvas.withGameScale(true);
this.canvas.size(core._PX_, core._PY_);
this.target.css(`z-index: 100`);
MotaRenderer.list.add(this);
@ -47,62 +36,61 @@ export class MotaRenderer extends Container {
* @param noCache 使true
* 使
*/
useCamera(camera: Camera, noCache: boolean = false) {
const cache = MotaOffscreenCanvas2D.clone(this.canvas);
this.cameraCache.set(this.camera, cache);
this.camera = camera;
const nowCache = this.cameraCache.get(camera);
if (!nowCache || !noCache) this.render();
else this.renderCache(nowCache);
}
// useCamera(camera: Camera, noCache: boolean = false) {
// const cache = MotaOffscreenCanvas2D.clone(this.canvas);
// this.cameraCache.set(this.camera, cache);
// this.camera = camera;
// const nowCache = this.cameraCache.get(camera);
// if (!nowCache || !noCache) this.render();
// else this.renderCache(nowCache);
// }
/**
*
* @param camera
*/
freeCameraCache(camera: Camera) {
this.cameraCache.delete(camera);
}
// freeCameraCache(camera: Camera) {
// this.cameraCache.delete(camera);
// }
/**
*
*/
render() {
// console.time();
const { canvas, ctx } = this.target;
const camera = this.camera;
this.emit('beforeRender');
ctx.clearRect(0, 0, canvas.width, canvas.height);
withCacheRender(this, canvas, ctx, camera, canvas => {
const { canvas: ca, ctx: ct, scale } = canvas;
this.sortedChildren.forEach(v => {
if (v.hidden) return;
if (v.type === 'absolute') {
ct.setTransform(scale, 0, 0, scale, 0, 0);
} else {
transformCanvas(canvas, camera, false);
}
ct.save();
if (!v.antiAliasing) {
ctx.imageSmoothingEnabled = false;
ct.imageSmoothingEnabled = false;
} else {
ctx.imageSmoothingEnabled = true;
ct.imageSmoothingEnabled = true;
}
v.render(this.target, camera);
ct.restore();
});
});
this.emit('afterRender');
// console.timeEnd();
}
// protected render(canvas: MotaOffscreenCanvas2D, transform: Transform) {
// console.time();
// const { canvas, ctx } = this.target;
// const camera = this.camera;
// this.emit('beforeRender', camera);
// ctx.clearRect(0, 0, canvas.width, canvas.height);
// withCacheRender(this, canvas, ctx, camera, canvas => {
// const { canvas: ca, ctx: ct, scale } = canvas;
// this.sortedChildren.forEach(v => {
// if (v.hidden) return;
// if (v.type === 'absolute') {
// ct.setTransform(scale, 0, 0, scale, 0, 0);
// } else {
// transformCanvas(canvas, camera, false);
// }
// ct.save();
// if (!v.antiAliasing) {
// ctx.imageSmoothingEnabled = false;
// ct.imageSmoothingEnabled = false;
// } else {
// ctx.imageSmoothingEnabled = true;
// ct.imageSmoothingEnabled = true;
// }
// v.renderContent(this.target, camera);
// ct.restore();
// });
// });
// this.emit('afterRender', camera);
// console.timeEnd();
// }
update(item?: RenderItem) {
if (this.needUpdate) return;
this.needUpdate = true;
this.requestRenderFrame(() => {
this.cache(this.using);
this.needUpdate = false;
this.refresh(item);
});
@ -110,20 +98,12 @@ export class MotaRenderer extends Container {
protected refresh(item?: RenderItem): void {
this.emit('beforeUpdate', item);
this.render();
// console.time();
this.render(this.target, this.transform);
// console.timeEnd();
this.emit('afterUpdate', item);
}
/**
*
* @param cache Canvas2D对象
*/
renderCache(cache: MotaOffscreenCanvas2D) {
const { canvas, ctx } = this.canvas;
ctx.clearRect(0, 0, canvas.width, canvas.width);
ctx.drawImage(cache.canvas, 0, 0);
}
/**
*
*/
@ -142,7 +122,7 @@ window.addEventListener('resize', () => {
Mota.require('var', 'hook').once('reset', () => {
const render = new MotaRenderer();
const camera = render.camera;
const transform = render.transform;
render.mount();
const layer = new LayerGroup();
@ -174,8 +154,8 @@ Mota.require('var', 'hook').once('reset', () => {
// }
// );
camera.move(240, 240);
transform.move(240, 240);
render.update();
console.log(layer);
console.log(render);
});

View File

@ -1,76 +1,39 @@
import { Camera } from './camera';
import {
ICanvasCachedRenderItem,
ERenderItemEvent,
RenderFunction,
RenderItem,
RenderItemPosition,
withCacheRender
RenderItemPosition
} from './item';
import { MotaOffscreenCanvas2D } from '../fx/canvas2d';
import { Transform } from './transform';
export class Sprite extends RenderItem implements ICanvasCachedRenderItem {
export interface ESpriteEvent extends ERenderItemEvent {}
export class Sprite<E extends ESpriteEvent = ESpriteEvent> extends RenderItem<
E | ESpriteEvent
> {
renderFn: RenderFunction;
canvas: MotaOffscreenCanvas2D;
private readonly enableCache: boolean;
/**
*
* @param type absolute表示绝对位置static表示跟随摄像机移动
* @param cache
*/
constructor(type: RenderItemPosition = 'static', cache: boolean = true) {
super();
super(cache);
this.type = type;
this.enableCache = cache;
this.renderFn = () => {};
this.canvas = new MotaOffscreenCanvas2D();
this.canvas.withGameScale(true);
}
render(canvas: MotaOffscreenCanvas2D, camera: Camera): void {
this.emit('beforeRender');
if (this.needUpdate) {
this.cache(this.using);
this.needUpdate = false;
}
if (this.enableCache) {
withCacheRender(this, canvas.canvas, canvas.ctx, camera, canvas => {
this.renderFn(canvas, camera);
});
} else {
this.renderFn(canvas, camera);
}
this.writing = void 0;
this.emit('afterRender');
}
size(width: number, height: number) {
this.width = width;
this.height = height;
this.canvas.size(width, height);
this.update(this);
}
pos(x: number, y: number) {
this.x = x;
this.y = y;
protected render(
canvas: MotaOffscreenCanvas2D,
transform: Transform
): void {
this.renderFn(canvas, transform);
}
setRenderFn(fn: RenderFunction) {
this.renderFn = fn;
}
setHD(hd: boolean): void {
this.highResolution = hd;
this.canvas.setHD(hd);
this.update(this);
}
setAntiAliasing(anti: boolean): void {
this.antiAliasing = anti;
this.canvas.setAntiAliasing(anti);
this.update(this);
}
}

View File

@ -1,9 +1,6 @@
import { ReadonlyMat3, ReadonlyVec3, mat3, vec2, vec3 } from 'gl-matrix';
import { EventEmitter } from '../common/eventEmitter';
import { mat3, ReadonlyMat3, ReadonlyVec3, vec2, vec3 } from 'gl-matrix';
interface CameraEvent {}
export class Camera extends EventEmitter<CameraEvent> {
export class Transform {
mat: mat3 = mat3.create();
x: number = 0;
@ -12,10 +9,11 @@ export class Camera extends EventEmitter<CameraEvent> {
scaleY: number = 1;
rad: number = 0;
private saveStack: number[][] = [];
/** 有没有对这个Transform进行过修改用于优化常规表现 */
private modified: boolean = false;
/**
*
*
*/
reset() {
mat3.identity(this.mat);
@ -24,28 +22,31 @@ export class Camera extends EventEmitter<CameraEvent> {
this.scaleX = 1;
this.scaleY = 1;
this.rad = 0;
this.modified = false;
}
/**
*
*
*/
scale(x: number, y: number = x) {
mat3.scale(this.mat, this.mat, [x, y]);
this.scaleX *= x;
this.scaleY *= y;
this.modified = true;
}
/**
*
*
*/
move(x: number, y: number) {
mat3.translate(this.mat, this.mat, [-x, -y]);
this.x += x;
this.y += y;
this.modified = true;
}
/**
*
*
*/
rotate(rad: number) {
mat3.rotate(this.mat, this.mat, rad);
@ -54,36 +55,40 @@ export class Camera extends EventEmitter<CameraEvent> {
const n = Math.floor(this.rad / Math.PI / 2);
this.rad -= n * Math.PI * 2;
}
this.modified = true;
}
/**
*
*
*/
setScale(x: number, y: number = x) {
mat3.scale(this.mat, this.mat, [x / this.scaleX, y / this.scaleY]);
this.scaleX = x;
this.scaleY = y;
this.modified = true;
}
/**
*
*
*/
setTranslate(x: number, y: number) {
mat3.translate(this.mat, this.mat, [this.x - x, this.y - y]);
this.x = x;
this.y = y;
this.modified = true;
}
/**
*
*
*/
setRotate(rad: number) {
mat3.rotate(this.mat, this.mat, rad - this.rad);
this.rad = rad;
this.modified = true;
}
/**
*
*
* @param a
* @param b
* @param c
@ -108,7 +113,7 @@ export class Camera extends EventEmitter<CameraEvent> {
}
/**
*
*
* @param a
* @param b
* @param c
@ -140,46 +145,62 @@ export class Camera extends EventEmitter<CameraEvent> {
this.scaleX = scaleX;
this.scaleY = scaleY;
this.rad = rad;
if (x === 0 && y === 0 && scaleX === 1 && scaleY === 1 && rad === 0) {
this.modified = false;
} else {
this.modified = true;
}
}
/**
*
*
* @param transform
*/
save() {
this.saveStack.push(Array.from(this.mat));
multiply(transform: Transform): Transform {
if (this.modified) {
const res = new Transform();
const mat = mat3.clone(this.mat);
mat3.multiply(mat, mat, transform.mat);
res.mat = mat;
return res;
} else {
return transform.clone();
}
}
/**
* 退
*
*/
restore() {
const data = this.saveStack.pop();
if (!data) return;
const [a, b, c, d, e, f, g, h, i] = data;
this.mat = mat3.fromValues(a, b, c, d, e, f, g, h, i);
clone() {
const transform = new Transform();
transform.mat = mat3.clone(this.mat);
return transform;
}
/**
*
* @param camera
* @param transform
* @param x
* @param y
*/
static transformed(camera: Camera, x: number, y: number) {
return multiplyVec3(camera.mat, [x, y, 1]);
static transformed(transform: Transform, x: number, y: number) {
return multiplyVec3(transform.mat, [x, y, 1]);
}
/**
*
* @param camera
* @param transform
* @param x
* @param y
*/
static untransformed(camera: Camera, x: number, y: number) {
static untransformed(transform: Transform, x: number, y: number) {
const invert = mat3.create();
mat3.invert(invert, camera.mat);
mat3.invert(invert, transform.mat);
return multiplyVec3(invert, [x, y, 1]);
}
/** 单位矩阵 */
static readonly identity = new Transform();
}
function multiplyVec3(mat: ReadonlyMat3, vec: ReadonlyVec3) {

View File

@ -10,6 +10,7 @@ import * as hero from './state/hero';
import * as miscMechanism from './mechanism/misc';
import * as study from './mechanism/study';
import { registerPresetState } from './state/preset';
import { ItemState } from './state/item';
// ----- 类注册
Mota.register('class', 'DamageEnemy', damage.DamageEnemy);
@ -32,6 +33,9 @@ Mota.register('module', 'Mechanism', {
BluePalace: miscMechanism.BluePalace,
Study: study
});
Mota.register('module', 'State', {
ItemState
});
main.loading = loading;

View File

@ -35,6 +35,7 @@ import type { Camera } from '@/core/render/camera';
import type { Image, Text } from '@/core/render/preset/misc';
import type { RenderItem } from '@/core/render/item';
import type { RenderAdapter } from '@/core/render/adapter';
import type { ItemState } from './state/item';
interface ClassInterface {
// 渲染进程与游戏进程通用
@ -115,6 +116,9 @@ interface ModuleInterface {
RenderItem: typeof RenderItem;
RenderAdapter: typeof RenderAdapter;
};
State: {
ItemState: typeof ItemState;
};
}
interface SystemInterfaceMap {

View File

@ -261,8 +261,6 @@ export function init() {
core.autosave();
}
console.trace();
moveDir = direction;
stepDir = direction;
await readyMove();