mirror of
https://github.com/unanmed/HumanBreak.git
synced 2025-01-19 20:59:37 +08:00
传送门绘制
This commit is contained in:
parent
f7c3f008d5
commit
af38d2e47e
223
src/core/fx/canvas2d.ts
Normal file
223
src/core/fx/canvas2d.ts
Normal file
@ -0,0 +1,223 @@
|
|||||||
|
import { parseCss } from '@/plugin/utils';
|
||||||
|
import { EmitableEvent, EventEmitter } from '../common/eventEmitter';
|
||||||
|
import { CSSObj } from '../interface';
|
||||||
|
|
||||||
|
interface OffscreenCanvasEvent extends EmitableEvent {
|
||||||
|
resize: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class MotaOffscreenCanvas2D extends EventEmitter<OffscreenCanvasEvent> {
|
||||||
|
static list: Set<MotaOffscreenCanvas2D> = new Set();
|
||||||
|
|
||||||
|
canvas: HTMLCanvasElement;
|
||||||
|
ctx: CanvasRenderingContext2D;
|
||||||
|
|
||||||
|
width: number;
|
||||||
|
height: number;
|
||||||
|
|
||||||
|
/** 是否自动跟随样板的core.domStyle.scale进行缩放 */
|
||||||
|
autoScale: boolean = false;
|
||||||
|
/** 是否是高清画布 */
|
||||||
|
highResolution: boolean = true;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.canvas = document.createElement('canvas');
|
||||||
|
this.ctx = this.canvas.getContext('2d')!;
|
||||||
|
this.width = this.canvas.width / devicePixelRatio;
|
||||||
|
this.height = this.canvas.height / devicePixelRatio;
|
||||||
|
|
||||||
|
this.canvas.style.position = 'absolute';
|
||||||
|
|
||||||
|
MotaOffscreenCanvas2D.list.add(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置画布的大小
|
||||||
|
*/
|
||||||
|
size(width: number, height: number) {
|
||||||
|
let ratio = this.highResolution ? devicePixelRatio : 1;
|
||||||
|
if (this.autoScale && this.highResolution) {
|
||||||
|
ratio *= core.domStyle.scale;
|
||||||
|
}
|
||||||
|
this.canvas.width = width * ratio;
|
||||||
|
this.canvas.height = height * ratio;
|
||||||
|
this.width = width;
|
||||||
|
this.height = height;
|
||||||
|
this.ctx.setTransform(1, 0, 0, 1, 0, 0);
|
||||||
|
this.ctx.scale(ratio, ratio);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置当前画布是否跟随样板的 core.domStyle.scale 一同进行缩放
|
||||||
|
*/
|
||||||
|
withGameScale(auto: boolean) {
|
||||||
|
this.autoScale = auto;
|
||||||
|
this.size(this.width, this.height);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置当前画布是否为高清画布
|
||||||
|
*/
|
||||||
|
setHD(hd: boolean) {
|
||||||
|
this.highResolution = hd;
|
||||||
|
this.size(this.width, this.height);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除这个画布
|
||||||
|
*/
|
||||||
|
delete() {
|
||||||
|
MotaCanvas2D.list.delete(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 复制一个离屏Canvas2D对象或Canvas2D对象,一般用于缓存等操作
|
||||||
|
* @param canvas 被复制的MotaOffscreenCanvas2D对象
|
||||||
|
* @returns 复制结果
|
||||||
|
*/
|
||||||
|
static clone(canvas: MotaOffscreenCanvas2D): MotaOffscreenCanvas2D {
|
||||||
|
const newCanvas = new MotaOffscreenCanvas2D();
|
||||||
|
newCanvas.setHD(canvas.highResolution);
|
||||||
|
newCanvas.withGameScale(canvas.autoScale);
|
||||||
|
newCanvas.size(canvas.width, canvas.height);
|
||||||
|
newCanvas.ctx.drawImage(canvas.canvas, 0, 0);
|
||||||
|
return newCanvas;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class MotaCanvas2D extends MotaOffscreenCanvas2D {
|
||||||
|
static map: Map<string, MotaCanvas2D> = new Map();
|
||||||
|
|
||||||
|
id: string = '';
|
||||||
|
|
||||||
|
x: number = 0;
|
||||||
|
y: number = 0;
|
||||||
|
|
||||||
|
private mounted: boolean = false;
|
||||||
|
private target!: HTMLElement;
|
||||||
|
/** 是否自动跟随样板的core.domStyle.scale进行缩放 */
|
||||||
|
autoScale: boolean = false;
|
||||||
|
/** 是否是高清画布 */
|
||||||
|
highResolution: boolean = true;
|
||||||
|
|
||||||
|
constructor(id: string = '', setTarget: boolean = true) {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.id = id;
|
||||||
|
if (setTarget) this.target = core.dom.gameDraw;
|
||||||
|
this.canvas = document.createElement('canvas');
|
||||||
|
this.canvas.id = id;
|
||||||
|
this.ctx = this.canvas.getContext('2d')!;
|
||||||
|
this.width = this.canvas.width / devicePixelRatio;
|
||||||
|
this.height = this.canvas.height / devicePixelRatio;
|
||||||
|
|
||||||
|
this.canvas.style.position = 'absolute';
|
||||||
|
|
||||||
|
MotaCanvas2D.map.set(this.id, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 修改画布的挂载目标,如果已经被挂载,那么会被重新挂载至新的目标元素
|
||||||
|
* @param target 画布将被挂载的目标
|
||||||
|
*/
|
||||||
|
setTarget(target: HTMLElement) {
|
||||||
|
this.target = target;
|
||||||
|
if (this.mounted) {
|
||||||
|
this.unmount();
|
||||||
|
this.mount();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置画布的大小
|
||||||
|
*/
|
||||||
|
size(width: number, height: number) {
|
||||||
|
let ratio = this.highResolution ? devicePixelRatio : 1;
|
||||||
|
if (this.autoScale) {
|
||||||
|
const scale = core.domStyle.scale;
|
||||||
|
if (this.highResolution) ratio *= scale;
|
||||||
|
this.canvas.style.width = `${width * scale}px`;
|
||||||
|
this.canvas.style.height = `${height * scale}px`;
|
||||||
|
} else {
|
||||||
|
this.canvas.style.width = `${width}px`;
|
||||||
|
this.canvas.style.height = `${height}px`;
|
||||||
|
}
|
||||||
|
this.canvas.width = width * ratio;
|
||||||
|
this.canvas.height = height * ratio;
|
||||||
|
this.width = width;
|
||||||
|
this.height = height;
|
||||||
|
this.ctx.setTransform(1, 0, 0, 1, 0, 0);
|
||||||
|
this.ctx.scale(ratio, ratio);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置画布的位置
|
||||||
|
*/
|
||||||
|
pos(x: number, y: number) {
|
||||||
|
this.canvas.style.left = `${x}px`;
|
||||||
|
this.canvas.style.top = `${y}px`;
|
||||||
|
this.x = x;
|
||||||
|
this.y = y;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置画布的css
|
||||||
|
* @param css 要设置成的css
|
||||||
|
*/
|
||||||
|
css(css: string | CSSObj) {
|
||||||
|
const s = typeof css === 'string' ? parseCss(css) : css;
|
||||||
|
for (const [key, value] of Object.entries(s)) {
|
||||||
|
this.canvas.style[key as CanParseCss] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除这个画布
|
||||||
|
*/
|
||||||
|
delete() {
|
||||||
|
super.delete();
|
||||||
|
this.unmount();
|
||||||
|
MotaCanvas2D.map.delete(this.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将这个画布添加至游戏画布
|
||||||
|
*/
|
||||||
|
mount() {
|
||||||
|
if (!this.mounted) {
|
||||||
|
this.mounted = true;
|
||||||
|
this.target.appendChild(this.canvas);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将这个画布从页面上移除
|
||||||
|
*/
|
||||||
|
unmount() {
|
||||||
|
if (this.mounted) {
|
||||||
|
this.mounted = false;
|
||||||
|
this.canvas.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 类似于 Symbol.for
|
||||||
|
*/
|
||||||
|
static for(id: string, setTarget?: boolean) {
|
||||||
|
const canvas = this.map.get(id);
|
||||||
|
return canvas ?? new MotaCanvas2D(id, setTarget);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addEventListener('resize', () => {
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
MotaOffscreenCanvas2D.list.forEach(v => {
|
||||||
|
if (v.autoScale) {
|
||||||
|
v.size(v.width, v.height);
|
||||||
|
v.emit('resize');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
241
src/core/fx/portal.ts
Normal file
241
src/core/fx/portal.ts
Normal file
@ -0,0 +1,241 @@
|
|||||||
|
import { Ticker } from 'mutate-animate';
|
||||||
|
import { MotaCanvas2D } from '@/core/fx/canvas2d';
|
||||||
|
import { MotaSettingItem, mainSetting } from '@/core/main/setting';
|
||||||
|
|
||||||
|
// 苍蓝殿左上角区域的传送门机制的绘制部分,传送部分看 src/game/machanism/misc.ts
|
||||||
|
|
||||||
|
interface DrawingPortal {
|
||||||
|
color: string;
|
||||||
|
x: number;
|
||||||
|
y: number;
|
||||||
|
particles: PortalParticle[];
|
||||||
|
/** v表示竖向,h表示横向 */
|
||||||
|
type: 'v' | 'h';
|
||||||
|
/** 上一次新增粒子的时间 */
|
||||||
|
lastParticle: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface PortalParticle {
|
||||||
|
fx: number;
|
||||||
|
fy: number;
|
||||||
|
totalTime: number;
|
||||||
|
time: number;
|
||||||
|
tx: number;
|
||||||
|
ty: number;
|
||||||
|
r: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
const MAX_PARTICLES = 10;
|
||||||
|
const PARTICLE_LAST = 2000;
|
||||||
|
const PARTICLE_INTERVAL = PARTICLE_LAST / MAX_PARTICLES;
|
||||||
|
|
||||||
|
const color: string[] = ['#0f0', '#ff0', '#0ff', '#fff', '#f0f'];
|
||||||
|
|
||||||
|
const drawing: DrawingPortal[] = [];
|
||||||
|
const ticker = new Ticker();
|
||||||
|
|
||||||
|
let canvas: MotaCanvas2D;
|
||||||
|
let ctx: CanvasRenderingContext2D;
|
||||||
|
let particleSetting: MotaSettingItem;
|
||||||
|
|
||||||
|
let lastTime = 0;
|
||||||
|
Mota.require('var', 'loading').once('coreInit', () => {
|
||||||
|
canvas = MotaCanvas2D.for('@portal');
|
||||||
|
ctx = canvas.ctx;
|
||||||
|
canvas.mount();
|
||||||
|
canvas.css(`z-index: 51`);
|
||||||
|
canvas.withGameScale(true);
|
||||||
|
canvas.pos(0, 0);
|
||||||
|
canvas.size(480, 480);
|
||||||
|
canvas.on('resize', () => {
|
||||||
|
canvas.css(`z-index: 51`);
|
||||||
|
});
|
||||||
|
particleSetting = mainSetting.getSetting('fx.portalParticle')!;
|
||||||
|
ticker.add(tickPortal);
|
||||||
|
});
|
||||||
|
|
||||||
|
Mota.require('var', 'hook').on('changingFloor', id => {
|
||||||
|
drawPortals(id);
|
||||||
|
});
|
||||||
|
|
||||||
|
let needDraw = false;
|
||||||
|
function tickPortal(time: number) {
|
||||||
|
const last = lastTime;
|
||||||
|
lastTime = time;
|
||||||
|
const p = particleSetting.value;
|
||||||
|
|
||||||
|
if (!core.isPlaying() || drawing.length === 0) return;
|
||||||
|
if (!p && !needDraw) return;
|
||||||
|
|
||||||
|
needDraw = false;
|
||||||
|
|
||||||
|
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
|
||||||
|
ctx.lineCap = 'round';
|
||||||
|
ctx.lineWidth = 3;
|
||||||
|
ctx.shadowOffsetX = 0;
|
||||||
|
ctx.shadowOffsetY = 0;
|
||||||
|
if (p) {
|
||||||
|
ctx.shadowBlur = 8;
|
||||||
|
} else {
|
||||||
|
ctx.shadowBlur = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
drawing.forEach(v => {
|
||||||
|
const { color, x, y, type, lastParticle, particles } = v;
|
||||||
|
|
||||||
|
ctx.strokeStyle = color;
|
||||||
|
ctx.fillStyle = color;
|
||||||
|
ctx.globalAlpha = 1;
|
||||||
|
ctx.shadowColor = color;
|
||||||
|
if (type === 'v') {
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.moveTo(x, y - 14);
|
||||||
|
ctx.lineTo(x, y + 30);
|
||||||
|
ctx.stroke();
|
||||||
|
} else {
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.moveTo(x + 2, y);
|
||||||
|
ctx.lineTo(x + 30, y);
|
||||||
|
ctx.stroke();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (p) {
|
||||||
|
// 绘制粒子效果
|
||||||
|
let needDelete = false;
|
||||||
|
const dt = time - last;
|
||||||
|
|
||||||
|
particles.forEach(v => {
|
||||||
|
const { fx, fy, tx, ty, time: t, totalTime, r } = v;
|
||||||
|
const progress = t / totalTime;
|
||||||
|
const nx = (tx - fx) * progress + fx;
|
||||||
|
const ny = (ty - fy) * progress + fy;
|
||||||
|
v.time += dt;
|
||||||
|
|
||||||
|
if (progress > 1) {
|
||||||
|
needDelete = true;
|
||||||
|
return;
|
||||||
|
} else if (progress > 0.75) {
|
||||||
|
ctx.globalAlpha = (1 - progress) * 4;
|
||||||
|
} else if (progress < 0.25) {
|
||||||
|
ctx.globalAlpha = progress * 4;
|
||||||
|
} else {
|
||||||
|
ctx.globalAlpha = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.arc(nx, ny, r, 0, Math.PI * 2);
|
||||||
|
ctx.closePath();
|
||||||
|
ctx.fill();
|
||||||
|
});
|
||||||
|
if (needDelete) {
|
||||||
|
particles.shift();
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
time - lastParticle >= PARTICLE_INTERVAL &&
|
||||||
|
particles.length < MAX_PARTICLES
|
||||||
|
) {
|
||||||
|
// 添加新粒子
|
||||||
|
const direction = Math.random();
|
||||||
|
const k = Math.random() / 2 - 0.3;
|
||||||
|
const verticle = Math.floor(Math.random() * 8 + 8);
|
||||||
|
const r = Math.random() * 2;
|
||||||
|
v.lastParticle = time;
|
||||||
|
if (direction > 0.5) {
|
||||||
|
// 左边 | 上边
|
||||||
|
if (type === 'h') {
|
||||||
|
const fx = Math.floor(Math.random() * 24 + x + 4);
|
||||||
|
particles.push({
|
||||||
|
fx: fx,
|
||||||
|
fy: y - 1,
|
||||||
|
tx: verticle * k + fx + 4,
|
||||||
|
ty: -verticle + y - 1,
|
||||||
|
r: r,
|
||||||
|
time: 0,
|
||||||
|
totalTime: PARTICLE_LAST
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
const fy = Math.floor(Math.random() * 44 + y - 14);
|
||||||
|
particles.push({
|
||||||
|
fy: fy,
|
||||||
|
fx: x - 1,
|
||||||
|
ty: verticle * k + fy + 4,
|
||||||
|
tx: -verticle + x - 1,
|
||||||
|
r: r,
|
||||||
|
time: 0,
|
||||||
|
totalTime: PARTICLE_LAST
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 右边 | 下边
|
||||||
|
if (type === 'h') {
|
||||||
|
const fx = Math.floor(Math.random() * 24 + x + 4);
|
||||||
|
particles.push({
|
||||||
|
fx: fx,
|
||||||
|
fy: y + 1,
|
||||||
|
tx: verticle * k + fx + 4,
|
||||||
|
ty: verticle + y - 1,
|
||||||
|
r: r,
|
||||||
|
time: 0,
|
||||||
|
totalTime: PARTICLE_LAST
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
const fy = Math.floor(Math.random() * 44 + y - 14);
|
||||||
|
particles.push({
|
||||||
|
fy: fy,
|
||||||
|
fx: x + 1,
|
||||||
|
ty: verticle * k + fy + 4,
|
||||||
|
tx: verticle + x + 1,
|
||||||
|
r: r,
|
||||||
|
time: 0,
|
||||||
|
totalTime: PARTICLE_LAST
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 绘制传送门
|
||||||
|
* @param floorId 要绘制传送门的楼层
|
||||||
|
*/
|
||||||
|
export function drawPortals(floorId: FloorIds) {
|
||||||
|
drawing.splice(0);
|
||||||
|
const p = Mota.require('module', 'Mechanism').BluePalace.portals[floorId];
|
||||||
|
if (!p) return;
|
||||||
|
p.forEach((v, i) => {
|
||||||
|
const c = color[i % color.length];
|
||||||
|
const { fx, fy, tx, ty, dir, toDir } = v;
|
||||||
|
|
||||||
|
let x1 = fx * 32;
|
||||||
|
let y1 = fy * 32;
|
||||||
|
let x2 = tx * 32;
|
||||||
|
let y2 = ty * 32;
|
||||||
|
|
||||||
|
if (dir === 'down') y1 += 32;
|
||||||
|
else if (dir === 'right') x1 += 32;
|
||||||
|
|
||||||
|
if (toDir === 'down') y2 += 32;
|
||||||
|
else if (toDir === 'right') x2 += 32;
|
||||||
|
|
||||||
|
drawing.push({
|
||||||
|
x: x1,
|
||||||
|
y: y1,
|
||||||
|
type: dir === 'left' || dir === 'right' ? 'v' : 'h',
|
||||||
|
color: c,
|
||||||
|
particles: [],
|
||||||
|
lastParticle: lastTime
|
||||||
|
});
|
||||||
|
|
||||||
|
drawing.push({
|
||||||
|
x: x2,
|
||||||
|
y: y2,
|
||||||
|
type: toDir === 'left' || toDir === 'right' ? 'v' : 'h',
|
||||||
|
color: c,
|
||||||
|
particles: [],
|
||||||
|
lastParticle: lastTime
|
||||||
|
});
|
||||||
|
});
|
||||||
|
needDraw = true;
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user