mirror of
https://github.com/unanmed/HumanBreak.git
synced 2025-04-25 00:23:25 +08:00
feat: 地图渲染大致完成
This commit is contained in:
parent
2316bc4cad
commit
117bd94928
@ -6,6 +6,7 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "ts-node-esm script/dev.ts",
|
"dev": "ts-node-esm script/dev.ts",
|
||||||
"build": "vue-tsc && vite build && ts-node-esm script/build.ts dist",
|
"build": "vue-tsc && vite build && ts-node-esm script/build.ts dist",
|
||||||
|
"build-local": "vue-tsc && vite build && ts-node-esm script/build.ts local",
|
||||||
"preview": "vite preview",
|
"preview": "vite preview",
|
||||||
"update": "ts-node-esm script/update.ts",
|
"update": "ts-node-esm script/update.ts",
|
||||||
"declare": "ts-node-esm script/declare.ts",
|
"declare": "ts-node-esm script/declare.ts",
|
||||||
|
@ -802,19 +802,19 @@ control.prototype.setHeroMoveInterval = function (callback) {
|
|||||||
if (core.status.replay.speed > 6) toAdd = 4;
|
if (core.status.replay.speed > 6) toAdd = 4;
|
||||||
if (core.status.replay.speed > 12) toAdd = 8;
|
if (core.status.replay.speed > 12) toAdd = 8;
|
||||||
|
|
||||||
Mota.r(() => {
|
// Mota.r(() => {
|
||||||
const render = Mota.require('module', 'Render').heroRender;
|
// const render = Mota.require('module', 'Render').heroRender;
|
||||||
render.move(true);
|
// render.move(true);
|
||||||
});
|
// });
|
||||||
|
|
||||||
core.interval.heroMoveInterval = window.setInterval(function () {
|
core.interval.heroMoveInterval = window.setInterval(function () {
|
||||||
render.offset += toAdd * 4;
|
// render.offset += toAdd * 4;
|
||||||
core.status.heroMoving += toAdd;
|
core.status.heroMoving += toAdd;
|
||||||
if (core.status.heroMoving >= 8) {
|
if (core.status.heroMoving >= 8) {
|
||||||
clearInterval(core.interval.heroMoveInterval);
|
clearInterval(core.interval.heroMoveInterval);
|
||||||
core.status.heroMoving = 0;
|
core.status.heroMoving = 0;
|
||||||
render.offset = 0;
|
// render.offset = 0;
|
||||||
render.move(false);
|
// render.move(false);
|
||||||
if (callback) callback();
|
if (callback) callback();
|
||||||
}
|
}
|
||||||
}, ((core.values.moveSpeed / 8) * toAdd) / core.status.replay.speed);
|
}, ((core.values.moveSpeed / 8) * toAdd) / core.status.replay.speed);
|
||||||
@ -1014,29 +1014,19 @@ control.prototype.tryMoveDirectly = function (destX, destY) {
|
|||||||
control.prototype.drawHero = function (status, offset = 0, frame) {
|
control.prototype.drawHero = function (status, offset = 0, frame) {
|
||||||
if (!core.isPlaying() || !core.status.floorId || core.status.gameOver)
|
if (!core.isPlaying() || !core.status.floorId || core.status.gameOver)
|
||||||
return;
|
return;
|
||||||
if (main.mode === 'play') {
|
|
||||||
Mota.require('module', 'Render').heroRender.draw();
|
|
||||||
if (!core.hasFlag('__lockViewport__')) {
|
|
||||||
const { x, y, direction } = core.status.hero.loc;
|
|
||||||
var way = core.utils.scan2[direction];
|
|
||||||
var dx = way.x,
|
|
||||||
dy = way.y;
|
|
||||||
var offsetX =
|
|
||||||
typeof offset == 'number' ? dx * offset : offset.x || 0;
|
|
||||||
var offsetY =
|
|
||||||
typeof offset == 'number' ? dy * offset : offset.y || 0;
|
|
||||||
offset = { x: offsetX, y: offsetY, offset: offset };
|
|
||||||
this._drawHero_updateViewport(x, y, offset);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var x = core.getHeroLoc('x'),
|
var x = core.getHeroLoc('x'),
|
||||||
y = core.getHeroLoc('y'),
|
y = core.getHeroLoc('y'),
|
||||||
direction = core.getHeroLoc('direction');
|
direction = core.getHeroLoc('direction');
|
||||||
status = status || 'stop';
|
status = status || 'stop';
|
||||||
if (!offset) offset = 0;
|
if (!offset) offset = 0;
|
||||||
|
|
||||||
|
var way = core.utils.scan2[direction];
|
||||||
|
var dx = way.x,
|
||||||
|
dy = way.y;
|
||||||
|
var offsetX = typeof offset == 'number' ? dx * offset : offset.x || 0;
|
||||||
|
var offsetY = typeof offset == 'number' ? dy * offset : offset.y || 0;
|
||||||
|
offset = { x: offsetX, y: offsetY, offset: offset };
|
||||||
|
|
||||||
core.clearAutomaticRouteNode(x + dx, y + dy);
|
core.clearAutomaticRouteNode(x + dx, y + dy);
|
||||||
core.clearMap('hero');
|
core.clearMap('hero');
|
||||||
core.status.heroCenter.px = 32 * x + offsetX + 16;
|
core.status.heroCenter.px = 32 * x + offsetX + 16;
|
||||||
@ -1048,6 +1038,9 @@ control.prototype.drawHero = function (status, offset = 0, frame) {
|
|||||||
delete core.canvas.hero._px;
|
delete core.canvas.hero._px;
|
||||||
delete core.canvas.hero._py;
|
delete core.canvas.hero._py;
|
||||||
core.status.preview.enabled = false;
|
core.status.preview.enabled = false;
|
||||||
|
if (!core.hasFlag('__lockViewport__')) {
|
||||||
|
this._drawHero_updateViewport(x, y, offset);
|
||||||
|
}
|
||||||
|
|
||||||
this._drawHero_draw(direction, x, y, status, offset, frame);
|
this._drawHero_draw(direction, x, y, status, offset, frame);
|
||||||
};
|
};
|
||||||
|
@ -3291,6 +3291,14 @@ maps.prototype.setBlock = function (number, x, y, floorId, noredraw) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Mota.require('var', 'hook').emit(
|
||||||
|
'setBlock',
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
floorId,
|
||||||
|
originBlock?.id ?? 0,
|
||||||
|
number
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
maps.prototype.animateSetBlock = function (
|
maps.prototype.animateSetBlock = function (
|
||||||
|
@ -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]},
|
"evilBat": {"name":"邪恶蝙蝠","hp":1000,"atk":800,"def":350,"money":1,"exp":40,"point":0,"special":[2],"bigImage":"bear.png"},
|
||||||
"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":[]},
|
||||||
|
@ -1,31 +1,31 @@
|
|||||||
main.floors.MT76=
|
main.floors.MT76=
|
||||||
{
|
{
|
||||||
"floorId": "MT76",
|
"floorId": "MT76",
|
||||||
"title": "苍蓝之殿-左上",
|
"title": "苍蓝之殿-左上",
|
||||||
"name": "76",
|
"name": "76",
|
||||||
"width": 15,
|
"width": 15,
|
||||||
"height": 15,
|
"height": 15,
|
||||||
"canFlyTo": true,
|
"canFlyTo": true,
|
||||||
"canFlyFrom": true,
|
"canFlyFrom": true,
|
||||||
"canUseQuickShop": true,
|
"canUseQuickShop": true,
|
||||||
"cannotViewMap": false,
|
"cannotViewMap": false,
|
||||||
"images": [],
|
"images": [],
|
||||||
"ratio": 8,
|
"ratio": 8,
|
||||||
"defaultGround": "T650",
|
"defaultGround": "T650",
|
||||||
"bgm": "palaceNorth.mp3",
|
"bgm": "palaceNorth.mp3",
|
||||||
"firstArrive": [],
|
"firstArrive": [],
|
||||||
"eachArrive": [],
|
"eachArrive": [],
|
||||||
"parallelDo": "",
|
"parallelDo": "",
|
||||||
"events": {},
|
"events": {},
|
||||||
"changeFloor": {},
|
"changeFloor": {},
|
||||||
"beforeBattle": {},
|
"beforeBattle": {},
|
||||||
"afterBattle": {},
|
"afterBattle": {},
|
||||||
"afterGetItem": {},
|
"afterGetItem": {},
|
||||||
"afterOpenDoor": {},
|
"afterOpenDoor": {},
|
||||||
"autoEvent": {},
|
"autoEvent": {},
|
||||||
"cannotMove": {},
|
"cannotMove": {},
|
||||||
"cannotMoveIn": {},
|
"cannotMoveIn": {},
|
||||||
"map": [
|
"map": [
|
||||||
[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||||
[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||||
[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||||
@ -42,4 +42,16 @@ main.floors.MT76=
|
|||||||
[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||||
[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
|
[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
|
||||||
],
|
],
|
||||||
|
"bgmap": [
|
||||||
|
|
||||||
|
],
|
||||||
|
"fgmap": [
|
||||||
|
|
||||||
|
],
|
||||||
|
"bg2map": [
|
||||||
|
|
||||||
|
],
|
||||||
|
"fg2map": [
|
||||||
|
|
||||||
|
]
|
||||||
}
|
}
|
@ -1,31 +1,31 @@
|
|||||||
main.floors.MT81=
|
main.floors.MT81=
|
||||||
{
|
{
|
||||||
"floorId": "MT81",
|
"floorId": "MT81",
|
||||||
"title": "苍蓝之殿-左上",
|
"title": "苍蓝之殿-左上",
|
||||||
"name": "81",
|
"name": "81",
|
||||||
"width": 15,
|
"width": 15,
|
||||||
"height": 15,
|
"height": 15,
|
||||||
"canFlyTo": true,
|
"canFlyTo": true,
|
||||||
"canFlyFrom": true,
|
"canFlyFrom": true,
|
||||||
"canUseQuickShop": true,
|
"canUseQuickShop": true,
|
||||||
"cannotViewMap": false,
|
"cannotViewMap": false,
|
||||||
"images": [],
|
"images": [],
|
||||||
"ratio": 8,
|
"ratio": 8,
|
||||||
"defaultGround": "T650",
|
"defaultGround": "T650",
|
||||||
"bgm": "palaceNorth.mp3",
|
"bgm": "palaceNorth.mp3",
|
||||||
"firstArrive": [],
|
"firstArrive": [],
|
||||||
"eachArrive": [],
|
"eachArrive": [],
|
||||||
"parallelDo": "",
|
"parallelDo": "",
|
||||||
"events": {},
|
"events": {},
|
||||||
"changeFloor": {},
|
"changeFloor": {},
|
||||||
"beforeBattle": {},
|
"beforeBattle": {},
|
||||||
"afterBattle": {},
|
"afterBattle": {},
|
||||||
"afterGetItem": {},
|
"afterGetItem": {},
|
||||||
"afterOpenDoor": {},
|
"afterOpenDoor": {},
|
||||||
"autoEvent": {},
|
"autoEvent": {},
|
||||||
"cannotMove": {},
|
"cannotMove": {},
|
||||||
"cannotMoveIn": {},
|
"cannotMoveIn": {},
|
||||||
"map": [
|
"map": [
|
||||||
[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||||
[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||||
[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||||
@ -42,4 +42,16 @@ main.floors.MT81=
|
|||||||
[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||||
[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
|
[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
|
||||||
],
|
],
|
||||||
|
"bgmap": [
|
||||||
|
|
||||||
|
],
|
||||||
|
"fgmap": [
|
||||||
|
|
||||||
|
],
|
||||||
|
"bg2map": [
|
||||||
|
|
||||||
|
],
|
||||||
|
"fg2map": [
|
||||||
|
|
||||||
|
]
|
||||||
}
|
}
|
@ -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":"铁门"},
|
"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"},
|
||||||
"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},
|
||||||
|
@ -392,7 +392,7 @@ p#name {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#hero {
|
#hero {
|
||||||
display: none;
|
/* display: none; */
|
||||||
z-index: 40;
|
z-index: 40;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -604,7 +604,7 @@ async function ensureConfig() {
|
|||||||
// 1. 启动vite服务
|
// 1. 启动vite服务
|
||||||
const vite = await createServer();
|
const vite = await createServer();
|
||||||
await vite.listen(5173);
|
await vite.listen(5173);
|
||||||
console.log(`游戏地址:http://localhost:5173/games/${config.name}/`);
|
console.log(`游戏地址:http://localhost:5173/`);
|
||||||
|
|
||||||
// 2. 启动样板http服务
|
// 2. 启动样板http服务
|
||||||
await ensureConfig();
|
await ensureConfig();
|
||||||
|
@ -3,6 +3,7 @@ import { EmitableEvent, EventEmitter } from '../common/eventEmitter';
|
|||||||
import { CSSObj } from '../interface';
|
import { CSSObj } from '../interface';
|
||||||
|
|
||||||
interface OffscreenCanvasEvent extends EmitableEvent {
|
interface OffscreenCanvasEvent extends EmitableEvent {
|
||||||
|
/** 当被动触发resize时(例如core.domStyle.scale变化、窗口大小变化)时触发,使用size函数并不会触发 */
|
||||||
resize: () => void;
|
resize: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -19,6 +20,8 @@ export class MotaOffscreenCanvas2D extends EventEmitter<OffscreenCanvasEvent> {
|
|||||||
autoScale: boolean = false;
|
autoScale: boolean = false;
|
||||||
/** 是否是高清画布 */
|
/** 是否是高清画布 */
|
||||||
highResolution: boolean = true;
|
highResolution: boolean = true;
|
||||||
|
/** 是否启用抗锯齿 */
|
||||||
|
antiAliasing: boolean = true;
|
||||||
|
|
||||||
scale: number = 1;
|
scale: number = 1;
|
||||||
|
|
||||||
@ -50,6 +53,7 @@ export class MotaOffscreenCanvas2D extends EventEmitter<OffscreenCanvasEvent> {
|
|||||||
this.height = height;
|
this.height = height;
|
||||||
this.ctx.setTransform(1, 0, 0, 1, 0, 0);
|
this.ctx.setTransform(1, 0, 0, 1, 0, 0);
|
||||||
this.ctx.scale(ratio, ratio);
|
this.ctx.scale(ratio, ratio);
|
||||||
|
this.ctx.imageSmoothingEnabled = this.antiAliasing;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -68,6 +72,24 @@ export class MotaOffscreenCanvas2D extends EventEmitter<OffscreenCanvasEvent> {
|
|||||||
this.size(this.width, this.height);
|
this.size(this.width, this.height);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置当前画布的抗锯齿设置
|
||||||
|
*/
|
||||||
|
setAntiAliasing(anti: boolean) {
|
||||||
|
this.antiAliasing = anti;
|
||||||
|
this.ctx.imageSmoothingEnabled = anti;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 清空画布
|
||||||
|
*/
|
||||||
|
clear() {
|
||||||
|
this.ctx.save();
|
||||||
|
this.ctx.setTransform(1, 0, 0, 1, 0, 0);
|
||||||
|
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
|
||||||
|
this.ctx.restore();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 删除这个画布
|
* 删除这个画布
|
||||||
*/
|
*/
|
||||||
|
@ -69,6 +69,7 @@ import { Sprite } from './render/sprite';
|
|||||||
import { Camera } from './render/camera';
|
import { Camera } from './render/camera';
|
||||||
import { Image, Text } from './render/preset/misc';
|
import { Image, Text } from './render/preset/misc';
|
||||||
import { RenderItem } from './render/item';
|
import { RenderItem } from './render/item';
|
||||||
|
import { texture } from './render/cache';
|
||||||
|
|
||||||
// ----- 类注册
|
// ----- 类注册
|
||||||
Mota.register('class', 'AudioPlayer', AudioPlayer);
|
Mota.register('class', 'AudioPlayer', AudioPlayer);
|
||||||
@ -148,6 +149,7 @@ Mota.register('module', 'Effect', {
|
|||||||
});
|
});
|
||||||
Mota.register('module', 'Render', {
|
Mota.register('module', 'Render', {
|
||||||
heroRender,
|
heroRender,
|
||||||
|
texture,
|
||||||
MotaRenderer,
|
MotaRenderer,
|
||||||
Container,
|
Container,
|
||||||
Sprite,
|
Sprite,
|
||||||
|
294
src/core/render/cache.ts
Normal file
294
src/core/render/cache.ts
Normal file
@ -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;
|
scaleY: number = 1;
|
||||||
rad: number = 0;
|
rad: number = 0;
|
||||||
|
|
||||||
|
private saveStack: number[][] = [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 重设摄像机的所有参数
|
* 重设摄像机的所有参数
|
||||||
*/
|
*/
|
||||||
@ -140,6 +142,23 @@ export class Camera extends EventEmitter<CameraEvent> {
|
|||||||
this.rad = rad;
|
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 摄像机
|
* @param camera 摄像机
|
||||||
|
@ -25,14 +25,19 @@ export class Container extends RenderItem implements ICanvasCachedRenderItem {
|
|||||||
ctx: CanvasRenderingContext2D,
|
ctx: CanvasRenderingContext2D,
|
||||||
camera: Camera
|
camera: Camera
|
||||||
): void {
|
): void {
|
||||||
this.emit('beforeUpdate', this);
|
this.emit('beforeRender');
|
||||||
withCacheRender(this, canvas, ctx, camera, c => {
|
withCacheRender(this, canvas, ctx, camera, c => {
|
||||||
this.sortedChildren.forEach(v => {
|
this.sortedChildren.forEach(v => {
|
||||||
|
if (!v.antiAliasing) {
|
||||||
|
ctx.imageSmoothingEnabled = false;
|
||||||
|
} else {
|
||||||
|
ctx.imageSmoothingEnabled = true;
|
||||||
|
}
|
||||||
v.render(c.canvas, c.ctx, camera);
|
v.render(c.canvas, c.ctx, camera);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
this.writing = void 0;
|
this.writing = void 0;
|
||||||
this.emit('afterUpdate', this);
|
this.emit('afterRender');
|
||||||
}
|
}
|
||||||
|
|
||||||
size(width: number, height: number) {
|
size(width: number, height: number) {
|
||||||
@ -55,8 +60,24 @@ export class Container extends RenderItem implements ICanvasCachedRenderItem {
|
|||||||
appendChild(children: RenderItem[]) {
|
appendChild(children: RenderItem[]) {
|
||||||
children.forEach(v => (v.parent = this));
|
children.forEach(v => (v.parent = this));
|
||||||
this.children.push(...children);
|
this.children.push(...children);
|
||||||
|
this.sortChildren();
|
||||||
|
}
|
||||||
|
|
||||||
|
sortChildren() {
|
||||||
this.sortedChildren = this.children
|
this.sortedChildren = this.children
|
||||||
.slice()
|
.slice()
|
||||||
.sort((a, b) => a.zIndex - b.zIndex);
|
.sort((a, b) => a.zIndex - b.zIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setHD(hd: boolean): void {
|
||||||
|
this.highResolution = hd;
|
||||||
|
this.canvas.setHD(hd);
|
||||||
|
this.update(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
setAntiAliasing(anti: boolean): void {
|
||||||
|
this.antiAliasing = anti;
|
||||||
|
this.canvas.setAntiAliasing(anti);
|
||||||
|
this.update(this);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -101,6 +101,7 @@ export class HeroRenderer extends EventEmitter<HeroRendererEvent> {
|
|||||||
*/
|
*/
|
||||||
draw() {
|
draw() {
|
||||||
if (!core.isPlaying()) return;
|
if (!core.isPlaying()) return;
|
||||||
|
return;
|
||||||
const { ctx, canvas: can } = canvas;
|
const { ctx, canvas: can } = canvas;
|
||||||
const { x, y, direction: dir } = core.status.hero.loc;
|
const { x, y, direction: dir } = core.status.hero.loc;
|
||||||
ctx.clearRect(0, 0, can.width, can.height);
|
ctx.clearRect(0, 0, can.width, can.height);
|
||||||
|
@ -2,6 +2,8 @@ import { isNil } from 'lodash-es';
|
|||||||
import { EmitableEvent, EventEmitter } from '../common/eventEmitter';
|
import { EmitableEvent, EventEmitter } from '../common/eventEmitter';
|
||||||
import { MotaOffscreenCanvas2D } from '../fx/canvas2d';
|
import { MotaOffscreenCanvas2D } from '../fx/canvas2d';
|
||||||
import { Camera } from './camera';
|
import { Camera } from './camera';
|
||||||
|
import { Ticker } from 'mutate-animate';
|
||||||
|
import type { Container } from './container';
|
||||||
|
|
||||||
export type RenderFunction = (
|
export type RenderFunction = (
|
||||||
canvas: MotaOffscreenCanvas2D,
|
canvas: MotaOffscreenCanvas2D,
|
||||||
@ -62,15 +64,48 @@ interface IRenderAnchor {
|
|||||||
setAnchor(x: number, y: number): void;
|
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 {
|
interface RenderItemEvent extends EmitableEvent {
|
||||||
beforeUpdate: (item?: RenderItem) => void;
|
beforeUpdate: (item?: RenderItem) => void;
|
||||||
afterUpdate: (item?: RenderItem) => void;
|
afterUpdate: (item?: RenderItem) => void;
|
||||||
|
beforeRender: () => void;
|
||||||
|
afterRender: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export abstract class RenderItem
|
export abstract class RenderItem
|
||||||
extends EventEmitter<RenderItemEvent>
|
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;
|
zIndex: number = 0;
|
||||||
|
|
||||||
x: number = 0;
|
x: number = 0;
|
||||||
@ -88,9 +123,15 @@ export abstract class RenderItem
|
|||||||
|
|
||||||
/** 渲染模式,absolute表示绝对位置,static表示跟随摄像机移动,只对顶层元素有效 */
|
/** 渲染模式,absolute表示绝对位置,static表示跟随摄像机移动,只对顶层元素有效 */
|
||||||
type: 'absolute' | 'static' = 'static';
|
type: 'absolute' | 'static' = 'static';
|
||||||
|
/** 是否是高清画布 */
|
||||||
|
highResolution: boolean = true;
|
||||||
|
/** 是否抗锯齿 */
|
||||||
|
antiAliasing: boolean = true;
|
||||||
|
|
||||||
parent?: RenderItem;
|
parent?: RenderItem;
|
||||||
|
|
||||||
|
protected needUpdate: boolean = false;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
@ -151,11 +192,52 @@ export abstract class RenderItem
|
|||||||
}
|
}
|
||||||
|
|
||||||
update(item?: RenderItem): void {
|
update(item?: RenderItem): void {
|
||||||
this.cache(this.writing);
|
if (this.needUpdate) return;
|
||||||
this.parent?.update(item);
|
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(
|
export function withCacheRender(
|
||||||
item: RenderItem & ICanvasCachedRenderItem,
|
item: RenderItem & ICanvasCachedRenderItem,
|
||||||
canvas: HTMLCanvasElement,
|
canvas: HTMLCanvasElement,
|
||||||
|
1229
src/core/render/preset/layer.ts
Normal file
1229
src/core/render/preset/layer.ts
Normal file
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,
|
CanvasImageSource,
|
||||||
VideoFrame | SVGElement
|
VideoFrame | SVGElement
|
||||||
>;
|
>;
|
||||||
|
@ -1,34 +1,41 @@
|
|||||||
import { isNil } from 'lodash-es';
|
import { Animation, hyper, sleep } from 'mutate-animate';
|
||||||
import { MotaCanvas2D, MotaOffscreenCanvas2D } from '../fx/canvas2d';
|
import { MotaCanvas2D, MotaOffscreenCanvas2D } from '../fx/canvas2d';
|
||||||
import { Camera } from './camera';
|
import { Camera } from './camera';
|
||||||
import { Container } from './container';
|
import { Container } from './container';
|
||||||
import { RenderItem, withCacheRender } from './item';
|
import { IRenderDestroyable, RenderItem, withCacheRender } from './item';
|
||||||
import { Image, Text } from './preset/misc';
|
import { Layer } from './preset/layer';
|
||||||
import { Animation, hyper } from 'mutate-animate';
|
|
||||||
|
export class MotaRenderer extends Container implements IRenderDestroyable {
|
||||||
|
static list: Set<MotaRenderer> = new Set();
|
||||||
|
|
||||||
export class MotaRenderer extends Container {
|
|
||||||
canvas: MotaOffscreenCanvas2D;
|
canvas: MotaOffscreenCanvas2D;
|
||||||
camera: Camera;
|
camera: Camera;
|
||||||
|
|
||||||
/** 摄像机缓存,如果是需要快速切换摄像机的场景,使用缓存可以大幅提升性能表现 */
|
/** 摄像机缓存,如果是需要快速切换摄像机的场景,使用缓存可以大幅提升性能表现 */
|
||||||
cameraCache: Map<Camera, MotaOffscreenCanvas2D> = new Map();
|
cameraCache: Map<Camera, MotaOffscreenCanvas2D> = new Map();
|
||||||
target: MotaCanvas2D;
|
target: MotaCanvas2D;
|
||||||
|
/** 这个渲染对象的id */
|
||||||
|
id: string;
|
||||||
|
|
||||||
private needUpdate: boolean = false;
|
protected needUpdate: boolean = false;
|
||||||
|
|
||||||
constructor() {
|
constructor(id: string = 'render-main') {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
|
this.id = id;
|
||||||
|
|
||||||
this.canvas = new MotaOffscreenCanvas2D();
|
this.canvas = new MotaOffscreenCanvas2D();
|
||||||
this.camera = new Camera();
|
this.camera = new Camera();
|
||||||
this.target = new MotaCanvas2D(`render-main`);
|
this.target = new MotaCanvas2D(id);
|
||||||
this.width = 480;
|
this.width = core._PX_;
|
||||||
this.height = 480;
|
this.height = core._PY_;
|
||||||
this.target.withGameScale(true);
|
this.target.withGameScale(true);
|
||||||
this.target.size(480, 480);
|
this.target.size(core._PX_, core._PY_);
|
||||||
this.canvas.withGameScale(true);
|
this.canvas.withGameScale(true);
|
||||||
this.canvas.size(480, 480);
|
this.canvas.size(core._PX_, core._PY_);
|
||||||
this.target.css(`z-index: 100`);
|
this.target.css(`z-index: 100`);
|
||||||
|
|
||||||
|
MotaRenderer.list.add(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -60,6 +67,7 @@ export class MotaRenderer extends Container {
|
|||||||
render() {
|
render() {
|
||||||
const { canvas, ctx } = this.target;
|
const { canvas, ctx } = this.target;
|
||||||
const camera = this.camera;
|
const camera = this.camera;
|
||||||
|
this.emit('beforeRender');
|
||||||
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||||
withCacheRender(this, canvas, ctx, camera, canvas => {
|
withCacheRender(this, canvas, ctx, camera, canvas => {
|
||||||
const { canvas: ca, ctx: ct, scale } = canvas;
|
const { canvas: ca, ctx: ct, scale } = canvas;
|
||||||
@ -72,15 +80,21 @@ export class MotaRenderer extends Container {
|
|||||||
const f = mat[7] * scale;
|
const f = mat[7] * scale;
|
||||||
this.sortedChildren.forEach(v => {
|
this.sortedChildren.forEach(v => {
|
||||||
if (v.type === 'absolute') {
|
if (v.type === 'absolute') {
|
||||||
ct.transform(scale, 0, 0, scale, 0, 0);
|
ct.setTransform(scale, 0, 0, scale, 0, 0);
|
||||||
} else {
|
} else {
|
||||||
ct.setTransform(1, 0, 0, 1, 0, 0);
|
ct.setTransform(1, 0, 0, 1, 0, 0);
|
||||||
ct.translate(ca.width / 2, ca.height / 2);
|
ct.translate(ca.width / 2, ca.height / 2);
|
||||||
ct.transform(a, b, c, d, e, f);
|
ct.transform(a, b, c, d, e, f);
|
||||||
}
|
}
|
||||||
|
if (!v.antiAliasing) {
|
||||||
|
ctx.imageSmoothingEnabled = false;
|
||||||
|
} else {
|
||||||
|
ctx.imageSmoothingEnabled = true;
|
||||||
|
}
|
||||||
v.render(ca, ct, camera);
|
v.render(ca, ct, camera);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
this.emit('afterRender');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -92,12 +106,16 @@ export class MotaRenderer extends Container {
|
|||||||
requestAnimationFrame(() => {
|
requestAnimationFrame(() => {
|
||||||
this.cache(this.writing);
|
this.cache(this.writing);
|
||||||
this.needUpdate = false;
|
this.needUpdate = false;
|
||||||
this.emit('beforeUpdate', item);
|
this.refresh(item);
|
||||||
this.render();
|
|
||||||
this.emit('afterUpdate', item);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected refresh(item?: RenderItem): void {
|
||||||
|
this.emit('beforeUpdate', item);
|
||||||
|
this.render();
|
||||||
|
this.emit('afterUpdate', item);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 将缓存内容渲染至画面
|
* 将缓存内容渲染至画面
|
||||||
* @param cache 渲染缓存,是一个离屏Canvas2D对象
|
* @param cache 渲染缓存,是一个离屏Canvas2D对象
|
||||||
@ -114,44 +132,51 @@ export class MotaRenderer extends Container {
|
|||||||
mount() {
|
mount() {
|
||||||
this.target.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', () => {
|
Mota.require('var', 'hook').once('reset', () => {
|
||||||
const render = new MotaRenderer();
|
const render = new MotaRenderer();
|
||||||
const con = new Container('static');
|
const layer = new Layer();
|
||||||
|
const bgLayer = new Layer();
|
||||||
const camera = render.camera;
|
const camera = render.camera;
|
||||||
render.mount();
|
render.mount();
|
||||||
|
|
||||||
const testText = new Text();
|
layer.zIndex = 2;
|
||||||
testText.setText('测试测试');
|
bgLayer.zIndex = 1;
|
||||||
testText.pos(240, 240);
|
render.appendChild([layer, bgLayer]);
|
||||||
testText.setFont('32px normal');
|
layer.bindThis('event', true);
|
||||||
testText.setStyle('#fff');
|
bgLayer.bindThis('bg', true);
|
||||||
con.size(480, 480);
|
bgLayer.setBackground(650);
|
||||||
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);
|
|
||||||
|
|
||||||
const ani = new Animation();
|
const ani = new Animation();
|
||||||
ani.mode(hyper('sin', 'in-out'))
|
|
||||||
.time(10000)
|
|
||||||
.absolute()
|
|
||||||
.rotate(360)
|
|
||||||
.scale(0.7)
|
|
||||||
.move(100, 100);
|
|
||||||
ani.ticker.add(() => {
|
ani.ticker.add(() => {
|
||||||
render.cache('@default');
|
|
||||||
camera.reset();
|
camera.reset();
|
||||||
camera.move(ani.x, ani.y);
|
|
||||||
camera.scale(ani.size);
|
|
||||||
camera.rotate((ani.angle / 180) * Math.PI);
|
camera.rotate((ani.angle / 180) * Math.PI);
|
||||||
|
camera.move(240, 240);
|
||||||
render.update(render);
|
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 { Camera } from './camera';
|
||||||
import { RenderFunction, RenderItem, withCacheRender } from './item';
|
import {
|
||||||
|
ICanvasCachedRenderItem,
|
||||||
|
RenderFunction,
|
||||||
|
RenderItem,
|
||||||
|
withCacheRender
|
||||||
|
} from './item';
|
||||||
import { MotaOffscreenCanvas2D } from '../fx/canvas2d';
|
import { MotaOffscreenCanvas2D } from '../fx/canvas2d';
|
||||||
|
|
||||||
export class Sprite extends RenderItem {
|
export class Sprite extends RenderItem implements ICanvasCachedRenderItem {
|
||||||
renderFn: RenderFunction;
|
renderFn: RenderFunction;
|
||||||
|
|
||||||
canvas: MotaOffscreenCanvas2D;
|
canvas: MotaOffscreenCanvas2D;
|
||||||
@ -19,12 +24,12 @@ export class Sprite extends RenderItem {
|
|||||||
ctx: CanvasRenderingContext2D,
|
ctx: CanvasRenderingContext2D,
|
||||||
camera: Camera
|
camera: Camera
|
||||||
): void {
|
): void {
|
||||||
this.emit('beforeUpdate', this);
|
this.emit('beforeRender');
|
||||||
withCacheRender(this, canvas, ctx, camera, canvas => {
|
withCacheRender(this, canvas, ctx, camera, canvas => {
|
||||||
this.renderFn(canvas, camera);
|
this.renderFn(canvas, camera);
|
||||||
});
|
});
|
||||||
this.writing = void 0;
|
this.writing = void 0;
|
||||||
this.emit('afterUpdate', this);
|
this.emit('afterRender');
|
||||||
}
|
}
|
||||||
|
|
||||||
size(width: number, height: number) {
|
size(width: number, height: number) {
|
||||||
@ -39,4 +44,20 @@ export class Sprite extends RenderItem {
|
|||||||
this.x = x;
|
this.x = x;
|
||||||
this.y = y;
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,7 +6,7 @@ interface GameLoadEvent extends EmitableEvent {
|
|||||||
coreLoaded: () => void;
|
coreLoaded: () => void;
|
||||||
autotileLoaded: () => void;
|
autotileLoaded: () => void;
|
||||||
coreInit: () => void;
|
coreInit: () => void;
|
||||||
materialLoaded: () => void;
|
loaded: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
class GameLoading extends EventEmitter<GameLoadEvent> {
|
class GameLoading extends EventEmitter<GameLoadEvent> {
|
||||||
@ -14,9 +14,6 @@ class GameLoading extends EventEmitter<GameLoadEvent> {
|
|||||||
private autotileNum?: number;
|
private autotileNum?: number;
|
||||||
private autotileListened: boolean = false;
|
private autotileListened: boolean = false;
|
||||||
|
|
||||||
private materialsNum: number = main.materials.length;
|
|
||||||
private materialsLoaded: number = 0;
|
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
this.on(
|
this.on(
|
||||||
@ -33,13 +30,6 @@ class GameLoading extends EventEmitter<GameLoadEvent> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
addMaterialLoaded() {
|
|
||||||
this.materialsLoaded++;
|
|
||||||
if (this.materialsLoaded === this.materialsNum) {
|
|
||||||
this.emit('materialLoaded');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
addAutotileLoaded() {
|
addAutotileLoaded() {
|
||||||
this.autotileLoaded++;
|
this.autotileLoaded++;
|
||||||
if (this.autotileLoaded === this.autotileNum) {
|
if (this.autotileLoaded === this.autotileNum) {
|
||||||
@ -100,11 +90,20 @@ export interface GameEvent extends EmitableEvent {
|
|||||||
afterBattle: (enemy: DamageEnemy, x?: number, y?: number) => void;
|
afterBattle: (enemy: DamageEnemy, x?: number, y?: number) => void;
|
||||||
/** Emitted in libs/events.js changingFloor */
|
/** Emitted in libs/events.js changingFloor */
|
||||||
changingFloor: (floorId: FloorIds, heroLoc: Loc) => void;
|
changingFloor: (floorId: FloorIds, heroLoc: Loc) => void;
|
||||||
|
|
||||||
drawHero: (
|
drawHero: (
|
||||||
status?: Exclude<keyof MaterialIcon['hero']['down'], 'loc'>,
|
status?: Exclude<keyof MaterialIcon['hero']['down'], 'loc'>,
|
||||||
offset?: number,
|
offset?: number,
|
||||||
frame?: number
|
frame?: number
|
||||||
) => void;
|
) => void;
|
||||||
|
/** Emitted in libs/maps.js setBlock */
|
||||||
|
setBlock: (
|
||||||
|
x: number,
|
||||||
|
y: number,
|
||||||
|
floorId: FloorIds,
|
||||||
|
oldBlock: AllNumbers,
|
||||||
|
newBlock: AllNumbers
|
||||||
|
) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const hook = new EventEmitter<GameEvent>();
|
export const hook = new EventEmitter<GameEvent>();
|
||||||
|
@ -28,6 +28,13 @@ import type * as misc from './mechanism/misc';
|
|||||||
import type { MotaCanvas2D } from '@/core/fx/canvas2d';
|
import type { MotaCanvas2D } from '@/core/fx/canvas2d';
|
||||||
import type * as portal from '@/core/fx/portal';
|
import type * as portal from '@/core/fx/portal';
|
||||||
import type { HeroRenderer } from '@/core/render/hero';
|
import type { HeroRenderer } from '@/core/render/hero';
|
||||||
|
import type { texture } from '@/core/render/cache';
|
||||||
|
import type { MotaRenderer } from '@/core/render/render';
|
||||||
|
import type { Container } from '@/core/render/container';
|
||||||
|
import type { Sprite } from '@/core/render/sprite';
|
||||||
|
import type { Camera } from '@/core/render/camera';
|
||||||
|
import type { Image, Text } from '@/core/render/preset/misc';
|
||||||
|
import type { RenderItem } from '@/core/render/item';
|
||||||
|
|
||||||
interface ClassInterface {
|
interface ClassInterface {
|
||||||
// 渲染进程与游戏进程通用
|
// 渲染进程与游戏进程通用
|
||||||
@ -99,6 +106,14 @@ interface ModuleInterface {
|
|||||||
};
|
};
|
||||||
Render: {
|
Render: {
|
||||||
heroRender: HeroRenderer;
|
heroRender: HeroRenderer;
|
||||||
|
texture: typeof texture;
|
||||||
|
MotaRenderer: MotaRenderer;
|
||||||
|
Container: Container;
|
||||||
|
Sprite: Sprite;
|
||||||
|
Camera: Camera;
|
||||||
|
Text: Text;
|
||||||
|
Image: Image;
|
||||||
|
RenderItem: RenderItem;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -57,11 +57,13 @@ export function init() {
|
|||||||
const hso = hyper('sin', 'out');
|
const hso = hyper('sin', 'out');
|
||||||
let time2 = Date.now();
|
let time2 = Date.now();
|
||||||
Mota.rewrite(core.control, '_moveAction_moving', 'front', () => {
|
Mota.rewrite(core.control, '_moveAction_moving', 'front', () => {
|
||||||
const t = setting.getValue('screen.smoothView', false) ? 200 : 0;
|
const f = core.status.floorId === 'tower6';
|
||||||
|
const t = setting.getValue('screen.smoothView', false) && !f ? 200 : 0;
|
||||||
if (Date.now() - time2 > 20) tran.mode(hso).time(t).absolute();
|
if (Date.now() - time2 > 20) tran.mode(hso).time(t).absolute();
|
||||||
});
|
});
|
||||||
Mota.rewrite(core.control, 'moveDirectly', 'front', () => {
|
Mota.rewrite(core.control, 'moveDirectly', 'front', () => {
|
||||||
const t = setting.getValue('screen.smoothView', false) ? 600 : 0;
|
const f = core.status.floorId === 'tower6';
|
||||||
|
const t = setting.getValue('screen.smoothView', false) && !f ? 600 : 0;
|
||||||
time2 = Date.now();
|
time2 = Date.now();
|
||||||
tran.mode(hso).time(t).absolute();
|
tran.mode(hso).time(t).absolute();
|
||||||
});
|
});
|
||||||
|
@ -14,6 +14,7 @@ export function drawHalo(
|
|||||||
if (main.replayChecking) return;
|
if (main.replayChecking) return;
|
||||||
const setting = Mota.require('var', 'mainSetting');
|
const setting = Mota.require('var', 'mainSetting');
|
||||||
if (!setting.getValue('screen.halo', true)) return;
|
if (!setting.getValue('screen.halo', true)) return;
|
||||||
|
Mota.require('fn', 'ensureFloorDamage')(floorId);
|
||||||
const col = core.status.maps[floorId].enemy;
|
const col = core.status.maps[floorId].enemy;
|
||||||
const [dx, dy] = col.translation;
|
const [dx, dy] = col.translation;
|
||||||
const list = col.haloList.concat(
|
const list = col.haloList.concat(
|
||||||
|
@ -103,7 +103,7 @@ function drawItemDetail(diff: any, x: number, y: number) {
|
|||||||
let color = '#fff';
|
let color = '#fff';
|
||||||
|
|
||||||
if (typeof diff[name] === 'number')
|
if (typeof diff[name] === 'number')
|
||||||
content = core.formatBigNumber(diff[name], true);
|
content = core.formatBigNumber(Math.round(diff[name]), true);
|
||||||
else content = diff[name];
|
else content = diff[name];
|
||||||
|
|
||||||
switch (name) {
|
switch (name) {
|
||||||
|
@ -192,7 +192,7 @@ export function init() {
|
|||||||
maps.prototype._getBgFgMapArray = function (
|
maps.prototype._getBgFgMapArray = function (
|
||||||
name: string,
|
name: string,
|
||||||
floorId: FloorIds,
|
floorId: FloorIds,
|
||||||
noCache: boolean
|
noCache: boolean = false
|
||||||
) {
|
) {
|
||||||
floorId = floorId || core.status.floorId;
|
floorId = floorId || core.status.floorId;
|
||||||
if (!floorId) return [];
|
if (!floorId) return [];
|
||||||
|
6
src/types/core.d.ts
vendored
6
src/types/core.d.ts
vendored
@ -76,7 +76,7 @@ type MaterialImages = {
|
|||||||
/**
|
/**
|
||||||
* 各个类型的图块的图片
|
* 各个类型的图块的图片
|
||||||
*/
|
*/
|
||||||
[C in Exclude<Cls, 'tilesets'>]: HTMLImageElement;
|
[C in Exclude<Cls, 'tileset' | 'autotile'>]: HTMLImageElement;
|
||||||
} & {
|
} & {
|
||||||
/**
|
/**
|
||||||
* 空气墙
|
* 空气墙
|
||||||
@ -1415,6 +1415,10 @@ interface MapDataOf<T extends keyof NumberToId> {
|
|||||||
* 图块的类型
|
* 图块的类型
|
||||||
*/
|
*/
|
||||||
cls: ClsOf<NumberToId[T]>;
|
cls: ClsOf<NumberToId[T]>;
|
||||||
|
|
||||||
|
bigImage?: ImageIds;
|
||||||
|
|
||||||
|
faceIds?: Record<Dir, AllIds>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
3
src/types/enemy.d.ts
vendored
3
src/types/enemy.d.ts
vendored
@ -89,6 +89,9 @@ type Enemy<I extends EnemyIds = EnemyIds> = {
|
|||||||
|
|
||||||
specialHalo?: number[];
|
specialHalo?: number[];
|
||||||
translation?: [number, number];
|
translation?: [number, number];
|
||||||
|
|
||||||
|
/** 大怪物绑定贴图 */
|
||||||
|
bigImage?: ImageIds;
|
||||||
} & {
|
} & {
|
||||||
[P in PartialNumbericEnemyProperty]?: number;
|
[P in PartialNumbericEnemyProperty]?: number;
|
||||||
} & {
|
} & {
|
||||||
|
6
src/types/map.d.ts
vendored
6
src/types/map.d.ts
vendored
@ -1397,6 +1397,12 @@ interface Maps {
|
|||||||
};
|
};
|
||||||
|
|
||||||
_getBigImageInfo(bigImage: HTMLImageElement, face: Dir, posX: number): any;
|
_getBigImageInfo(bigImage: HTMLImageElement, face: Dir, posX: number): any;
|
||||||
|
|
||||||
|
_getBgFgMapArray(
|
||||||
|
name: string,
|
||||||
|
floorId: FloorIds,
|
||||||
|
noCache?: boolean
|
||||||
|
): number[][];
|
||||||
}
|
}
|
||||||
|
|
||||||
declare const maps: new () => Maps;
|
declare const maps: new () => Maps;
|
||||||
|
@ -74,6 +74,7 @@ onMounted(async () => {
|
|||||||
core._afterLoadResources(props.callback);
|
core._afterLoadResources(props.callback);
|
||||||
logger.log(`Resource load end.`);
|
logger.log(`Resource load end.`);
|
||||||
loadDiv.style.opacity = '0';
|
loadDiv.style.opacity = '0';
|
||||||
|
Mota.require('var', 'loading').emit('loaded');
|
||||||
await sleep(1000);
|
await sleep(1000);
|
||||||
fixedUi.close(props.num);
|
fixedUi.close(props.num);
|
||||||
fixedUi.open('start');
|
fixedUi.open('start');
|
||||||
|
Loading…
Reference in New Issue
Block a user