mirror of
https://github.com/unanmed/HumanBreak.git
synced 2025-04-19 17:16:08 +08:00
fix: 光环显示 & 删除无用内容
This commit is contained in:
parent
9597831d74
commit
470be5edba
@ -142,7 +142,7 @@
|
||||
<canvas class='gameCanvas draw-canvas hide' id='event2'></canvas>
|
||||
<canvas class='gameCanvas draw-canvas hide' id='fg'></canvas>
|
||||
<canvas class='gameCanvas hide' id='damage'></canvas>
|
||||
<canvas class='gameCanvas' id='animate'></canvas>
|
||||
<canvas class='gameCanvas hide' id='animate'></canvas>
|
||||
<canvas class='gameCanvas hide' id='curtain'></canvas>
|
||||
<canvas class='gameCanvas' id='ui'></canvas>
|
||||
<canvas class='gameCanvas' id='data'>此浏览器不支持HTML5</canvas>
|
||||
|
@ -121,6 +121,7 @@ hook.once('reset', () => {
|
||||
Mota.rewrite(core.control, 'loadData', 'add', () => {
|
||||
if (!main.replayChecking) {
|
||||
Shadow.update(true);
|
||||
LayerShadowExtends.shadowList.forEach(v => v.update());
|
||||
}
|
||||
});
|
||||
// Mota.require('var', 'hook').on('changingFloor', (floorId) => {
|
||||
@ -141,7 +142,7 @@ hook.on('setBlock', () => {
|
||||
})
|
||||
hook.on('changingFloor', floorId => {
|
||||
Shadow.clearBuffer();
|
||||
Shadow.update();
|
||||
Shadow.update(true);
|
||||
// setCanvasFilterByFloorId(floorId);
|
||||
LayerShadowExtends.shadowList.forEach(v => v.update());
|
||||
})
|
||||
|
@ -17,8 +17,6 @@
|
||||
// import frag from '@/plugin/fx/frag';
|
||||
// import { Mota } from '.';
|
||||
|
||||
import * as shadow from '@/plugin/shadow/shadow';
|
||||
import * as gameShadow from '@/plugin/shadow/gameShadow';
|
||||
import * as fly from '@/plugin/ui/fly';
|
||||
import * as chase from '@/plugin/chase/chase';
|
||||
import * as completion from '@/plugin/completion';
|
||||
@ -29,8 +27,6 @@ import * as gameCanvas from '@/plugin/fx/gameCanvas';
|
||||
import * as smooth from '@/plugin/fx/smoothView';
|
||||
import * as animateController from '@/plugin/animateController';
|
||||
|
||||
Mota.Plugin.register('shadow_r', shadow, shadow.init);
|
||||
Mota.Plugin.register('gameShadow_r', gameShadow, gameShadow.init);
|
||||
Mota.Plugin.register('fly_r', fly);
|
||||
Mota.Plugin.register('chase_r', chase);
|
||||
Mota.Plugin.register('completion_r', completion, completion.init);
|
||||
|
@ -8,6 +8,7 @@ import { LayerShadowExtends } from '../fx/shadow';
|
||||
import { LayerGroupFilter } from '@/plugin/fx/gameCanvas';
|
||||
import { LayerGroupAnimate } from './preset/animate';
|
||||
import { LayerGroupPortal } from '@/plugin/fx/portal';
|
||||
import { LayerGroupHalo } from '@/plugin/fx/halo';
|
||||
|
||||
let main: MotaRenderer;
|
||||
|
||||
@ -35,16 +36,19 @@ Mota.require('var', 'loading').once('loaded', () => {
|
||||
const filter = new LayerGroupFilter();
|
||||
const animate = new LayerGroupAnimate();
|
||||
const portal = new LayerGroupPortal();
|
||||
const halo = new LayerGroupHalo();
|
||||
layer.extends(damage);
|
||||
layer.extends(detail);
|
||||
layer.extends(filter);
|
||||
layer.extends(portal);
|
||||
layer.extends(halo);
|
||||
layer.getLayer('event')?.extends(hero);
|
||||
layer.getLayer('event')?.extends(door);
|
||||
layer.getLayer('event')?.extends(shadow);
|
||||
layer.extends(animate);
|
||||
|
||||
render.appendChild(layer);
|
||||
// console.log(render);
|
||||
});
|
||||
|
||||
Mota.require('var', 'hook').on('reset', () => {
|
||||
|
@ -56,6 +56,7 @@ export class FloorDamageExtends
|
||||
private create() {
|
||||
if (this.sprite) return;
|
||||
const sprite = new Damage();
|
||||
sprite.setZIndex(80);
|
||||
this.group.appendChild(sprite);
|
||||
this.sprite = sprite;
|
||||
}
|
||||
|
@ -83,6 +83,14 @@ export interface ILayerGroupRenderExtends {
|
||||
|
||||
export type FloorLayer = 'bg' | 'bg2' | 'event' | 'fg' | 'fg2';
|
||||
|
||||
const layerZIndex: Record<FloorLayer, number> = {
|
||||
bg: 10,
|
||||
bg2: 20,
|
||||
event: 30,
|
||||
fg: 40,
|
||||
fg2: 50
|
||||
};
|
||||
|
||||
export class LayerGroup extends Container implements IAnimateFrame {
|
||||
/** 地图组列表 */
|
||||
// static list: Set<LayerGroup> = new Set();
|
||||
@ -190,6 +198,7 @@ export class LayerGroup extends Container implements IAnimateFrame {
|
||||
const l = new Layer();
|
||||
l.layer = layer;
|
||||
if (l.layer) this.layers.set(l.layer, l);
|
||||
l.setZIndex(layerZIndex[layer]);
|
||||
this.appendChild(l);
|
||||
|
||||
for (const ex of this.extend.values()) {
|
||||
|
@ -1,6 +1,8 @@
|
||||
import { Animation, linear, sleep } from 'mutate-animate';
|
||||
import { has } from '../utils';
|
||||
|
||||
// todo: 移植到渲染树
|
||||
|
||||
interface SplittedImage {
|
||||
canvas: HTMLCanvasElement;
|
||||
x: number;
|
||||
|
@ -5,10 +5,6 @@ import {
|
||||
LayerGroup
|
||||
} from '@/core/render/preset/layer';
|
||||
|
||||
export default function init() {
|
||||
return {};
|
||||
}
|
||||
|
||||
const filterMap: [FloorIds[], string][] = [];
|
||||
|
||||
function getCanvasFilterByFloorId(floorId: FloorIds = core.status.floorId) {
|
||||
|
106
src/plugin/fx/halo.ts
Normal file
106
src/plugin/fx/halo.ts
Normal file
@ -0,0 +1,106 @@
|
||||
import { logger } from '@/core/common/logger';
|
||||
import { MotaOffscreenCanvas2D } from '@/core/fx/canvas2d';
|
||||
import { mainSetting } from '@/core/main/setting';
|
||||
import { LayerGroupFloorBinder } from '@/core/render/preset/floor';
|
||||
import {
|
||||
ILayerGroupRenderExtends,
|
||||
LayerGroup
|
||||
} from '@/core/render/preset/layer';
|
||||
import { Sprite } from '@/core/render/sprite';
|
||||
import { Transform } from '@/core/render/transform';
|
||||
|
||||
export class LayerGroupHalo implements ILayerGroupRenderExtends {
|
||||
id: string = 'halo';
|
||||
|
||||
group!: LayerGroup;
|
||||
binder!: LayerGroupFloorBinder;
|
||||
halo!: Halo;
|
||||
|
||||
awake(group: LayerGroup): void {
|
||||
this.group = group;
|
||||
const ex = group.getExtends('floor-binder');
|
||||
if (ex instanceof LayerGroupFloorBinder) {
|
||||
this.binder = ex;
|
||||
this.halo = new Halo();
|
||||
this.halo.setHD(true);
|
||||
this.halo.size(group.width, group.height);
|
||||
this.halo.setZIndex(75);
|
||||
this.halo.binder = ex;
|
||||
group.appendChild(this.halo);
|
||||
} else {
|
||||
logger.error(
|
||||
1401,
|
||||
`Halo extends needs 'floor-binder' extends as dependency.`
|
||||
);
|
||||
group.removeExtends('halo');
|
||||
}
|
||||
}
|
||||
|
||||
onDestroy(group: LayerGroup): void {
|
||||
this.halo?.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
const haloColor: Record<number, string[]> = {
|
||||
21: ['cyan'],
|
||||
25: ['purple'],
|
||||
26: ['blue'],
|
||||
27: ['red'],
|
||||
29: ['#3CFF49']
|
||||
};
|
||||
|
||||
class Halo extends Sprite {
|
||||
/** 单元格大小 */
|
||||
cellSize: number = 32;
|
||||
/** 当前楼层,用于获取有哪些光环 */
|
||||
binder!: LayerGroupFloorBinder;
|
||||
|
||||
constructor() {
|
||||
super('static', false);
|
||||
|
||||
this.setRenderFn((canvas, transform) => {
|
||||
this.drawHalo(canvas, transform);
|
||||
});
|
||||
}
|
||||
|
||||
drawHalo(canvas: MotaOffscreenCanvas2D, transform: Transform) {
|
||||
if (!mainSetting.getValue('screen.halo', true)) return;
|
||||
const floorId = this.binder.getFloor();
|
||||
if (!floorId) return;
|
||||
const col = core.status.maps[floorId].enemy;
|
||||
if (!col) return;
|
||||
const [dx, dy] = col.translation;
|
||||
const list = col.haloList.concat(
|
||||
Object.keys(flags[`melt_${floorId}`] ?? {}).map(v => {
|
||||
const [x, y] = v.split(',').map(v => parseInt(v));
|
||||
return {
|
||||
type: 'square',
|
||||
data: {
|
||||
x: x + dx,
|
||||
y: y + dy,
|
||||
d: 3
|
||||
},
|
||||
special: 25
|
||||
};
|
||||
})
|
||||
);
|
||||
const { ctx } = canvas;
|
||||
const cell = this.cellSize;
|
||||
ctx.lineWidth = 1;
|
||||
for (const halo of list) {
|
||||
if (halo.type === 'square') {
|
||||
const { x, y, d } = halo.data;
|
||||
const [color, border] = haloColor[halo.special];
|
||||
const r = Math.floor(d / 2);
|
||||
const left = x - r;
|
||||
const top = y - r;
|
||||
ctx.fillStyle = color;
|
||||
ctx.strokeStyle = border ?? color;
|
||||
ctx.globalAlpha = 0.1;
|
||||
ctx.fillRect(left * cell, top * cell, d * cell, d * cell);
|
||||
ctx.globalAlpha = 0.6;
|
||||
ctx.strokeRect(left * cell, top * cell, d * cell, d * cell);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -227,7 +227,7 @@ export class FloorItemDetail implements ILayerGroupRenderExtends {
|
||||
for (const [key, value] of Object.entries(diff)) {
|
||||
if (!value) continue;
|
||||
const color = FloorItemDetail.detailColor[key] ?? '#fff';
|
||||
const text = value.toString();
|
||||
const text = Math.floor(value).toString();
|
||||
const renderable: DamageRenderable = {
|
||||
x: x * this.sprite.cellSize + 2,
|
||||
y: y * this.sprite.cellSize + 31 - n * 10,
|
||||
|
@ -1,54 +0,0 @@
|
||||
export default function init() {
|
||||
return { createGaussNoise };
|
||||
}
|
||||
|
||||
interface GaussNoiseConfig {
|
||||
/** 分辨率,最小为1,最大为画布长宽的最大值,默认为画布长宽最大值的一半,过大可能会卡顿 */
|
||||
resolution?: number;
|
||||
/** 画布宽度 */
|
||||
width?: number;
|
||||
/** 画布高度 */
|
||||
height?: number;
|
||||
/** 噪声灰度的均值,范围 0 ~ 255 */
|
||||
expectation: number;
|
||||
/** 噪声灰度方差 */
|
||||
deviation: number;
|
||||
/** 目标画布,如果不指定会创建一个新的 */
|
||||
canvas?: HTMLCanvasElement;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建一个高斯噪声
|
||||
* @param config 噪声选项
|
||||
* @returns 噪声画布
|
||||
*/
|
||||
export function createGaussNoise(config: GaussNoiseConfig): HTMLCanvasElement {
|
||||
const canvas = config.canvas ?? document.createElement('canvas');
|
||||
canvas.width = config.width ?? core._PX_;
|
||||
canvas.height = config.height ?? core._PY_;
|
||||
canvas.style.imageRendering = 'pixelated';
|
||||
const ctx = canvas.getContext('2d')!;
|
||||
ctx.imageSmoothingEnabled = false;
|
||||
|
||||
const max = Math.max(canvas.width, canvas.height);
|
||||
const resolution = Math.min(max, config.resolution ?? max / 2);
|
||||
|
||||
const step =
|
||||
max === canvas.width
|
||||
? canvas.width / resolution
|
||||
: canvas.height / resolution;
|
||||
|
||||
for (let x = 0; x < canvas.width; x += step) {
|
||||
for (let y = 0; y < canvas.height; y += step) {
|
||||
const random =
|
||||
Math.sqrt(Math.log(Math.random()) * -2) *
|
||||
Math.sin(2 * Math.PI * Math.random());
|
||||
const gray = 255 - random * config.deviation - config.expectation;
|
||||
|
||||
ctx.fillStyle = `rgba(${gray},${gray},${gray},${gray / 255})`;
|
||||
ctx.fillRect(x, y, step, step);
|
||||
}
|
||||
}
|
||||
|
||||
return canvas;
|
||||
}
|
@ -1,5 +1,4 @@
|
||||
import { has, ofDir } from '@/plugin/game/utils';
|
||||
import { drawHalo } from '../fx/halo';
|
||||
|
||||
export function init() {
|
||||
// 伤害弹出
|
||||
|
@ -986,11 +986,8 @@ export function init() {
|
||||
core.setHeroLoc('y', ny);
|
||||
core.setHeroLoc('direction', action);
|
||||
}
|
||||
if (!main.replayChecking) {
|
||||
|
||||
setTimeout(core.replay, 100);
|
||||
} else {
|
||||
core.replay();
|
||||
}
|
||||
|
||||
return true;
|
||||
} else {
|
||||
|
@ -1,69 +0,0 @@
|
||||
const haloColor: Record<number, string[]> = {
|
||||
21: ['cyan'],
|
||||
25: ['purple'],
|
||||
26: ['blue'],
|
||||
27: ['red'],
|
||||
29: ['#3CFF49']
|
||||
};
|
||||
|
||||
export function drawHalo(
|
||||
ctx: CanvasRenderingContext2D,
|
||||
onMap: boolean,
|
||||
floorId: FloorIds
|
||||
) {
|
||||
if (main.replayChecking) return;
|
||||
const setting = Mota.require('var', 'mainSetting');
|
||||
if (!setting.getValue('screen.halo', true)) return;
|
||||
Mota.require('fn', 'ensureFloorDamage')(floorId);
|
||||
const col = core.status.maps[floorId].enemy;
|
||||
const [dx, dy] = col.translation;
|
||||
const list = col.haloList.concat(
|
||||
Object.keys(flags[`melt_${floorId}`] ?? {}).map(v => {
|
||||
const [x, y] = v.split(',').map(v => parseInt(v));
|
||||
return {
|
||||
type: 'square',
|
||||
data: {
|
||||
x: x + dx,
|
||||
y: y + dy,
|
||||
d: 3
|
||||
},
|
||||
special: 25
|
||||
};
|
||||
})
|
||||
);
|
||||
ctx.save();
|
||||
for (const halo of list) {
|
||||
if (halo.type === 'square') {
|
||||
const { x, y, d } = halo.data;
|
||||
const [color, border] = haloColor[halo.special];
|
||||
const r = Math.floor(d / 2);
|
||||
let left = x - r,
|
||||
right = x + r,
|
||||
top = y - r,
|
||||
bottom = y + r;
|
||||
if (onMap && core.bigmap.v2) {
|
||||
left -= core.bigmap.posX;
|
||||
top -= core.bigmap.posY;
|
||||
right -= core.bigmap.posX;
|
||||
bottom -= core.bigmap.posY;
|
||||
if (
|
||||
right < -1 ||
|
||||
left > core._PX_ / 32 + 1 ||
|
||||
top < -1 ||
|
||||
bottom > core._PY_ / 32 + 1
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
ctx.fillStyle = color;
|
||||
ctx.strokeStyle = border ?? color;
|
||||
ctx.lineWidth = 1;
|
||||
ctx.globalAlpha = 0.1;
|
||||
ctx.fillRect(left * 32, top * 32, d * 32, d * 32);
|
||||
ctx.globalAlpha = 0.6;
|
||||
ctx.strokeRect(left * 32, top * 32, d * 32, d * 32);
|
||||
}
|
||||
}
|
||||
|
||||
ctx.restore();
|
||||
}
|
@ -1,34 +0,0 @@
|
||||
function drawHeroDetail(px: number, py: number) {
|
||||
const setting = Mota.require('var', 'mainSetting');
|
||||
if (!setting.getValue('screen.heroDetail', false)) return;
|
||||
const { hp, atk, def } = core.status.hero;
|
||||
const toDraw = {
|
||||
atk: {
|
||||
value: atk,
|
||||
color: '#FF7A7A'
|
||||
},
|
||||
def: {
|
||||
value: def,
|
||||
color: '#00E6F1'
|
||||
},
|
||||
hp: {
|
||||
value: hp,
|
||||
color: '#F9FFFF0'
|
||||
}
|
||||
};
|
||||
|
||||
let i = 0;
|
||||
for (const [key, value] of Object.entries(toDraw)) {
|
||||
const ctx = core.canvas['hero'];
|
||||
core.fillBoldText(
|
||||
ctx,
|
||||
core.formatBigNumber(value.value),
|
||||
px,
|
||||
py - 10 * i,
|
||||
value.color
|
||||
);
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
export { drawHeroDetail };
|
@ -1,60 +0,0 @@
|
||||
// @ts-nocheck
|
||||
|
||||
var heroMoving = timestamp => {
|
||||
if (core.status.heroMoving <= 0) return;
|
||||
if (timestamp - core.animateFrame.moveTime > core.values.moveSpeed) {
|
||||
core.animateFrame.leftLeg++;
|
||||
core.animateFrame.moveTime = timestamp;
|
||||
}
|
||||
core.drawHero(
|
||||
['stop', 'leftFoot', 'midFoot', 'rightFoot'][
|
||||
core.animateFrame.leftLeg % 4
|
||||
],
|
||||
4 * core.status.heroMoving
|
||||
);
|
||||
};
|
||||
|
||||
export function init() {
|
||||
['up', 'down', 'left', 'right'].forEach(one => {
|
||||
// 指定中间帧动画
|
||||
core.material.icons.hero[one].midFoot = 2;
|
||||
});
|
||||
|
||||
core.registerAnimationFrame('heroMoving', true, heroMoving);
|
||||
|
||||
core.events._eventMoveHero_moving = function (step, moveSteps) {
|
||||
var curr = moveSteps[0];
|
||||
var direction = curr[0],
|
||||
x = core.getHeroLoc('x'),
|
||||
y = core.getHeroLoc('y');
|
||||
// ------ 前进/后退
|
||||
var o = direction == 'backward' ? -1 : 1;
|
||||
if (direction == 'forward' || direction == 'backward')
|
||||
direction = core.getHeroLoc('direction');
|
||||
var faceDirection = direction;
|
||||
if (direction == 'leftup' || direction == 'leftdown')
|
||||
faceDirection = 'left';
|
||||
if (direction == 'rightup' || direction == 'rightdown')
|
||||
faceDirection = 'right';
|
||||
core.setHeroLoc('direction', direction);
|
||||
if (curr[1] <= 0) {
|
||||
core.setHeroLoc('direction', faceDirection);
|
||||
moveSteps.shift();
|
||||
return true;
|
||||
}
|
||||
if (step <= 4) core.drawHero('stop', 4 * o * step);
|
||||
else if (step <= 8) core.drawHero('leftFoot', 4 * o * step);
|
||||
else if (step <= 12) core.drawHero('midFoot', 4 * o * (step - 8));
|
||||
else if (step <= 16) core.drawHero('rightFoot', 4 * o * (step - 8)); // if (step == 8) {
|
||||
if (step == 8 || step == 16) {
|
||||
core.setHeroLoc('x', x + o * core.utils.scan2[direction].x, true);
|
||||
core.setHeroLoc('y', y + o * core.utils.scan2[direction].y, true);
|
||||
core.updateFollowers();
|
||||
curr[1]--;
|
||||
if (curr[1] <= 0) moveSteps.shift();
|
||||
core.setHeroLoc('direction', faceDirection);
|
||||
return step == 16;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
}
|
@ -27,118 +27,7 @@ export function init() {
|
||||
|
||||
// floor.enemy.render(true);
|
||||
|
||||
getItemDetail(floorId, onMap); // 宝石血瓶详细信息
|
||||
// getItemDetail(floorId, onMap); // 宝石血瓶详细信息
|
||||
// this.drawDamage(ctx, floorId);
|
||||
};
|
||||
}
|
||||
|
||||
// 获取宝石信息 并绘制
|
||||
function getItemDetail(floorId: FloorIds, onMap: boolean) {
|
||||
const setting = Mota.require('var', 'mainSetting');
|
||||
if (!setting.getValue('screen.itemDetail')) return;
|
||||
floorId ??= core.status.thisMap.floorId;
|
||||
let diff: Record<string | symbol, number | undefined> = {};
|
||||
const before = core.status.hero;
|
||||
const hero = core.clone(core.status.hero);
|
||||
const handler: ProxyHandler<any> = {
|
||||
set(target, key, v) {
|
||||
diff[key] = v - (target[key] || 0);
|
||||
if (!diff[key]) diff[key] = void 0;
|
||||
return true;
|
||||
}
|
||||
};
|
||||
core.status.hero = new Proxy(hero, handler);
|
||||
|
||||
core.status.maps[floorId].blocks.forEach(function (block) {
|
||||
if (block.event.cls !== 'items' || block.disable) return;
|
||||
const x = block.x,
|
||||
y = block.y;
|
||||
// v2优化,只绘制范围内的部分
|
||||
if (onMap && core.bigmap.v2) {
|
||||
if (
|
||||
x < core.bigmap.posX - core.bigmap.extend ||
|
||||
x > core.bigmap.posX + core._PX_ + core.bigmap.extend ||
|
||||
y < core.bigmap.posY - core.bigmap.extend ||
|
||||
y > core.bigmap.posY + core._PY_ + core.bigmap.extend
|
||||
) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
diff = {};
|
||||
const id = block.event.id as AllIdsOf<'items'>;
|
||||
const item = core.material.items[id];
|
||||
if (item.cls === 'equips') {
|
||||
// 装备也显示
|
||||
const diff: Record<string, any> = core.clone(
|
||||
item.equip.value ?? {}
|
||||
);
|
||||
const per = item.equip.percentage ?? {};
|
||||
for (const name in per) {
|
||||
diff[name + 'per'] =
|
||||
per[name as SelectKey<HeroStatus, number>].toString() + '%';
|
||||
}
|
||||
drawItemDetail(diff, x, y);
|
||||
return;
|
||||
}
|
||||
// 跟数据统计原理一样 执行效果 前后比较
|
||||
core.setFlag('__statistics__', true);
|
||||
try {
|
||||
eval(item.itemEffect!);
|
||||
} catch (error) {}
|
||||
drawItemDetail(diff, x, y);
|
||||
});
|
||||
core.status.hero = before;
|
||||
window.hero = before;
|
||||
window.flags = before.flags;
|
||||
}
|
||||
|
||||
// 绘制
|
||||
function drawItemDetail(diff: any, x: number, y: number) {
|
||||
const px = 32 * x + 2,
|
||||
py = 32 * y + 31;
|
||||
let content = '';
|
||||
// 获得数据和颜色
|
||||
let i = 0;
|
||||
for (const name in diff) {
|
||||
if (!diff[name]) continue;
|
||||
let color = '#fff';
|
||||
|
||||
if (typeof diff[name] === 'number')
|
||||
content = core.formatBigNumber(Math.round(diff[name]), true);
|
||||
else content = diff[name];
|
||||
|
||||
switch (name) {
|
||||
case 'atk':
|
||||
case 'atkper':
|
||||
color = '#FF7A7A';
|
||||
break;
|
||||
case 'def':
|
||||
case 'defper':
|
||||
color = '#00E6F1';
|
||||
break;
|
||||
case 'mdef':
|
||||
case 'mdefper':
|
||||
color = '#6EFF83';
|
||||
break;
|
||||
case 'hp':
|
||||
color = '#A4FF00';
|
||||
break;
|
||||
case 'hpmax':
|
||||
case 'hpmaxper':
|
||||
color = '#F9FF00';
|
||||
break;
|
||||
case 'manaper':
|
||||
case 'mana':
|
||||
color = '#c66';
|
||||
break;
|
||||
}
|
||||
// 绘制
|
||||
core.status.damage.data.push({
|
||||
text: content,
|
||||
px: px,
|
||||
py: py - 10 * i,
|
||||
color: color as Color
|
||||
});
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
@ -1,11 +1,9 @@
|
||||
/* @__PURE__ */ import './dev/hotReload'; // 仅开发会用到
|
||||
import * as fiveLayer from './fiveLayer';
|
||||
import * as heroFourFrames from './fx/heroFourFrames';
|
||||
import * as itemDetail from './fx/itemDetail';
|
||||
import * as replay from './replay';
|
||||
import * as ui from './ui';
|
||||
import * as rewrite from './fx/rewrite';
|
||||
import * as halo from './fx/halo';
|
||||
import * as loopMap from './loopMap';
|
||||
import * as removeMap from './removeMap';
|
||||
import * as shop from './shop';
|
||||
@ -29,10 +27,8 @@ Mota.Plugin.register('chase_g', chase);
|
||||
Mota.Plugin.register('skill_g', skill);
|
||||
Mota.Plugin.register('towerBoss_g', towerBoss);
|
||||
Mota.Plugin.register('fiveLayer_g', fiveLayer, fiveLayer.init);
|
||||
Mota.Plugin.register('heroFourFrames_g', heroFourFrames, heroFourFrames.init);
|
||||
Mota.Plugin.register('rewrite_g', rewrite, rewrite.init);
|
||||
Mota.Plugin.register('itemDetail_g', itemDetail, itemDetail.init);
|
||||
Mota.Plugin.register('halo_g', halo);
|
||||
// Mota.Plugin.register('study_g', study);
|
||||
Mota.Plugin.register('remainEnemy_g', remainEnemy);
|
||||
Mota.Plugin.register('checkBlock_g', checkBlock, checkBlock.init);
|
||||
|
@ -1,345 +0,0 @@
|
||||
import { has } from '../utils';
|
||||
|
||||
type CanvasStyle = string | CanvasPattern | CanvasGradient;
|
||||
|
||||
export class Layout {
|
||||
/** 画布 */
|
||||
canvas: HTMLCanvasElement;
|
||||
/** 绘制上下文 */
|
||||
ctx: CanvasRenderingContext2D;
|
||||
|
||||
static readonly CLEAR: number = 1;
|
||||
static readonly MASK: number = 2;
|
||||
static readonly IMAGE: number = 4;
|
||||
|
||||
static readonly FILL: number = 1;
|
||||
static readonly STROKE: number = 2;
|
||||
|
||||
constructor(canvas: HTMLCanvasElement) {
|
||||
this.canvas = canvas;
|
||||
this.ctx = canvas.getContext('2d')!;
|
||||
}
|
||||
|
||||
image(layout: Layout | CanvasImageSource | Path2D, type: number): this;
|
||||
image(
|
||||
layout: Layout | CanvasImageSource | Path2D,
|
||||
type: number,
|
||||
x: number,
|
||||
y: number
|
||||
): this;
|
||||
image(
|
||||
layout: Layout | CanvasImageSource | Path2D,
|
||||
type: number,
|
||||
x: number,
|
||||
y: number,
|
||||
w: number,
|
||||
h: number
|
||||
): this;
|
||||
image(
|
||||
layout: Layout | CanvasImageSource | Path2D,
|
||||
type: number,
|
||||
sx: number,
|
||||
sy: number,
|
||||
sw: number,
|
||||
sh: number,
|
||||
dx: number,
|
||||
dy: number,
|
||||
dw: number,
|
||||
dh: number
|
||||
): this;
|
||||
image(
|
||||
layout: Layout | CanvasImageSource | Path2D,
|
||||
type: number,
|
||||
sx: number = 0,
|
||||
sy: number = 0,
|
||||
sw?: number,
|
||||
sh?: number,
|
||||
dx?: number,
|
||||
dy?: number,
|
||||
dw?: number,
|
||||
dh?: number
|
||||
) {
|
||||
const img = layout instanceof Layout ? layout.canvas : layout;
|
||||
const fill = () => {
|
||||
if (img instanceof Path2D) {
|
||||
this.ctx.fill(img);
|
||||
} else {
|
||||
if (!has(sw)) {
|
||||
this.ctx.drawImage(img, sx, sy);
|
||||
} else if (!has(dx)) {
|
||||
this.ctx.drawImage(img, sx, sy, sw, sh!);
|
||||
} else {
|
||||
this.ctx.drawImage(img, sx, sy, sw, sh!, dx, dy!, dw!, dh!);
|
||||
}
|
||||
}
|
||||
};
|
||||
if (type & Layout.IMAGE) {
|
||||
// 绘制图片
|
||||
fill();
|
||||
}
|
||||
if (type & Layout.CLEAR) {
|
||||
// 按照图片清除一个区域
|
||||
this.ctx.save();
|
||||
this.ctx.globalCompositeOperation = 'destination-out';
|
||||
fill();
|
||||
this.ctx.restore();
|
||||
}
|
||||
if (type & Layout.MASK) {
|
||||
// 蒙版,只显示蒙版内的东西
|
||||
this.ctx.save();
|
||||
this.ctx.globalCompositeOperation = 'destination-in';
|
||||
fill();
|
||||
this.ctx.restore();
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 擦除一个矩形
|
||||
*/
|
||||
clear(x: number, y: number, w: number, h: number): this {
|
||||
this.ctx.clearRect(x, y, w, h);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 绘制文字
|
||||
* @param str 文字
|
||||
* @param type 绘制类型,FILL表示填充,STROKE表示描边,FILL | STROKE 表示既填充又描边
|
||||
* @param x 横坐标
|
||||
* @param y 纵坐标
|
||||
* @param maxWidth 最大宽度
|
||||
*/
|
||||
text(
|
||||
str: string,
|
||||
type: number,
|
||||
x: number,
|
||||
y: number,
|
||||
maxWidth?: number
|
||||
): this {
|
||||
if (type & Layout.FILL) this.ctx.fillText(str, x, y, maxWidth);
|
||||
if (type & Layout.STROKE) this.ctx.strokeText(str, x, y, maxWidth);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据路径进行绘制
|
||||
* @param path 路径
|
||||
* @param type 绘制类型
|
||||
*/
|
||||
path(path: Path2D, type: number, rule?: CanvasFillRule): this {
|
||||
if (type & Layout.FILL) this.ctx.fill(path, rule);
|
||||
if (type & Layout.STROKE) this.ctx.stroke(path);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存画布状态
|
||||
*/
|
||||
save(): this {
|
||||
this.ctx.save();
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 回退画布状态
|
||||
*/
|
||||
restore(): this {
|
||||
this.ctx.restore();
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置填充样式
|
||||
* @param style 样式
|
||||
*/
|
||||
fillStyle(style: CanvasStyle): this {
|
||||
this.ctx.fillStyle = style;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置描边样式
|
||||
* @param style 样式
|
||||
*/
|
||||
strokeStyle(style: CanvasStyle): this {
|
||||
this.ctx.strokeStyle = style;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置文本对齐
|
||||
* @param align 文本左右对齐方式
|
||||
*/
|
||||
textAlign(align: CanvasTextAlign): this {
|
||||
this.ctx.textAlign = align;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置文本基线
|
||||
* @param align 文本基线,即文本上下对齐方式
|
||||
*/
|
||||
textBaseline(align: CanvasTextBaseline): this {
|
||||
this.ctx.textBaseline = align;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置滤镜
|
||||
* @param filter 滤镜
|
||||
*/
|
||||
filter(filter: string): this {
|
||||
this.ctx.filter = filter;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置阴影信息
|
||||
* @param shadow 阴影信息
|
||||
*/
|
||||
shadow(shadow: Partial<CanvasShadowStyles>): this {
|
||||
for (const [p, v] of Object.entries(shadow)) {
|
||||
// @ts-ignore
|
||||
this.ctx[p] = v;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置线宽(描边宽度,包括字体描边)
|
||||
* @param width 宽度
|
||||
*/
|
||||
lineWidth(width: number): this {
|
||||
this.ctx.lineWidth = width;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置线尾样式
|
||||
* @param cap 线尾样式
|
||||
*/
|
||||
lineCap(cap: CanvasLineCap): this {
|
||||
this.ctx.lineCap = cap;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置线段连接方式样式
|
||||
* @param join 线段连接方式
|
||||
*/
|
||||
lineJoin(join: CanvasLineJoin): this {
|
||||
this.ctx.lineJoin = join;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置画布的字体
|
||||
* @param font 字体
|
||||
*/
|
||||
font(font: string): this {
|
||||
this.ctx.font = font;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置画布之后绘制的不透明度
|
||||
* @param alpha 不透明度
|
||||
*/
|
||||
alpha(alpha: number): this {
|
||||
this.ctx.globalAlpha = alpha;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置虚线样式
|
||||
* @param dash 虚线样式
|
||||
*/
|
||||
lineDash(dash: number[]): this {
|
||||
this.ctx.setLineDash(dash);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 放缩画布
|
||||
* @param x 横向放缩量
|
||||
* @param y 纵向放缩量
|
||||
*/
|
||||
scale(x: number, y: number): this {
|
||||
this.ctx.scale(x, y);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 旋转画布
|
||||
* @param rad 顺时针旋转的弧度数
|
||||
*/
|
||||
rotate(rad: number): this {
|
||||
this.ctx.rotate(rad);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 平移画布
|
||||
* @param x 水平平移量
|
||||
* @param y 竖直平移量
|
||||
*/
|
||||
translate(x: number, y: number): this {
|
||||
this.ctx.translate(x, y);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 重设变换矩阵
|
||||
*/
|
||||
transform(): Layout;
|
||||
/**
|
||||
* 叠加变换矩阵(当前画布的矩阵乘以传入的矩阵)
|
||||
* 矩阵说明:
|
||||
* [a c e]
|
||||
* [b d f]
|
||||
* [0 0 0]
|
||||
* @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
|
||||
): Layout;
|
||||
transform(
|
||||
a?: number,
|
||||
b?: number,
|
||||
c?: number,
|
||||
d?: number,
|
||||
e?: number,
|
||||
f?: number
|
||||
) {
|
||||
if (!has(a)) this.ctx.resetTransform();
|
||||
else this.ctx.transform(a, b!, c!, d!, e!, f!);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置混合方式,像image的蒙版功能与擦除功能本质上也是通过设置混合方式实现的
|
||||
* @param value 混合方式
|
||||
*/
|
||||
composite(value: GlobalCompositeOperation): this {
|
||||
this.ctx.globalCompositeOperation = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除这个布局
|
||||
*/
|
||||
destroy() {
|
||||
this.canvas.remove();
|
||||
}
|
||||
}
|
@ -1,255 +0,0 @@
|
||||
import { Layout } from './layout';
|
||||
|
||||
export class LayoutGame extends Layout {
|
||||
/** 画布id */
|
||||
id: string;
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
x: number,
|
||||
y: number,
|
||||
w: number,
|
||||
h: number,
|
||||
z: number
|
||||
) {
|
||||
const ctx = core.createCanvas(id, x, y, w, h, z);
|
||||
super(ctx.canvas);
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
image2(
|
||||
layout: Layout | CanvasImageSource | Path2D | ImageIds,
|
||||
type: number
|
||||
): LayoutGame;
|
||||
image2(
|
||||
layout: Layout | CanvasImageSource | Path2D | ImageIds,
|
||||
type: number,
|
||||
x: number,
|
||||
y: number
|
||||
): LayoutGame;
|
||||
image2(
|
||||
layout: Layout | CanvasImageSource | Path2D | ImageIds,
|
||||
type: number,
|
||||
x: number,
|
||||
y: number,
|
||||
w: number,
|
||||
h: number
|
||||
): LayoutGame;
|
||||
image2(
|
||||
layout: Layout | CanvasImageSource | Path2D | ImageIds,
|
||||
type: number,
|
||||
sx: number,
|
||||
sy: number,
|
||||
sw: number,
|
||||
sh: number,
|
||||
dx: number,
|
||||
dy: number,
|
||||
dw: number,
|
||||
dh: number
|
||||
): LayoutGame;
|
||||
image2(
|
||||
layout: Layout | CanvasImageSource | Path2D | ImageIds,
|
||||
type: number,
|
||||
sx?: number,
|
||||
sy?: number,
|
||||
sw?: number,
|
||||
sh?: number,
|
||||
dx?: number,
|
||||
dy?: number,
|
||||
dw?: number,
|
||||
dh?: number
|
||||
): this {
|
||||
const img =
|
||||
typeof layout === 'string'
|
||||
? core.material.images.images[layout]
|
||||
: layout;
|
||||
|
||||
this.image(img, type, sx!, sy!, sw!, sh!, dx!, dy!, dw!, dh!);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 绘制一个图标
|
||||
* @param id 图标id
|
||||
* @param x 横坐标
|
||||
* @param y 纵坐标
|
||||
* @param w 宽度
|
||||
* @param h 高度
|
||||
* @param frame 绘制第几帧
|
||||
*/
|
||||
icon(
|
||||
id: AllIds,
|
||||
x: number,
|
||||
y: number,
|
||||
w?: number,
|
||||
h?: number,
|
||||
frame?: number
|
||||
): this {
|
||||
core.drawIcon(this.ctx, id, x, y, w, h, frame);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 绘制WindowSkin
|
||||
* @param direction 指向箭头的方向
|
||||
*/
|
||||
winskin(
|
||||
background: any,
|
||||
x: number,
|
||||
y: number,
|
||||
w: number,
|
||||
h: number,
|
||||
direction?: 'up' | 'down',
|
||||
px?: number,
|
||||
py?: number
|
||||
): this {
|
||||
core.drawWindowSkin(
|
||||
background,
|
||||
this.ctx,
|
||||
x,
|
||||
y,
|
||||
w,
|
||||
h,
|
||||
direction,
|
||||
px,
|
||||
py
|
||||
);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 绘制一个箭头
|
||||
* @param x1 起始点横坐标
|
||||
* @param y1 起始点纵坐标
|
||||
* @param x2 终止点横坐标
|
||||
* @param y2 终止点纵坐标
|
||||
*/
|
||||
arrow(x1: number, y1: number, x2: number, y2: number): this {
|
||||
core.drawArrow(this.ctx, x1, y1, x2, y2);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 绘制线段
|
||||
*/
|
||||
line(x1: number, y1: number, x2: number, y2: number): this {
|
||||
this.ctx.beginPath();
|
||||
this.ctx.moveTo(x1, y1);
|
||||
this.ctx.lineTo(x2, y2);
|
||||
this.ctx.stroke();
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 绘制圆弧或扇形
|
||||
* @param type 绘制类型
|
||||
* @param start 起始弧度
|
||||
* @param end 终止弧度
|
||||
* @param anticlockwise 是否逆时针
|
||||
*/
|
||||
arc(
|
||||
type: number,
|
||||
x: number,
|
||||
y: number,
|
||||
r: number,
|
||||
start: number,
|
||||
end: number,
|
||||
anticlockwise: boolean = false
|
||||
): this {
|
||||
this.ctx.beginPath();
|
||||
this.ctx.arc(x, y, r, start, end, anticlockwise);
|
||||
this.draw(type);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 绘制一个圆
|
||||
* @param type 绘制类型
|
||||
*/
|
||||
circle(type: number, x: number, y: number, r: number): this {
|
||||
return this.arc(type, x, y, r, 0, Math.PI * 2);
|
||||
}
|
||||
|
||||
/**
|
||||
* 绘制椭圆
|
||||
*/
|
||||
ellipse(
|
||||
type: number,
|
||||
x: number,
|
||||
y: number,
|
||||
a: number,
|
||||
b: number,
|
||||
rotation: number = 0,
|
||||
start: number = 0,
|
||||
end: number = Math.PI * 2,
|
||||
anticlockwise: boolean = false
|
||||
): this {
|
||||
this.ctx.beginPath();
|
||||
this.ctx.ellipse(x, y, a, b, rotation, start, end, anticlockwise);
|
||||
this.draw(type);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 绘制多边形
|
||||
* @param type 绘制类型
|
||||
* @param nodes 多边形节点
|
||||
* @param close 是否闭合路径
|
||||
*/
|
||||
polygon(type: number, nodes: LocArr[], close: boolean = true): this {
|
||||
this.ctx.beginPath();
|
||||
this.ctx.moveTo(nodes[0][0], nodes[0][1]);
|
||||
for (let i = 1; i < nodes.length; i++) {
|
||||
this.ctx.lineTo(nodes[i][0], nodes[i][1]);
|
||||
}
|
||||
if (close) this.ctx.closePath();
|
||||
this.draw(type);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 绘制矩形
|
||||
*/
|
||||
rect(type: number, x: number, y: number, w: number, h: number): this {
|
||||
if (type & Layout.FILL) this.ctx.fillRect(x, y, w, h);
|
||||
if (type & Layout.STROKE) this.ctx.strokeRect(x, y, w, h);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 绘制圆角矩形
|
||||
*/
|
||||
roundRect(
|
||||
type: number,
|
||||
x: number,
|
||||
y: number,
|
||||
w: number,
|
||||
h: number,
|
||||
r: number
|
||||
) {
|
||||
this.ctx.beginPath();
|
||||
this.ctx.moveTo(x + r, y);
|
||||
this.ctx.lineTo(x + w - r, y);
|
||||
this.ctx.quadraticCurveTo(x + w, y, x + w, y + r);
|
||||
this.ctx.lineTo(x + w, y + h - r);
|
||||
this.ctx.quadraticCurveTo(x + w, y + h, x + w - r, y + h);
|
||||
this.ctx.lineTo(x + r, y + h);
|
||||
this.ctx.quadraticCurveTo(x, y + h, x, y + h - r);
|
||||
this.ctx.lineTo(x, y + r);
|
||||
this.ctx.quadraticCurveTo(x, y, x + r, y);
|
||||
this.ctx.closePath();
|
||||
this.draw(type);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除这个布局
|
||||
*/
|
||||
destroy() {
|
||||
core.deleteCanvas(this.id);
|
||||
}
|
||||
|
||||
private draw(type: number) {
|
||||
if (type & Layout.FILL) this.ctx.fill();
|
||||
if (type & Layout.STROKE) this.ctx.stroke();
|
||||
}
|
||||
}
|
@ -1,243 +0,0 @@
|
||||
import { Polygon } from './polygon';
|
||||
import {
|
||||
Light,
|
||||
getAllLights,
|
||||
initShadowCanvas,
|
||||
refreshLight,
|
||||
removeAllLights,
|
||||
setBackground,
|
||||
setBlur,
|
||||
setLightList,
|
||||
setShadowNodes
|
||||
} from './shadow';
|
||||
import { pColor } from '../utils';
|
||||
import { setCanvasFilterByFloorId } from '../fx/gameCanvas';
|
||||
|
||||
export function init() {
|
||||
// 勇士身上的光源
|
||||
// Mota.rewrite(core.control, 'drawHero', 'add', () => {
|
||||
// if (core.getFlag('__heroOpacity__') !== 0) {
|
||||
// getAllLights().forEach(v => {
|
||||
// if (!v.followHero) return;
|
||||
// v._offset ??= { x: v.x, y: v.y };
|
||||
// v.x = core.status.heroCenter.px + v._offset.x;
|
||||
// v.y = core.status.heroCenter.py + v._offset.y;
|
||||
// refreshLight();
|
||||
// });
|
||||
// }
|
||||
// });
|
||||
// // 更新地形数据
|
||||
// Mota.rewrite(core.maps, 'removeBlock', 'add', success => {
|
||||
// if (success && main.replayChecking) updateShadow(true);
|
||||
// return success;
|
||||
// });
|
||||
// Mota.rewrite(core.maps, 'setBlock', 'add', () => {
|
||||
// if (main.replayChecking) updateShadow(true);
|
||||
// });
|
||||
// Mota.rewrite(core.events, 'changingFloor', 'add', (_, floorId) => {
|
||||
// if (!main.replayChecking) {
|
||||
// updateShadow();
|
||||
// setCanvasFilterByFloorId(floorId);
|
||||
// }
|
||||
// });
|
||||
// // 初始化画布信息
|
||||
// Mota.rewrite(core.ui, 'deleteAllCanvas', 'add', () => {
|
||||
// if (main.mode === 'play' && !main.replayChecking) initShadowCanvas();
|
||||
// });
|
||||
}
|
||||
|
||||
const shadowInfo: Partial<Record<FloorIds, Light[]>> = {
|
||||
MT50: [
|
||||
{
|
||||
id: 'mt50_1',
|
||||
x: 144,
|
||||
y: 144,
|
||||
decay: 20,
|
||||
r: 150,
|
||||
color: pColor('#e953'),
|
||||
noShelter: true
|
||||
},
|
||||
{
|
||||
id: 'mt50_2',
|
||||
x: 336,
|
||||
y: 144,
|
||||
decay: 20,
|
||||
r: 150,
|
||||
color: pColor('#e953'),
|
||||
noShelter: true
|
||||
},
|
||||
{
|
||||
id: 'mt50_2',
|
||||
x: 336,
|
||||
y: 336,
|
||||
decay: 20,
|
||||
r: 150,
|
||||
color: pColor('#e953'),
|
||||
noShelter: true
|
||||
},
|
||||
{
|
||||
id: 'mt50_2',
|
||||
x: 144,
|
||||
y: 336,
|
||||
decay: 20,
|
||||
r: 150,
|
||||
color: pColor('#e953'),
|
||||
noShelter: true
|
||||
}
|
||||
],
|
||||
MT52: [
|
||||
{
|
||||
id: 'mt51_1',
|
||||
x: 13 * 32 + 16,
|
||||
y: 6 * 32 + 16,
|
||||
decay: 20,
|
||||
r: 250,
|
||||
color: pColor('#e953')
|
||||
},
|
||||
{
|
||||
id: 'mt51_2',
|
||||
x: 1 * 32 + 16,
|
||||
y: 13 * 32 + 16,
|
||||
decay: 20,
|
||||
r: 350,
|
||||
color: pColor('#e953')
|
||||
}
|
||||
]
|
||||
};
|
||||
const backgroundInfo: Partial<Record<FloorIds, Color>> = {
|
||||
MT50: pColor('#0006'),
|
||||
MT51: pColor('#0004'),
|
||||
MT52: pColor('#0004')
|
||||
};
|
||||
const blurInfo: Partial<Record<FloorIds, number>> = {};
|
||||
const immersionInfo: Partial<Record<FloorIds, number>> = {};
|
||||
const shadowCache: Partial<Record<FloorIds, Polygon[]>> = {};
|
||||
|
||||
let calMapShadow = true;
|
||||
|
||||
Mota.require('var', 'loading').once('coreInit', () => {
|
||||
core.floorIds.slice(61).forEach(v => {
|
||||
shadowInfo[v] ??= [];
|
||||
shadowInfo[v]?.push({
|
||||
id: `${v}_hero`,
|
||||
x: 0,
|
||||
y: 0,
|
||||
decay: 50,
|
||||
r: 300,
|
||||
color: 'transparent',
|
||||
followHero: true
|
||||
});
|
||||
core.floors[v].map.forEach((arr, y) => {
|
||||
arr.forEach((num, x) => {
|
||||
if (num === 103) {
|
||||
shadowInfo[v]?.push({
|
||||
id: `${v}_${x}_${y}`,
|
||||
x: x * 32 + 16,
|
||||
y: y * 32 + 16,
|
||||
decay: 20,
|
||||
r: 350,
|
||||
color: pColor('#e953')
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
export function updateShadow(nocache: boolean = false) {
|
||||
// todo: 需要优化,优化成bfs
|
||||
const floor = core.status.floorId;
|
||||
if (!shadowInfo[floor] || !backgroundInfo[floor]) {
|
||||
removeAllLights();
|
||||
setShadowNodes([]);
|
||||
setBackground('transparent');
|
||||
return;
|
||||
}
|
||||
const f = core.status.thisMap;
|
||||
const w = f.width;
|
||||
const h = f.height;
|
||||
const nodes: Polygon[] = [];
|
||||
if (calMapShadow) {
|
||||
if (shadowCache[floor] && !nocache) {
|
||||
setShadowNodes(shadowCache[floor]!);
|
||||
} else {
|
||||
core.extractBlocks();
|
||||
const blocks = core.getMapBlocksObj();
|
||||
core.status.maps[floor].blocks.forEach(v => {
|
||||
if (
|
||||
!['terrains', 'autotile', 'tileset', 'animates'].includes(
|
||||
v.event.cls
|
||||
)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (v.event.noPass) {
|
||||
const immerse = immersionInfo[floor] ?? 4;
|
||||
const x = v.x;
|
||||
const y = v.y;
|
||||
let left = x * 32 + immerse;
|
||||
let top = y * 32 + immerse;
|
||||
let right = left + 32 - immerse * 2;
|
||||
let bottom = top + 32 - immerse * 2;
|
||||
const l: LocString = `${x - 1},${y}`;
|
||||
const r: LocString = `${x + 1},${y}`;
|
||||
const t: LocString = `${x},${y - 1}`;
|
||||
const b: LocString = `${x},${y + 1}`;
|
||||
|
||||
if (x === 0) left -= immerse * 2;
|
||||
if (x + 1 === w) right += immerse * 2;
|
||||
if (y === 0) top -= immerse * 2;
|
||||
if (y + 1 === h) bottom += immerse * 2;
|
||||
|
||||
if (blocks[l] && blocks[l].event.noPass) {
|
||||
left -= immerse;
|
||||
}
|
||||
if (blocks[r] && blocks[r].event.noPass) {
|
||||
right += immerse;
|
||||
}
|
||||
if (blocks[t] && blocks[t].event.noPass) {
|
||||
top -= immerse;
|
||||
}
|
||||
if (blocks[b] && blocks[b].event.noPass) {
|
||||
bottom += immerse;
|
||||
}
|
||||
nodes.push(
|
||||
new Polygon([
|
||||
[left, top],
|
||||
[right, top],
|
||||
[right, bottom],
|
||||
[left, bottom]
|
||||
])
|
||||
);
|
||||
return;
|
||||
}
|
||||
});
|
||||
shadowCache[floor] = nodes;
|
||||
setShadowNodes(nodes);
|
||||
}
|
||||
} else {
|
||||
setShadowNodes([]);
|
||||
setBlur(0);
|
||||
}
|
||||
setLightList(shadowInfo[floor]!);
|
||||
setBackground(backgroundInfo[floor]!);
|
||||
setBlur(blurInfo[floor] ?? 3);
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除某一层的墙壁缓存
|
||||
* @param floorId 楼层id
|
||||
*/
|
||||
export function clearShadowCache(floorId: FloorIds) {
|
||||
delete shadowCache[floorId];
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置是否不计算墙壁遮挡,对所有灯光有效
|
||||
* @param n 目标值
|
||||
*/
|
||||
export function setCalShadow(n: boolean) {
|
||||
calMapShadow = n;
|
||||
updateShadow();
|
||||
}
|
@ -1,91 +0,0 @@
|
||||
export class Polygon {
|
||||
/**
|
||||
* 多边形的节点
|
||||
*/
|
||||
nodes: LocArr[];
|
||||
|
||||
private cache: Record<string, LocArr[][]> = {};
|
||||
|
||||
static from(...polygons: LocArr[][]) {
|
||||
return polygons.map(v => new Polygon(v));
|
||||
}
|
||||
|
||||
constructor(nodes: LocArr[]) {
|
||||
if (nodes.length < 3) {
|
||||
throw new Error(`Nodes number delivered is less than 3!`);
|
||||
}
|
||||
this.nodes = nodes.map(v => [v[0] + 32, v[1] + 32]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取一个点光源下的阴影
|
||||
*/
|
||||
shadowArea(x: number, y: number, r: number): LocArr[][] {
|
||||
const id = `${x},${y}`;
|
||||
if (this.cache[id]) return this.cache[id];
|
||||
const res: LocArr[][] = [];
|
||||
const w = (core._PX_ ?? core.__PIXELS__) + 64;
|
||||
const h = (core._PY_ ?? core.__PIXELS__) + 64;
|
||||
|
||||
const aspect = h / w;
|
||||
|
||||
const intersect = (nx: number, ny: number): LocArr => {
|
||||
const k = (ny - y) / (nx - x);
|
||||
if (k > aspect || k < -aspect) {
|
||||
if (ny < y) {
|
||||
const ix = x + y / k;
|
||||
return [2 * x - ix, 0];
|
||||
} else {
|
||||
const ix = x + (h - y) / k;
|
||||
return [ix, h];
|
||||
}
|
||||
} else {
|
||||
if (nx < x) {
|
||||
const iy = y + k * x;
|
||||
return [0, 2 * y - iy];
|
||||
} else {
|
||||
const iy = y + k * (w - x);
|
||||
return [w, iy];
|
||||
}
|
||||
}
|
||||
};
|
||||
const l = this.nodes.length;
|
||||
let now = intersect(...this.nodes[0]);
|
||||
for (let i = 0; i < l; i++) {
|
||||
const next = (i + 1) % l;
|
||||
const nextInter = intersect(...this.nodes[next]);
|
||||
const start = [this.nodes[i], now];
|
||||
const end = [nextInter, this.nodes[next]];
|
||||
let path: LocArr[];
|
||||
if (
|
||||
(now[0] === 0 && nextInter[1] === 0) ||
|
||||
(now[1] === 0 && nextInter[0] === 0)
|
||||
) {
|
||||
path = [...start, [0, 0], ...end];
|
||||
} else if (
|
||||
(now[0] === 0 && nextInter[1] === h) ||
|
||||
(now[1] === h && nextInter[0] === 0)
|
||||
) {
|
||||
path = [...start, [0, h], ...end];
|
||||
} else if (
|
||||
(now[0] === w && nextInter[1] === 0) ||
|
||||
(now[1] === 0 && nextInter[0] === w)
|
||||
) {
|
||||
path = [...start, [w, 0], ...end];
|
||||
} else if (
|
||||
(now[0] === w && nextInter[1] === h) ||
|
||||
(now[1] === h && nextInter[0] === w)
|
||||
) {
|
||||
path = [...start, [w, h], ...end];
|
||||
} else {
|
||||
path = [...start, ...end];
|
||||
}
|
||||
res.push(path);
|
||||
now = nextInter;
|
||||
}
|
||||
|
||||
this.cache[id] = res;
|
||||
|
||||
return res;
|
||||
}
|
||||
}
|
@ -1,468 +0,0 @@
|
||||
import {
|
||||
Animation,
|
||||
linear,
|
||||
PathFn,
|
||||
TimingFn,
|
||||
Transition
|
||||
} from 'mutate-animate';
|
||||
import { has } from '../utils';
|
||||
import { Polygon } from './polygon';
|
||||
|
||||
interface TransitionInfo {
|
||||
time: number;
|
||||
mode: TimingFn;
|
||||
}
|
||||
|
||||
export interface Light {
|
||||
id: string;
|
||||
x: number;
|
||||
y: number;
|
||||
r: number;
|
||||
/** 衰减开始半径 */
|
||||
decay: number;
|
||||
/** 颜色,每个值的范围0.0~1.0 */
|
||||
color: Color;
|
||||
/** 是否可以被物体遮挡 */
|
||||
noShelter?: boolean;
|
||||
/** 是否跟随勇士 */
|
||||
followHero?: boolean;
|
||||
/** 正在动画的属性 */
|
||||
_animating?: Record<string, boolean>;
|
||||
/** 执行渐变的属性 */
|
||||
_transition?: Record<string, TransitionInfo>;
|
||||
/** 表示是否是代理,只有设置渐变后才会变为true */
|
||||
_isProxy?: boolean;
|
||||
/** 跟随勇士的时候的偏移量 */
|
||||
_offset?: Loc;
|
||||
}
|
||||
|
||||
export function init() {
|
||||
core.registerAnimationFrame('shadow', true, () => {
|
||||
if (!needRefresh) return;
|
||||
drawShadow();
|
||||
});
|
||||
}
|
||||
|
||||
let canvas: HTMLCanvasElement;
|
||||
let ctx: CanvasRenderingContext2D;
|
||||
let lights: Light[] = [];
|
||||
let needRefresh = false;
|
||||
let shadowNodes: Polygon[] = [];
|
||||
let background: Color;
|
||||
let blur = 3;
|
||||
const temp1 = document.createElement('canvas');
|
||||
const temp2 = document.createElement('canvas');
|
||||
const temp3 = document.createElement('canvas');
|
||||
const ct1 = temp1.getContext('2d')!;
|
||||
const ct2 = temp2.getContext('2d')!;
|
||||
const ct3 = temp3.getContext('2d')!;
|
||||
|
||||
const animationList: Record<string, Animation> = {};
|
||||
const transitionList: Record<string, Transition> = {};
|
||||
|
||||
/**
|
||||
* 初始化阴影画布
|
||||
*/
|
||||
export function initShadowCanvas() {
|
||||
const w = core._PX_ ?? core.__PIXELS__;
|
||||
const h = core._PY_ ?? core.__PIXELS__;
|
||||
ctx = core.createCanvas('shadow', -32, -32, w + 64, h + 64, 55);
|
||||
canvas = ctx.canvas;
|
||||
|
||||
const s = core.domStyle.scale * devicePixelRatio;
|
||||
temp1.width = (w + 64) * s;
|
||||
temp1.height = (h + 64) * s;
|
||||
temp2.width = (w + 64) * s;
|
||||
temp2.height = (h + 64) * s;
|
||||
temp3.width = (w + 64) * s;
|
||||
temp3.height = (h + 64) * s;
|
||||
ct1.scale(s, s);
|
||||
ct2.scale(s, s);
|
||||
ct3.scale(s, s);
|
||||
canvas.style.filter = `blur(${blur}px)`;
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加一个光源
|
||||
* @param info 光源信息
|
||||
*/
|
||||
export function addLight(info: Light) {
|
||||
lights.push(info);
|
||||
needRefresh = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除一个光源
|
||||
* @param id 光源id
|
||||
*/
|
||||
export function removeLight(id: string) {
|
||||
const index = lights.findIndex(v => v.id === id);
|
||||
if (index === -1) {
|
||||
throw new ReferenceError(`You are going to remove nonexistent light!`);
|
||||
}
|
||||
lights.splice(index, 1);
|
||||
needRefresh = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置一个光源的信息
|
||||
* @param id 光源id
|
||||
* @param info 光源信息
|
||||
*/
|
||||
export function setLight(id: string, info: Partial<Light>) {
|
||||
if (has(info.id)) delete info.id;
|
||||
const light = lights.find(v => v.id === id);
|
||||
if (!light) {
|
||||
throw new ReferenceError(`You are going to set nonexistent light!`);
|
||||
}
|
||||
for (const [p, v] of Object.entries(info)) {
|
||||
light[p as SelectKey<Light, number>] = v as number;
|
||||
}
|
||||
needRefresh = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置当前的光源列表
|
||||
* @param list 光源列表
|
||||
*/
|
||||
export function setLightList(list: Light[]) {
|
||||
lights = list;
|
||||
needRefresh = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 去除所有的光源
|
||||
*/
|
||||
export function removeAllLights() {
|
||||
lights = [];
|
||||
needRefresh = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取一个灯光
|
||||
* @param id 灯光id
|
||||
*/
|
||||
export function getLight(id: string) {
|
||||
return lights.find(v => v.id === id);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有灯光
|
||||
*/
|
||||
export function getAllLights() {
|
||||
return lights;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置背景色
|
||||
* @param color 背景色
|
||||
*/
|
||||
export function setBackground(color: Color) {
|
||||
background = color;
|
||||
needRefresh = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 刷新灯光信息并重绘
|
||||
*/
|
||||
export function refreshLight() {
|
||||
needRefresh = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 动画改变一个属性的值
|
||||
* @param id 灯光id
|
||||
* @param key 动画属性,x,y,r,decay,颜色请使用animateLightColor(下个版本会加)
|
||||
* @param n 目标值
|
||||
* @param time 动画时间
|
||||
* @param mode 动画方式,渐变函数,高级动画提供了大量内置的渐变函数
|
||||
* @param relative 相对方式,是绝对还是相对
|
||||
*/
|
||||
export function animateLight<K extends Exclude<keyof Light, 'id'>>(
|
||||
id: string,
|
||||
key: K,
|
||||
n: Light[K],
|
||||
time: number = 1000,
|
||||
mode: TimingFn = linear(),
|
||||
relative: boolean = false
|
||||
) {
|
||||
const light = getLight(id);
|
||||
if (!has(light)) {
|
||||
throw new ReferenceError(`You are going to animate nonexistent light`);
|
||||
}
|
||||
if (typeof n !== 'number') {
|
||||
light[key] = n;
|
||||
}
|
||||
const ani = animationList[id] ?? (animationList[id] = new Animation());
|
||||
if (typeof ani.value[key] !== 'number') {
|
||||
ani.register(key, light[key] as number);
|
||||
} else {
|
||||
ani.time(0)
|
||||
.mode(linear())
|
||||
.absolute()
|
||||
.apply(key, light[key] as number);
|
||||
}
|
||||
ani.time(time)
|
||||
.mode(mode)
|
||||
[relative ? 'relative' : 'absolute']()
|
||||
.apply(key, n as number);
|
||||
const start = Date.now();
|
||||
const fn = () => {
|
||||
if (Date.now() - start > time + 50) {
|
||||
ani.ticker.remove(fn);
|
||||
light._animating![key] = false;
|
||||
}
|
||||
needRefresh = true;
|
||||
light[key as SelectKey<Light, number>] = ani.value[key];
|
||||
};
|
||||
ani.ticker.add(fn);
|
||||
light._animating ??= {};
|
||||
light._animating[key] = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 把一个属性设置为渐变模式
|
||||
* @param id 灯光id
|
||||
* @param key 渐变的属性
|
||||
* @param time 渐变时长
|
||||
* @param mode 渐变方式,渐变函数,高级动画提供了大量内置的渐变函数
|
||||
*/
|
||||
export function transitionLight<K extends Exclude<keyof Light, 'id'>>(
|
||||
id: string,
|
||||
key: K,
|
||||
time: number = 1000,
|
||||
mode: TimingFn = linear()
|
||||
) {
|
||||
const index = lights.findIndex(v => v.id === id);
|
||||
if (index === -1) {
|
||||
throw new ReferenceError(`You are going to transite nonexistent light`);
|
||||
}
|
||||
const light = lights[index];
|
||||
if (typeof light[key] !== 'number') return;
|
||||
light._transition ??= {};
|
||||
light._transition[key] = { time, mode };
|
||||
const tran = transitionList[id] ?? (transitionList[id] = new Transition());
|
||||
tran.value[key] = light[key] as number;
|
||||
if (!light._isProxy) {
|
||||
const handler: ProxyHandler<Light> = {
|
||||
set(t, p, v) {
|
||||
if (typeof p === 'symbol') return false;
|
||||
const start = Date.now();
|
||||
if (
|
||||
!light._transition![p] ||
|
||||
light._animating?.[key] ||
|
||||
typeof v !== 'number'
|
||||
) {
|
||||
t[p as SelectKey<Light, number>] = v;
|
||||
return true;
|
||||
}
|
||||
// @ts-ignore
|
||||
t[p] = light[p];
|
||||
const info = light._transition![p];
|
||||
tran.mode(info.mode).time(info.time);
|
||||
const fn = () => {
|
||||
if (Date.now() - start > info.time + 50) {
|
||||
tran.ticker.remove(fn);
|
||||
}
|
||||
needRefresh = true;
|
||||
t[p as SelectKey<Light, number>] = tran.value[key];
|
||||
};
|
||||
tran.ticker.add(fn);
|
||||
tran.transition(p, v);
|
||||
return true;
|
||||
}
|
||||
};
|
||||
lights[index] = new Proxy(light, handler);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 移动一个灯光
|
||||
* @param id 灯光id
|
||||
* @param x 目标横坐标
|
||||
* @param y 目标纵坐标
|
||||
* @param time 移动时间
|
||||
* @param mode 移动方式,渐变函数
|
||||
* @param relative 相对模式,相对还是绝对
|
||||
*/
|
||||
export function moveLight(
|
||||
id: string,
|
||||
x: number,
|
||||
y: number,
|
||||
time: number = 1000,
|
||||
mode: TimingFn = linear(),
|
||||
relative: boolean = false
|
||||
) {
|
||||
animateLight(id, 'x', x, time, mode, relative);
|
||||
animateLight(id, 'y', y, time, mode, relative);
|
||||
}
|
||||
|
||||
/**
|
||||
* 以一个路径移动光源
|
||||
* @param id 灯光id
|
||||
* @param time 移动时长
|
||||
* @param path 移动路径
|
||||
* @param mode 移动方式,渐变函数,表示移动的完成度
|
||||
* @param relative 相对模式,相对还是绝对
|
||||
*/
|
||||
export function moveLightAs(
|
||||
id: string,
|
||||
time: number,
|
||||
path: PathFn,
|
||||
mode: TimingFn = linear(),
|
||||
relative: boolean = true
|
||||
) {
|
||||
const light = getLight(id);
|
||||
if (!has(light)) {
|
||||
throw new ReferenceError(`You are going to animate nonexistent light`);
|
||||
}
|
||||
const ani = animationList[id] ?? (animationList[id] = new Animation());
|
||||
ani.mode(linear()).time(0).move(light.x, light.y);
|
||||
ani.time(time)
|
||||
.mode(mode)
|
||||
[relative ? 'relative' : 'absolute']()
|
||||
.moveAs(path);
|
||||
const start = Date.now();
|
||||
const fn = () => {
|
||||
if (Date.now() - start > time + 50) {
|
||||
ani.ticker.remove(fn);
|
||||
light._animating!.x = false;
|
||||
light._animating!.y = false;
|
||||
}
|
||||
needRefresh = true;
|
||||
light.x = ani.x;
|
||||
light.y = ani.y;
|
||||
};
|
||||
ani.ticker.add(fn);
|
||||
light._animating ??= {};
|
||||
light._animating.x = true;
|
||||
light._animating.y = true;
|
||||
}
|
||||
|
||||
export function animateLightColor(
|
||||
id: string,
|
||||
target: Color,
|
||||
time: number = 1000,
|
||||
mode: TimingFn = linear()
|
||||
) {
|
||||
// todo
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据坐标数组设置物体节点
|
||||
* @param nodes 坐标数组
|
||||
*/
|
||||
export function setShadowNodes(nodes: LocArr[][]): void;
|
||||
/**
|
||||
* 根据多边形数组设置物体节点
|
||||
* @param nodes 多边形数组
|
||||
*/
|
||||
export function setShadowNodes(nodes: Polygon[]): void;
|
||||
export function setShadowNodes(nodes: LocArr[][] | Polygon[]) {
|
||||
if (nodes.length === 0) {
|
||||
shadowNodes = [];
|
||||
needRefresh = true;
|
||||
}
|
||||
if (nodes[0] instanceof Polygon) shadowNodes = nodes as Polygon[];
|
||||
else shadowNodes = Polygon.from(...(nodes as LocArr[][]));
|
||||
needRefresh = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据坐标数组添加物体节点
|
||||
* @param polygons 坐标数组
|
||||
*/
|
||||
export function addPolygon(...polygons: LocArr[][]): void;
|
||||
/**
|
||||
* 根据多边形数组添加物体节点
|
||||
* @param polygons 多边形数组
|
||||
*/
|
||||
export function addPolygon(...polygons: Polygon[]): void;
|
||||
export function addPolygon(...polygons: Polygon[] | LocArr[][]) {
|
||||
if (polygons.length === 0) return;
|
||||
if (polygons[0] instanceof Polygon)
|
||||
shadowNodes.push(...(polygons as Polygon[]));
|
||||
else shadowNodes.push(...Polygon.from(...(polygons as LocArr[][])));
|
||||
needRefresh = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置光源的虚化程度
|
||||
* @param n 虚化程度
|
||||
*/
|
||||
export function setBlur(n: number) {
|
||||
blur = n;
|
||||
canvas.style.filter = `blur(${n}px)`;
|
||||
}
|
||||
|
||||
/**
|
||||
* 绘制阴影
|
||||
*/
|
||||
export function drawShadow() {
|
||||
const w = (core._PX_ ?? core.__PIXELS__) + 64;
|
||||
const h = (core._PY_ ?? core.__PIXELS__) + 64;
|
||||
needRefresh = false;
|
||||
ctx.clearRect(0, 0, w, h);
|
||||
ct1.clearRect(0, 0, w, h);
|
||||
ct2.clearRect(0, 0, w, h);
|
||||
ct3.clearRect(0, 0, w, h);
|
||||
|
||||
const b = core.arrayToRGBA(background);
|
||||
ctx.globalCompositeOperation = 'source-over';
|
||||
ct3.globalCompositeOperation = 'source-over';
|
||||
|
||||
// 绘制阴影,一个光源一个光源地绘制,然后source-out获得光,然后把光叠加,再source-out获得最终阴影
|
||||
for (let i = 0; i < lights.length; i++) {
|
||||
const { x, y, r, decay, color, noShelter } = lights[i];
|
||||
const rx = x + 32;
|
||||
const ry = y + 32;
|
||||
// 绘制阴影
|
||||
ct1.clearRect(0, 0, w, h);
|
||||
ct2.clearRect(0, 0, w, h);
|
||||
if (!noShelter) {
|
||||
for (const polygon of shadowNodes) {
|
||||
const area = polygon.shadowArea(rx, ry, r);
|
||||
area.forEach(v => {
|
||||
ct1.beginPath();
|
||||
ct1.moveTo(v[0][0], v[0][1]);
|
||||
for (let i = 1; i < v.length; i++) {
|
||||
ct1.lineTo(v[i][0], v[i][1]);
|
||||
}
|
||||
ct1.closePath();
|
||||
ct1.fillStyle = '#000';
|
||||
ct1.globalCompositeOperation = 'source-over';
|
||||
ct1.fill();
|
||||
});
|
||||
}
|
||||
}
|
||||
// 存入ct2,用于绘制真实阴影
|
||||
ct2.globalCompositeOperation = 'source-over';
|
||||
ct2.drawImage(temp1, 0, 0, w, h);
|
||||
ct2.globalCompositeOperation = 'source-out';
|
||||
const gra = ct2.createRadialGradient(rx, ry, decay, rx, ry, r);
|
||||
gra.addColorStop(0, core.arrayToRGBA(color));
|
||||
gra.addColorStop(1, 'transparent');
|
||||
ct2.fillStyle = gra;
|
||||
ct2.beginPath();
|
||||
ct2.arc(rx, ry, r, 0, Math.PI * 2);
|
||||
ct2.fill();
|
||||
ctx.drawImage(temp2, 0, 0, w, h);
|
||||
// 再绘制ct1的阴影,然后绘制到ct3叠加
|
||||
ct1.globalCompositeOperation = 'source-out';
|
||||
const gra2 = ct1.createRadialGradient(rx, ry, decay, rx, ry, r);
|
||||
gra2.addColorStop(0, '#fff');
|
||||
gra2.addColorStop(1, '#fff0');
|
||||
ct1.beginPath();
|
||||
ct1.arc(rx, ry, r, 0, Math.PI * 2);
|
||||
ct1.fillStyle = gra2;
|
||||
ct1.fill();
|
||||
// 绘制到ct3上
|
||||
ct3.drawImage(temp1, 0, 0, w, h);
|
||||
}
|
||||
// 绘制真实阴影
|
||||
ct3.globalCompositeOperation = 'source-out';
|
||||
ct3.fillStyle = b;
|
||||
ct3.fillRect(0, 0, w, h);
|
||||
ctx.globalCompositeOperation = 'destination-over';
|
||||
ctx.drawImage(temp3, 0, 0, w, h);
|
||||
}
|
Loading…
Reference in New Issue
Block a user