feat: 地图渲染基本完成

This commit is contained in:
unanmed 2024-05-18 17:05:01 +08:00
parent 117bd94928
commit 78efe81423
10 changed files with 400 additions and 378 deletions

View File

@ -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":[]}, "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":[]}, "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}, "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":"弱小,无助,哀嚎,这就是残酷的现实。哪怕你身处极寒之中,也难有人对你伸出援手。人类总会帮助他人,但在这绝望的环境下,人类的本性便暴露无遗。这一个个精致却又无情的骷髅,便是那些在极寒之中死去的冤魂。"}, "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":[]}, "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":[]}, "goldSlimelord": {"name":"金怪王","hp":0,"atk":0,"def":0,"money":0,"exp":0,"point":0,"special":[]},

View File

@ -32,6 +32,7 @@ var functions_d6ad677b_427a_4623_b50f_a445a3b0ef8a = {
// 初始化地图 // 初始化地图
core.status.floorId = floorId; core.status.floorId = floorId;
core.status.maps = maps; core.status.maps = maps;
core.extractBlocks(floorId);
core.maps._resetFloorImages(); core.maps._resetFloorImages();
// 初始化怪物和道具 // 初始化怪物和道具
core.material.enemys = core.enemys.getEnemys(); core.material.enemys = core.enemys.getEnemys();

View File

@ -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":"红门"}, "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":"绿门"}, "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":"机关门"}, "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}, "87": {"cls":"terrains","id":"upFloor","canPass":true},
"88": {"cls":"terrains","id":"downFloor","canPass":true}, "88": {"cls":"terrains","id":"downFloor","canPass":true},
"89": {"cls":"animates","id":"portal","canPass":true}, "89": {"cls":"animates","id":"portal","canPass":true},

View File

@ -1,5 +1,7 @@
import { EmitableEvent, EventEmitter } from '../common/eventEmitter'; import { EmitableEvent, EventEmitter } from '../common/eventEmitter';
import { logger } from '../common/logger';
import { MotaOffscreenCanvas2D } from '../fx/canvas2d'; 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 // 经过测试https://www.measurethat.net/Benchmarks/Show/30741/1/drawimage-img-vs-canvas-vs-bitmap-cropping-fix-loading
// 得出结论ImageBitmap和Canvas的绘制性能不如Image于是直接画Image就行所以缓存基本上就是存Image // 得出结论ImageBitmap和Canvas的绘制性能不如Image于是直接画Image就行所以缓存基本上就是存Image
@ -39,6 +41,27 @@ interface TextureRequire {
images: Record<ImageIds, HTMLImageElement>; images: Record<ImageIds, HTMLImageElement>;
} }
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<string, SizedCanvasImageSource>;
autotile: true;
bigImage: false;
}
interface TextureCacheEvent extends EmitableEvent {} interface TextureCacheEvent extends EmitableEvent {}
class TextureCache extends EventEmitter<TextureCacheEvent> { class TextureCache extends EventEmitter<TextureCacheEvent> {
@ -49,6 +72,9 @@ class TextureCache extends EventEmitter<TextureCacheEvent> {
idNumberMap!: IdToNumber; idNumberMap!: IdToNumber;
/** 渲染信息 */
renderable: Map<number, RenderableData | AutotileRenderable> = new Map();
constructor() { constructor() {
super(); super();
this.material = imageMap as Record<ImageMapKeys, HTMLImageElement>; this.material = imageMap as Record<ImageMapKeys, HTMLImageElement>;
@ -64,6 +90,7 @@ class TextureCache extends EventEmitter<TextureCacheEvent> {
this.tileset = core.material.images.tilesets; this.tileset = core.material.images.tilesets;
this.autotile = splitAutotiles(this.idNumberMap); this.autotile = splitAutotiles(this.idNumberMap);
this.images = core.material.images.images; this.images = core.material.images.images;
this.calRenderable();
}); });
} }
@ -78,6 +105,191 @@ class TextureCache extends EventEmitter<TextureCacheEvent> {
): TextureRequire[T][K] { ): TextureRequire[T][K] {
return this[type][key]; 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<AllNumbers, 0>];
// 地狱般的分支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<Cls, 'tileset' | 'autotile'>
];
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(); export const texture = new TextureCache();

