feat: 新的渲染系统

This commit is contained in:
unanmed 2024-05-09 23:49:53 +08:00
parent af38d2e47e
commit 2f8fbd375c
34 changed files with 1173 additions and 179 deletions

View File

@ -802,11 +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(() => {
const render = Mota.require('module', 'Render').heroRender;
render.move(true);
});
core.interval.heroMoveInterval = window.setInterval(function () { core.interval.heroMoveInterval = window.setInterval(function () {
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.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);
@ -1003,22 +1011,32 @@ control.prototype.tryMoveDirectly = function (destX, destY) {
}; };
////// 绘制勇士 ////// ////// 绘制勇士 //////
control.prototype.drawHero = function (status, offset, 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;
@ -1030,9 +1048,6 @@ control.prototype.drawHero = function (status, offset, 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);
}; };
@ -3499,75 +3514,6 @@ control.prototype.hideStatusBar = function (showToolbox) {
////// 改变工具栏为按钮1-8 ////// ////// 改变工具栏为按钮1-8 //////
control.prototype.setToolbarButton = function (useButton) { control.prototype.setToolbarButton = function (useButton) {
// Deprecated. Use CustomToolbar instead. // Deprecated. Use CustomToolbar instead.
return;
if (!core.domStyle.showStatusBar) {
// 隐藏状态栏时检查竖屏
if (!core.domStyle.isVertical && !core.flags.extendToolbar) {
for (var i = 0; i < core.dom.tools.length; ++i)
core.dom.tools[i].style.display = 'none';
return;
}
if (!core.hasFlag('showToolbox')) return;
else core.dom.tools.hard.style.display = 'block';
}
if (useButton == null) useButton = core.domStyle.toolbarBtn;
if (!core.domStyle.isVertical && !core.flags.extendToolbar)
useButton = false;
core.domStyle.toolbarBtn = useButton;
if (useButton) {
[
'book',
'fly',
'toolbox',
'keyboard',
'shop',
'save',
'load',
'settings'
].forEach(function (t) {
core.statusBar.image[t].style.display = 'none';
});
[
'btn1',
'btn2',
'btn3',
'btn4',
'btn5',
'btn6',
'btn7',
'btn8'
].forEach(function (t) {
core.statusBar.image[t].style.display = 'block';
});
main.statusBar.image.btn8.style.filter = core.getLocalStorage('altKey')
? 'sepia(1) contrast(1.5)'
: '';
} else {
[
'btn1',
'btn2',
'btn3',
'btn4',
'btn5',
'btn6',
'btn7',
'btn8'
].forEach(function (t) {
core.statusBar.image[t].style.display = 'none';
});
['book', 'fly', 'toolbox', 'save', 'load', 'settings'].forEach(
function (t) {
core.statusBar.image[t].style.display = 'block';
}
);
core.statusBar.image.keyboard.style.display =
core.statusBar.image.shop.style.display =
core.domStyle.isVertical || core.flags.extendToolbar
? 'block'
: 'none';
}
}; };
////// ------ resize处理 ------ // ////// ------ resize处理 ------ //
@ -3789,16 +3735,4 @@ control.prototype._resize_toolBar = function (obj) {
control.prototype._resize_tools = function (obj) { control.prototype._resize_tools = function (obj) {
// Deprecated. Use CustomToolbar instead. // Deprecated. Use CustomToolbar instead.
// var toolsHeight = 32 * core.domStyle.scale;
// var toolsMarginLeft;
// toolsMarginLeft = (core._HALF_WIDTH_ - 3) * 3 * core.domStyle.scale;
// for (var i = 0; i < core.dom.tools.length; ++i) {
// var style = core.dom.tools[i].style;
// style.height = toolsHeight + 'px';
// style.marginLeft = toolsMarginLeft + 'px';
// style.marginTop = 3 * core.domStyle.scale + 'px';
// }
// core.dom.hard.style.lineHeight = toolsHeight + 'px';
// core.dom.hard.style.width =
// obj.outerWidth - 9 * toolsMarginLeft - 8.5 * toolsHeight - 12 + 'px';
}; };

View File

@ -843,6 +843,7 @@ events.prototype._changeFloor_afterChange = function (info, callback) {
events.prototype.changingFloor = function (floorId, heroLoc) { events.prototype.changingFloor = function (floorId, heroLoc) {
this.eventdata.changingFloor(floorId, heroLoc); this.eventdata.changingFloor(floorId, heroLoc);
Mota.require('var', 'hook').emit('changingFloor', floorId, heroLoc);
}; };
////// 转换楼层结束的事件 ////// ////// 转换楼层结束的事件 //////
@ -4252,6 +4253,7 @@ events.prototype._vibrate_update = function (shakeInfo) {
/////// 使用事件让勇士移动。这个函数将不会触发任何事件 ////// /////// 使用事件让勇士移动。这个函数将不会触发任何事件 //////
events.prototype.eventMoveHero = function (steps, time, callback) { events.prototype.eventMoveHero = function (steps, time, callback) {
time = time || core.values.moveSpeed; time = time || core.values.moveSpeed;
// const render = Mota.require('module', 'Render').heroRender;
var step = 0, var step = 0,
moveSteps = (steps || []) moveSteps = (steps || [])
.map(function (t) { .map(function (t) {
@ -4275,9 +4277,11 @@ events.prototype.eventMoveHero = function (steps, time, callback) {
); );
}); });
core.status.heroMoving = -1; core.status.heroMoving = -1;
// render.move(false);
var _run = function () { var _run = function () {
var cb = function () { var cb = function () {
core.status.heroMoving = 0; core.status.heroMoving = 0;
// render.move(false);
core.drawHero(); core.drawHero();
if (callback) callback(); if (callback) callback();
}; };
@ -4339,15 +4343,18 @@ events.prototype.jumpHero = function (ex, ey, time, callback) {
}; };
events.prototype._jumpHero_doJump = function (jumpInfo, callback) { events.prototype._jumpHero_doJump = function (jumpInfo, callback) {
// const render = Mota.require('module', 'Render').heroRender;
var cb = function () { var cb = function () {
core.setHeroLoc('x', jumpInfo.ex); core.setHeroLoc('x', jumpInfo.ex);
core.setHeroLoc('y', jumpInfo.ey); core.setHeroLoc('y', jumpInfo.ey);
render.move(false);
core.status.heroMoving = 0; core.status.heroMoving = 0;
core.drawHero(); core.drawHero();
if (callback) callback(); if (callback) callback();
}; };
core.status.heroMoving = -1; core.status.heroMoving = -1;
// render.move(false);
var animate = window.setInterval(function () { var animate = window.setInterval(function () {
if (jumpInfo.jump_count > 0) core.events._jumpHero_jumping(jumpInfo); if (jumpInfo.jump_count > 0) core.events._jumpHero_jumping(jumpInfo);
else { else {
@ -4386,6 +4393,7 @@ events.prototype.setHeroIcon = function (name, noDraw) {
core.material.images.hero = img; core.material.images.hero = img;
core.material.icons.hero.width = img.width / 4; core.material.icons.hero.width = img.width / 4;
core.material.icons.hero.height = img.height / 4; core.material.icons.hero.height = img.height / 4;
// Mota.require('module', 'Render').heroRender.setHero();
if (!noDraw) core.drawHero(); if (!noDraw) core.drawHero();
}; };

View File

@ -738,28 +738,29 @@ var data_a1e2fb4a_e986_4524_b0da_9b7ba7c0874d =
}, },
{ {
"need": "100000", "need": "100000",
"title": "级智人", "title": "级智人",
"clear": true, "clear": true,
"action": [ "action": [
{ {
"type": "setValue", "type": "setValue",
"name": "status:mdef", "name": "status:mdef",
"operator": "+=", "operator": "+=",
"value": "2000" "value": "10000"
}, },
{ {
"type": "setValue", "type": "setValue",
"name": "status:atk", "name": "status:atk",
"operator": "+=", "operator": "+=",
"value": "25" "value": "250"
}, },
{ {
"type": "setValue", "type": "setValue",
"name": "status:def", "name": "status:def",
"operator": "+=", "operator": "+=",
"value": "25" "value": "250"
}, },
"恭喜升级!攻防+25智慧+2000" "恭喜升级!攻防+250智慧+10000",
"这是这个游戏的最后一级,第三章的玩法会改变,不再设置等级"
] ]
} }
] ]

View File

@ -16,7 +16,11 @@ main.floors.MT59=
"firstArrive": [], "firstArrive": [],
"eachArrive": [], "eachArrive": [],
"parallelDo": "", "parallelDo": "",
"events": {}, "events": {
"12,5": [
"注意这个boss现在不是必打的只要能打过去苍蓝殿上方区域的那个黄卫兵就能去上面的区域了之后回头再来清这个boss也可以"
]
},
"changeFloor": { "changeFloor": {
"14,7": { "14,7": {
"floorId": "MT58", "floorId": "MT58",
@ -113,7 +117,7 @@ main.floors.MT59=
[648,491, 28,491,648, 0,648, 0,648,648,648,648, 0, 29,648], [648,491, 28,491,648, 0,648, 0,648,648,648,648, 0, 29,648],
[648, 27,468, 27,648,487,648, 21,648, 27, 0,648, 27, 0,648], [648, 27,468, 27,648,487,648, 21,648, 27, 0,648, 27, 0,648],
[648, 0, 28, 0, 85, 0,648,487,648, 0, 29,657, 0, 28,648], [648, 0, 28, 0, 85, 0,648,487,648, 0, 29,657, 0, 28,648],
[648,648,648,648,648,390,648,390,648,484,648,648,648,648,648], [648,648,648,648,648,390,648,390,648,484,648,648,129,648,648],
[648,482, 0,482,648, 0,249, 0,648,539,648,482, 0,482,648], [648,482, 0,482,648, 0,249, 0,648,539,648,482, 0,482,648],
[648, 0,666, 0,492,403,648,648,648, 0,492, 0,381, 0, 94], [648, 0,666, 0,492,403,648,648,648, 0,492, 0,381, 0, 94],
[648,482, 0,482,648, 0,249, 0,648,539,648,482, 0,482,648], [648,482, 0,482,648, 0,249, 0,648,539,648,482, 0,482,648],

View File

@ -21,10 +21,10 @@ main.floors.MT69=
"提示一个本地图的较优解法:从此处直接向右走,然后反过来把红骑士杀了,就基本上没光环了" "提示一个本地图的较优解法:从此处直接向右走,然后反过来把红骑士杀了,就基本上没光环了"
], ],
"2,7": [ "2,7": [
"中间的那个木牌会提示一种较优解法" "不要被这一层吓到了(,实际上光环只有四个黄光环,其他的都是黄光环产生的。如果实在想不到解法,中间的那个木牌会提示一种较优解法"
], ],
"6,13": [ "6,13": [
"中间的那个木牌会提示一种较优解法" "不要被这一层吓到了(,实际上光环只有四个黄光环,其他的都是黄光环产生的。如果实在想不到解法,中间的那个木牌会提示一种较优解法"
] ]
}, },
"changeFloor": { "changeFloor": {

View File

@ -29,6 +29,13 @@ main.floors.MT73=
7, 7,
0 0
] ]
},
"7,0": {
"floorId": "MT74",
"loc": [
7,
14
]
} }
}, },
"beforeBattle": {}, "beforeBattle": {},

View File

@ -17,7 +17,22 @@ main.floors.MT74=
"eachArrive": [], "eachArrive": [],
"parallelDo": "", "parallelDo": "",
"events": {}, "events": {},
"changeFloor": {}, "changeFloor": {
"7,0": {
"floorId": "MT75",
"loc": [
7,
14
]
},
"7,14": {
"floorId": "MT73",
"loc": [
7,
0
]
}
},
"beforeBattle": {}, "beforeBattle": {},
"afterBattle": {}, "afterBattle": {},
"afterGetItem": {}, "afterGetItem": {},
@ -26,21 +41,21 @@ main.floors.MT74=
"cannotMove": {}, "cannotMove": {},
"cannotMoveIn": {}, "cannotMoveIn": {},
"map": [ "map": [
[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [648,648,648,648,648,648,648, 91,648,648,648,648,648,648,648],
[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [648,430,472,420,648, 0, 0, 0, 0, 0,648,420,472,430,648],
[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [648,648, 85,648,648, 0, 0, 0, 0, 0,648,648, 85,648,648],
[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [648, 0, 0, 0,648, 0, 0, 0, 0, 0,648, 0, 0, 0,648],
[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [648, 0,487, 0,648, 0, 0, 0, 0, 0,648, 0,487, 0,648],
[ 0, 0, 0, 0, 0,103, 0, 0, 0,103, 0, 0, 0, 0, 0], [648, 0, 0, 0,648,103, 0, 0, 0,103,648, 0, 0, 0,648],
[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [648,648,492,648,648, 0, 0, 0, 0, 0,648,648,492,648,648],
[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [ 92, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 94],
[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [648,648,492,648,648, 0, 0, 0, 0, 0,648,648,492,648,648],
[ 0, 0, 0, 0, 0,103, 0, 0, 0,103, 0, 0, 0, 0, 0], [648, 0, 0, 0,648,103, 0, 0, 0,103,648, 0, 0, 0,648],
[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [648, 0,487, 0,648, 0, 0, 0, 0, 0,648, 0,487, 0,648],
[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [648, 0, 0, 0,648, 0, 0, 0, 0, 0,648, 0, 0, 0,648],
[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [648,648, 85,648,648, 0, 0, 0, 0, 0,648,648, 85,648,648],
[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [648,430,472,420,648, 0, 0, 0, 0, 0,648,420,472,430,648],
[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] [648,648,648,648,648,648,648, 93,648,648,648,648,648,648,648]
], ],
"bgmap": [ "bgmap": [
[ 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],

View File

@ -17,7 +17,15 @@ main.floors.MT75=
"eachArrive": [], "eachArrive": [],
"parallelDo": "", "parallelDo": "",
"events": {}, "events": {},
"changeFloor": {}, "changeFloor": {
"7,14": {
"floorId": "MT74",
"loc": [
7,
0
]
}
},
"beforeBattle": {}, "beforeBattle": {},
"afterBattle": {}, "afterBattle": {},
"afterGetItem": {}, "afterGetItem": {},
@ -26,21 +34,21 @@ main.floors.MT75=
"cannotMove": {}, "cannotMove": {},
"cannotMoveIn": {}, "cannotMoveIn": {},
"map": [ "map": [
[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [648,648,648,648,648,648,648,648,648,648,648,648,648,648,648],
[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [648,491,491,494,491,494, 0, 0, 0,494,491,494,491,491,648],
[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [648,648,648,648,648,648, 0, 0, 0,648,648,648,648,648,648],
[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [648,484,484,492,484,492, 0, 0, 0,492,484,492,484,484,648],
[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [648,648,648,648,648,648, 0, 0, 0,648,648,648,648,648,648],
[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [648, 0, 0, 0, 0,103, 0, 0, 0,103, 0, 0, 0, 0,648],
[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [648, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,648],
[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [ 92, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 94],
[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [648, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,648],
[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [648, 0, 0, 0, 0,103, 0, 0, 0,103, 0, 0, 0, 0,648],
[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [648,648,648,648,648,648, 0, 0, 0,648,648,648,648,648,648],
[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [648,484,484,492,484,492, 0, 0, 0,492,484,492,484,484,648],
[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [648,648,648,648,648,648, 0, 0, 0,648,648,648,648,648,648],
[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [648,491,491,494,491,494, 0, 0, 0,494,491,494,491,491,648],
[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] [648,648,648,648,648,648,648, 93,648,648,648,648,648,648,648]
], ],
"bgmap": [ "bgmap": [
[ 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],

View File

@ -226,27 +226,6 @@ var functions_d6ad677b_427a_4623_b50f_a445a3b0ef8a = {
if (core.flags.flyRecordPosition) { if (core.flags.flyRecordPosition) {
loc = core.getFlag('__leaveLoc__', {})[toId] || null; loc = core.getFlag('__leaveLoc__', {})[toId] || null;
} }
if (
core.status.maps[toId].flyPoint != null &&
core.status.maps[toId].flyPoint.length == 2
) {
loc = {
x: core.status.maps[toId].flyPoint[0],
y: core.status.maps[toId].flyPoint[1]
};
}
if (loc == null) {
// 获得两个楼层的索引,以决定是上楼梯还是下楼梯
var fromIndex = core.floorIds.indexOf(fromId),
toIndex = core.floorIds.indexOf(toId);
var stair = fromIndex <= toIndex ? 'downFloor' : 'upFloor';
// 地下层:同层传送至上楼梯
if (
fromIndex == toIndex &&
core.status.maps[fromId].underGround
)
stair = 'upFloor';
}
// 记录录像 // 记录录像
core.status.route.push('fly:' + toId); core.status.route.push('fly:' + toId);

View File

@ -1048,7 +1048,7 @@ var items_296f5d02_12fd_4166_a7c1_b5e830c9ee3a =
}, },
"I472": { "I472": {
"cls": "items", "cls": "items",
"name": "新物品", "name": "传奇绿宝石",
"text": ",防御+${core.values.blueGem}", "text": ",防御+${core.values.blueGem}",
"itemEffect": "core.status.hero.mdef += Math.round(640 * core.status.thisMap.ratio / core.getFlag(\"hard\") * (Mota.Plugin.require('skillTree_g').getSkillLevel(12) / 20 + 1))", "itemEffect": "core.status.hero.mdef += Math.round(640 * core.status.thisMap.ratio / core.getFlag(\"hard\") * (Mota.Plugin.require('skillTree_g').getSkillLevel(12) / 20 + 1))",
"itemEffectTip": ",智慧+${Math.round(640 * core.status.thisMap.ratio / core.getFlag(\"hard\") * (Mota.Plugin.require('skillTree_g').getSkillLevel(12) / 20 + 1))}", "itemEffectTip": ",智慧+${Math.round(640 * core.status.thisMap.ratio / core.getFlag(\"hard\") * (Mota.Plugin.require('skillTree_g').getSkillLevel(12) / 20 + 1))}",

View File

@ -392,6 +392,7 @@ p#name {
} }
#hero { #hero {
display: none;
z-index: 40; z-index: 40;
} }

View File

@ -20,6 +20,8 @@ export class MotaOffscreenCanvas2D extends EventEmitter<OffscreenCanvasEvent> {
/** 是否是高清画布 */ /** 是否是高清画布 */
highResolution: boolean = true; highResolution: boolean = true;
scale: number = 1;
constructor() { constructor() {
super(); super();
@ -41,6 +43,7 @@ export class MotaOffscreenCanvas2D extends EventEmitter<OffscreenCanvasEvent> {
if (this.autoScale && this.highResolution) { if (this.autoScale && this.highResolution) {
ratio *= core.domStyle.scale; ratio *= core.domStyle.scale;
} }
this.scale = ratio;
this.canvas.width = width * ratio; this.canvas.width = width * ratio;
this.canvas.height = height * ratio; this.canvas.height = height * ratio;
this.width = width; this.width = width;
@ -82,7 +85,13 @@ export class MotaOffscreenCanvas2D extends EventEmitter<OffscreenCanvasEvent> {
newCanvas.setHD(canvas.highResolution); newCanvas.setHD(canvas.highResolution);
newCanvas.withGameScale(canvas.autoScale); newCanvas.withGameScale(canvas.autoScale);
newCanvas.size(canvas.width, canvas.height); newCanvas.size(canvas.width, canvas.height);
newCanvas.ctx.drawImage(canvas.canvas, 0, 0); newCanvas.ctx.drawImage(
canvas.canvas,
0,
0,
canvas.width,
canvas.height
);
return newCanvas; return newCanvas;
} }
} }
@ -144,6 +153,7 @@ export class MotaCanvas2D extends MotaOffscreenCanvas2D {
this.canvas.style.width = `${width}px`; this.canvas.style.width = `${width}px`;
this.canvas.style.height = `${height}px`; this.canvas.style.height = `${height}px`;
} }
this.scale = ratio;
this.canvas.width = width * ratio; this.canvas.width = width * ratio;
this.canvas.height = height * ratio; this.canvas.height = height * ratio;
this.width = width; this.width = width;

View File

@ -114,18 +114,18 @@ Mota.require('var', 'hook').once('reset', () => {
Shadow.update(true); Shadow.update(true);
} }
}); });
Mota.rewrite(core.events, 'changingFloor', 'add', (_, floorId) => {
if (!main.replayChecking) {
Shadow.clearBuffer();
Shadow.update();
setCanvasFilterByFloorId(floorId);
}
});
Mota.rewrite(core.control, 'loadData', 'add', () => { Mota.rewrite(core.control, 'loadData', 'add', () => {
if (!main.replayChecking) { if (!main.replayChecking) {
Shadow.update(true); Shadow.update(true);
} }
}); });
Mota.require('var', 'hook').on('changingFloor', (floorId) => {
if (!main.replayChecking) {
Shadow.clearBuffer();
Shadow.update();
setCanvasFilterByFloorId(floorId);
}
})
}); });
// 深度测试着色器 // 深度测试着色器

View File

@ -60,6 +60,15 @@ import { ResourceController } from './loader/controller';
import { logger } from './common/logger'; import { logger } from './common/logger';
import { Danmaku } from './main/custom/danmaku'; import { Danmaku } from './main/custom/danmaku';
import * as Shadow from './fx/shadow'; import * as Shadow from './fx/shadow';
import { MotaCanvas2D } from './fx/canvas2d';
import * as portal from './fx/portal';
import { heroRender } from './render/hero';
import { MotaRenderer } from './render/render';
import { Container } from './render/container';
import { Sprite } from './render/sprite';
import { Camera } from './render/camera';
import { Image, Text } from './render/preset/misc';
import { RenderItem } from './render/item';
// ----- 类注册 // ----- 类注册
Mota.register('class', 'AudioPlayer', AudioPlayer); Mota.register('class', 'AudioPlayer', AudioPlayer);
@ -78,6 +87,7 @@ Mota.register('class', 'UiController', UiController);
Mota.register('class', 'MComponent', MComponent); Mota.register('class', 'MComponent', MComponent);
Mota.register('class', 'ResourceController', ResourceController); Mota.register('class', 'ResourceController', ResourceController);
Mota.register('class', 'Danmaku', Danmaku); Mota.register('class', 'Danmaku', Danmaku);
Mota.register('class', 'MotaCanvas2D', MotaCanvas2D);
// ----- 函数注册 // ----- 函数注册
Mota.register('fn', 'm', m); Mota.register('fn', 'm', m);
Mota.register('fn', 'unwrapBinary', unwarpBinary); Mota.register('fn', 'unwrapBinary', unwarpBinary);
@ -133,6 +143,19 @@ Mota.register('module', 'UIComponents', {
}); });
Mota.register('module', 'MCGenerator', MCGenerator); Mota.register('module', 'MCGenerator', MCGenerator);
Mota.register('module', 'Shadow', Shadow); Mota.register('module', 'Shadow', Shadow);
Mota.register('module', 'Effect', {
Portal: portal
});
Mota.register('module', 'Render', {
heroRender,
MotaRenderer,
Container,
Sprite,
Camera,
Text,
Image,
RenderItem
});
main.renderLoaded = true; main.renderLoaded = true;
Mota.require('var', 'hook').emit('renderLoaded'); Mota.require('var', 'hook').emit('renderLoaded');

View File

@ -17,3 +17,5 @@ export interface ResponseBase {
code: number; code: number;
message: string; message: string;
} }
export type CSSObj = Partial<Record<CanParseCss, string>>;

View File

@ -27,6 +27,8 @@ import {
FolderOpenOutlined, FolderOpenOutlined,
LayoutOutlined, LayoutOutlined,
MessageOutlined, MessageOutlined,
RetweetOutlined,
RollbackOutlined,
SwapOutlined SwapOutlined
} from '@ant-design/icons-vue'; } from '@ant-design/icons-vue';
import { generateKeyboardEvent } from '../custom/keyboard'; import { generateKeyboardEvent } from '../custom/keyboard';
@ -888,7 +890,7 @@ Mota.require('var', 'hook').once('reset', () => {
() => { () => {
core.doSL('autoSave', 'load'); core.doSL('autoSave', 'load');
}, },
h(BackwardOutlined) h(RollbackOutlined)
); );
CustomToolbar.misc.register( CustomToolbar.misc.register(
'redo', 'redo',
@ -896,7 +898,7 @@ Mota.require('var', 'hook').once('reset', () => {
() => { () => {
core.doSL('autoSave', 'reload'); core.doSL('autoSave', 'reload');
}, },
h(SwapOutlined) h(RetweetOutlined)
); );
CustomToolbar.misc.register( CustomToolbar.misc.register(
'setting', 'setting',

View File

@ -474,6 +474,7 @@ mainSetting
new MotaSetting() new MotaSetting()
.register('paraLight', '野外阴影', true, COM.Boolean) .register('paraLight', '野外阴影', true, COM.Boolean)
.register('frag', '打怪特效', true, COM.Boolean) .register('frag', '打怪特效', true, COM.Boolean)
.register('portalParticle', '传送门特效', true, COM.Boolean)
) )
.register( .register(
'ui', 'ui',
@ -512,6 +513,7 @@ loading.once('coreInit', () => {
'utils.autoScale': !!storage.getValue('utils.autoScale', true), 'utils.autoScale': !!storage.getValue('utils.autoScale', true),
'fx.paraLight': !!storage.getValue('fx.paraLight', true), 'fx.paraLight': !!storage.getValue('fx.paraLight', true),
'fx.frag': !!storage.getValue('fx.frag', true), 'fx.frag': !!storage.getValue('fx.frag', true),
'fx.portalParticle': !!storage.getValue('fx.portalParticle', true),
'ui.mapScale': storage.getValue( 'ui.mapScale': storage.getValue(
'ui.mapScale', 'ui.mapScale',
isMobile ? 300 : Math.floor(window.innerWidth / 600) * 50 isMobile ? 300 : Math.floor(window.innerWidth / 600) * 50
@ -565,7 +567,8 @@ mainSetting
.setDescription('ui.danmaku', '是否显示弹幕') .setDescription('ui.danmaku', '是否显示弹幕')
.setDescription('ui.danmakuSpeed', '弹幕速度,刷新或开关弹幕显示后起效') .setDescription('ui.danmakuSpeed', '弹幕速度,刷新或开关弹幕显示后起效')
.setDescription('screen.fontSizeStatus', `修改状态栏的字体大小`) .setDescription('screen.fontSizeStatus', `修改状态栏的字体大小`)
.setDescription('screen.blur', '打开任意ui界面时是否有背景虚化效果移动端打开后可能会有掉帧或者发热现象。关闭ui后生效'); .setDescription('screen.blur', '打开任意ui界面时是否有背景虚化效果移动端打开后可能会有掉帧或者发热现象。关闭ui后生效')
.setDescription('fx.portalParticle', '是否启用苍蓝之殿的传送门粒子特效,启用后可能对性能及设备发热有所影响');
function setFontSize() { function setFontSize() {
const absoluteSize = storage.getValue( const absoluteSize = storage.getValue(

184
src/core/render/camera.ts Normal file
View File

@ -0,0 +1,184 @@
import { ReadonlyMat3, ReadonlyVec3, mat3, vec2, vec3 } from 'gl-matrix';
import { EmitableEvent, EventEmitter } from '../common/eventEmitter';
interface CameraEvent extends EmitableEvent {}
export class Camera extends EventEmitter<CameraEvent> {
mat: mat3 = mat3.create();
x: number = 0;
y: number = 0;
scaleX: number = 1;
scaleY: number = 1;
rad: number = 0;
/**
*
*/
reset() {
mat3.identity(this.mat);
this.x = 0;
this.y = 0;
this.scaleX = 1;
this.scaleY = 1;
this.rad = 0;
}
/**
*
*/
scale(x: number, y: number = x) {
mat3.scale(this.mat, this.mat, [x, y]);
this.scaleX *= x;
this.scaleY *= y;
}
/**
*
*/
move(x: number, y: number) {
mat3.translate(this.mat, this.mat, [x, y]);
this.x += x;
this.y += y;
}
/**
*
*/
rotate(rad: number) {
mat3.rotate(this.mat, this.mat, rad);
this.rad += rad;
if (this.rad >= Math.PI * 2) {
const n = Math.floor(this.rad / Math.PI / 2);
this.rad -= n * Math.PI * 2;
}
}
/**
*
*/
setScale(x: number, y: number = x) {
mat3.scale(this.mat, this.mat, [x / this.scaleX, y / this.scaleY]);
this.scaleX = x;
this.scaleY = y;
}
/**
*
*/
setTranslate(x: number, y: number) {
mat3.translate(this.mat, this.mat, [x - this.x, y - this.y]);
this.x = x;
this.y = y;
}
/**
*
*/
setRotate(rad: number) {
mat3.rotate(this.mat, this.mat, rad - this.rad);
this.rad = rad;
}
/**
*
* @param a
* @param b
* @param c
* @param d
* @param e
* @param f
*/
transform(
a: number,
b: number,
c: number,
d: number,
e: number,
f: number
) {
mat3.multiply(
this.mat,
this.mat,
mat3.fromValues(a, b, 0, c, d, 0, e, f, 1)
);
this.calAttributes();
}
/**
*
* @param a
* @param b
* @param c
* @param d
* @param e
* @param f
*/
setTransform(
a: number,
b: number,
c: number,
d: number,
e: number,
f: number
) {
mat3.set(this.mat, a, b, 0, c, d, 0, e, f, 1);
this.calAttributes();
}
/**
* translation scaling rotation
*/
calAttributes() {
const [x, y] = getTranslation(this.mat);
const [scaleX, scaleY] = getScaling(this.mat);
const rad = getRotation(this.mat);
this.x = x;
this.y = y;
this.scaleX = scaleX;
this.scaleY = scaleY;
this.rad = rad;
}
/**
*
* @param camera
* @param x
* @param y
*/
static transformed(camera: Camera, x: number, y: number) {
return multiplyVec3(camera.mat, [x, y, 1]);
}
/**
*
* @param camera
* @param x
* @param y
*/
static untransformed(camera: Camera, x: number, y: number) {
const invert = mat3.create();
mat3.invert(invert, camera.mat);
return multiplyVec3(invert, [x, y, 1]);
}
}
function multiplyVec3(mat: ReadonlyMat3, vec: ReadonlyVec3) {
return vec3.fromValues(
mat[0] * vec[0] + mat[3] * vec[1] + mat[6] * vec[2],
mat[1] * vec[0] + mat[4] * vec[1] + mat[7] * vec[2],
mat[2] * vec[0] + mat[5] * vec[1] + mat[8] * vec[2]
);
}
function getTranslation(mat: ReadonlyMat3): vec2 {
return [mat[6], mat[7]];
}
function getScaling(mat: ReadonlyMat3): vec2 {
return [Math.hypot(mat[0], mat[3]), Math.hypot(mat[1], mat[4])];
}
function getRotation(mat: ReadonlyMat3): number {
return Math.atan2(mat[3], mat[0]);
}

View File

@ -0,0 +1,61 @@
import { MotaOffscreenCanvas2D } from '../fx/canvas2d';
import { Camera } from './camera';
import {
ICanvasCachedRenderItem,
RenderItem,
RenderItemPosition,
withCacheRender
} from './item';
export class Container extends RenderItem implements ICanvasCachedRenderItem {
children: RenderItem[] = [];
sortedChildren: RenderItem[] = [];
canvas: MotaOffscreenCanvas2D;
constructor(type: RenderItemPosition = 'static') {
super();
this.canvas = new MotaOffscreenCanvas2D();
this.type = type;
this.canvas.withGameScale(true);
}
render(
canvas: HTMLCanvasElement,
ctx: CanvasRenderingContext2D,
camera: Camera
): void {
this.emit('beforeUpdate', this);
withCacheRender(this, canvas, ctx, camera, c => {
this.sortedChildren.forEach(v => {
v.render(c.canvas, c.ctx, camera);
});
});
this.emit('afterUpdate', this);
}
size(width: number, height: number) {
this.width = width;
this.height = height;
this.canvas.size(width, height);
this.writing = this.using;
this.update(this);
}
pos(x: number, y: number) {
this.x = x;
this.y = y;
}
/**
* tick执行更新
* @param children
*/
appendChild(children: RenderItem[]) {
children.forEach(v => (v.parent = this));
this.children.push(...children);
this.sortedChildren = this.children
.slice()
.sort((a, b) => a.zIndex - b.zIndex);
}
}

174
src/core/render/hero.ts Normal file
View File

@ -0,0 +1,174 @@
import { Ticker } from 'mutate-animate';
import { MotaCanvas2D } from '../fx/canvas2d';
import { EmitableEvent, EventEmitter } from '../common/eventEmitter';
import { debounce } from 'lodash-es';
// 重写样板的勇士绘制
const canvas = MotaCanvas2D.for('@hero', false);
Mota.require('var', 'loading').once('coreInit', () => {
canvas.withGameScale(true);
canvas.setHD(false);
canvas.size(480, 480);
canvas.pos(0, 0);
canvas.css(`z-index: 40`);
canvas.canvas.classList.add('no-anti-aliasing');
canvas.setTarget(core.dom.gameDraw);
canvas.mount();
});
const DIR_INDEX: Record<Dir, number> = {
down: 0,
left: 1,
right: 2,
up: 3
};
interface HeroDrawItem {
id: AllIds | 'hero';
image: HTMLCanvasElement;
}
interface HeroDrawing extends HeroDrawItem {
x: number;
y: number;
dir: Dir;
}
interface HeroRendererEvent extends EmitableEvent {
beforeDraw: () => void;
afterDraw: () => void;
}
const resetMoving = debounce((render: HeroRenderer) => {
render.moving = 0;
}, 500);
export class HeroRenderer extends EventEmitter<HeroRendererEvent> {
followers: HeroDrawItem[] = [];
hero!: HeroDrawItem;
/** 移动状态 */
moving: number = 0;
/** 移动过程中的偏移 */
offset: number = 0;
/** 勇士绘制时的alpha通道 */
alpha: number = 1;
ticker: Ticker = new Ticker();
/** 是否是后退状态 */
private back: boolean = false;
/** 是否正在移动 */
private isMoving: boolean = false;
/** 上一次换腿(?的时间 */
private lastMoving: number = 0;
constructor() {
super();
Mota.require('var', 'loading').once('coreInit', () => {
this.ticker.add(time => {
if (core.status.heroMoving < 0 || !this.isMoving) return;
if (time - this.lastMoving > core.values.moveSpeed) {
this.lastMoving = time;
this.moving++;
this.moving %= 4;
}
this.draw();
});
});
}
/**
*
*/
setHero() {
const image = core.material.images.hero;
const canvas = new MotaCanvas2D();
canvas.setHD(false);
canvas.size(image.width, image.height);
canvas.ctx.drawImage(image, 0, 0);
this.hero = {
id: 'hero',
image: canvas.canvas
};
}
/**
*
*/
draw() {
if (!core.isPlaying()) return;
const { ctx, canvas: can } = canvas;
const { x, y, direction: dir } = core.status.hero.loc;
ctx.clearRect(0, 0, can.width, can.height);
ctx.globalAlpha = this.alpha;
this.emit('beforeDraw');
const data: HeroDrawing[] = [];
data.push({ ...this.hero, x, y, dir });
core.status.hero.followers.forEach((v, i) => {
const { id, image } = this.followers[i];
data.push({ x: v.x, y: v.y, dir: v.direction, id, image });
});
const { offsetX: ox, offsetY: oy } = core.bigmap;
const sgn = this.back ? -1 : 1;
const offset = this.offset * sgn;
const frame = this.isMoving ? this.moving % 4 : 0;
data.forEach(v => {
// hero offset x
const hox = offset * core.utils.scan[v.dir].x;
const hoy = offset * core.utils.scan[v.dir].y;
const cx = v.x * 32 + 16 - ox + hox;
const cy = v.y * 32 + 16 - oy + hoy;
const { width, height } = v.image;
const pw = width / 4;
const ph = height / 4;
const line = DIR_INDEX[v.dir];
const px = cx - pw / 2;
const py = cy - ph + 16;
const sx = frame * pw;
const sy = line * ph;
ctx.drawImage(v.image, sx, sy, pw, ph, px, py, pw, ph);
});
this.emit('afterDraw');
}
/**
*
*/
move(moving: boolean) {
this.isMoving = moving;
if (!moving) {
resetMoving(this);
}
}
/**
* 退
*/
backward(back: boolean) {
this.back = back;
}
/**
* alpha
*/
setAlpha(alpha: number) {
this.alpha = alpha;
}
}
const render = new HeroRenderer();
export { render as heroRender };

200
src/core/render/item.ts Normal file
View File

@ -0,0 +1,200 @@
import { isNil } from 'lodash-es';
import { EmitableEvent, EventEmitter } from '../common/eventEmitter';
import { MotaOffscreenCanvas2D } from '../fx/canvas2d';
import { Camera } from './camera';
export type RenderFunction = (
canvas: MotaOffscreenCanvas2D,
camera: Camera
) => void;
export type RenderItemPosition = 'absolute' | 'static';
interface IRenderCache {
/** 缓存列表 */
cacheList: Map<string, MotaOffscreenCanvas2D>;
/** 当前正在使用的缓存 */
using?: string;
/** 下次绘制需要写入的缓存 */
writing?: string;
/**
* 使使
* @param index 使
*/
useCache(index?: string): void;
/**
* 使
* @param index
*/
cache(index?: string): void;
/**
*
* @param index
*/
clearCache(index?: string): void;
}
export interface IRenderUpdater {
/**
*
* @param item
*/
update(item?: RenderItem): void;
}
export interface ICanvasCachedRenderItem {
/** 离屏画布首先渲染到它上面然后由Renderer渲染到最终画布上 */
canvas: MotaOffscreenCanvas2D;
}
interface IRenderAnchor {
anchorX: number;
anchorY: number;
/**
*
* @param x 01
* @param y 01
*/
setAnchor(x: number, y: number): void;
}
interface RenderItemEvent extends EmitableEvent {
beforeUpdate: (item?: RenderItem) => void;
afterUpdate: (item?: RenderItem) => void;
}
export abstract class RenderItem
extends EventEmitter<RenderItemEvent>
implements IRenderCache, IRenderUpdater, IRenderAnchor
{
zIndex: number = 0;
x: number = 0;
y: number = 0;
width: number = 200;
height: number = 200;
cacheList: Map<string, MotaOffscreenCanvas2D> = new Map();
using?: string;
writing?: string;
anchorX: number = 0;
anchorY: number = 0;
/** 渲染模式absolute表示绝对位置static表示跟随摄像机移动只对顶层元素有效 */
type: 'absolute' | 'static' = 'static';
parent?: RenderItem;
constructor() {
super();
this.using = '@default';
}
/**
*
* @param canvas
* @param ctx
* @param camera 使
*/
abstract render(
canvas: HTMLCanvasElement,
ctx: CanvasRenderingContext2D,
camera: Camera
): void;
/**
*
*/
abstract size(width: number, height: number): void;
/**
*
*/
abstract pos(x: number, y: number): void;
useCache(index?: string): void {
if (isNil(index)) {
this.using = void 0;
return;
}
if (!this.cacheList.has(index)) {
this.writing = index;
}
this.using = index;
}
cache(index?: string): void {
this.writing = index;
this.using = index;
}
clearCache(index?: string): void {
if (isNil(index)) {
this.writing = void 0;
this.using = void 0;
this.cacheList.clear();
} else {
this.cacheList.delete(index);
}
}
setAnchor(x: number, y: number): void {
this.anchorX = x;
this.anchorY = y;
}
update(item?: RenderItem): void {
this.writing = this.using;
this.using = void 0;
this.parent?.update(item);
}
}
export function withCacheRender(
item: RenderItem & ICanvasCachedRenderItem,
canvas: HTMLCanvasElement,
ctx: CanvasRenderingContext2D,
camera: Camera,
fn: RenderFunction
) {
const { width, height } = item;
const ax = width * item.anchorX;
const ay = height * item.anchorY;
let write = item.writing;
if (!isNil(item.using)) {
const cache = item.cacheList.get(item.using);
if (cache) {
ctx.drawImage(
cache.canvas,
item.x - ax,
item.y - ay,
item.width,
item.height
);
return;
}
write = item.using;
}
const { canvas: c, ctx: ct } = item.canvas;
ct.clearRect(0, 0, c.width, c.height);
fn(item.canvas, camera);
if (!isNil(write)) {
const cache = item.cacheList.get(write);
if (cache) {
const { canvas, ctx } = cache;
ctx.drawImage(c, 0, 0);
} else {
item.cacheList.set(write, MotaOffscreenCanvas2D.clone(item.canvas));
}
}
ctx.drawImage(c, item.x - ax, item.y - ay, item.width, item.height);
item.using = write;
}

View File

@ -0,0 +1,123 @@
import { Sprite } from '../sprite';
type CanvasStyle = string | CanvasGradient | CanvasPattern;
export class Text extends Sprite {
text: string;
fillStyle?: CanvasStyle = '#fff';
strokeStyle?: CanvasStyle;
font?: string = '';
private length: number = 0;
private descent: number = 0;
constructor(text: string = '') {
super();
this.text = text;
if (text.length > 0) this.calBox();
this.renderFn = ({ canvas, ctx }) => {
ctx.save();
ctx.textBaseline = 'bottom';
ctx.fillStyle = this.fillStyle ?? 'transparent';
ctx.strokeStyle = this.strokeStyle ?? 'transparent';
ctx.font = this.font ?? '';
if (this.fillStyle) {
ctx.fillText(this.text, 0, this.descent);
}
if (this.strokeStyle) {
ctx.strokeText(this.text, 0, this.descent);
}
ctx.restore();
};
}
/**
*
*/
measure() {
this.canvas.ctx.save();
this.canvas.ctx.textBaseline = 'bottom';
this.canvas.ctx.font = this.font ?? '';
const res = this.canvas.ctx.measureText(this.text);
this.canvas.ctx.restore();
return res;
}
/**
*
* @param text
*/
setText(text: string) {
this.text = text;
this.writing = this.using;
this.using = void 0;
this.calBox();
if (this.parent) this.update(this);
}
/**
* 使
* @param font
*/
setFont(font: string) {
this.font = font;
this.calBox();
if (this.parent) this.update(this);
}
/**
*
* @param fill
* @param stroke
*/
setStyle(fill?: CanvasStyle, stroke?: CanvasStyle) {
this.fillStyle = fill;
this.strokeStyle = stroke;
}
/**
*
*/
calBox() {
const { width, fontBoundingBoxAscent } = this.measure();
this.length = width;
this.descent = fontBoundingBoxAscent;
this.size(width, fontBoundingBoxAscent);
}
}
type SizedCanvasImageSource = Exclude<
CanvasImageSource,
VideoFrame | SVGElement
>;
export class Image extends Sprite {
image: SizedCanvasImageSource;
constructor(image: SizedCanvasImageSource) {
super();
this.image = image;
this.canvas.withGameScale(false);
this.size(image.width, image.height);
this.renderFn = ({ canvas, ctx }) => {
ctx.drawImage(this.image, 0, 0);
};
}
/**
*
* @param image
*/
setImage(image: SizedCanvasImageSource) {
this.image = image;
this.size(image.width, image.height);
this.writing = this.using;
this.using = void 0;
this.update(this);
}
}

142
src/core/render/render.ts Normal file
View File

@ -0,0 +1,142 @@
import { isNil } from 'lodash-es';
import { MotaCanvas2D, MotaOffscreenCanvas2D } from '../fx/canvas2d';
import { Camera } from './camera';
import { Container } from './container';
import { RenderItem, withCacheRender } from './item';
import { Image, Text } from './preset/misc';
export class MotaRenderer extends Container {
canvas: MotaOffscreenCanvas2D;
camera: Camera;
/** 摄像机缓存,如果是需要快速切换摄像机的场景,使用缓存可以大幅提升性能表现 */
cameraCache: Map<Camera, MotaOffscreenCanvas2D> = new Map();
target: MotaCanvas2D;
private needUpdate: boolean = false;
constructor() {
super();
this.canvas = new MotaOffscreenCanvas2D();
this.camera = new Camera();
this.target = new MotaCanvas2D(`render-main`);
this.width = 480;
this.height = 480;
this.target.withGameScale(true);
this.target.size(480, 480);
this.canvas.withGameScale(true);
this.canvas.size(480, 480);
this.target.css(`z-index: 100`);
}
/**
* 使
* @param camera 使
* @param noCache 使true
* 使
*/
useCamera(camera: Camera, noCache: boolean = false) {
const cache = MotaOffscreenCanvas2D.clone(this.canvas);
this.cameraCache.set(this.camera, cache);
this.camera = camera;
const nowCache = this.cameraCache.get(camera);
if (!nowCache || !noCache) this.render();
else this.renderCache(nowCache);
}
/**
*
* @param camera
*/
freeCameraCache(camera: Camera) {
this.cameraCache.delete(camera);
}
/**
*
*/
render() {
const { canvas, ctx } = this.target;
const camera = this.camera;
ctx.clearRect(0, 0, canvas.width, canvas.height);
withCacheRender(this, canvas, ctx, camera, canvas => {
const { canvas: ca, ctx: ct, scale } = canvas;
const mat = camera.mat;
const a = mat[0] * scale;
const b = mat[1];
const c = mat[3];
const d = mat[4] * scale;
const e = mat[6];
const f = mat[7];
this.sortedChildren.forEach(v => {
if (v.type === 'absolute') {
ct.setTransform(scale, 0, 0, scale, 0, 0);
} else {
ct.setTransform(a, b, c, d, e, f);
}
v.render(ca, ct, camera);
});
});
}
/**
* tick更新
*/
update(item?: RenderItem) {
if (this.needUpdate) return;
this.needUpdate = true;
requestAnimationFrame(() => {
this.writing = this.using;
this.using = void 0;
this.needUpdate = false;
this.emit('beforeUpdate', item);
this.render();
this.emit('afterUpdate', item);
this.using = this.writing;
});
}
/**
*
* @param cache Canvas2D对象
*/
renderCache(cache: MotaOffscreenCanvas2D) {
const { canvas, ctx } = this.canvas;
ctx.clearRect(0, 0, canvas.width, canvas.width);
ctx.drawImage(cache.canvas, 0, 0);
}
/**
*
*/
mount() {
this.target.mount();
}
}
Mota.require('var', 'hook').once('reset', () => {
const render = new MotaRenderer();
const con = new Container('static');
const camera = render.camera;
render.mount();
const testText = new Text();
testText.setText('测试测试');
testText.pos(100, 100);
testText.setFont('32px normal');
testText.setStyle('#fff');
con.size(480, 480);
const testImage = new Image(core.material.images.images['arrow.png']);
testImage.pos(200, 200);
con.appendChild([testText, testImage]);
render.appendChild([con]);
render.update(render);
setTimeout(() => {
testText.setFont('18px normal');
}, 2000);
});

41
src/core/render/sprite.ts Normal file
View File

@ -0,0 +1,41 @@
import { Camera } from './camera';
import { RenderFunction, RenderItem, withCacheRender } from './item';
import { MotaOffscreenCanvas2D } from '../fx/canvas2d';
export class Sprite extends RenderItem {
renderFn: RenderFunction;
canvas: MotaOffscreenCanvas2D;
constructor() {
super();
this.renderFn = () => {};
this.canvas = new MotaOffscreenCanvas2D();
this.canvas.withGameScale(true);
}
render(
canvas: HTMLCanvasElement,
ctx: CanvasRenderingContext2D,
camera: Camera
): void {
this.emit('beforeUpdate', this);
withCacheRender(this, canvas, ctx, camera, canvas => {
this.renderFn(canvas, camera);
});
this.emit('afterUpdate', this);
}
size(width: number, height: number) {
this.width = width;
this.height = height;
this.canvas.size(width, height);
this.writing = this.using;
this.update(this);
}
pos(x: number, y: number) {
this.x = x;
this.y = y;
}
}

View File

@ -1,5 +1,5 @@
import { EmitableEvent, EventEmitter } from '../core/common/eventEmitter'; import { EmitableEvent, EventEmitter } from '../core/common/eventEmitter';
import { DamageEnemy } from './enemy/damage'; import type { DamageEnemy } from './enemy/damage';
// ----- 加载事件 // ----- 加载事件
interface GameLoadEvent extends EmitableEvent { interface GameLoadEvent extends EmitableEvent {
@ -75,25 +75,36 @@ class GameLoading extends EventEmitter<GameLoadEvent> {
export const loading = new GameLoading(); export const loading = new GameLoading();
export interface GameEvent extends EmitableEvent { export interface GameEvent extends EmitableEvent {
/** Emitted in events.prototype.resetGame. */ /** Emitted in libs/events.js resetGame. */
reset: () => void; reset: () => void;
/** Emitted in src/App.vue setup. */ /** Emitted in src/App.vue setup. */
mounted: () => void; mounted: () => void;
/** Emitted in plugin/ui.js */ /** Emitted in plugin/game/ui.ts updateStatusBar_update */
statusBarUpdate: () => void; statusBarUpdate: () => void;
/** Emitted in core/index.ts */ /** Emitted in core/index.ts */
renderLoaded: () => void; renderLoaded: () => void;
// /** Emitted in libs/events.js */ /** Emitted in libs/events.js getItem */
afterGetItem: ( afterGetItem: (
itemId: AllIdsOf<'items'>, itemId: AllIdsOf<'items'>,
x: number, x: number,
y: number, y: number,
isGentleClick: boolean isGentleClick: boolean
) => void; ) => void;
/** Emitted in libs/events.js _openDoor_animate */
afterOpenDoor: (doorId: AllIdsOf<'animates'>, x: number, y: number) => void; afterOpenDoor: (doorId: AllIdsOf<'animates'>, x: number, y: number) => void;
/** Emitted in project/functions.js afterChangeFloor */
afterChangeFloor: (floorId: FloorIds) => void; afterChangeFloor: (floorId: FloorIds) => void;
/** Emitted in project/functions.js moveOneStep */
moveOneStep: (x: number, y: number, floorId: FloorIds) => void; moveOneStep: (x: number, y: number, floorId: FloorIds) => void;
/** Emitted in src/game/enemy/battle.ts afterBattle */
afterBattle: (enemy: DamageEnemy, x?: number, y?: number) => void; afterBattle: (enemy: DamageEnemy, x?: number, y?: number) => void;
/** Emitted in libs/events.js changingFloor */
changingFloor: (floorId: FloorIds, heroLoc: Loc) => void;
drawHero: (
status?: Exclude<keyof MaterialIcon['hero']['down'], 'loc'>,
offset?: number,
frame?: number
) => void;
} }
export const hook = new EventEmitter<GameEvent>(); export const hook = new EventEmitter<GameEvent>();

View File

@ -1,8 +1,11 @@
import { has } from '@/plugin/game/utils'; import { has } from '@/plugin/game/utils';
import { loading } from '../game';
export namespace BluePalace { export namespace BluePalace {
type DoorConvertInfo = [id: AllIds, x: number, y: number]; type DoorConvertInfo = [id: AllIds, x: number, y: number];
// ---------- 黄蓝门转换
function drawDoors( function drawDoors(
ctx: CanvasRenderingContext2D, ctx: CanvasRenderingContext2D,
convert: DoorConvertInfo[], convert: DoorConvertInfo[],
@ -75,4 +78,30 @@ export namespace BluePalace {
}); });
}); });
} }
// ---------- 传送门部分
interface Portal {
fx: number;
fy: number;
dir: Dir;
tx: number;
ty: number;
toDir: Dir;
}
export const portals: Partial<Record<FloorIds, Portal[]>> = {
MT75: [
{ fx: 7, fy: 7, dir: 'left', tx: 9, ty: 9, toDir: 'down' },
{ fx: 5, fy: 11, dir: 'right', tx: 7, ty: 9, toDir: 'up' },
{ fx: 4, fy: 6, dir: 'right', tx: 9, ty: 4, toDir: 'up' },
{ fx: 5, fy: 9, dir: 'right', tx: 3, ty: 7, toDir: 'up' },
{ fx: 7, fy: 5, dir: 'right', tx: 4, ty: 9, toDir: 'up' }
]
};
loading.once('coreInit', initPortals);
function initPortals() {
// 主要是复写勇士绘制以及传送判定,还有自动寻路
}
} }

View File

@ -25,13 +25,16 @@ import type * as damage from './enemy/damage';
import type { Logger } from '@/core/common/logger'; import type { Logger } from '@/core/common/logger';
import type { Danmaku } from '@/core/main/custom/danmaku'; import type { Danmaku } from '@/core/main/custom/danmaku';
import type * as misc from './mechanism/misc'; import type * as misc from './mechanism/misc';
import type { MotaCanvas2D } from '@/core/fx/canvas2d';
import type * as portal from '@/core/fx/portal';
import type { HeroRenderer } from '@/core/render/hero';
interface ClassInterface { interface ClassInterface {
// 渲染进程与游戏进程通用 // 渲染进程与游戏进程通用
EventEmitter: typeof EventEmitter; EventEmitter: typeof EventEmitter;
IndexedEventEmitter: typeof IndexedEventEmitter; IndexedEventEmitter: typeof IndexedEventEmitter;
Disposable: typeof Disposable; Disposable: typeof Disposable;
// 定义于渲染进程录像中会进行polyfill但是不执行任何内容 // 定义于渲染进程
GameStorage: typeof GameStorage; GameStorage: typeof GameStorage;
MotaSetting: typeof MotaSetting; MotaSetting: typeof MotaSetting;
SettingDisplayer: typeof SettingDisplayer; SettingDisplayer: typeof SettingDisplayer;
@ -45,12 +48,13 @@ interface ClassInterface {
SoundEffect: typeof SoundEffect; SoundEffect: typeof SoundEffect;
SoundController: typeof SoundController; SoundController: typeof SoundController;
BgmController: typeof BgmController; BgmController: typeof BgmController;
MotaCanvas2D: typeof MotaCanvas2D;
Danmaku: typeof Danmaku;
// todo: 放到插件 ShaderEffect: typeof ShaderEffect; // todo: 放到插件 ShaderEffect: typeof ShaderEffect;
// 定义于游戏进程,渲染进程依然可用 // 定义于游戏进程,渲染进程依然可用
Range: typeof Range; Range: typeof Range;
EnemyCollection: typeof EnemyCollection; EnemyCollection: typeof EnemyCollection;
DamageEnemy: typeof DamageEnemy; DamageEnemy: typeof DamageEnemy;
Danmaku: typeof Danmaku;
} }
type _IBattle = typeof battle; type _IBattle = typeof battle;
@ -90,6 +94,12 @@ interface ModuleInterface {
Mechanism: { Mechanism: {
BluePalace: typeof misc.BluePalace; BluePalace: typeof misc.BluePalace;
}; };
Effect: {
Portal: typeof portal;
};
Render: {
heroRender: HeroRenderer;
};
} }
interface SystemInterfaceMap { interface SystemInterfaceMap {

View File

@ -6,7 +6,7 @@ let pop: any[] = [];
let time = 0; let time = 0;
export function init() { export function init() {
core.registerAnimationFrame('pop', true, popValue); // core.registerAnimationFrame('pop', true, popValue);
} }
/** /**

View File

@ -1,3 +1,4 @@
import { sleep } from 'mutate-animate';
import { tip } from './utils'; import { tip } from './utils';
export default function init() { export default function init() {
@ -29,6 +30,12 @@ window.addEventListener('resize', () => {
}); });
checkMobile(); checkMobile();
sleep(2000).then(() => {
if (!isMobile) {
tip('info', `注意,不推荐使用浏览器的缩放功能,使用游戏内的缩放即可`);
}
});
function checkMobile() { function checkMobile() {
if (isMobile && !alerted) { if (isMobile && !alerted) {
tip( tip(

View File

@ -242,9 +242,6 @@ export function tip(
class: 'antdv-message' class: 'antdv-message'
}); });
} }
sleep(2000).then(() => {
tip('info', `注意,不推荐使用浏览器的缩放功能,使用游戏内的缩放即可`);
});
/** /**
* *

View File

@ -368,7 +368,7 @@ interface Control {
*/ */
drawHero( drawHero(
status?: Exclude<keyof MaterialIcon['hero']['down'], 'loc'>, status?: Exclude<keyof MaterialIcon['hero']['down'], 'loc'>,
offset?: number, offset?: number | (Loc & { offset: number }),
frame?: number frame?: number
): void; ): void;
@ -1071,6 +1071,14 @@ interface Control {
_drawHero_updateViewport(x: number, y: number, offset: Loc): void; _drawHero_updateViewport(x: number, y: number, offset: Loc): void;
_moveAction_moving(callback: () => void): void; _moveAction_moving(callback: () => void): void;
_drawHero_draw(
direction: Dir,
x: number,
y: number,
status: Exclude<keyof MaterialIcon['hero']['down'], 'loc'>,
offset: Loc & { offset: number },
frame?: number
): void;
} }
declare const control: new () => Control; declare const control: new () => Control;

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

@ -240,6 +240,7 @@ interface AnimateFrame {
readonly animateTime: number; readonly animateTime: number;
/** /**
* @deprecated
* *
*/ */
moveTime: number; moveTime: number;
@ -251,6 +252,7 @@ interface AnimateFrame {
lastLegTime: number; lastLegTime: number;
/** /**
* @deprecated
* 使 * 使
*/ */
leftLeg: boolean; leftLeg: boolean;

View File

@ -628,6 +628,7 @@ interface InitGameStatus {
lockControl: boolean; lockControl: boolean;
/** /**
* @deprecated \
* libs翻西西 * libs翻西西
*/ */
heroMoving: number; heroMoving: number;
@ -792,6 +793,11 @@ interface Follower {
* id * id
*/ */
name: ImageIds; name: ImageIds;
direction: Dir;
x: number;
y: number;
stop: boolean;
} }
interface HeroStatistics { interface HeroStatistics {

View File

@ -290,10 +290,10 @@ gameKey
} }
}) })
.realize('@shop_add', () => { .realize('@shop_add', () => {
count.value--; count.value++;
}) })
.realize('@shop_min', () => { .realize('@shop_min', () => {
count.value++; count.value--;
}) })
.realize('exit', () => { .realize('exit', () => {
exit(); exit();
@ -311,10 +311,12 @@ function exit() {
} }
onMounted(async () => { onMounted(async () => {
core.lockControl();
core.status.route.push(`openShop:${id}`); core.status.route.push(`openShop:${id}`);
}); });
onUnmounted(() => { onUnmounted(() => {
core.unlockControl();
gameKey.dispose(props.ui.symbol); gameKey.dispose(props.ui.symbol);
}); });
</script> </script>