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

View File

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

View File

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

View File

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

View File

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

View File

@ -6,7 +6,7 @@ import {
Layer, Layer,
LayerGroup LayerGroup
} from './layer'; } from './layer';
import { Sprite } from '../sprite'; import { ESpriteEvent, Sprite } from '../sprite';
import { BlockCacher } from './block'; import { BlockCacher } from './block';
import type { import type {
DamageEnemy, DamageEnemy,
@ -14,22 +14,28 @@ import type {
MapDamage MapDamage
} from '@/game/enemy/damage'; } from '@/game/enemy/damage';
import { MotaOffscreenCanvas2D } from '@/core/fx/canvas2d'; import { MotaOffscreenCanvas2D } from '@/core/fx/canvas2d';
import { Camera } from '../camera';
import { isNil } from 'lodash-es'; import { isNil } from 'lodash-es';
import { getDamageColor } from '@/plugin/utils'; import { getDamageColor } from '@/plugin/utils';
import { transformCanvas } from '../item'; import { transformCanvas } from '../item';
import EventEmitter from 'eventemitter3';
import { Transform } from '../transform';
const ensureFloorDamage = Mota.require('fn', 'ensureFloorDamage'); 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'; id: string = 'floor-damage';
floorBinder!: LayerGroupFloorBinder; floorBinder!: LayerGroupFloorBinder;
group!: LayerGroup; group!: LayerGroup;
sprite!: Damage; sprite!: Damage;
static listenedDamage: Set<FloorDamageExtends> = new Set();
/** /**
* *
*/ */
@ -41,6 +47,7 @@ export class FloorDamageExtends implements ILayerGroupRenderExtends {
const enemy = core.status.maps[floor].enemy; const enemy = core.status.maps[floor].enemy;
this.sprite.updateCollection(enemy); this.sprite.updateCollection(enemy);
this.emit('update', floor);
} }
/** /**
@ -89,18 +96,16 @@ export class FloorDamageExtends implements ILayerGroupRenderExtends {
); );
group.removeExtends('floor-damage'); group.removeExtends('floor-damage');
} }
FloorDamageExtends.listenedDamage.add(this);
}); });
} }
onDestroy(group: LayerGroup): void { onDestroy(group: LayerGroup): void {
this.floorBinder.off('update', this.onUpdate); this.floorBinder.off('update', this.onUpdate);
this.floorBinder.off('setBlock', this.onSetBlock); this.floorBinder.off('setBlock', this.onSetBlock);
FloorDamageExtends.listenedDamage.delete(this);
} }
} }
interface DamageRenderable { export interface DamageRenderable {
x: number; x: number;
y: number; y: number;
align: CanvasTextAlign; align: CanvasTextAlign;
@ -117,7 +122,12 @@ interface DamageCache {
symbol: number; 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; mapWidth: number = 0;
mapHeight: number = 0; mapHeight: number = 0;
@ -188,6 +198,8 @@ export class Damage extends Sprite {
this.blockData.set(i, new Map()); this.blockData.set(i, new Map());
this.renderable.set(i, new Set()); 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) { 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) { } else if (this.parent instanceof Layer) {
// 如果是地图的子元素直接调用Layer的计算函数 // 如果是地图的子元素直接调用Layer的计算函数
return this.parent.calNeedRender(camera); return this.parent.calNeedRender(transform);
} else { } 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'); // console.time('damage');
const { ctx } = this.damageMap; const { ctx } = this.damageMap;
ctx.save(); 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 block = this.block;
const cell = this.cellSize; const cell = this.cellSize;
const size = cell * block.blockSize; const size = cell * block.blockSize;
this.emit('beforeDamageRender', render, transform);
render.forEach(v => { render.forEach(v => {
const [x, y] = block.getBlockXYByIndex(v); const [x, y] = block.getBlockXYByIndex(v);
const bx = x * block.blockSize; const bx = x * block.blockSize;

View File

@ -107,6 +107,10 @@ export class LayerGroupFloorBinder
this.emit('update', floor); 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.moveId = layer.delegateTicker(() => {
this.moveTick(Date.now()); this.moveTick(Date.now());
}); });
console.log(this);
} }
onDestroy(layer: Layer): void { onDestroy(layer: Layer): void {

View File

@ -1,18 +1,13 @@
import { MotaOffscreenCanvas2D } from '@/core/fx/canvas2d'; import { MotaOffscreenCanvas2D } from '@/core/fx/canvas2d';
import { Container } from '../container'; import { Container } from '../container';
import { Sprite } from '../sprite'; import { Sprite } from '../sprite';
import { Camera } from '../camera';
import { TimingFn } from 'mutate-animate'; import { TimingFn } from 'mutate-animate';
import { import { IAnimateFrame, renderEmits, RenderItem } from '../item';
IAnimateFrame,
renderEmits,
RenderItem,
transformCanvas
} from '../item';
import { logger } from '@/core/common/logger'; import { logger } from '@/core/common/logger';
import { AutotileRenderable, RenderableData, texture } from '../cache'; import { RenderableData, texture } from '../cache';
import { glMatrix } from 'gl-matrix'; import { glMatrix } from 'gl-matrix';
import { BlockCacher } from './block'; import { BlockCacher } from './block';
import { Transform } from '../transform';
export interface ILayerGroupRenderExtends { export interface ILayerGroupRenderExtends {
/** 拓展的唯一标识符 */ /** 拓展的唯一标识符 */
@ -58,13 +53,6 @@ export interface ILayerGroupRenderExtends {
*/ */
onEmptyLayer?(group: LayerGroup): void; onEmptyLayer?(group: LayerGroup): void;
/**
*
* @param group LayerGroup实例
* @param floor
*/
// onDataUpdate?: (group: LayerGroup, floor: FloorIds) => void;
/** /**
* *
* @param group LayerGroup实例 * @param group LayerGroup实例
@ -72,13 +60,6 @@ export interface ILayerGroupRenderExtends {
*/ */
onFrameUpdate?(group: LayerGroup, frame: number): void; onFrameUpdate?(group: LayerGroup, frame: number): void;
/**
*
* @param group LayerGroup实例
* @param floor id
*/
// onBindFloor?: (group: LayerGroup, floor?: FloorIds) => void;
/** /**
* LayerGroup被销毁 * LayerGroup被销毁
* @param group LayerGroup实例 * @param group LayerGroup实例
@ -267,13 +248,13 @@ export class LayerGroup extends Container implements IAnimateFrame {
/** /**
* *
* @param camera * @param transform
* @param blockData * @param blockData
*/ */
cacheNeedRender(camera: Camera, block: BlockCacher<any>) { cacheNeedRender(transform: Transform, block: BlockCacher<any>) {
return ( return (
this.needRender ?? 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() { updateFrameAnimate() {
this.layers.forEach(v => {
v.cache(v.using);
});
this.update(this); this.update(this);
for (const ex of this.extend.values()) { for (const ex of this.extend.values()) {
@ -308,7 +286,7 @@ export class LayerGroup extends Container implements IAnimateFrame {
} }
export function calNeedRenderOf( export function calNeedRenderOf(
camera: Camera, transform: Transform,
cell: number, cell: number,
block: BlockCacher<any> block: BlockCacher<any>
): NeedRenderData { ): NeedRenderData {
@ -317,10 +295,10 @@ export function calNeedRenderOf(
const size = block.blockSize; const size = block.blockSize;
// -1是因为宽度是core._PX_从0开始的话末尾索引就是core._PX_ - 1 // -1是因为宽度是core._PX_从0开始的话末尾索引就是core._PX_ - 1
const [x1, y1] = Camera.untransformed(camera, -w, -h); const [x1, y1] = Transform.untransformed(transform, -w, -h);
const [x2, y2] = Camera.untransformed(camera, w - 1, -h); const [x2, y2] = Transform.untransformed(transform, w - 1, -h);
const [x3, y3] = Camera.untransformed(camera, w - 1, h - 1); const [x3, y3] = Transform.untransformed(transform, w - 1, h - 1);
const [x4, y4] = Camera.untransformed(camera, -w, h - 1); const [x4, y4] = Transform.untransformed(transform, -w, h - 1);
const res: Set<number> = new Set(); const res: Set<number> = new Set();
/** 一个纵坐标对应的所有横坐标,用于填充 */ /** 一个纵坐标对应的所有横坐标,用于填充 */
@ -469,8 +447,6 @@ export interface ILayerRenderExtends {
*/ */
onBackgroundSet?(layer: Layer, background: AllNumbers): void; onBackgroundSet?(layer: Layer, background: AllNumbers): void;
// onBackgroundBind?: (layer: Layer, floorId: FloorIds) => void;
/** /**
* *
* @param layer Layer实例 * @param layer Layer实例
@ -568,18 +544,26 @@ export interface ILayerRenderExtends {
/** /**
* *
* @param layer Layer实例 * @param layer Layer实例
* @param camera * @param transform
* @param need * @param need
*/ */
onBeforeRender?(layer: Layer, camera: Camera, need: NeedRenderData): void; onBeforeRender?(
layer: Layer,
transform: Transform,
need: NeedRenderData
): void;
/** /**
* *
* @param layer Layer实例 * @param layer Layer实例
* @param camera * @param transform
* @param need * @param need
*/ */
onAfterRender?(layer: Layer, camera: Camera, need: NeedRenderData): void; onAfterRender?(
layer: Layer,
transform: Transform,
need: NeedRenderData
): void;
/** /**
* Layer被销毁 * Layer被销毁
@ -606,38 +590,6 @@ interface NeedRenderData {
back: [x: number, y: number][]; 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 { export class Layer extends Container {
// 一些会用到的常量 // 一些会用到的常量
static readonly FRAME_0 = 1; static readonly FRAME_0 = 1;
@ -654,7 +606,7 @@ export class Layer extends Container {
protected backMap: MotaOffscreenCanvas2D = new MotaOffscreenCanvas2D(); protected backMap: MotaOffscreenCanvas2D = new MotaOffscreenCanvas2D();
/** 最终渲染至的Sprite */ /** 最终渲染至的Sprite */
main: Sprite = new Sprite(); main: Sprite = new Sprite('static', false);
/** 渲染的层 */ /** 渲染的层 */
layer?: FloorLayer; layer?: FloorLayer;
@ -714,13 +666,13 @@ export class Layer extends Container {
this.main.size(core._PX_, core._PY_); this.main.size(core._PX_, core._PY_);
this.appendChild(this.main); this.appendChild(this.main);
this.main.setRenderFn((canvas, camera) => { this.main.setRenderFn((canvas, transform) => {
const { ctx } = canvas; const { ctx } = canvas;
const { width, height } = canvas.canvas; const { width, height } = canvas.canvas;
ctx.save(); ctx.save();
ctx.imageSmoothingEnabled = false; ctx.imageSmoothingEnabled = false;
const need = this.calNeedRender(camera); const need = this.calNeedRender(transform);
this.renderMap(camera, need); this.renderMap(transform, need);
ctx.setTransform(1, 0, 0, 1, 0, 0); ctx.setTransform(1, 0, 0, 1, 0, 0);
ctx.drawImage(this.backMap.canvas, 0, 0, width, height); ctx.drawImage(this.backMap.canvas, 0, 0, width, height);
ctx.drawImage(this.staticMap.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.updateBlocks(x, y, width, height);
this.updateBigImages(x, y, width, height); this.updateBigImages(x, y, width, height);
// this.update(this);
for (const ex of this.extend.values()) { for (const ex of this.extend.values()) {
ex.onDataPut?.(this, data, width, x, y, calAutotile); 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) { if (this.parent instanceof LayerGroup) {
// 如果处于地图组中,每个地图的渲染区域应该是一样的,因此可以缓存优化 // 如果处于地图组中,每个地图的渲染区域应该是一样的,因此可以缓存优化
return this.parent.cacheNeedRender(camera, this.block); return this.parent.cacheNeedRender(transform, this.block);
} else { } 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.staticMap.clear();
this.movingMap.clear(); this.movingMap.clear();
this.backMap.clear(); this.backMap.clear();
@ -1146,32 +1096,31 @@ export class Layer extends Container {
if (this.needUpdateMoving) this.updateMovingRenderable(); if (this.needUpdateMoving) this.updateMovingRenderable();
for (const ex of this.extend.values()) { for (const ex of this.extend.values()) {
ex.onBeforeRender?.(this, camera, need); ex.onBeforeRender?.(this, transform, need);
} }
this.renderBack(camera, need); this.renderBack(transform, need);
this.renderStatic(camera, need); this.renderStatic(transform, need);
this.renderMoving(camera); this.renderMoving(transform);
for (const ex of this.extend.values()) { for (const ex of this.extend.values()) {
ex.onAfterRender?.(this, camera, need); ex.onAfterRender?.(this, transform, need);
} }
} }
/** /**
* *
* @param camera * @param transform
* @param need * @param need
*/ */
protected renderBack(camera: Camera, need: NeedRenderData) { protected renderBack(transform: Transform, need: NeedRenderData) {
const cell = this.cellSize; const cell = this.cellSize;
const frame = (RenderItem.animatedFrame % 4) + 1; const frame = (RenderItem.animatedFrame % 4) + 1;
const blockSize = this.block.blockSize; const blockSize = this.block.blockSize;
const { back } = need; const { back } = need;
const { ctx } = this.backMap; const { ctx } = this.backMap;
const mat = camera.mat; const mat = transform.mat;
const [a, b, , c, d, , e, f] = mat; const [a, b, , c, d, , e, f] = mat;
ctx.setTransform(1, 0, 0, 1, 0, 0); ctx.setTransform(1, 0, 0, 1, 0, 0);
ctx.translate(core._PX_ / 2, core._PY_ / 2);
ctx.transform(a, b, c, d, e, f); ctx.transform(a, b, c, d, e, f);
if (this.background !== 0) { 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 cell = this.cellSize;
const frame = RenderItem.animatedFrame % 4; const frame = RenderItem.animatedFrame % 4;
const { width } = this.block.blockData; const { width } = this.block.blockData;
@ -1205,7 +1154,9 @@ export class Layer extends Container {
ctx.save(); ctx.save();
const { res: render } = need; 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 => { render.forEach(v => {
const x = v % width; 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 frame = (RenderItem.animatedFrame % 4) + 1;
const cell = this.cellSize; const cell = this.cellSize;
const halfCell = cell / 2; const halfCell = cell / 2;
const { ctx } = this.movingMap; const { ctx } = this.movingMap;
ctx.save(); ctx.save();
const mat = camera.mat; const mat = transform.mat;
const [a, b, , c, d, , e, f] = mat; const [a, b, , c, d, , e, f] = mat;
ctx.setTransform(1, 0, 0, 1, 0, 0); ctx.setTransform(1, 0, 0, 1, 0, 0);
ctx.translate(core._PX_ / 2, core._PY_ / 2);
ctx.transform(a, b, c, d, e, f); ctx.transform(a, b, c, d, e, f);
const max1 = Math.max(a, b, c, d) ** 2; const max1 = Math.max(a, b, c, d) ** 2;
const max2 = Math.max(core._PX_, core._PY_) * 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'; import { Sprite } from '../sprite';
type CanvasStyle = string | CanvasGradient | CanvasPattern; type CanvasStyle = string | CanvasGradient | CanvasPattern;
@ -12,6 +13,8 @@ export class Text extends Sprite {
private length: number = 0; private length: number = 0;
private descent: number = 0; private descent: number = 0;
private static measureCanvas = new MotaOffscreenCanvas2D();
constructor(text: string = '') { constructor(text: string = '') {
super(); super();
@ -39,11 +42,10 @@ export class Text extends Sprite {
* *
*/ */
measure() { measure() {
this.canvas.ctx.save(); const ctx = Text.measureCanvas.ctx;
this.canvas.ctx.textBaseline = 'bottom'; ctx.textBaseline = 'bottom';
this.canvas.ctx.font = this.font ?? ''; ctx.font = this.font ?? '';
const res = this.canvas.ctx.measureText(this.text); const res = ctx.measureText(this.text);
this.canvas.ctx.restore();
return res; return res;
} }
@ -53,8 +55,6 @@ export class Text extends Sprite {
*/ */
setText(text: string) { setText(text: string) {
this.text = text; this.text = text;
this.writing = this.using;
this.using = void 0;
this.calBox(); this.calBox();
if (this.parent) this.update(this); if (this.parent) this.update(this);
} }
@ -101,8 +101,6 @@ export class Image extends Sprite {
constructor(image: SizedCanvasImageSource) { constructor(image: SizedCanvasImageSource) {
super(); super();
this.image = image; this.image = image;
this.canvas.withGameScale(false);
this.canvas.setHD(false);
this.size(image.width, image.height); this.size(image.width, image.height);
this.renderFn = ({ canvas, ctx }) => { this.renderFn = ({ canvas, ctx }) => {
@ -117,8 +115,6 @@ export class Image extends Sprite {
setImage(image: SizedCanvasImageSource) { setImage(image: SizedCanvasImageSource) {
this.image = image; this.image = image;
this.size(image.width, image.height); this.size(image.width, image.height);
this.writing = this.using;
this.using = void 0;
this.update(this); this.update(this);
} }
} }

View File

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

View File

@ -1,76 +1,39 @@
import { Camera } from './camera';
import { import {
ICanvasCachedRenderItem, ERenderItemEvent,
RenderFunction, RenderFunction,
RenderItem, RenderItem,
RenderItemPosition, RenderItemPosition
withCacheRender
} from './item'; } from './item';
import { MotaOffscreenCanvas2D } from '../fx/canvas2d'; 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; renderFn: RenderFunction;
canvas: MotaOffscreenCanvas2D;
private readonly enableCache: boolean;
/** /**
* *
* @param type absolute表示绝对位置static表示跟随摄像机移动 * @param type absolute表示绝对位置static表示跟随摄像机移动
* @param cache * @param cache
*/ */
constructor(type: RenderItemPosition = 'static', cache: boolean = true) { constructor(type: RenderItemPosition = 'static', cache: boolean = true) {
super(); super(cache);
this.type = type; this.type = type;
this.enableCache = cache;
this.renderFn = () => {}; this.renderFn = () => {};
this.canvas = new MotaOffscreenCanvas2D();
this.canvas.withGameScale(true);
} }
render(canvas: MotaOffscreenCanvas2D, camera: Camera): void { protected render(
this.emit('beforeRender'); canvas: MotaOffscreenCanvas2D,
if (this.needUpdate) { transform: Transform
this.cache(this.using); ): void {
this.needUpdate = false; this.renderFn(canvas, transform);
}
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;
} }
setRenderFn(fn: RenderFunction) { setRenderFn(fn: RenderFunction) {
this.renderFn = fn; 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); this.update(this);
} }
} }

View File

@ -1,9 +1,6 @@
import { ReadonlyMat3, ReadonlyVec3, mat3, vec2, vec3 } from 'gl-matrix'; import { mat3, ReadonlyMat3, ReadonlyVec3, vec2, vec3 } from 'gl-matrix';
import { EventEmitter } from '../common/eventEmitter';
interface CameraEvent {} export class Transform {
export class Camera extends EventEmitter<CameraEvent> {
mat: mat3 = mat3.create(); mat: mat3 = mat3.create();
x: number = 0; x: number = 0;
@ -12,10 +9,11 @@ export class Camera extends EventEmitter<CameraEvent> {
scaleY: number = 1; scaleY: number = 1;
rad: number = 0; rad: number = 0;
private saveStack: number[][] = []; /** 有没有对这个Transform进行过修改用于优化常规表现 */
private modified: boolean = false;
/** /**
* *
*/ */
reset() { reset() {
mat3.identity(this.mat); mat3.identity(this.mat);
@ -24,28 +22,31 @@ export class Camera extends EventEmitter<CameraEvent> {
this.scaleX = 1; this.scaleX = 1;
this.scaleY = 1; this.scaleY = 1;
this.rad = 0; this.rad = 0;
this.modified = false;
} }
/** /**
* *
*/ */
scale(x: number, y: number = x) { scale(x: number, y: number = x) {
mat3.scale(this.mat, this.mat, [x, y]); mat3.scale(this.mat, this.mat, [x, y]);
this.scaleX *= x; this.scaleX *= x;
this.scaleY *= y; this.scaleY *= y;
this.modified = true;
} }
/** /**
* *
*/ */
move(x: number, y: number) { move(x: number, y: number) {
mat3.translate(this.mat, this.mat, [-x, -y]); mat3.translate(this.mat, this.mat, [-x, -y]);
this.x += x; this.x += x;
this.y += y; this.y += y;
this.modified = true;
} }
/** /**
* *
*/ */
rotate(rad: number) { rotate(rad: number) {
mat3.rotate(this.mat, this.mat, rad); 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); const n = Math.floor(this.rad / Math.PI / 2);
this.rad -= n * Math.PI * 2; this.rad -= n * Math.PI * 2;
} }
this.modified = true;
} }
/** /**
* *
*/ */
setScale(x: number, y: number = x) { setScale(x: number, y: number = x) {
mat3.scale(this.mat, this.mat, [x / this.scaleX, y / this.scaleY]); mat3.scale(this.mat, this.mat, [x / this.scaleX, y / this.scaleY]);
this.scaleX = x; this.scaleX = x;
this.scaleY = y; this.scaleY = y;
this.modified = true;
} }
/** /**
* *
*/ */
setTranslate(x: number, y: number) { setTranslate(x: number, y: number) {
mat3.translate(this.mat, this.mat, [this.x - x, this.y - y]); mat3.translate(this.mat, this.mat, [this.x - x, this.y - y]);
this.x = x; this.x = x;
this.y = y; this.y = y;
this.modified = true;
} }
/** /**
* *
*/ */
setRotate(rad: number) { setRotate(rad: number) {
mat3.rotate(this.mat, this.mat, rad - this.rad); mat3.rotate(this.mat, this.mat, rad - this.rad);
this.rad = rad; this.rad = rad;
this.modified = true;
} }
/** /**
* *
* @param a * @param a
* @param b * @param b
* @param c * @param c
@ -108,7 +113,7 @@ export class Camera extends EventEmitter<CameraEvent> {
} }
/** /**
* *
* @param a * @param a
* @param b * @param b
* @param c * @param c
@ -140,46 +145,62 @@ export class Camera extends EventEmitter<CameraEvent> {
this.scaleX = scaleX; this.scaleX = scaleX;
this.scaleY = scaleY; this.scaleY = scaleY;
this.rad = rad; this.rad = rad;
if (x === 0 && y === 0 && scaleX === 1 && scaleY === 1 && rad === 0) {
this.modified = false;
} else {
this.modified = true;
}
} }
/** /**
* *
* @param transform
*/ */
save() { multiply(transform: Transform): Transform {
this.saveStack.push(Array.from(this.mat)); 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() { clone() {
const data = this.saveStack.pop(); const transform = new Transform();
if (!data) return; transform.mat = mat3.clone(this.mat);
const [a, b, c, d, e, f, g, h, i] = data; return transform;
this.mat = mat3.fromValues(a, b, c, d, e, f, g, h, i);
} }
/** /**
* *
* @param camera * @param transform
* @param x * @param x
* @param y * @param y
*/ */
static transformed(camera: Camera, x: number, y: number) { static transformed(transform: Transform, x: number, y: number) {
return multiplyVec3(camera.mat, [x, y, 1]); return multiplyVec3(transform.mat, [x, y, 1]);
} }
/** /**
* *
* @param camera * @param transform
* @param x * @param x
* @param y * @param y
*/ */
static untransformed(camera: Camera, x: number, y: number) { static untransformed(transform: Transform, x: number, y: number) {
const invert = mat3.create(); const invert = mat3.create();
mat3.invert(invert, camera.mat); mat3.invert(invert, transform.mat);
return multiplyVec3(invert, [x, y, 1]); return multiplyVec3(invert, [x, y, 1]);
} }
/** 单位矩阵 */
static readonly identity = new Transform();
} }
function multiplyVec3(mat: ReadonlyMat3, vec: ReadonlyVec3) { 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 miscMechanism from './mechanism/misc';
import * as study from './mechanism/study'; import * as study from './mechanism/study';
import { registerPresetState } from './state/preset'; import { registerPresetState } from './state/preset';
import { ItemState } from './state/item';
// ----- 类注册 // ----- 类注册
Mota.register('class', 'DamageEnemy', damage.DamageEnemy); Mota.register('class', 'DamageEnemy', damage.DamageEnemy);
@ -32,6 +33,9 @@ Mota.register('module', 'Mechanism', {
BluePalace: miscMechanism.BluePalace, BluePalace: miscMechanism.BluePalace,
Study: study Study: study
}); });
Mota.register('module', 'State', {
ItemState
});
main.loading = loading; 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 { Image, Text } from '@/core/render/preset/misc';
import type { RenderItem } from '@/core/render/item'; import type { RenderItem } from '@/core/render/item';
import type { RenderAdapter } from '@/core/render/adapter'; import type { RenderAdapter } from '@/core/render/adapter';
import type { ItemState } from './state/item';
interface ClassInterface { interface ClassInterface {
// 渲染进程与游戏进程通用 // 渲染进程与游戏进程通用
@ -115,6 +116,9 @@ interface ModuleInterface {
RenderItem: typeof RenderItem; RenderItem: typeof RenderItem;
RenderAdapter: typeof RenderAdapter; RenderAdapter: typeof RenderAdapter;
}; };
State: {
ItemState: typeof ItemState;
};
} }
interface SystemInterfaceMap { interface SystemInterfaceMap {

View File

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