View File

@ -26,6 +26,10 @@ export class Container extends RenderItem implements ICanvasCachedRenderItem {
camera: Camera camera: Camera
): void { ): void {
this.emit('beforeRender'); this.emit('beforeRender');
if (this.needUpdate) {
this.cache(this.writing);
this.needUpdate = false;
}
withCacheRender(this, canvas, ctx, camera, c => { withCacheRender(this, canvas, ctx, camera, c => {
this.sortedChildren.forEach(v => { this.sortedChildren.forEach(v => {
if (!v.antiAliasing) { if (!v.antiAliasing) {

View File

@ -77,7 +77,7 @@ interface IRenderConfig {
setHD(hd: boolean): void; setHD(hd: boolean): void;
/** /**
* 齿 * 齿
* @param anti 齿 * @param anti 齿
*/ */
setAntiAliasing(anti: boolean): void; setAntiAliasing(anti: boolean): void;
@ -135,7 +135,7 @@ export abstract class RenderItem
constructor() { constructor() {
super(); super();
this.using = '@default'; // this.using = '@default';
} }
/** /**
@ -194,21 +194,7 @@ export abstract class RenderItem
update(item?: RenderItem): void { update(item?: RenderItem): void {
if (this.needUpdate) return; if (this.needUpdate) return;
this.needUpdate = true; this.needUpdate = true;
requestAnimationFrame(() => { this.parent?.update(item);
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 { setHD(hd: boolean): void {
@ -223,7 +209,7 @@ export abstract class RenderItem
setZIndex(zIndex: number) { setZIndex(zIndex: number) {
this.zIndex = zIndex; this.zIndex = zIndex;
(this.parent as Container).sortChildren?.(); (this.parent as Container)?.sortChildren?.();
} }
} }

View File

@ -5,8 +5,7 @@ import { Camera } from '../camera';
import { TimingFn } from 'mutate-animate'; import { TimingFn } from 'mutate-animate';
import { IRenderDestroyable, RenderItem } from '../item'; import { IRenderDestroyable, RenderItem } from '../item';
import { logger } from '@/core/common/logger'; import { logger } from '@/core/common/logger';
import { texture } from '../cache'; import { AutotileRenderable, RenderableData, texture } from '../cache';
import { SizedCanvasImageSource } from './misc';
import { glMatrix } from 'gl-matrix'; import { glMatrix } from 'gl-matrix';
interface LayerCacheItem { interface LayerCacheItem {
@ -14,24 +13,12 @@ interface LayerCacheItem {
canvas: HTMLCanvasElement; canvas: HTMLCanvasElement;
} }
interface LayerRenderableData { interface LayerMovingRenderable extends RenderableData {
image: SizedCanvasImageSource;
frame: number;
render: [x: number, y: number, width: number, height: number][];
}
interface LayerMovingRenderable extends LayerRenderableData {
zIndex: number; zIndex: number;
x: number; x: number;
y: number; y: number;
} }
interface BigImageData {
image: HTMLImageElement;
line: number;
totalLines: number;
}
interface NeedRenderData { interface NeedRenderData {
/** 需要渲染的地图内容 */ /** 需要渲染的地图内容 */
res: Set<number>; res: Set<number>;
@ -82,7 +69,7 @@ interface MovingBlock {
/** 目标纵坐标 */ /** 目标纵坐标 */
y: number; y: number;
/** 渲染信息 */ /** 渲染信息 */
render: LayerRenderableData; render: RenderableData | AutotileRenderable;
/** 当前的纵深 */ /** 当前的纵深 */
nowZ: number; nowZ: number;
} }
@ -124,10 +111,6 @@ export class Layer extends Container implements IRenderDestroyable {
layer?: FloorLayer; layer?: FloorLayer;
/** 渲染数据 */ /** 渲染数据 */
renderData: number[] = []; renderData: number[] = [];
/** 可以直接被渲染的内容 */
renderable: Map<number, LayerRenderableData> = new Map();
/** 移动层中可以直接被渲染的内容 */
movingRenderable: LayerMovingRenderable[] = [];
/** 自动元件的连接信息键表示图块在渲染数据中的索引值表示连接信息是个8位二进制 */ /** 自动元件的连接信息键表示图块在渲染数据中的索引值表示连接信息是个8位二进制 */
autotiles: Record<number, number> = {}; autotiles: Record<number, number> = {};
/** 楼层宽度 */ /** 楼层宽度 */
@ -136,8 +119,6 @@ export class Layer extends Container implements IRenderDestroyable {
mapHeight: number = 0; mapHeight: number = 0;
/** 每个图块的大小 */ /** 每个图块的大小 */
cellSize: number = 32; cellSize: number = 32;
/** moving层的缓存信息从低位到高位依次是第1帧至第4帧 */
movingCached: number = 0b0000;
/** 背景图块 */ /** 背景图块 */
background: AllNumbers = 0; background: AllNumbers = 0;
@ -152,28 +133,33 @@ export class Layer extends Container implements IRenderDestroyable {
restWidth: 0 restWidth: 0
}; };
blockSize: number = core._WIDTH_; blockSize: number = core._WIDTH_;
/** 正在移动的图块 */ /** 正在移动的图块 */
moving: MovingBlock[] = []; moving: MovingBlock[] = [];
/** 大怪物(大图块)信息,键是图块在渲染数据中的索引,值是大怪物所用图片 */ /** 大怪物渲染信息 */
bigImage: Map<number, BigImageData> = new Map(); bigImages: Map<number, LayerMovingRenderable> = new Map();
/** 移动层的渲染信息 */
movingRenderable: LayerMovingRenderable[] = [];
/** 下一此渲染时是否需要更新移动层的渲染信息 */
needUpdateMoving: boolean = false;
constructor() { constructor() {
super('absolute'); super('absolute');
this.setHD(false); // this.setHD(false);
this.setAntiAliasing(false); this.setAntiAliasing(false);
this.size(core._PX_, core._PY_); this.size(core._PX_, core._PY_);
this.staticMap.setHD(false); this.staticMap.setHD(false);
this.staticMap.setAntiAliasing(false); // this.staticMap.setAntiAliasing(false);
this.staticMap.withGameScale(false); this.staticMap.withGameScale(false);
this.staticMap.size(core._PX_, core._PY_); this.staticMap.size(core._PX_, core._PY_);
this.movingMap.setHD(false); this.movingMap.setHD(false);
this.movingMap.setAntiAliasing(false); // this.movingMap.setAntiAliasing(false);
this.movingMap.withGameScale(false); this.movingMap.withGameScale(false);
this.movingMap.size(core._PX_, core._PY_); this.movingMap.size(core._PX_, core._PY_);
this.backMap.setHD(false); this.backMap.setHD(false);
this.backMap.setAntiAliasing(false); // this.backMap.setAntiAliasing(false);
this.backMap.withGameScale(false); this.backMap.withGameScale(false);
this.backMap.size(core._PX_, core._PY_); this.backMap.size(core._PX_, core._PY_);
this.main.setAntiAliasing(false); this.main.setAntiAliasing(false);
@ -211,7 +197,7 @@ export class Layer extends Container implements IRenderDestroyable {
generateBackground() { generateBackground() {
const num = this.background; const num = this.background;
const data = this.getRenderableByNum(num); const data = texture.getRenderable(num);
this.backImage = []; this.backImage = [];
if (!data) return; if (!data) return;
@ -231,7 +217,7 @@ export class Layer extends Container implements IRenderDestroyable {
temp.withGameScale(false); temp.withGameScale(false);
temp.size(w, h); 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); tempCtx.drawImage(img, sx, sy, w, h, 0, 0, w, h);
const pattern = ctx.createPattern(temp.canvas, 'repeat'); const pattern = ctx.createPattern(temp.canvas, 'repeat');
if (!pattern) continue; if (!pattern) continue;
@ -256,8 +242,6 @@ export class Layer extends Container implements IRenderDestroyable {
y: number = 0, y: number = 0,
calAutotile: boolean = true calAutotile: boolean = true
) { ) {
console.trace();
if (data.length % width !== 0) { if (data.length % width !== 0) {
logger.warn( logger.warn(
8, 8,
@ -286,251 +270,38 @@ export class Layer extends Container implements IRenderDestroyable {
} }
} }
if (calAutotile) this.calAutotiles(x, y, width, height); if (calAutotile) this.calAutotiles(x, y, width, height);
this.updateBigImages(x, y, width, height); // this.updateBigImages(x, y, width, height);
this.updateRenderableData(x, y, width, height); // this.updateRenderableData(x, y, width, height);
this.updateBlocks(x, y, width, height); this.updateBlocks(x, y, width, height);
this.update(this); this.update(this);
} }
/** /**
* *
*/ */
updateBigImages(x: number, y: number, width: number, height: number) { updateBigImages(x: number, y: number, width: number, height: number) {
const ex = Math.min(x + width, this.mapWidth); const ex = x + width;
const ey = Math.min(y + height, this.mapHeight); const ey = y + height;
const size = this.blockSize; const w = this.mapWidth;
const images = this.bigImage;
const data = this.renderData; 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 nx = x; nx < ex; nx++) {
for (let ny = Math.max(y, 0); ny < ey; ny++) { for (let ny = y; ny < ey; ny++) {
const index = ny * size + nx; const index = ny * w + nx;
images.delete(index); this.bigImages.delete(index);
const num = data[index]; const num = data[index];
const renderable = texture.getRenderable(num);
// 如果不存在图块或图块是空气墙,跳过 if (!renderable || !renderable.bigImage) continue;
if (num === 0 || num === 17 || num >= 10000) continue; this.bigImages.set(index, {
...renderable,
let { cls, id, bigImage, faceIds } = x: nx,
map[num as Exclude<AllNumbers, 0>]; y: ny,
if (cls === 'enemys' || cls === 'enemy48') { zIndex: ny
// 怪物需要特殊处理,因为它的大怪物信息不在 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
});
}
} }
} }
}
/** this.needUpdateMoving = true;
*
* @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<AllNumbers, 0>];
// 普通素材
if (cls !== 'autotile') {
const image =
core.material.images[
cls as Exclude<Cls, 'tileset' | 'autotile'>
];
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<AllNumbers, 0>];
// 普通素材
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);
}
}
}
} }
/** /**
@ -590,12 +361,11 @@ export class Layer extends Container implements IRenderDestroyable {
for (let nx = x; nx < ex; nx++) { for (let nx = x; nx < ex; nx++) {
for (let ny = y; ny < ey; ny++) { for (let ny = y; ny < ey; ny++) {
if (nx > w || ny > w) continue; if (nx > w || ny > h) continue;
const index = nx + ny * h; const index = nx + ny * w;
const num = data[index]; const num = data[index];
// 特判空气墙与空图块 // 特判空气墙与空图块
if (num === 17 || num >= 10000 || num <= 0) continue; if (num === 0 || num === 17 || num >= 10000) continue;
console.log(this);
const info = map[num as Exclude<AllNumbers, 0>]; const info = map[num as Exclude<AllNumbers, 0>];
const { cls } = info; const { cls } = info;
@ -683,8 +453,8 @@ export class Layer extends Container implements IRenderDestroyable {
const size = this.blockSize; const size = this.blockSize;
this.blockData = { this.blockData = {
width: Math.floor(this.mapWidth / size), width: Math.ceil(this.mapWidth / size),
height: Math.floor(this.mapHeight / size), height: Math.ceil(this.mapHeight / size),
restWidth: this.mapWidth % size, restWidth: this.mapWidth % size,
restHeight: this.mapHeight % size restHeight: this.mapHeight % size
}; };
@ -808,21 +578,31 @@ export class Layer extends Container implements IRenderDestroyable {
* @param camera * @param camera
*/ */
calNeedRender(camera: Camera): NeedRenderData { 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 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); // -1是因为宽度是core._PX_从0开始的话末尾索引就是core._PX_ - 1
const [x2, y2] = Camera.transformed(camera, w * cell, 0); const [x1, y1] = Camera.untransformed(camera, -w, -h);
const [x3, y3] = Camera.transformed(camera, w * cell, h * cell); const [x2, y2] = Camera.untransformed(camera, w - 1, -h);
const [x4, y4] = Camera.transformed(camera, 0, h * cell); const [x3, y3] = Camera.untransformed(camera, w - 1, h - 1);
const [x4, y4] = Camera.untransformed(camera, -w, h - 1);
const res: Set<number> = new Set(); const res: Set<number> = new Set();
/** 一个纵坐标对应的所有横坐标,用于填充 */ /** 一个纵坐标对应的所有横坐标,用于填充 */
const xyMap: Map<number, number[]> = new Map(); const xyMap: Map<number, number[]> = 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], [x1, y1, x2, y2],
[x2, y2, x3, y3], [x2, y2, x3, y3],
@ -837,43 +617,26 @@ export class Layer extends Container implements IRenderDestroyable {
const dy = ty - fy; const dy = ty - fy;
const k = dy / dx; const k = dy / dx;
// console.log(fx, fy, tx, ty);
// 斜率无限的时候,竖直 // 斜率无限的时候,竖直
if (!isFinite(k)) { if (!isFinite(k)) {
const min = k < 0 ? ty : fy; const min = k < 0 ? ty : fy;
const max = k < 0 ? fy : ty; const max = k < 0 ? fy : ty;
const [x, y] = this.getBlockXYByLoc(fx, min); const [x, y] = this.getBlockXYByLoc(fx, min);
// 在地图左侧或右侧时将每个纵坐标对应的横坐标填充为0
const p = x < 0 ? 0 : x >= width ? width - 1 : x;
const [, ey] = this.getBlockXYByLoc(fx, max); const [, ey] = this.getBlockXYByLoc(fx, max);
for (let i = y; i <= ey; i++) { for (let i = y; i <= ey; i++) {
let arr = xyMap.get(i); pushXY(x, i);
if (!arr) {
arr = [];
xyMap.set(i, arr);
}
arr.push(p);
} }
// console.log(y, ey, p);
return; return;
} }
const [fbx, fby] = this.getBlockXYByLoc(fx, fy); const [fbx, fby] = this.getBlockXYByLoc(fx, fy);
// 当斜率为0时 // 当斜率为0时
if (glMatrix.equals(k, 0)) { if (glMatrix.equals(k, 0)) {
const [ex] = this.getBlockXYByLoc(tx, fy); const [ex] = this.getBlockXYByLoc(tx, fy);
let arr = xyMap.get(fby); pushXY(fby, fbx).push(ex);
if (!arr) {
arr = [];
xyMap.set(fby, arr);
}
arr.push(fbx, ex);
// console.log(fbx, ex);
return; return;
} }
@ -881,54 +644,52 @@ export class Layer extends Container implements IRenderDestroyable {
if (Math.abs(k) >= 1) { if (Math.abs(k) >= 1) {
// 斜率大于一y方向递增 // 斜率大于一y方向递增
const d = Math.sign(dy) * size; const d = Math.sign(dy) * size;
const f = dx > 0 ? fby * size : (fby + 1) * size; const f = fby * size;
const dir = dy > 0; const dir = dy > 0;
const ex = Math.floor(tx / size);
const ey = Math.floor(ty / size);
pushXY(ex, ey);
let now = f; let now = f;
let last = fbx; let last = fbx;
let ny = fby; let ny = fby;
do { do {
const bx = Math.floor(fx + (now - fy) / k); const bx = Math.floor((fx + (now - fy) / k) / size);
let arr = xyMap.get(ny); pushXY(bx, ny);
if (!arr) {
arr = [];
xyMap.set(ny, arr);
}
if (bx !== last) { if (bx !== last) {
arr.push(last); if (dir) pushXY(bx, ny - Math.sign(dy));
else pushXY(last, ny);
} }
arr.push(bx);
last = bx; last = bx;
ny++; ny += Math.sign(dy);
} while (dir ? (now += d) < ty : (now += d) > ty); now += d;
} while (dir ? ny <= ey : ny >= ey);
} else { } else {
// 斜率小于一x方向递增 // 斜率小于一x方向递增
const d = Math.sign(dx) * size; const d = Math.sign(dx) * size;
const f = dx > 0 ? fbx * size : (fbx + 1) * size; const f = fbx * size;
const dir = dx > 0; const dir = dx > 0;
const ex = Math.floor(tx / size);
const ey = Math.floor(ty / size);
pushXY(ex, ey);
let now = f; let now = f;
let last = fby; let last = fby;
let nx = fbx; let nx = fbx;
do { do {
const by = Math.floor(fy + k * (now - fx)); const by = Math.floor((fy + k * (now - fx)) / size);
if (by !== last) { if (by !== last) {
let arr = xyMap.get(last); if (dir) pushXY(nx - Math.sign(dx), by);
if (!arr) { else pushXY(nx, last);
arr = [];
xyMap.set(last, arr);
}
arr.push(nx);
} }
let arr = xyMap.get(nx); pushXY(nx, by);
if (!arr) { last = by;
arr = []; nx += Math.sign(dx);
xyMap.set(by, arr); now += d;
} } while (dir ? nx <= ex : nx >= ex);
arr.push(nx);
nx++;
} while (dir ? (now += d) < tx : (now += d) > tx);
} }
}); });
@ -939,7 +700,8 @@ export class Layer extends Container implements IRenderDestroyable {
if (x.length === 1) { if (x.length === 1) {
const index = y * bw + x[0]; 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; if (index < 0 || index >= bw * bh) return;
res.add(index); res.add(index);
} }
@ -949,17 +711,44 @@ export class Layer extends Container implements IRenderDestroyable {
for (let i = min; i <= max; i++) { for (let i = min; i <= max; i++) {
const index = y * bw + 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; if (index < 0 || index >= bw * bh) continue;
res.add(index); res.add(index);
} }
}); });
// console.log([...res], xyMap);
return { res, back }; 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.movingMap.clear();
this.backMap.clear(); this.backMap.clear();
if (this.needUpdateMoving) this.updateMovingRenderable();
this.renderBack(camera, need); this.renderBack(camera, need);
this.renderStatic(camera, need); this.renderStatic(camera, need);
this.renderMoving(camera); this.renderMoving(camera);
@ -981,10 +772,9 @@ export class Layer extends Container implements IRenderDestroyable {
protected renderBack(camera: Camera, need: NeedRenderData) { protected renderBack(camera: Camera, need: NeedRenderData) {
const cell = this.cellSize; const cell = this.cellSize;
const frame = (RenderItem.animatedFrame % 4) + 1; const frame = (RenderItem.animatedFrame % 4) + 1;
const { width } = this.blockData;
const blockSize = this.blockSize; const blockSize = this.blockSize;
const { back } = need; const { back } = need;
const { ctx, canvas } = this.backMap; const { ctx } = this.backMap;
const mat = camera.mat; const mat = camera.mat;
const a = mat[0]; const a = mat[0];
@ -1020,16 +810,14 @@ export class Layer extends Container implements IRenderDestroyable {
*/ */
protected renderStatic(camera: Camera, need: NeedRenderData) { protected renderStatic(camera: Camera, need: NeedRenderData) {
const cell = this.cellSize; const cell = this.cellSize;
const renderable = this.renderable;
const frame = (RenderItem.animatedFrame % 4) + 1; const frame = (RenderItem.animatedFrame % 4) + 1;
const { width } = this.blockData; const { width } = this.blockData;
const blockSize = this.blockSize; const blockSize = this.blockSize;
const { ctx, canvas } = this.staticMap; const { ctx } = this.staticMap;
ctx.save(); ctx.save();
const { res: render } = need; const { res: render } = need;
// console.log(render);
const mat = camera.mat; const mat = camera.mat;
const a = mat[0]; const a = mat[0];
const b = mat[1]; const b = mat[1];
@ -1061,8 +849,8 @@ export class Layer extends Container implements IRenderDestroyable {
return; return;
} }
const ex = sx + blockSize; const ex = Math.min(sx + blockSize, this.mapWidth);
const ey = sy + blockSize; const ey = Math.min(sy + blockSize, this.mapHeight);
const temp = new MotaOffscreenCanvas2D(); const temp = new MotaOffscreenCanvas2D();
temp.setAntiAliasing(false); temp.setAntiAliasing(false);
@ -1074,17 +862,28 @@ export class Layer extends Container implements IRenderDestroyable {
for (let nx = sx; nx < ex; nx++) { for (let nx = sx; nx < ex; nx++) {
for (let ny = sy; ny < ey; ny++) { for (let ny = sy; ny < ey; ny++) {
const blockIndex = nx + ny * this.mapWidth; const blockIndex = nx + ny * this.mapWidth;
const data = renderable.get(blockIndex); const num = this.renderData[blockIndex];
if (!data) continue; if (num === 0 || num === 17) continue;
const data = texture.getRenderable(num);
if (!data || data.bigImage) continue;
const f = frame % data.frame; const f = frame % data.frame;
const i = frame === 4 && data.frame === 3 ? 1 : f; const i =
const [sx, sy, w, h] = data.render[i]; data.animate === -1
const px = nx * cell; ? frame === 4 && data.frame === 3
const py = ny * cell; ? 1
const image = data.image; : f
: data.animate;
temp.ctx.drawImage(image, sx, sy, w, h, px, py, w, h); 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( ctx.drawImage(
@ -1126,10 +925,6 @@ export class Layer extends Container implements IRenderDestroyable {
const r = const r =
Math.max(a, b, c, d) ** 2 * Math.max(core._PX_, core._PY_) * 2; 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 => { this.movingRenderable.forEach(v => {
const { x, y, image, frame: blockFrame, render } = v; const { x, y, image, frame: blockFrame, render } = v;
const f = frame % 4; const f = frame % 4;

View File

@ -149,11 +149,11 @@ Mota.require('var', 'hook').once('reset', () => {
const camera = render.camera; const camera = render.camera;
render.mount(); render.mount();
layer.zIndex = 2; layer.setZIndex(2);
bgLayer.zIndex = 1; bgLayer.setZIndex(1);
render.appendChild([layer, bgLayer]); render.appendChild([layer, bgLayer]);
layer.bindThis('event', true); layer.bindThis('event');
bgLayer.bindThis('bg', true); bgLayer.bindThis('bg');
bgLayer.setBackground(650); bgLayer.setBackground(650);
const ani = new Animation(); const ani = new Animation();
@ -161,17 +161,35 @@ Mota.require('var', 'hook').once('reset', () => {
ani.ticker.add(() => { ani.ticker.add(() => {
camera.reset(); camera.reset();
camera.rotate((ani.angle / 180) * Math.PI); camera.rotate((ani.angle / 180) * Math.PI);
camera.move(240, 240); camera.move(ani.x, ani.y);
camera.scale(ani.size);
render.update(render); 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(() => { 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(() => { sleep(100).then(() => {
ani.time(3000).rotate(0); ani.time(3000).rotate(0);
}); });
sleep(3100).then(() => { 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) // ani.mode(shake2(5, hyper('sin', 'in-out')), true)
// .time(5000) // .time(5000)

View File

@ -25,6 +25,10 @@ export class Sprite extends RenderItem implements ICanvasCachedRenderItem {
camera: Camera camera: Camera
): void { ): void {
this.emit('beforeRender'); this.emit('beforeRender');
if (this.needUpdate) {
this.cache(this.writing);
this.needUpdate = false;
}
withCacheRender(this, canvas, ctx, camera, canvas => { withCacheRender(this, canvas, ctx, camera, canvas => {
this.renderFn(canvas, camera); this.renderFn(canvas, camera);
}); });

2
src/types/core.d.ts vendored
View File

@ -1419,6 +1419,8 @@ interface MapDataOf<T extends keyof NumberToId> {
bigImage?: ImageIds; bigImage?: ImageIds;
faceIds?: Record<Dir, AllIds>; faceIds?: Record<Dir, AllIds>;
animate?: number;
} }
/** /**