mirror of
https://github.com/unanmed/HumanBreak.git
synced 2026-11-01 07:24:30 +08:00
feat: 地图渲染大致完成
This commit is contained in:
1 parent
2316bc4cad
commit
117bd94928
30 files changed
+1921
-148
No files matched your search
@@ -0,0 +1,294 @@
|
||||
import { EmitableEvent, EventEmitter } from '../common/eventEmitter';
|
||||
import { MotaOffscreenCanvas2D } from '../fx/canvas2d';
|
||||
|
||||
// 经过测试(https://www.measurethat.net/Benchmarks/Show/30741/1/drawimage-img-vs-canvas-vs-bitmap-cropping-fix-loading)
|
||||
// 得出结论,ImageBitmap和Canvas的绘制性能不如Image,于是直接画Image就行,所以缓存基本上就是存Image
|
||||
|
||||
type ImageMapKeys = Exclude<Cls, 'tileset' | 'autotile'>;
|
||||
type ImageMap = Record<ImageMapKeys, HTMLImageElement>;
|
||||
|
||||
const i = (img: ImageMapKeys) => {
|
||||
return core.material.images[img];
|
||||
};
|
||||
|
||||
const imageMap: Partial<ImageMap> = {};
|
||||
|
||||
Mota.require('var', 'loading').once('loaded', () => {
|
||||
[
|
||||
'enemys',
|
||||
'enemy48',
|
||||
'npcs',
|
||||
'npc48',
|
||||
'terrains',
|
||||
'items',
|
||||
'animates'
|
||||
].forEach(v => (imageMap[v as ImageMapKeys] = i(v as ImageMapKeys)));
|
||||
});
|
||||
|
||||
interface AutotileCache {
|
||||
parent?: Set<AllNumbersOf<'autotile'>>;
|
||||
frame: number;
|
||||
cache: Record<string, HTMLCanvasElement>;
|
||||
}
|
||||
type AutotileCaches = Record<AllNumbersOf<'autotile'>, AutotileCache>;
|
||||
|
||||
interface TextureRequire {
|
||||
tileset: Record<string, HTMLImageElement>;
|
||||
material: Record<ImageMapKeys, HTMLImageElement>;
|
||||
autotile: AutotileCaches;
|
||||
images: Record<ImageIds, HTMLImageElement>;
|
||||
}
|
||||
|
||||
interface TextureCacheEvent extends EmitableEvent {}
|
||||
|
||||
class TextureCache extends EventEmitter<TextureCacheEvent> {
|
||||
tileset!: Record<string, HTMLImageElement>;
|
||||
material: Record<ImageMapKeys, HTMLImageElement>;
|
||||
autotile!: AutotileCaches;
|
||||
images!: Record<ImageIds, HTMLImageElement>;
|
||||
|
||||
idNumberMap!: IdToNumber;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.material = imageMap as Record<ImageMapKeys, HTMLImageElement>;
|
||||
|
||||
Mota.require('var', 'loading').once('loaded', () => {
|
||||
const map = maps_90f36752_8815_4be8_b32b_d7fad1d0542e;
|
||||
// @ts-ignore
|
||||
this.idNumberMap = {};
|
||||
for (const [key, { id }] of Object.entries(map)) {
|
||||
// @ts-ignore
|
||||
this.idNumberMap[id] = parseInt(key) as AllNumbers;
|
||||
}
|
||||
this.tileset = core.material.images.tilesets;
|
||||
this.autotile = splitAutotiles(this.idNumberMap);
|
||||
this.images = core.material.images.images;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取纹理
|
||||
* @param type 纹理类型
|
||||
* @param key 纹理名称
|
||||
*/
|
||||
require<T extends keyof TextureRequire, K extends keyof TextureRequire[T]>(
|
||||
type: T,
|
||||
key: K
|
||||
): TextureRequire[T][K] {
|
||||
return this[type][key];
|
||||
}
|
||||
}
|
||||
|
||||
export const texture = new TextureCache();
|
||||
|
||||
// 3x4 与 2x3 的自动元件信息
|
||||
// 将自动元件按 16x16 切分后,数组分别表示 左上 右上 右下 左下 所在图块位置
|
||||
const bigAutotile: Record<number, [number, number, number, number]> = {};
|
||||
const smallAutotile: Record<number, [number, number, number, number]> = {};
|
||||
|
||||
function getAutotileIndices() {
|
||||
// 应当从 0 - 255 进行枚举
|
||||
// 二进制从高位到低位依次是 左上 上 右上 右 右下 下 左下 左
|
||||
// 首先是3x4的
|
||||
// 有兴趣可以研究下这个算法
|
||||
const get = (
|
||||
target: Record<number, [number, number, number, number]>,
|
||||
mode: 1 | 2
|
||||
) => {
|
||||
const h = mode === 1 ? 4 : 2;
|
||||
const v = mode === 1 ? 24 : 8;
|
||||
const luo = mode === 1 ? 12 : 8; // leftup origin
|
||||
const ruo = mode === 1 ? 17 : 11; // rightup origin
|
||||
const ldo = mode === 1 ? 42 : 20; // leftdown origin
|
||||
const rdo = mode === 1 ? 47 : 23; // rightdown origin
|
||||
const luc = mode === 1 ? 4 : 2; // leftup corner
|
||||
const ruc = mode === 1 ? 5 : 3; // rightup corner
|
||||
const rdc = mode === 1 ? 11 : 7; // rightdown corner
|
||||
const ldc = mode === 1 ? 10 : 6; // leftdown corner
|
||||
|
||||
for (let i = 0; i <= 0b11111111; i++) {
|
||||
let lu = luo; // leftup
|
||||
let ru = ruo; // rightup
|
||||
let ld = ldo; // leftdown
|
||||
let rd = rdo; // rightdown
|
||||
|
||||
// 先看四个方向,最后看斜角方向
|
||||
if (i & 0b00000001) {
|
||||
lu += h;
|
||||
ld += h;
|
||||
if (i & 0b00010000) {
|
||||
ru += h / 2;
|
||||
rd += h / 2;
|
||||
}
|
||||
}
|
||||
if (i & 0b00000100) {
|
||||
ld -= v;
|
||||
rd -= v;
|
||||
if (i & 0b01000000) {
|
||||
lu -= v / 2;
|
||||
ru -= v / 2;
|
||||
}
|
||||
}
|
||||
if (i & 0b00010000) {
|
||||
ru -= h;
|
||||
rd -= h;
|
||||
if (i & 0b00000001) {
|
||||
lu -= h / 2;
|
||||
ld -= h / 2;
|
||||
}
|
||||
}
|
||||
if (i & 0b01000000) {
|
||||
lu += v;
|
||||
ru += v;
|
||||
if (i & 0b00000100) {
|
||||
ld += v / 2;
|
||||
rd += v / 2;
|
||||
}
|
||||
}
|
||||
// 斜角
|
||||
if ((i & 0b11000001) === 0b01000001) {
|
||||
lu = luc;
|
||||
}
|
||||
if ((i & 0b01110000) === 0b01010000) {
|
||||
ru = ruc;
|
||||
}
|
||||
if ((i & 0b00011100) === 0b00010100) {
|
||||
rd = rdc;
|
||||
}
|
||||
if ((i & 0b00000111) === 0b00000101) {
|
||||
ld = ldc;
|
||||
}
|
||||
target[i] = [lu, ru, rd, ld];
|
||||
}
|
||||
};
|
||||
|
||||
get(bigAutotile, 1);
|
||||
get(smallAutotile, 2);
|
||||
}
|
||||
getAutotileIndices();
|
||||
|
||||
function getRepeatMap() {
|
||||
// 实际上3x4与2x3的重复映射是一致的,因此只需要计算一个就行了
|
||||
// 这里使用2x3的进行计算
|
||||
const calculated: Record<string, number> = {};
|
||||
const repeatMap: Record<number, number> = {};
|
||||
for (const [num, [lu, ru, rd, ld]] of Object.entries(smallAutotile)) {
|
||||
const n = lu + ru * 24 + rd * 24 ** 2 + ld * 24 ** 3;
|
||||
calculated[num] = n;
|
||||
for (const [num2, n2] of Object.entries(calculated)) {
|
||||
if (n2 === n) {
|
||||
repeatMap[Number(num)] = Number(num2);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return repeatMap;
|
||||
}
|
||||
|
||||
function splitAutotiles(map: IdToNumber): AutotileCaches {
|
||||
const cache: Partial<AutotileCaches> = {};
|
||||
/** 重复映射,由于自动元件只有48种,其余的208种是重复的,因此需要获取重复映射 */
|
||||
const repeatMap: Record<number, number> = getRepeatMap();
|
||||
/** 每个自动元件左上角32*32的内容,用于判断父子关系 */
|
||||
const masterMap: Partial<Record<AllNumbersOf<'autotile'>, string>> = {};
|
||||
|
||||
for (const [key, img] of Object.entries(core.material.images.autotile)) {
|
||||
const auto = map[key as AllIdsOf<'autotile'>];
|
||||
|
||||
// 判断自动元件类型
|
||||
let mode: 1 | 2 = 1;
|
||||
let frame = 1;
|
||||
if (img.width === 384) {
|
||||
frame = 4;
|
||||
} else if (img.width === 192) {
|
||||
mode = 2;
|
||||
frame = 3;
|
||||
} else if (img.width === 64) {
|
||||
mode = 2;
|
||||
}
|
||||
|
||||
cache[auto] = {
|
||||
frame,
|
||||
cache: {}
|
||||
};
|
||||
|
||||
// 父子关系,截取本图块的左上角存入map
|
||||
const master = new MotaOffscreenCanvas2D();
|
||||
master.setHD(false);
|
||||
master.setAntiAliasing(false);
|
||||
master.withGameScale(false);
|
||||
master.size(32, 32);
|
||||
master.ctx.drawImage(img, 0, 0, 32, 32, 0, 0, 32, 32);
|
||||
masterMap[auto] = master.canvas.toDataURL('image/png');
|
||||
|
||||
// 自动图块的绘制信息
|
||||
for (let i = 0; i <= 0b11111111; i++) {
|
||||
const re = repeatMap[i];
|
||||
if (re) {
|
||||
const cached = cache[auto]!.cache[re];
|
||||
if (cached) {
|
||||
cache[auto]!.cache[i] = cached;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
const data = (mode === 1 ? bigAutotile : smallAutotile)[i];
|
||||
const row = mode === 1 ? 6 : 4;
|
||||
const info: [number, number][] = data.map(v => [
|
||||
(v % row) * 16,
|
||||
Math.floor(v / row) * 16
|
||||
]);
|
||||
|
||||
const canvas = new MotaOffscreenCanvas2D();
|
||||
canvas.setHD(false);
|
||||
canvas.setAntiAliasing(false);
|
||||
canvas.withGameScale(false);
|
||||
canvas.size(32 * frame, 32);
|
||||
const ctx = canvas.ctx;
|
||||
for (let i = 0; i < frame; i++) {
|
||||
const dx = 32 * i;
|
||||
const sx1 = info[0][0];
|
||||
const sx2 = info[1][0];
|
||||
const sx3 = info[2][0];
|
||||
const sx4 = info[3][0];
|
||||
const sy1 = info[0][1];
|
||||
const sy2 = info[1][1];
|
||||
const sy3 = info[2][1];
|
||||
const sy4 = info[3][1];
|
||||
|
||||
ctx.drawImage(img, sx1, sy1, 16, 16, dx, 0, 16, 16);
|
||||
ctx.drawImage(img, sx2, sy2, 16, 16, dx + 16, 0, 16, 16);
|
||||
ctx.drawImage(img, sx3, sy3, 16, 16, dx + 16, 16, 16, 16);
|
||||
ctx.drawImage(img, sx4, sy4, 16, 16, dx, 16, 16, 16);
|
||||
}
|
||||
cache[auto]!.cache[i] = canvas.canvas;
|
||||
}
|
||||
}
|
||||
|
||||
// 进行父子关系判断
|
||||
for (const [key, img] of Object.entries(core.material.images.autotile)) {
|
||||
const auto = map[key as AllIdsOf<'autotile'>];
|
||||
|
||||
// 只针对3*4的图块进行,截取第一行中间的,然后判断
|
||||
const judge = new MotaOffscreenCanvas2D();
|
||||
judge.setHD(false);
|
||||
judge.setAntiAliasing(false);
|
||||
judge.withGameScale(false);
|
||||
judge.size(32, 32);
|
||||
judge.ctx.drawImage(img, 32, 0, 32, 32, 0, 0, 32, 32);
|
||||
const data = judge.canvas.toDataURL('image/png');
|
||||
|
||||
for (const [key, data2] of Object.entries(masterMap)) {
|
||||
const auto2 = map[key as AllIdsOf<'autotile'>];
|
||||
|
||||
if (data === data2) {
|
||||
cache[auto]!.parent ??= new Set();
|
||||
cache[auto]!.parent!.add(auto2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return cache as AutotileCaches;
|
||||
}
|
||||
@@ -12,6 +12,8 @@ export class Camera extends EventEmitter<CameraEvent> {
|
||||
scaleY: number = 1;
|
||||
rad: number = 0;
|
||||
|
||||
private saveStack: number[][] = [];
|
||||
|
||||
/**
|
||||
* 重设摄像机的所有参数
|
||||
*/
|
||||
@@ -140,6 +142,23 @@ export class Camera extends EventEmitter<CameraEvent> {
|
||||
this.rad = rad;
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存当前摄像机状态
|
||||
*/
|
||||
save() {
|
||||
this.saveStack.push(Array.from(this.mat));
|
||||
}
|
||||
|
||||
/**
|
||||
* 回退当前摄像机状态
|
||||
*/
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据摄像机的信息,将一个点转换为计算后的位置
|
||||
* @param camera 摄像机
|
||||
|
||||
@@ -25,14 +25,19 @@ export class Container extends RenderItem implements ICanvasCachedRenderItem {
|
||||
ctx: CanvasRenderingContext2D,
|
||||
camera: Camera
|
||||
): void {
|
||||
this.emit('beforeUpdate', this);
|
||||
this.emit('beforeRender');
|
||||
withCacheRender(this, canvas, ctx, camera, c => {
|
||||
this.sortedChildren.forEach(v => {
|
||||
if (!v.antiAliasing) {
|
||||
ctx.imageSmoothingEnabled = false;
|
||||
} else {
|
||||
ctx.imageSmoothingEnabled = true;
|
||||
}
|
||||
v.render(c.canvas, c.ctx, camera);
|
||||
});
|
||||
});
|
||||
this.writing = void 0;
|
||||
this.emit('afterUpdate', this);
|
||||
this.emit('afterRender');
|
||||
}
|
||||
|
||||
size(width: number, height: number) {
|
||||
@@ -55,8 +60,24 @@ export class Container extends RenderItem implements ICanvasCachedRenderItem {
|
||||
appendChild(children: RenderItem[]) {
|
||||
children.forEach(v => (v.parent = this));
|
||||
this.children.push(...children);
|
||||
this.sortChildren();
|
||||
}
|
||||
|
||||
sortChildren() {
|
||||
this.sortedChildren = this.children
|
||||
.slice()
|
||||
.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);
|
||||
}
|
||||
}
|
||||
@@ -101,6 +101,7 @@ export class HeroRenderer extends EventEmitter<HeroRendererEvent> {
|
||||
*/
|
||||
draw() {
|
||||
if (!core.isPlaying()) return;
|
||||
return;
|
||||
const { ctx, canvas: can } = canvas;
|
||||
const { x, y, direction: dir } = core.status.hero.loc;
|
||||
ctx.clearRect(0, 0, can.width, can.height);
|
||||
|
||||
+85
-3
@@ -2,6 +2,8 @@ import { isNil } from 'lodash-es';
|
||||
import { EmitableEvent, EventEmitter } from '../common/eventEmitter';
|
||||
import { MotaOffscreenCanvas2D } from '../fx/canvas2d';
|
||||
import { Camera } from './camera';
|
||||
import { Ticker } from 'mutate-animate';
|
||||
import type { Container } from './container';
|
||||
|
||||
export type RenderFunction = (
|
||||
canvas: MotaOffscreenCanvas2D,
|
||||
@@ -62,15 +64,48 @@ interface IRenderAnchor {
|
||||
setAnchor(x: number, y: number): void;
|
||||
}
|
||||
|
||||
interface IRenderConfig {
|
||||
/** 是否是高清画布 */
|
||||
highResolution: boolean;
|
||||
/** 是否启用抗锯齿 */
|
||||
antiAliasing: boolean;
|
||||
|
||||
/**
|
||||
* 设置当前渲染元素是否使用高清画布
|
||||
* @param hd 是否高清
|
||||
*/
|
||||
setHD(hd: boolean): void;
|
||||
|
||||
/**
|
||||
* 设置当前渲染原始是否启用抗锯齿
|
||||
* @param anti 是否抗锯齿
|
||||
*/
|
||||
setAntiAliasing(anti: boolean): void;
|
||||
}
|
||||
|
||||
export interface IRenderDestroyable {
|
||||
/**
|
||||
* 摧毁这个渲染对象,被摧毁后理应不被继续使用
|
||||
*/
|
||||
destroy(): void;
|
||||
}
|
||||
|
||||
interface RenderItemEvent extends EmitableEvent {
|
||||
beforeUpdate: (item?: RenderItem) => void;
|
||||
afterUpdate: (item?: RenderItem) => void;
|
||||
beforeRender: () => void;
|
||||
afterRender: () => void;
|
||||
}
|
||||
|
||||
export abstract class RenderItem
|
||||
extends EventEmitter<RenderItemEvent>
|
||||
implements IRenderCache, IRenderUpdater, IRenderAnchor
|
||||
implements IRenderCache, IRenderUpdater, IRenderAnchor, IRenderConfig
|
||||
{
|
||||
/** 渲染的全局ticker */
|
||||
static ticker: Ticker = new Ticker();
|
||||
/** 包括但不限于怪物、npc、自动元件的动画帧数 */
|
||||
static animatedFrame: number = 0;
|
||||
|
||||
zIndex: number = 0;
|
||||
|
||||
x: number = 0;
|
||||
@@ -88,9 +123,15 @@ export abstract class RenderItem
|
||||
|
||||
/** 渲染模式,absolute表示绝对位置,static表示跟随摄像机移动,只对顶层元素有效 */
|
||||
type: 'absolute' | 'static' = 'static';
|
||||
/** 是否是高清画布 */
|
||||
highResolution: boolean = true;
|
||||
/** 是否抗锯齿 */
|
||||
antiAliasing: boolean = true;
|
||||
|
||||
parent?: RenderItem;
|
||||
|
||||
protected needUpdate: boolean = false;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
@@ -151,11 +192,52 @@ export abstract class RenderItem
|
||||
}
|
||||
|
||||
update(item?: RenderItem): void {
|
||||
this.cache(this.writing);
|
||||
this.parent?.update(item);
|
||||
if (this.needUpdate) return;
|
||||
this.needUpdate = true;
|
||||
requestAnimationFrame(() => {
|
||||
this.needUpdate = false;
|
||||
if (!this.parent) return;
|
||||
this.cache(this.writing);
|
||||
this.refresh(item);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 立刻更新这个组件,不延迟到下一个tick
|
||||
*/
|
||||
protected refresh(item?: RenderItem) {
|
||||
this.emit('beforeUpdate', item);
|
||||
this.parent?.refresh(item);
|
||||
this.emit('afterUpdate', item);
|
||||
}
|
||||
|
||||
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 as Container).sortChildren?.();
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
export function withCacheRender(
|
||||
item: RenderItem & ICanvasCachedRenderItem,
|
||||
canvas: HTMLCanvasElement,
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -90,7 +90,7 @@ export class Text extends Sprite {
|
||||
}
|
||||
}
|
||||
|
||||
type SizedCanvasImageSource = Exclude<
|
||||
export type SizedCanvasImageSource = Exclude<
|
||||
CanvasImageSource,
|
||||
VideoFrame | SVGElement
|
||||
>;
|
||||
|
||||
+67
-42
@@ -1,34 +1,41 @@
|
||||
import { isNil } from 'lodash-es';
|
||||
import { Animation, hyper, sleep } from 'mutate-animate';
|
||||
import { MotaCanvas2D, MotaOffscreenCanvas2D } from '../fx/canvas2d';
|
||||
import { Camera } from './camera';
|
||||
import { Container } from './container';
|
||||
import { RenderItem, withCacheRender } from './item';
|
||||
import { Image, Text } from './preset/misc';
|
||||
import { Animation, hyper } from 'mutate-animate';
|
||||
import { IRenderDestroyable, RenderItem, withCacheRender } from './item';
|
||||
import { Layer } from './preset/layer';
|
||||
|
||||
export class MotaRenderer extends Container implements IRenderDestroyable {
|
||||
static list: Set<MotaRenderer> = new Set();
|
||||
|
||||
export class MotaRenderer extends Container {
|
||||
canvas: MotaOffscreenCanvas2D;
|
||||
camera: Camera;
|
||||
|
||||
/** 摄像机缓存,如果是需要快速切换摄像机的场景,使用缓存可以大幅提升性能表现 */
|
||||
cameraCache: Map<Camera, MotaOffscreenCanvas2D> = new Map();
|
||||
target: MotaCanvas2D;
|
||||
/** 这个渲染对象的id */
|
||||
id: string;
|
||||
|
||||
private needUpdate: boolean = false;
|
||||
protected needUpdate: boolean = false;
|
||||
|
||||
constructor() {
|
||||
constructor(id: string = 'render-main') {
|
||||
super();
|
||||
|
||||
this.id = id;
|
||||
|
||||
this.canvas = new MotaOffscreenCanvas2D();
|
||||
this.camera = new Camera();
|
||||
this.target = new MotaCanvas2D(`render-main`);
|
||||
this.width = 480;
|
||||
this.height = 480;
|
||||
this.target = new MotaCanvas2D(id);
|
||||
this.width = core._PX_;
|
||||
this.height = core._PY_;
|
||||
this.target.withGameScale(true);
|
||||
this.target.size(480, 480);
|
||||
this.target.size(core._PX_, core._PY_);
|
||||
this.canvas.withGameScale(true);
|
||||
this.canvas.size(480, 480);
|
||||
this.canvas.size(core._PX_, core._PY_);
|
||||
this.target.css(`z-index: 100`);
|
||||
|
||||
MotaRenderer.list.add(this);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -60,6 +67,7 @@ export class MotaRenderer extends Container {
|
||||
render() {
|
||||
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;
|
||||
@@ -72,15 +80,21 @@ export class MotaRenderer extends Container {
|
||||
const f = mat[7] * scale;
|
||||
this.sortedChildren.forEach(v => {
|
||||
if (v.type === 'absolute') {
|
||||
ct.transform(scale, 0, 0, scale, 0, 0);
|
||||
ct.setTransform(scale, 0, 0, scale, 0, 0);
|
||||
} else {
|
||||
ct.setTransform(1, 0, 0, 1, 0, 0);
|
||||
ct.translate(ca.width / 2, ca.height / 2);
|
||||
ct.transform(a, b, c, d, e, f);
|
||||
}
|
||||
if (!v.antiAliasing) {
|
||||
ctx.imageSmoothingEnabled = false;
|
||||
} else {
|
||||
ctx.imageSmoothingEnabled = true;
|
||||
}
|
||||
v.render(ca, ct, camera);
|
||||
});
|
||||
});
|
||||
this.emit('afterRender');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -92,12 +106,16 @@ export class MotaRenderer extends Container {
|
||||
requestAnimationFrame(() => {
|
||||
this.cache(this.writing);
|
||||
this.needUpdate = false;
|
||||
this.emit('beforeUpdate', item);
|
||||
this.render();
|
||||
this.emit('afterUpdate', item);
|
||||
this.refresh(item);
|
||||
});
|
||||
}
|
||||
|
||||
protected refresh(item?: RenderItem): void {
|
||||
this.emit('beforeUpdate', item);
|
||||
this.render();
|
||||
this.emit('afterUpdate', item);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将缓存内容渲染至画面
|
||||
* @param cache 渲染缓存,是一个离屏Canvas2D对象
|
||||
@@ -114,44 +132,51 @@ export class MotaRenderer extends Container {
|
||||
mount() {
|
||||
this.target.mount();
|
||||
}
|
||||
|
||||
destroy() {
|
||||
MotaRenderer.list.delete(this);
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener('resize', () => {
|
||||
MotaRenderer.list.forEach(v => v.update(v));
|
||||
});
|
||||
|
||||
Mota.require('var', 'hook').once('reset', () => {
|
||||
const render = new MotaRenderer();
|
||||
const con = new Container('static');
|
||||
const layer = new Layer();
|
||||
const bgLayer = new Layer();
|
||||
const camera = render.camera;
|
||||
render.mount();
|
||||
|
||||
const testText = new Text();
|
||||
testText.setText('测试测试');
|
||||
testText.pos(240, 240);
|
||||
testText.setFont('32px normal');
|
||||
testText.setStyle('#fff');
|
||||
con.size(480, 480);
|
||||
con.pos(-240, -240);
|
||||
const testImage = new Image(core.material.images.images['arrow.png']);
|
||||
testImage.pos(240, 240);
|
||||
|
||||
con.appendChild([testText, testImage]);
|
||||
|
||||
render.appendChild([con]);
|
||||
|
||||
render.update(render);
|
||||
layer.zIndex = 2;
|
||||
bgLayer.zIndex = 1;
|
||||
render.appendChild([layer, bgLayer]);
|
||||
layer.bindThis('event', true);
|
||||
bgLayer.bindThis('bg', true);
|
||||
bgLayer.setBackground(650);
|
||||
|
||||
const ani = new Animation();
|
||||
ani.mode(hyper('sin', 'in-out'))
|
||||
.time(10000)
|
||||
.absolute()
|
||||
.rotate(360)
|
||||
.scale(0.7)
|
||||
.move(100, 100);
|
||||
|
||||
ani.ticker.add(() => {
|
||||
render.cache('@default');
|
||||
camera.reset();
|
||||
camera.move(ani.x, ani.y);
|
||||
camera.scale(ani.size);
|
||||
camera.rotate((ani.angle / 180) * Math.PI);
|
||||
camera.move(240, 240);
|
||||
render.update(render);
|
||||
});
|
||||
setTimeout(() => ani.ticker.destroy(), 10000);
|
||||
|
||||
sleep(1000).then(() => {
|
||||
ani.mode(hyper('sin', 'out')).time(100).absolute().rotate(30);
|
||||
sleep(100).then(() => {
|
||||
ani.time(3000).rotate(0);
|
||||
});
|
||||
sleep(3100).then(() => {
|
||||
ani.time(5000).mode(hyper('tan', 'in-out')).rotate(3600);
|
||||
});
|
||||
// ani.mode(shake2(5, hyper('sin', 'in-out')), true)
|
||||
// .time(5000)
|
||||
// .shake(1, 0);
|
||||
});
|
||||
|
||||
console.log(layer);
|
||||
});
|
||||
@@ -1,8 +1,13 @@
|
||||
import { Camera } from './camera';
|
||||
import { RenderFunction, RenderItem, withCacheRender } from './item';
|
||||
import {
|
||||
ICanvasCachedRenderItem,
|
||||
RenderFunction,
|
||||
RenderItem,
|
||||
withCacheRender
|
||||
} from './item';
|
||||
import { MotaOffscreenCanvas2D } from '../fx/canvas2d';
|
||||
|
||||
export class Sprite extends RenderItem {
|
||||
export class Sprite extends RenderItem implements ICanvasCachedRenderItem {
|
||||
renderFn: RenderFunction;
|
||||
|
||||
canvas: MotaOffscreenCanvas2D;
|
||||
@@ -19,12 +24,12 @@ export class Sprite extends RenderItem {
|
||||
ctx: CanvasRenderingContext2D,
|
||||
camera: Camera
|
||||
): void {
|
||||
this.emit('beforeUpdate', this);
|
||||
this.emit('beforeRender');
|
||||
withCacheRender(this, canvas, ctx, camera, canvas => {
|
||||
this.renderFn(canvas, camera);
|
||||
});
|
||||
this.writing = void 0;
|
||||
this.emit('afterUpdate', this);
|
||||
this.emit('afterRender');
|
||||
}
|
||||
|
||||
size(width: number, height: number) {
|
||||
@@ -39,4 +44,20 @@ export class Sprite extends RenderItem {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user