From 78efe81423adab2d2fb273e64f86cff7ba6524f2 Mon Sep 17 00:00:00 2001 From: unanmed <1319491857@qq.com> Date: Sat, 18 May 2024 17:05:01 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=9C=B0=E5=9B=BE=E6=B8=B2=E6=9F=93?= =?UTF-8?q?=E5=9F=BA=E6=9C=AC=E5=AE=8C=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- public/project/enemys.js | 2 +- public/project/functions.js | 1 + public/project/maps.js | 2 +- src/core/render/cache.ts | 212 ++++++++++++++ src/core/render/container.ts | 4 + src/core/render/item.ts | 22 +- src/core/render/preset/layer.ts | 497 ++++++++++---------------------- src/core/render/render.ts | 32 +- src/core/render/sprite.ts | 4 + src/types/core.d.ts | 2 + 10 files changed, 400 insertions(+), 378 deletions(-) diff --git a/public/project/enemys.js b/public/project/enemys.js index 6a804b2..eaed825 100644 --- a/public/project/enemys.js +++ b/public/project/enemys.js @@ -40,7 +40,7 @@ var enemys_fcae963b_31c9_42b4_b48c_bb48d09f3f80 = "angel": {"name":"天使","hp":0,"atk":0,"def":0,"money":0,"exp":0,"point":0,"special":[]}, "elemental": {"name":"元素生物","hp":0,"atk":0,"def":0,"money":0,"exp":0,"point":0,"special":[]}, "steelGuard": {"name":"铁守卫","hp":0,"atk":0,"def":0,"money":0,"exp":0,"point":0,"special":[18],"value":20}, - "evilBat": {"name":"邪恶蝙蝠","hp":1000,"atk":800,"def":350,"money":1,"exp":40,"point":0,"special":[2],"bigImage":"bear.png"}, + "evilBat": {"name":"邪恶蝙蝠","hp":1000,"atk":800,"def":350,"money":1,"exp":40,"point":0,"special":[2],"bigImage":null}, "frozenSkeleton": {"name":"冻死骨","hp":7500,"atk":2500,"def":1000,"money":2,"exp":90,"point":0,"special":[1,20],"crit":500,"ice":10,"description":"弱小,无助,哀嚎,这就是残酷的现实。哪怕你身处极寒之中,也难有人对你伸出援手。人类总会帮助他人,但在这绝望的环境下,人类的本性便暴露无遗。这一个个精致却又无情的骷髅,便是那些在极寒之中死去的冤魂。"}, "silverSlimelord": {"name":"银怪王","hp":0,"atk":0,"def":0,"money":0,"exp":0,"point":0,"special":[]}, "goldSlimelord": {"name":"金怪王","hp":0,"atk":0,"def":0,"money":0,"exp":0,"point":0,"special":[]}, diff --git a/public/project/functions.js b/public/project/functions.js index a2dfe1e..6160fdd 100644 --- a/public/project/functions.js +++ b/public/project/functions.js @@ -32,6 +32,7 @@ var functions_d6ad677b_427a_4623_b50f_a445a3b0ef8a = { // 初始化地图 core.status.floorId = floorId; core.status.maps = maps; + core.extractBlocks(floorId); core.maps._resetFloorImages(); // 初始化怪物和道具 core.material.enemys = core.enemys.getEnemys(); diff --git a/public/project/maps.js b/public/project/maps.js index 86ba03a..f7d88f5 100644 --- a/public/project/maps.js +++ b/public/project/maps.js @@ -73,7 +73,7 @@ var maps_90f36752_8815_4be8_b32b_d7fad1d0542e = "83": {"cls":"animates","id":"redDoor","trigger":"openDoor","animate":1,"doorInfo":{"time":160,"openSound":"door.mp3","closeSound":"door.mp3","keys":{"redKey":1}},"name":"红门"}, "84": {"cls":"animates","id":"greenDoor","trigger":"openDoor","animate":1,"doorInfo":{"time":160,"openSound":"door.mp3","closeSound":"door.mp3","keys":{"greenKey":1}},"name":"绿门"}, "85": {"cls":"animates","id":"specialDoor","trigger":"openDoor","animate":1,"doorInfo":{"time":160,"openSound":"door.mp3","closeSound":"door.mp3","keys":{"specialKey":1}},"name":"机关门"}, - "86": {"cls":"animates","id":"steelDoor","trigger":"openDoor","animate":1,"doorInfo":{"time":160,"openSound":"door.mp3","closeSound":"door.mp3","keys":{"steelKey":1}},"name":"铁门","bigImage":"bear.png"}, + "86": {"cls":"animates","id":"steelDoor","trigger":"openDoor","animate":1,"doorInfo":{"time":160,"openSound":"door.mp3","closeSound":"door.mp3","keys":{"steelKey":1}},"name":"铁门","bigImage":null}, "87": {"cls":"terrains","id":"upFloor","canPass":true}, "88": {"cls":"terrains","id":"downFloor","canPass":true}, "89": {"cls":"animates","id":"portal","canPass":true}, diff --git a/src/core/render/cache.ts b/src/core/render/cache.ts index c216d2f..fb88fa6 100644 --- a/src/core/render/cache.ts +++ b/src/core/render/cache.ts @@ -1,5 +1,7 @@ import { EmitableEvent, EventEmitter } from '../common/eventEmitter'; +import { logger } from '../common/logger'; import { MotaOffscreenCanvas2D } from '../fx/canvas2d'; +import { SizedCanvasImageSource } from './preset/misc'; // 经过测试(https://www.measurethat.net/Benchmarks/Show/30741/1/drawimage-img-vs-canvas-vs-bitmap-cropping-fix-loading) // 得出结论,ImageBitmap和Canvas的绘制性能不如Image,于是直接画Image就行,所以缓存基本上就是存Image @@ -39,6 +41,27 @@ interface TextureRequire { images: Record; } +interface RenderableDataBase { + /** 图块的总帧数 */ + frame: number; + /** 对应图块属性的动画帧数,-1表示没有设定 */ + animate: number; + /** 是否是大怪物 */ + bigImage: boolean; + render: [x: number, y: number, width: number, height: number][]; +} + +export interface RenderableData extends RenderableDataBase { + image: SizedCanvasImageSource; + autotile: false; +} + +export interface AutotileRenderable extends RenderableDataBase { + image: Record; + autotile: true; + bigImage: false; +} + interface TextureCacheEvent extends EmitableEvent {} class TextureCache extends EventEmitter { @@ -49,6 +72,9 @@ class TextureCache extends EventEmitter { idNumberMap!: IdToNumber; + /** 渲染信息 */ + renderable: Map = new Map(); + constructor() { super(); this.material = imageMap as Record; @@ -64,6 +90,7 @@ class TextureCache extends EventEmitter { this.tileset = core.material.images.tilesets; this.autotile = splitAutotiles(this.idNumberMap); this.images = core.material.images.images; + this.calRenderable(); }); } @@ -78,6 +105,191 @@ class TextureCache extends EventEmitter { ): TextureRequire[T][K] { return this[type][key]; } + + /** + * 计算每个图块的可渲染信息 + */ + calRenderable() { + const map = maps_90f36752_8815_4be8_b32b_d7fad1d0542e; + for (const [key, data] of Object.entries(map)) { + this.calRenderableByNum(parseInt(key)); + } + } + + calRenderableByNum( + num: number + ): RenderableData | AutotileRenderable | null { + const map = maps_90f36752_8815_4be8_b32b_d7fad1d0542e; + const enemys = enemys_fcae963b_31c9_42b4_b48c_bb48d09f3f80; + const icons = icons_4665ee12_3a1f_44a4_bea3_0fccba634dc1; + + /** 特判空图块与空气墙 */ + if (num === 0 || num === 17) return null; + + // 额外素材 + if (num >= 10000) { + const offset = core.getTilesetOffset(num); + if (!offset) return null; + const { image, x, y } = offset; + const data: RenderableData = { + image: this.tileset[image], + frame: 1, + render: [[x * 32, y * 32, 32, 32]], + animate: 0, + autotile: false, + bigImage: false + }; + this.renderable.set(num, data); + return data; + } + + const data = map[num as Exclude]; + // 地狱般的分支if + if (data) { + let { cls, faceIds, bigImage, id, animate } = data; + if (cls === 'enemys' || cls === 'enemy48') { + // 怪物需要特殊处理,因为它的大怪物信息不在 maps 里面 + ({ bigImage, faceIds } = enemys[id as EnemyIds]); + } + if (bigImage) { + const image = core.material.images.images[bigImage]; + if (!image) { + logger.warn( + 10, + `Cannot resolve big image of enemy '${id}'.` + ); + return null; + } + let line = 0; + if (faceIds) { + const arr = ['down', 'left', 'right', 'up']; + for (let i = 0; i < arr.length; i++) { + if (faceIds[arr[i] as Dir] === id) { + line = i; + break; + } + } + } + const totalLines = image.width / image.height >= 2 ? 1 : 4; + const w = Math.round(image.width / 4); + const h = Math.round(image.height / totalLines); + const y = h * line; + const data: RenderableData = { + image, + frame: 4, + render: [ + [0, y, w, h], + [w, y, w, h], + [w * 2, y, w, h], + [w * 3, y, w, h] + ], + animate: (animate ?? 0) - 1, + autotile: false, + bigImage: true + }; + this.renderable.set(num, data); + return data; + } + // enemy48和npc48都应该视为大怪物 + if (cls === 'enemy48' || cls === 'npc48') { + const img = core.material.images[cls]; + // @ts-ignore + const line = icons[cls][id]; + const w = 32; + const h = 48; + const y = h * line; + const data: RenderableData = { + image: img, + frame: 4, + render: [ + [0, y, w, h], + [w, y, w, h], + [w * 2, y, w, h], + [w * 3, y, w, h] + ], + animate: (animate ?? 0) - 1, + autotile: false, + bigImage: true + }; + this.renderable.set(num, data); + return data; + } + // 自动元件 + if (cls === 'autotile') { + const auto = this.autotile[num as AllNumbersOf<'autotile'>]; + const cell = 32; + const render: [number, number, number, number][] = []; + if (auto.frame >= 1) { + render.push([0, 0, cell, cell]); + } + if (auto.frame >= 3) { + render.push( + [cell, 0, cell, cell], + [cell * 2, 0, cell, cell] + ); + } + if (auto.frame >= 4) { + render.push([cell * 3, 0, cell, cell]); + } + const data: AutotileRenderable = { + image: auto.cache, + frame: auto.frame, + render, + autotile: true, + bigImage: false, + animate: (animate ?? 0) - 1 + }; + this.renderable.set(num, data); + return data; + } else { + const image = + core.material.images[ + cls as Exclude + ]; + const frame = core.getAnimateFrames(cls); + const cell = 32; + // @ts-ignore + const offset = (icons[cls][id] as number) * cell; + const render: [number, number, number, number][] = [ + [0, offset, cell, cell] + ]; + if (frame === 2) { + render.push([cell, offset, cell, cell]); + } + if (frame === 4) { + render.push( + [cell, offset, cell, cell], + [cell * 2, offset, cell, cell], + [cell * 3, offset, cell, cell] + ); + } + const data: RenderableData = { + image, + frame: frame, + render, + autotile: false, + bigImage: false, + animate: (animate ?? 0) - 1 + }; + this.renderable.set(num, data); + return data; + } + } else { + logger.warn( + 11, + `Cannot resolve material ${num}. Material not exists.` + ); + return null; + } + } + + /** + * 获取一个图块的渲染信息,自动元件会特别标明autotile属性 + * @param num 图块数字 + */ + getRenderable(num: number) { + return this.renderable.get(num) ?? this.calRenderableByNum(num); + } } export const texture = new TextureCache(); diff --git a/src/core/render/container.ts b/src/core/render/container.ts index c850bb2..ee34400 100644 --- a/src/core/render/container.ts +++ b/src/core/render/container.ts @@ -26,6 +26,10 @@ export class Container extends RenderItem implements ICanvasCachedRenderItem { camera: Camera ): void { this.emit('beforeRender'); + if (this.needUpdate) { + this.cache(this.writing); + this.needUpdate = false; + } withCacheRender(this, canvas, ctx, camera, c => { this.sortedChildren.forEach(v => { if (!v.antiAliasing) { diff --git a/src/core/render/item.ts b/src/core/render/item.ts index b4d914f..a882d68 100644 --- a/src/core/render/item.ts +++ b/src/core/render/item.ts @@ -77,7 +77,7 @@ interface IRenderConfig { setHD(hd: boolean): void; /** - * 设置当前渲染原始是否启用抗锯齿 + * 设置当前渲染元素是否启用抗锯齿 * @param anti 是否抗锯齿 */ setAntiAliasing(anti: boolean): void; @@ -135,7 +135,7 @@ export abstract class RenderItem constructor() { super(); - this.using = '@default'; + // this.using = '@default'; } /** @@ -194,21 +194,7 @@ export abstract class RenderItem update(item?: RenderItem): void { 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); + this.parent?.update(item); } setHD(hd: boolean): void { @@ -223,7 +209,7 @@ export abstract class RenderItem setZIndex(zIndex: number) { this.zIndex = zIndex; - (this.parent as Container).sortChildren?.(); + (this.parent as Container)?.sortChildren?.(); } } diff --git a/src/core/render/preset/layer.ts b/src/core/render/preset/layer.ts index e19ed22..dcb00c2 100644 --- a/src/core/render/preset/layer.ts +++ b/src/core/render/preset/layer.ts @@ -5,8 +5,7 @@ import { Camera } from '../camera'; import { TimingFn } from 'mutate-animate'; import { IRenderDestroyable, RenderItem } from '../item'; import { logger } from '@/core/common/logger'; -import { texture } from '../cache'; -import { SizedCanvasImageSource } from './misc'; +import { AutotileRenderable, RenderableData, texture } from '../cache'; import { glMatrix } from 'gl-matrix'; interface LayerCacheItem { @@ -14,24 +13,12 @@ interface LayerCacheItem { canvas: HTMLCanvasElement; } -interface LayerRenderableData { - image: SizedCanvasImageSource; - frame: number; - render: [x: number, y: number, width: number, height: number][]; -} - -interface LayerMovingRenderable extends LayerRenderableData { +interface LayerMovingRenderable extends RenderableData { zIndex: number; x: number; y: number; } -interface BigImageData { - image: HTMLImageElement; - line: number; - totalLines: number; -} - interface NeedRenderData { /** 需要渲染的地图内容 */ res: Set; @@ -82,7 +69,7 @@ interface MovingBlock { /** 目标纵坐标 */ y: number; /** 渲染信息 */ - render: LayerRenderableData; + render: RenderableData | AutotileRenderable; /** 当前的纵深 */ nowZ: number; } @@ -124,10 +111,6 @@ export class Layer extends Container implements IRenderDestroyable { layer?: FloorLayer; /** 渲染数据 */ renderData: number[] = []; - /** 可以直接被渲染的内容 */ - renderable: Map = new Map(); - /** 移动层中可以直接被渲染的内容 */ - movingRenderable: LayerMovingRenderable[] = []; /** 自动元件的连接信息,键表示图块在渲染数据中的索引,值表示连接信息,是个8位二进制 */ autotiles: Record = {}; /** 楼层宽度 */ @@ -136,8 +119,6 @@ export class Layer extends Container implements IRenderDestroyable { mapHeight: number = 0; /** 每个图块的大小 */ cellSize: number = 32; - /** moving层的缓存信息,从低位到高位依次是第1帧至第4帧 */ - movingCached: number = 0b0000; /** 背景图块 */ background: AllNumbers = 0; @@ -152,28 +133,33 @@ export class Layer extends Container implements IRenderDestroyable { restWidth: 0 }; blockSize: number = core._WIDTH_; + /** 正在移动的图块 */ moving: MovingBlock[] = []; - /** 大怪物(大图块)信息,键是图块在渲染数据中的索引,值是大怪物所用图片 */ - bigImage: Map = new Map(); + /** 大怪物渲染信息 */ + bigImages: Map = new Map(); + /** 移动层的渲染信息 */ + movingRenderable: LayerMovingRenderable[] = []; + /** 下一此渲染时是否需要更新移动层的渲染信息 */ + needUpdateMoving: boolean = false; constructor() { super('absolute'); - this.setHD(false); + // this.setHD(false); this.setAntiAliasing(false); this.size(core._PX_, core._PY_); this.staticMap.setHD(false); - this.staticMap.setAntiAliasing(false); + // this.staticMap.setAntiAliasing(false); this.staticMap.withGameScale(false); this.staticMap.size(core._PX_, core._PY_); this.movingMap.setHD(false); - this.movingMap.setAntiAliasing(false); + // this.movingMap.setAntiAliasing(false); this.movingMap.withGameScale(false); this.movingMap.size(core._PX_, core._PY_); this.backMap.setHD(false); - this.backMap.setAntiAliasing(false); + // this.backMap.setAntiAliasing(false); this.backMap.withGameScale(false); this.backMap.size(core._PX_, core._PY_); this.main.setAntiAliasing(false); @@ -211,7 +197,7 @@ export class Layer extends Container implements IRenderDestroyable { generateBackground() { const num = this.background; - const data = this.getRenderableByNum(num); + const data = texture.getRenderable(num); this.backImage = []; if (!data) return; @@ -231,7 +217,7 @@ export class Layer extends Container implements IRenderDestroyable { temp.withGameScale(false); temp.size(w, h); - const img = data.image; + const img = data.autotile ? data.image[0b11111111] : data.image; tempCtx.drawImage(img, sx, sy, w, h, 0, 0, w, h); const pattern = ctx.createPattern(temp.canvas, 'repeat'); if (!pattern) continue; @@ -256,8 +242,6 @@ export class Layer extends Container implements IRenderDestroyable { y: number = 0, calAutotile: boolean = true ) { - console.trace(); - if (data.length % width !== 0) { logger.warn( 8, @@ -286,251 +270,38 @@ export class Layer extends Container implements IRenderDestroyable { } } if (calAutotile) this.calAutotiles(x, y, width, height); - this.updateBigImages(x, y, width, height); - this.updateRenderableData(x, y, width, height); + // this.updateBigImages(x, y, width, height); + // this.updateRenderableData(x, y, width, height); this.updateBlocks(x, y, width, height); this.update(this); } /** - * 更新给定区域内的大怪物信息 + * 更新大怪物的渲染信息 */ updateBigImages(x: number, y: number, width: number, height: number) { - const ex = Math.min(x + width, this.mapWidth); - const ey = Math.min(y + height, this.mapHeight); - const size = this.blockSize; - const images = this.bigImage; + const ex = x + width; + const ey = y + height; + const w = this.mapWidth; const data = this.renderData; - const enemys = enemys_fcae963b_31c9_42b4_b48c_bb48d09f3f80; - const icons = icons_4665ee12_3a1f_44a4_bea3_0fccba634dc1; - const map = maps_90f36752_8815_4be8_b32b_d7fad1d0542e; - for (let nx = Math.max(x, 0); nx < ex; nx++) { - for (let ny = Math.max(y, 0); ny < ey; ny++) { - const index = ny * size + nx; - images.delete(index); + for (let nx = x; nx < ex; nx++) { + for (let ny = y; ny < ey; ny++) { + const index = ny * w + nx; + this.bigImages.delete(index); const num = data[index]; - - // 如果不存在图块或图块是空气墙,跳过 - if (num === 0 || num === 17 || num >= 10000) continue; - - let { cls, id, bigImage, faceIds } = - map[num as Exclude]; - if (cls === 'enemys' || cls === 'enemy48') { - // 怪物需要特殊处理,因为它的大怪物信息不在 maps 里面 - ({ bigImage, faceIds } = enemys[id as EnemyIds]); - } - if (bigImage) { - const image = core.material.images.images[bigImage]; - if (!image) { - logger.warn( - 10, - `Cannot resolve big image of enemy '${id}'.` - ); - continue; - } - let line = 0; - if (faceIds) { - const arr = ['down', 'left', 'right', 'up']; - for (let i = 0; i < arr.length; i++) { - if (faceIds[arr[i] as Dir] === id) { - line = i; - break; - } - } - } - const totalLines = image.width / image.height >= 2 ? 1 : 4; - images.set(index, { - image, - line, - totalLines - }); - } - if (cls === 'enemy48' || cls === 'npc48') { - // 32 * 48 视为大怪物 - const img = core.material.images[cls]; - const totalLines = Math.round(img.height / 48); - // @ts-ignore - const line = icons[cls][id]; - images.set(index, { - image: img, - line, - totalLines - }); - } + const renderable = texture.getRenderable(num); + if (!renderable || !renderable.bigImage) continue; + this.bigImages.set(index, { + ...renderable, + x: nx, + y: ny, + zIndex: ny + }); } } - } - /** - * 根据图块数字获取渲染信息 - * @param num 图块数字 - */ - getRenderableByNum(num: number): LayerRenderableData | null { - const cell = this.cellSize; - const map = maps_90f36752_8815_4be8_b32b_d7fad1d0542e; - const icons = icons_4665ee12_3a1f_44a4_bea3_0fccba634dc1; - const auto = texture.autotile; - - if (num >= 10000) { - // 额外素材 - const offset = core.getTilesetOffset(num); - if (!offset) return null; - const { image, x, y } = offset; - return { - image: core.material.images.tilesets[image], - frame: 1, - render: [[x * cell, y * cell, cell, cell]] - }; - } else { - if (num === 0 || num === 17) return null; - const { cls, id } = map[num as Exclude]; - // 普通素材 - if (cls !== 'autotile') { - const image = - core.material.images[ - cls as Exclude - ]; - const frame = core.getAnimateFrames(cls); - // @ts-ignore - const offset = (icons[cls][id] as number) * cell; - const render: [number, number, number, number][] = [ - [0, offset, cell, cell] - ]; - if (frame === 2) { - render.push([cell, offset, cell, cell]); - } - if (frame === 4) { - render.push( - [cell, offset, cell, cell], - [cell * 2, offset, cell, cell], - [cell * 3, offset, cell, cell] - ); - } - return { - image, - frame, - render - }; - } else { - // 自动元件 - const tile = auto[num as AllNumbersOf<'autotile'>]; - const image = tile.cache[0b11111111]; - const frame = tile.frame; - const render: [number, number, number, number][] = [ - [0, 0, cell, cell] - ]; - if (frame === 4) { - render.push( - [cell, 0, cell, cell], - [cell * 2, 0, cell, cell], - [cell * 3, 0, cell, cell] - ); - } - return { - image, - frame, - render - }; - } - } - } - - /** - * 根据索引及横坐标获取对应的位置图块的渲染信息 - * @param x 横坐标 - * @param index 索引 - */ - getRenderableData( - x: number, - _y: number, - index: number - ): LayerRenderableData | LayerMovingRenderable | null { - const data = this.renderData; - const cell = this.cellSize; - const map = maps_90f36752_8815_4be8_b32b_d7fad1d0542e; - const icons = icons_4665ee12_3a1f_44a4_bea3_0fccba634dc1; - const auto = texture.autotile; - - const bigImage = this.bigImage.get(index); - if (bigImage) { - // 对于大怪物 - const img = bigImage.image; - const w = Math.round(img.width / 4); - const h = Math.round(img.height / bigImage.totalLines); - const y = h * bigImage.line; - return { - image: bigImage.image, - frame: 4, - render: [ - [0, y, w, h], - [w, y, w, h], - [w * 2, y, w, h], - [w * 3, y, w, h] - ], - x: x, - y: y, - zIndex: y - }; - } else { - // 对于普通图块 - const num = data[index]; - if (num >= 10000) { - // 额外素材 - return this.getRenderableByNum(num); - } else { - if (num === 0 || num === 17) return null; - const { cls } = map[num as Exclude]; - // 普通素材 - if (cls !== 'autotile') { - return this.getRenderableByNum(num); - } else { - // 自动元件 - const tile = auto[num as AllNumbersOf<'autotile'>]; - const link = this.autotiles[index]; - const image = tile.cache[link]; - const frame = tile.frame; - const render: [number, number, number, number][] = [ - [0, 0, cell, cell] - ]; - if (frame === 4) { - render.push( - [cell, 0, cell, cell], - [cell * 2, 0, cell, cell], - [cell * 3, 0, cell, cell] - ); - } - return { - image, - frame, - render - }; - } - } - } - } - - /** - * 更新给定区域内的绘制信息 - */ - updateRenderableData(x: number, y: number, width: number, height: number) { - const ex = Math.min(x + width, this.mapWidth); - const ey = Math.min(y + height, this.mapHeight); - const size = this.blockSize; - - for (let nx = Math.max(x, 0); nx < ex; nx++) { - for (let ny = Math.max(y, 0); ny < ey; ny++) { - const index = ny * size + nx; - const bigImage = this.bigImage.get(index); - const data = this.getRenderableData(nx, ny, index); - if (!data) continue; - if (bigImage) { - this.movingRenderable.push(data as LayerMovingRenderable); - } else { - this.renderable.set(index, data); - } - } - } + this.needUpdateMoving = true; } /** @@ -590,12 +361,11 @@ export class Layer extends Container implements IRenderDestroyable { for (let nx = x; nx < ex; nx++) { for (let ny = y; ny < ey; ny++) { - if (nx > w || ny > w) continue; - const index = nx + ny * h; + if (nx > w || ny > h) continue; + const index = nx + ny * w; const num = data[index]; // 特判空气墙与空图块 - if (num === 17 || num >= 10000 || num <= 0) continue; - console.log(this); + if (num === 0 || num === 17 || num >= 10000) continue; const info = map[num as Exclude]; const { cls } = info; @@ -683,8 +453,8 @@ export class Layer extends Container implements IRenderDestroyable { const size = this.blockSize; this.blockData = { - width: Math.floor(this.mapWidth / size), - height: Math.floor(this.mapHeight / size), + width: Math.ceil(this.mapWidth / size), + height: Math.ceil(this.mapHeight / size), restWidth: this.mapWidth % size, restHeight: this.mapHeight % size }; @@ -808,21 +578,31 @@ export class Layer extends Container implements IRenderDestroyable { * @param camera 摄像机 */ calNeedRender(camera: Camera): NeedRenderData { - const w = core._WIDTH_; - const h = core._HEIGHT_; - const size = this.blockSize; - const { width } = this.blockData; const cell = this.cellSize; + const w = (core._WIDTH_ * cell) / 2; + const h = (core._HEIGHT_ * cell) / 2; + const size = this.blockSize; - const [x1, y1] = Camera.transformed(camera, 0, 0); - const [x2, y2] = Camera.transformed(camera, w * cell, 0); - const [x3, y3] = Camera.transformed(camera, w * cell, h * cell); - const [x4, y4] = Camera.transformed(camera, 0, h * cell); + // -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 res: Set = new Set(); /** 一个纵坐标对应的所有横坐标,用于填充 */ const xyMap: Map = new Map(); + const pushXY = (x: number, y: number) => { + let arr = xyMap.get(y); + if (!arr) { + arr = []; + xyMap.set(y, arr); + } + arr.push(x); + return arr; + }; + [ [x1, y1, x2, y2], [x2, y2, x3, y3], @@ -837,43 +617,26 @@ export class Layer extends Container implements IRenderDestroyable { const dy = ty - fy; const k = dy / dx; - // console.log(fx, fy, tx, ty); - // 斜率无限的时候,竖直 if (!isFinite(k)) { const min = k < 0 ? ty : fy; const max = k < 0 ? fy : ty; const [x, y] = this.getBlockXYByLoc(fx, min); - // 在地图左侧或右侧时,将每个纵坐标对应的横坐标填充为0 - - const p = x < 0 ? 0 : x >= width ? width - 1 : x; const [, ey] = this.getBlockXYByLoc(fx, max); for (let i = y; i <= ey; i++) { - let arr = xyMap.get(i); - if (!arr) { - arr = []; - xyMap.set(i, arr); - } - arr.push(p); + pushXY(x, i); } - // console.log(y, ey, p); return; } const [fbx, fby] = this.getBlockXYByLoc(fx, fy); + // 当斜率为0时 if (glMatrix.equals(k, 0)) { const [ex] = this.getBlockXYByLoc(tx, fy); - let arr = xyMap.get(fby); - if (!arr) { - arr = []; - xyMap.set(fby, arr); - } - arr.push(fbx, ex); - // console.log(fbx, ex); - + pushXY(fby, fbx).push(ex); return; } @@ -881,54 +644,52 @@ export class Layer extends Container implements IRenderDestroyable { if (Math.abs(k) >= 1) { // 斜率大于一,y方向递增 const d = Math.sign(dy) * size; - const f = dx > 0 ? fby * size : (fby + 1) * size; + const f = fby * size; const dir = dy > 0; + const ex = Math.floor(tx / size); + const ey = Math.floor(ty / size); + pushXY(ex, ey); + let now = f; let last = fbx; let ny = fby; do { - const bx = Math.floor(fx + (now - fy) / k); - let arr = xyMap.get(ny); - if (!arr) { - arr = []; - xyMap.set(ny, arr); - } + const bx = Math.floor((fx + (now - fy) / k) / size); + pushXY(bx, ny); if (bx !== last) { - arr.push(last); + if (dir) pushXY(bx, ny - Math.sign(dy)); + else pushXY(last, ny); } - arr.push(bx); + last = bx; - ny++; - } while (dir ? (now += d) < ty : (now += d) > ty); + ny += Math.sign(dy); + now += d; + } while (dir ? ny <= ey : ny >= ey); } else { // 斜率小于一,x方向递增 const d = Math.sign(dx) * size; - const f = dx > 0 ? fbx * size : (fbx + 1) * size; + const f = fbx * size; const dir = dx > 0; + const ex = Math.floor(tx / size); + const ey = Math.floor(ty / size); + pushXY(ex, ey); + let now = f; let last = fby; let nx = fbx; do { - const by = Math.floor(fy + k * (now - fx)); - + const by = Math.floor((fy + k * (now - fx)) / size); if (by !== last) { - let arr = xyMap.get(last); - if (!arr) { - arr = []; - xyMap.set(last, arr); - } - arr.push(nx); + if (dir) pushXY(nx - Math.sign(dx), by); + else pushXY(nx, last); } - let arr = xyMap.get(nx); - if (!arr) { - arr = []; - xyMap.set(by, arr); - } - arr.push(nx); - nx++; - } while (dir ? (now += d) < tx : (now += d) > tx); + pushXY(nx, by); + last = by; + nx += Math.sign(dx); + now += d; + } while (dir ? nx <= ex : nx >= ex); } }); @@ -939,7 +700,8 @@ export class Layer extends Container implements IRenderDestroyable { if (x.length === 1) { const index = y * bw + x[0]; - back.push([x[0], y]); + if (!back.some(v => v[0] === x[0] && v[1] === y)) + back.push([x[0], y]); if (index < 0 || index >= bw * bh) return; res.add(index); } @@ -949,17 +711,44 @@ export class Layer extends Container implements IRenderDestroyable { for (let i = min; i <= max; i++) { const index = y * bw + i; - back.push([i, y]); + if (!back.some(v => v[0] === i && v[1] === y)) + back.push([i, y]); if (index < 0 || index >= bw * bh) continue; res.add(index); } }); - // console.log([...res], xyMap); - return { res, back }; } + /** + * 更新移动层的渲染信息 + */ + updateMovingRenderable() { + this.movingRenderable = []; + this.movingRenderable.push(...this.bigImages.values()); + this.moving.forEach(v => { + if (!v.render.autotile) { + this.movingRenderable.push({ + ...v.render, + x: v.x, + y: v.y, + zIndex: v.nowZ + }); + } else { + this.movingRenderable.push({ + ...v.render, + x: v.x, + y: v.y, + zIndex: v.nowZ, + image: v.render.image[0b00000000], + autotile: false + }); + } + }); + this.movingRenderable.sort((a, b) => a.zIndex - b.zIndex); + } + /** * 渲染当前地图 */ @@ -968,6 +757,8 @@ export class Layer extends Container implements IRenderDestroyable { this.movingMap.clear(); this.backMap.clear(); + if (this.needUpdateMoving) this.updateMovingRenderable(); + this.renderBack(camera, need); this.renderStatic(camera, need); this.renderMoving(camera); @@ -981,10 +772,9 @@ export class Layer extends Container implements IRenderDestroyable { protected renderBack(camera: Camera, need: NeedRenderData) { const cell = this.cellSize; const frame = (RenderItem.animatedFrame % 4) + 1; - const { width } = this.blockData; const blockSize = this.blockSize; const { back } = need; - const { ctx, canvas } = this.backMap; + const { ctx } = this.backMap; const mat = camera.mat; const a = mat[0]; @@ -1020,16 +810,14 @@ export class Layer extends Container implements IRenderDestroyable { */ protected renderStatic(camera: Camera, need: NeedRenderData) { const cell = this.cellSize; - const renderable = this.renderable; const frame = (RenderItem.animatedFrame % 4) + 1; const { width } = this.blockData; const blockSize = this.blockSize; - const { ctx, canvas } = this.staticMap; + const { ctx } = this.staticMap; ctx.save(); const { res: render } = need; - // console.log(render); const mat = camera.mat; const a = mat[0]; const b = mat[1]; @@ -1061,8 +849,8 @@ export class Layer extends Container implements IRenderDestroyable { return; } - const ex = sx + blockSize; - const ey = sy + blockSize; + const ex = Math.min(sx + blockSize, this.mapWidth); + const ey = Math.min(sy + blockSize, this.mapHeight); const temp = new MotaOffscreenCanvas2D(); temp.setAntiAliasing(false); @@ -1074,17 +862,28 @@ export class Layer extends Container implements IRenderDestroyable { for (let nx = sx; nx < ex; nx++) { for (let ny = sy; ny < ey; ny++) { const blockIndex = nx + ny * this.mapWidth; - const data = renderable.get(blockIndex); - if (!data) continue; - + const num = this.renderData[blockIndex]; + if (num === 0 || num === 17) continue; + const data = texture.getRenderable(num); + if (!data || data.bigImage) continue; const f = frame % data.frame; - const i = frame === 4 && data.frame === 3 ? 1 : f; - const [sx, sy, w, h] = data.render[i]; - const px = nx * cell; - const py = ny * cell; - const image = data.image; - - temp.ctx.drawImage(image, sx, sy, w, h, px, py, w, h); + const i = + data.animate === -1 + ? frame === 4 && data.frame === 3 + ? 1 + : f + : data.animate; + const [isx, isy, w, h] = data.render[i]; + const px = (nx - sx) * cell; + const py = (ny - sy) * cell; + const { image, autotile } = data; + if (!autotile) { + temp.ctx.drawImage(image, isx, isy, w, h, px, py, w, h); + } else { + const link = this.autotiles[blockIndex]; + const i = image[link]; + temp.ctx.drawImage(i, isx, isy, w, h, px, py, w, h); + } } } ctx.drawImage( @@ -1126,10 +925,6 @@ export class Layer extends Container implements IRenderDestroyable { const r = Math.max(a, b, c, d) ** 2 * Math.max(core._PX_, core._PY_) * 2; - this.movingRenderable.sort((a, b) => { - return a.zIndex - b.zIndex; - }); - this.movingRenderable.forEach(v => { const { x, y, image, frame: blockFrame, render } = v; const f = frame % 4; diff --git a/src/core/render/render.ts b/src/core/render/render.ts index 8f5f31b..e2ff14c 100644 --- a/src/core/render/render.ts +++ b/src/core/render/render.ts @@ -149,11 +149,11 @@ Mota.require('var', 'hook').once('reset', () => { const camera = render.camera; render.mount(); - layer.zIndex = 2; - bgLayer.zIndex = 1; + layer.setZIndex(2); + bgLayer.setZIndex(1); render.appendChild([layer, bgLayer]); - layer.bindThis('event', true); - bgLayer.bindThis('bg', true); + layer.bindThis('event'); + bgLayer.bindThis('bg'); bgLayer.setBackground(650); const ani = new Animation(); @@ -161,17 +161,35 @@ Mota.require('var', 'hook').once('reset', () => { ani.ticker.add(() => { camera.reset(); camera.rotate((ani.angle / 180) * Math.PI); - camera.move(240, 240); + camera.move(ani.x, ani.y); + camera.scale(ani.size); render.update(render); }); + camera.rotate(Math.PI * 1.23); + camera.move(230, 380); + camera.scale(0.7); + render.update(); + + // sleep(2000).then(() => { + // render.update(); + // }); + sleep(1000).then(() => { - ani.mode(hyper('sin', 'out')).time(100).absolute().rotate(30); + ani.mode(hyper('sin', 'out')) + .time(100) + .absolute() + .rotate(30) + .move(240, 240); sleep(100).then(() => { ani.time(3000).rotate(0); }); sleep(3100).then(() => { - ani.time(5000).mode(hyper('tan', 'in-out')).rotate(3600); + ani.time(5000) + .mode(hyper('sin', 'in-out')) + .rotate(360) + .move(200, 480) + .scale(0.5); }); // ani.mode(shake2(5, hyper('sin', 'in-out')), true) // .time(5000) diff --git a/src/core/render/sprite.ts b/src/core/render/sprite.ts index fcc3f89..f4e916e 100644 --- a/src/core/render/sprite.ts +++ b/src/core/render/sprite.ts @@ -25,6 +25,10 @@ export class Sprite extends RenderItem implements ICanvasCachedRenderItem { camera: Camera ): void { this.emit('beforeRender'); + if (this.needUpdate) { + this.cache(this.writing); + this.needUpdate = false; + } withCacheRender(this, canvas, ctx, camera, canvas => { this.renderFn(canvas, camera); }); diff --git a/src/types/core.d.ts b/src/types/core.d.ts index 85a24c5..ccb75a5 100644 --- a/src/types/core.d.ts +++ b/src/types/core.d.ts @@ -1419,6 +1419,8 @@ interface MapDataOf { bigImage?: ImageIds; faceIds?: Record; + + animate?: number; } /**