mirror of
https://github.com/unanmed/HumanBreak.git
synced 2025-04-24 16:13:24 +08:00
Compare commits
8 Commits
53fa7272fc
...
48cc34aa2d
Author | SHA1 | Date | |
---|---|---|---|
48cc34aa2d | |||
4b2d94e422 | |||
b448f18616 | |||
69da048438 | |||
ed229e1601 | |||
6f10ac3d81 | |||
b9f4804c8c | |||
19a0c29015 |
@ -2549,8 +2549,8 @@ packages:
|
||||
resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
caniuse-lite@1.0.30001651:
|
||||
resolution: {integrity: sha512-9Cf+Xv1jJNe1xPZLGuUXLNkE1BoDkqRqYyFJ9TDYSqhduqA4hu4oR9HluGoWYQC/aj8WHjsGVV+bwkh0+tegRg==}
|
||||
caniuse-lite@1.0.30001700:
|
||||
resolution: {integrity: sha512-2S6XIXwaE7K7erT8dY+kLQcpa5ms63XlRkMkReXjle+kf6c5g38vyMl+Z5y8dSxOFDhcFe+nxnn261PLxBSQsQ==}
|
||||
|
||||
ccount@2.0.1:
|
||||
resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==}
|
||||
@ -7241,7 +7241,7 @@ snapshots:
|
||||
autoprefixer@10.4.20(postcss@8.4.49):
|
||||
dependencies:
|
||||
browserslist: 4.23.3
|
||||
caniuse-lite: 1.0.30001651
|
||||
caniuse-lite: 1.0.30001700
|
||||
fraction.js: 4.3.7
|
||||
normalize-range: 0.1.2
|
||||
picocolors: 1.0.1
|
||||
@ -7320,7 +7320,7 @@ snapshots:
|
||||
|
||||
browserslist@4.23.3:
|
||||
dependencies:
|
||||
caniuse-lite: 1.0.30001651
|
||||
caniuse-lite: 1.0.30001700
|
||||
electron-to-chromium: 1.5.11
|
||||
node-releases: 2.0.18
|
||||
update-browserslist-db: 1.1.0(browserslist@4.23.3)
|
||||
@ -7395,7 +7395,7 @@ snapshots:
|
||||
|
||||
camelcase@6.3.0: {}
|
||||
|
||||
caniuse-lite@1.0.30001651: {}
|
||||
caniuse-lite@1.0.30001700: {}
|
||||
|
||||
ccount@2.0.1: {}
|
||||
|
||||
|
@ -2853,11 +2853,7 @@ control.prototype.getMappedName = function (name) {
|
||||
////// 更改天气效果 //////
|
||||
control.prototype.setWeather = function (type, level) {
|
||||
// Deprecated. Use WeatherController API instead.
|
||||
Mota.r(() => {
|
||||
const controller = Mota.require('module', 'Weather').controller;
|
||||
controller.clearWeather();
|
||||
if (type !== null && type !== void 0) controller.activate(type, level);
|
||||
});
|
||||
// Fallback see src/module/fallback/weather.ts
|
||||
};
|
||||
|
||||
////// 注册一个天气 //////
|
||||
|
@ -75,7 +75,6 @@ import './render/index';
|
||||
import * as RenderUtils from './render/utils';
|
||||
import '@/module';
|
||||
import { MotaOffscreenCanvas2D } from './fx/canvas2d';
|
||||
import { TextboxStore } from './render/index';
|
||||
|
||||
// ----- 类注册
|
||||
Mota.register('class', 'CustomToolbar', CustomToolbar);
|
||||
@ -157,8 +156,7 @@ Mota.register('module', 'Render', {
|
||||
LayerGroupFloorBinder,
|
||||
Camera,
|
||||
MotaOffscreenCanvas2D,
|
||||
Utils: RenderUtils,
|
||||
TextboxStore
|
||||
Utils: RenderUtils
|
||||
});
|
||||
Mota.register('module', 'Action', {
|
||||
HeroKeyMover
|
||||
|
@ -97,10 +97,10 @@ class TextureCache extends EventEmitter<TextureCacheEvent> {
|
||||
|
||||
Mota.require('var', 'loading').once('loaded', () => {
|
||||
const map = maps_90f36752_8815_4be8_b32b_d7fad1d0542e;
|
||||
// @ts-ignore
|
||||
// @ts-expect-error 无法推导
|
||||
this.idNumberMap = {};
|
||||
for (const [key, { id }] of Object.entries(map)) {
|
||||
// @ts-ignore
|
||||
// @ts-expect-error 无法推导
|
||||
this.idNumberMap[id] = parseInt(key) as AllNumbers;
|
||||
}
|
||||
this.tileset = core.material.images.tilesets;
|
||||
@ -128,7 +128,7 @@ class TextureCache extends EventEmitter<TextureCacheEvent> {
|
||||
*/
|
||||
private calRenderable() {
|
||||
const map = maps_90f36752_8815_4be8_b32b_d7fad1d0542e;
|
||||
for (const [key, data] of Object.entries(map)) {
|
||||
for (const key of Object.keys(map)) {
|
||||
this.calRenderableByNum(parseInt(key));
|
||||
}
|
||||
}
|
||||
@ -167,7 +167,8 @@ class TextureCache extends EventEmitter<TextureCacheEvent> {
|
||||
const data = map[num as Exclude<AllNumbers, 0>];
|
||||
// 地狱般的分支if
|
||||
if (data) {
|
||||
let { cls, faceIds, bigImage, id, animate } = data;
|
||||
let { faceIds, bigImage } = data;
|
||||
const { cls, id, animate } = data;
|
||||
if (cls === 'enemys' || cls === 'enemy48') {
|
||||
// 怪物需要特殊处理,因为它的大怪物信息不在 maps 里面
|
||||
({ bigImage, faceIds } = enemys[id as EnemyIds]);
|
||||
@ -212,7 +213,7 @@ class TextureCache extends EventEmitter<TextureCacheEvent> {
|
||||
if (cls === 'enemy48' || cls === 'npc48') {
|
||||
const img = core.material.images[cls];
|
||||
if (!img) return null;
|
||||
// @ts-ignore
|
||||
// @ts-expect-error 无法推导
|
||||
const line = icons[cls][id];
|
||||
const w = 32;
|
||||
const h = 48;
|
||||
@ -269,7 +270,7 @@ class TextureCache extends EventEmitter<TextureCacheEvent> {
|
||||
if (!image) return null;
|
||||
const frame = core.getAnimateFrames(cls);
|
||||
const cell = 32;
|
||||
// @ts-ignore
|
||||
// @ts-expect-error 无法推导
|
||||
const offset = (icons[cls][id] as number) * cell;
|
||||
const render: [number, number, number, number][] = [
|
||||
[0, offset, cell, cell]
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { MotaOffscreenCanvas2D } from '../fx/canvas2d';
|
||||
import { ActionType, EventProgress, ActionEventMap } from './event';
|
||||
import {
|
||||
ERenderItemEvent,
|
||||
IRenderChildable,
|
||||
@ -77,12 +78,58 @@ export class Container<E extends EContainerEvent = EContainerEvent>
|
||||
this.update(this);
|
||||
}
|
||||
|
||||
append(parent: RenderItem): void {
|
||||
super.append(parent);
|
||||
if (this.root) {
|
||||
this.forEachChild(ele => {
|
||||
ele.checkRoot();
|
||||
this.root?.connect(ele);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 遍历这个元素中的每个子元素,并执行传入的函数
|
||||
* @param fn 对每个元素执行的函数
|
||||
*/
|
||||
forEachChild(fn: (ele: RenderItem) => void) {
|
||||
const stack: RenderItem[] = [this];
|
||||
while (stack.length > 0) {
|
||||
const ele = stack.pop()!;
|
||||
stack.push(...ele.children);
|
||||
fn(ele);
|
||||
}
|
||||
}
|
||||
|
||||
private sortChildren() {
|
||||
this.sortedChildren = [...this.children].sort(
|
||||
(a, b) => a.zIndex - b.zIndex
|
||||
);
|
||||
}
|
||||
|
||||
protected propagateEvent<T extends ActionType>(
|
||||
type: T,
|
||||
progress: EventProgress,
|
||||
event: ActionEventMap[T]
|
||||
): void {
|
||||
const len = this.sortedChildren.length;
|
||||
if (progress === EventProgress.Capture) {
|
||||
let success = false;
|
||||
for (let i = len - 1; i >= 0; i--) {
|
||||
if (this.sortedChildren[i].captureEvent(type, event)) {
|
||||
success = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
// 如果没有子元素能够触发,那么自身触发冒泡
|
||||
if (!success) {
|
||||
this.bubbleEvent(type, event);
|
||||
}
|
||||
} else {
|
||||
this.parent?.bubbleEvent(type, event);
|
||||
}
|
||||
}
|
||||
|
||||
destroy(): void {
|
||||
super.destroy();
|
||||
this.children.forEach(v => {
|
||||
|
155
src/core/render/event.ts
Normal file
155
src/core/render/event.ts
Normal file
@ -0,0 +1,155 @@
|
||||
import type { RenderItem } from './item';
|
||||
|
||||
export const enum MouseType {
|
||||
/** 没有按键按下 */
|
||||
None = 0,
|
||||
/** 左键 */
|
||||
Left = 1 << 0,
|
||||
/** 中键,即按下滚轮 */
|
||||
Middle = 1 << 1,
|
||||
/** 右键 */
|
||||
Right = 1 << 2,
|
||||
/** 侧键后退 */
|
||||
Back = 1 << 3,
|
||||
/** 侧键前进 */
|
||||
Forward = 1 << 4
|
||||
}
|
||||
|
||||
export const enum WheelType {
|
||||
None,
|
||||
/** 以像素为单位 */
|
||||
Pixel,
|
||||
/** 以行为单位,每行长度视浏览器设置而定,约为 1rem */
|
||||
Line,
|
||||
/** 以页为单位,一般为一个屏幕高度 */
|
||||
Page
|
||||
}
|
||||
|
||||
export const enum ActionType {
|
||||
/** 点击事件,即按下与抬起都在该元素上时触发 */
|
||||
Click,
|
||||
/** 鼠标或手指按下事件 */
|
||||
Down,
|
||||
/** 鼠标或手指移动事件 */
|
||||
Move,
|
||||
/** 鼠标或手指抬起事件 */
|
||||
Up,
|
||||
/** 鼠标或手指移动入该元素时触发的事件 */
|
||||
Enter,
|
||||
/** 鼠标或手指移出该元素时触发的事件 */
|
||||
Leave,
|
||||
/** 鼠标在该元素上滚轮时触发的事件 */
|
||||
Wheel
|
||||
}
|
||||
|
||||
export const enum EventProgress {
|
||||
/** 捕获阶段 */
|
||||
Capture,
|
||||
/** 冒泡阶段 */
|
||||
Bubble
|
||||
}
|
||||
|
||||
export interface IActionEvent {
|
||||
/** 当前事件是监听的哪个元素 */
|
||||
target: RenderItem;
|
||||
/** 这次操作的标识符,在按下、移动、抬起阶段中保持不变 */
|
||||
identifier: number;
|
||||
/** 是触摸操作还是鼠标操作 */
|
||||
touch: boolean;
|
||||
/** 相对于触发元素左上角的横坐标 */
|
||||
offsetX: number;
|
||||
/** 相对于触发元素左上角的纵坐标 */
|
||||
offsetY: number;
|
||||
/** 相对于整个画布左上角的横坐标 */
|
||||
absoluteX: number;
|
||||
/** 相对于整个画布左上角的纵坐标 */
|
||||
absoluteY: number;
|
||||
/**
|
||||
* 触发的按键种类,会出现在点击、按下、抬起三个事件中,而其他的如移动等该值只会是 {@link MouseType.None},
|
||||
* 电脑端可以有左键、中键、右键等,手机只会触发左键,每一项的值参考 {@link MouseType}
|
||||
*/
|
||||
type: MouseType;
|
||||
/**
|
||||
* 当前按下了哪些按键。该值是一个数字,可以通过位运算判断是否按下了某个按键。
|
||||
* 例如通过 `buttons & MouseType.Left` 来判断是否按下了左键。
|
||||
* 注意在鼠标抬起或鼠标点击事件中,并不会包含触发的那个按键
|
||||
*/
|
||||
buttons: number;
|
||||
/** 触发时是否按下了 alt 键 */
|
||||
altKey: boolean;
|
||||
/** 触发时是否按下了 shift 键 */
|
||||
shiftKey: boolean;
|
||||
/** 触发时是否按下了 ctrl 键 */
|
||||
ctrlKey: boolean;
|
||||
/** 触发时是否按下了 Windows(Windows) / Command(Mac) 键 */
|
||||
metaKey: boolean;
|
||||
|
||||
/**
|
||||
* 调用后将停止事件的继续传播。
|
||||
* 在捕获阶段,将会阻止捕获的进一步进行,在冒泡阶段,将会阻止冒泡的进一步进行。
|
||||
* 如果当前元素有很多监听器,该方法并不会阻止其他监听器的执行。
|
||||
*/
|
||||
stopPropagation(): void;
|
||||
}
|
||||
|
||||
export interface IWheelEvent extends IActionEvent {
|
||||
/** 滚轮事件的鼠标横向滚动量 */
|
||||
wheelX: number;
|
||||
/** 滚轮事件的鼠标纵向滚动量 */
|
||||
wheelY: number;
|
||||
/** 滚轮事件的鼠标垂直屏幕的滚动量 */
|
||||
wheelZ: number;
|
||||
/** 滚轮事件的滚轮类型,表示了对应值的单位 */
|
||||
wheelType: WheelType;
|
||||
}
|
||||
|
||||
export interface ERenderItemActionEvent {
|
||||
/** 当这个元素被点击时的捕获阶段触发 */
|
||||
clickCapture: [ev: Readonly<IActionEvent>];
|
||||
/** 当这个元素被点击时的冒泡阶段触发 */
|
||||
click: [ev: Readonly<IActionEvent>];
|
||||
/** 当鼠标或手指在该元素上按下的捕获阶段触发 */
|
||||
downCapture: [ev: Readonly<IActionEvent>];
|
||||
/** 当鼠标或手指在该元素上按下的冒泡阶段触发 */
|
||||
down: [ev: Readonly<IActionEvent>];
|
||||
/** 当鼠标或手指在该元素上移动的捕获阶段触发 */
|
||||
moveCapture: [ev: Readonly<IActionEvent>];
|
||||
/** 当鼠标或手指在该元素上移动的冒泡阶段触发 */
|
||||
move: [ev: Readonly<IActionEvent>];
|
||||
/** 当鼠标或手指在该元素上抬起的捕获阶段触发 */
|
||||
upCapture: [ev: Readonly<IActionEvent>];
|
||||
/** 当鼠标或手指在该元素上抬起的冒泡阶段触发 */
|
||||
up: [ev: Readonly<IActionEvent>];
|
||||
/** 当鼠标或手指进入该元素的捕获阶段触发 */
|
||||
enterCapture: [ev: Readonly<IActionEvent>];
|
||||
/** 当鼠标或手指进入该元素的冒泡阶段触发 */
|
||||
enter: [ev: Readonly<IActionEvent>];
|
||||
/** 当鼠标或手指离开该元素的捕获阶段触发 */
|
||||
leaveCapture: [ev: Readonly<IActionEvent>];
|
||||
/** 当鼠标或手指离开该元素的冒泡阶段触发 */
|
||||
leave: [ev: Readonly<IActionEvent>];
|
||||
/** 当鼠标滚轮时的捕获阶段触发 */
|
||||
wheelCapture: [ev: Readonly<IWheelEvent>];
|
||||
/** 当鼠标滚轮时的冒泡阶段触发 */
|
||||
wheel: [ev: Readonly<IWheelEvent>];
|
||||
}
|
||||
|
||||
export interface ActionEventMap {
|
||||
[ActionType.Click]: IActionEvent;
|
||||
[ActionType.Down]: IActionEvent;
|
||||
[ActionType.Enter]: IActionEvent;
|
||||
[ActionType.Leave]: IActionEvent;
|
||||
[ActionType.Move]: IActionEvent;
|
||||
[ActionType.Up]: IActionEvent;
|
||||
[ActionType.Wheel]: IWheelEvent;
|
||||
}
|
||||
|
||||
export const eventNameMap: Record<ActionType, string> = {
|
||||
[ActionType.Click]: 'click',
|
||||
[ActionType.Down]: 'down',
|
||||
[ActionType.Move]: 'move',
|
||||
[ActionType.Up]: 'up',
|
||||
[ActionType.Enter]: 'enter',
|
||||
[ActionType.Leave]: 'leave',
|
||||
[ActionType.Wheel]: 'wheel'
|
||||
};
|
52
src/core/render/frame.ts
Normal file
52
src/core/render/frame.ts
Normal file
@ -0,0 +1,52 @@
|
||||
import EventEmitter from 'eventemitter3';
|
||||
import { RenderItem } from './item';
|
||||
|
||||
export interface IAnimateFrame {
|
||||
updateFrameAnimate(frame: number, time: number): void;
|
||||
}
|
||||
|
||||
interface RenderEvent {
|
||||
animateFrame: [frame: number, time: number];
|
||||
}
|
||||
|
||||
class RenderEmits extends EventEmitter<RenderEvent> {
|
||||
private framer: Set<IAnimateFrame> = new Set();
|
||||
|
||||
/**
|
||||
* 添加一个可更新帧动画的对象
|
||||
*/
|
||||
addFramer(framer: IAnimateFrame) {
|
||||
this.framer.add(framer);
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除一个可更新帧动画的对象
|
||||
*/
|
||||
removeFramer(framer: IAnimateFrame) {
|
||||
this.framer.delete(framer);
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新所有帧动画
|
||||
* @param frame 帧数
|
||||
* @param time 帧动画时刻
|
||||
*/
|
||||
emitAnimateFrame(frame: number, time: number) {
|
||||
this.framer.forEach(v => v.updateFrameAnimate(frame, time));
|
||||
this.emit('animateFrame', frame, time);
|
||||
}
|
||||
}
|
||||
|
||||
export const renderEmits = new RenderEmits();
|
||||
|
||||
Mota.require('var', 'hook').once('reset', () => {
|
||||
let lastTime = 0;
|
||||
RenderItem.ticker.add(time => {
|
||||
if (!core.isPlaying()) return;
|
||||
if (time - lastTime > core.values.animateSpeed) {
|
||||
RenderItem.animatedFrame++;
|
||||
lastTime = time;
|
||||
renderEmits.emitAnimateFrame(RenderItem.animatedFrame, time);
|
||||
}
|
||||
});
|
||||
});
|
@ -184,62 +184,66 @@ export abstract class GL2<E extends EGL2Event = EGL2Event> extends RenderItem<
|
||||
this.init();
|
||||
}
|
||||
|
||||
setHD(hd: boolean): void {
|
||||
super.setHD(hd);
|
||||
this.sizeGL(this.width, this.height);
|
||||
}
|
||||
|
||||
size(width: number, height: number): void {
|
||||
super.size(width, height);
|
||||
this.sizeGL(width, height);
|
||||
}
|
||||
|
||||
private sizeGL(width: number, height: number) {
|
||||
const ratio = this.highResolution ? devicePixelRatio : 1;
|
||||
const scale = ratio * core.domStyle.scale;
|
||||
this.canvas.width = width * scale;
|
||||
this.canvas.height = height * scale;
|
||||
}
|
||||
|
||||
protected render(
|
||||
canvas: MotaOffscreenCanvas2D,
|
||||
transform: Transform
|
||||
): void {
|
||||
if (!GL2.support || !this.program) return;
|
||||
if (!GL2.support || !this.program || !this.gl) return;
|
||||
const compile = this.program.requestCompile();
|
||||
if (compile) {
|
||||
this.gl.useProgram(this.program.program);
|
||||
}
|
||||
|
||||
if (this.cacheDirty) {
|
||||
this.drawScene(canvas, transform);
|
||||
// 清空画布
|
||||
const gl = this.gl;
|
||||
gl.viewport(0, 0, this.canvas.width, this.canvas.height);
|
||||
gl.clearColor(0, 0, 0, 0);
|
||||
gl.clearDepth(1);
|
||||
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
|
||||
this.drawScene(canvas, gl, this.program, transform);
|
||||
this.cacheDirty = false;
|
||||
}
|
||||
|
||||
canvas.ctx.drawImage(this.canvas, 0, 0, this.width, this.height);
|
||||
}
|
||||
|
||||
drawScene(canvas: MotaOffscreenCanvas2D, transform: Transform) {
|
||||
const gl = this.gl;
|
||||
const program = this.program;
|
||||
if (!gl || !program) return;
|
||||
const ready = program.ready();
|
||||
if (!ready) return;
|
||||
|
||||
// 清空画布
|
||||
gl.viewport(0, 0, this.canvas.width, this.canvas.height);
|
||||
gl.clearColor(0, 0, 0, 0);
|
||||
gl.clearDepth(1);
|
||||
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
|
||||
|
||||
const pre = this.preDraw(canvas, transform, gl, program);
|
||||
if (!pre) {
|
||||
this.postDraw(canvas, transform, gl, program);
|
||||
return;
|
||||
}
|
||||
|
||||
this.draw(gl, program);
|
||||
|
||||
this.postDraw(canvas, transform, gl, program);
|
||||
}
|
||||
|
||||
protected abstract preDraw(
|
||||
/**
|
||||
* 渲染当前 gl2 画布
|
||||
* @param canvas 渲染至的目标画布,注意系统会自动将 gl2 画布渲染至目标画布,不需要手动画到该画布上
|
||||
* @param gl 当前正在渲染的 gl2 画布
|
||||
* @param program 当前元素正在使用的着色器程序
|
||||
* @param transform 当前元素相对父元素的变换矩阵
|
||||
*/
|
||||
protected abstract drawScene(
|
||||
canvas: MotaOffscreenCanvas2D,
|
||||
transform: Transform,
|
||||
gl: WebGL2RenderingContext,
|
||||
program: GL2Program
|
||||
): boolean;
|
||||
|
||||
protected abstract postDraw(
|
||||
canvas: MotaOffscreenCanvas2D,
|
||||
transform: Transform,
|
||||
gl: WebGL2RenderingContext,
|
||||
program: GL2Program
|
||||
program: GL2Program,
|
||||
transform: Transform
|
||||
): void;
|
||||
|
||||
/**
|
||||
* 执行顶点绘制
|
||||
* @param gl 当前正在渲染的 gl2 画布
|
||||
* @param program 当前元素正在使用的着色器程序
|
||||
*/
|
||||
draw(gl: WebGL2RenderingContext, program: GL2Program) {
|
||||
const indices = program.usingIndices;
|
||||
const param = program.getDrawParams(program.renderMode);
|
||||
@ -1675,7 +1679,7 @@ export class GL2Program extends EventEmitter<ShaderProgramEvent> {
|
||||
}
|
||||
|
||||
/**
|
||||
* 摧毁这个着色器程序,不要直接调用,请使用 {@link Shader.deleteProgram} 来删除一个着色器程序
|
||||
* 摧毁这个着色器程序,不要直接调用,请使用 {@link GL2.deleteProgram} 来删除一个着色器程序
|
||||
*/
|
||||
destroy() {
|
||||
this.clearProgram();
|
||||
|
14
src/core/render/index.ts
Normal file
14
src/core/render/index.ts
Normal file
@ -0,0 +1,14 @@
|
||||
export * from './preset';
|
||||
export * from './renderer';
|
||||
export * from './adapter';
|
||||
export * from './cache';
|
||||
export * from './camera';
|
||||
export * from './container';
|
||||
export * from './frame';
|
||||
export * from './gl2';
|
||||
export * from './item';
|
||||
export * from './render';
|
||||
export * from './shader';
|
||||
export * from './sprite';
|
||||
export * from './transform';
|
||||
export * from './utils';
|
@ -5,6 +5,17 @@ import { Ticker, TickerFn } from 'mutate-animate';
|
||||
import { Transform } from './transform';
|
||||
import { logger } from '../common/logger';
|
||||
import { ElementNamespace, ComponentInternalInstance } from 'vue';
|
||||
import { transformCanvas } from './utils';
|
||||
import {
|
||||
ActionEventMap,
|
||||
ActionType,
|
||||
ERenderItemActionEvent,
|
||||
eventNameMap,
|
||||
EventProgress,
|
||||
IActionEvent,
|
||||
MouseType
|
||||
} from './event';
|
||||
import { vec3 } from 'gl-matrix';
|
||||
|
||||
export type RenderFunction = (
|
||||
canvas: MotaOffscreenCanvas2D,
|
||||
@ -21,7 +32,7 @@ export interface IRenderUpdater {
|
||||
update(item?: RenderItem): void;
|
||||
}
|
||||
|
||||
interface IRenderAnchor {
|
||||
export interface IRenderAnchor {
|
||||
/** 锚点横坐标,0表示最左端,1表示最右端 */
|
||||
anchorX: number;
|
||||
/** 锚点纵坐标,0表示最上端,1表示最下端 */
|
||||
@ -35,7 +46,7 @@ interface IRenderAnchor {
|
||||
setAnchor(x: number, y: number): void;
|
||||
}
|
||||
|
||||
interface IRenderConfig {
|
||||
export interface IRenderConfig {
|
||||
/** 是否是高清画布 */
|
||||
highResolution: boolean;
|
||||
/** 是否启用抗锯齿 */
|
||||
@ -76,7 +87,7 @@ export interface IRenderChildable {
|
||||
requestSort(): void;
|
||||
}
|
||||
|
||||
interface IRenderFrame {
|
||||
export interface IRenderFrame {
|
||||
/**
|
||||
* 在下一帧渲染之前执行函数,常用于渲染前数据更新,理论上不应当用于渲染,不保证运行顺序
|
||||
* @param fn 执行的函数
|
||||
@ -96,7 +107,7 @@ interface IRenderFrame {
|
||||
requestRenderFrame(fn: () => void): void;
|
||||
}
|
||||
|
||||
interface IRenderTickerSupport {
|
||||
export interface IRenderTickerSupport {
|
||||
/**
|
||||
* 委托ticker,让其在指定时间范围内每帧执行对应函数,超过时间后自动删除
|
||||
* @param fn 每帧执行的函数
|
||||
@ -121,7 +132,7 @@ interface IRenderTickerSupport {
|
||||
hasTicker(id: number): boolean;
|
||||
}
|
||||
|
||||
interface IRenderVueSupport {
|
||||
export interface IRenderVueSupport {
|
||||
/**
|
||||
* 在 jsx, vue 中当属性改变后触发此函数,用于处理响应式等情况
|
||||
* @param key 属性键名
|
||||
@ -139,12 +150,39 @@ interface IRenderVueSupport {
|
||||
): void;
|
||||
}
|
||||
|
||||
export interface ERenderItemEvent {
|
||||
export interface IRenderTreeRoot {
|
||||
readonly isRoot: true;
|
||||
|
||||
/**
|
||||
* 将一个渲染元素连接到此根元素
|
||||
* @param item 要连接到此根元素的渲染元素
|
||||
*/
|
||||
connect(item: RenderItem): void;
|
||||
|
||||
/**
|
||||
* 将已连接的渲染元素从此根元素中去掉
|
||||
* @param item 要取消连接的渲染元素
|
||||
*/
|
||||
disconnect(item: RenderItem): void;
|
||||
|
||||
/**
|
||||
* 修改已连接的元素的 id
|
||||
* @param item 修改了 id 的元素
|
||||
* @param previous 先前的元素 id
|
||||
* @param current 现在的元素 id
|
||||
*/
|
||||
modifyId(item: RenderItem, previous: string, current: string): void;
|
||||
|
||||
/**
|
||||
* 获取渲染至的目标画布,即显示在画面上的画布
|
||||
*/
|
||||
getCanvas(): HTMLCanvasElement;
|
||||
}
|
||||
|
||||
export interface ERenderItemEvent extends ERenderItemActionEvent {
|
||||
beforeRender: [transform: Transform];
|
||||
afterRender: [transform: Transform];
|
||||
destroy: [];
|
||||
/** 当这个元素被点击时触发 */
|
||||
clickCapture: [x: number, y: number, type: number, ev: MouseEvent];
|
||||
}
|
||||
|
||||
interface TickerDelegation {
|
||||
@ -178,25 +216,22 @@ export abstract class RenderItem<E extends ERenderItemEvent = ERenderItemEvent>
|
||||
/** ticker委托id */
|
||||
static tickerId: number = 0;
|
||||
|
||||
/** id到渲染元素的映射 */
|
||||
static itemMap: Map<string, RenderItem> = new Map();
|
||||
|
||||
readonly uid: number = count++;
|
||||
|
||||
private _id: string = '';
|
||||
//#region 元素属性
|
||||
|
||||
private _id: string = '';
|
||||
/**
|
||||
* 元素的 id,原则上不可重复
|
||||
*/
|
||||
get id(): string {
|
||||
return this._id;
|
||||
}
|
||||
set id(v: string) {
|
||||
if (this.isRoot || this.findRoot()) {
|
||||
if (RenderItem.itemMap.has(this._id)) {
|
||||
logger.warn(23, this._id);
|
||||
RenderItem.itemMap.delete(this._id);
|
||||
}
|
||||
RenderItem.itemMap.set(v, this);
|
||||
}
|
||||
this.checkRoot();
|
||||
const prev = this._id;
|
||||
this._id = v;
|
||||
this._root?.modifyId(this, prev, v);
|
||||
}
|
||||
|
||||
/** 元素纵深,表示了遮挡关系 */
|
||||
@ -205,8 +240,9 @@ export abstract class RenderItem<E extends ERenderItemEvent = ERenderItemEvent>
|
||||
width: number = 200;
|
||||
height: number = 200;
|
||||
|
||||
// 渲染锚点,(0,0)表示左上角,(1,1)表示右下角
|
||||
/** 渲染锚点,(0,0)表示左上角,(1,1)表示右下角 */
|
||||
anchorX: number = 0;
|
||||
/** 渲染锚点,(0,0)表示左上角,(1,1)表示右下角 */
|
||||
anchorY: number = 0;
|
||||
|
||||
/** 渲染模式,absolute表示绝对位置,static表示跟随摄像机移动 */
|
||||
@ -224,27 +260,52 @@ export abstract class RenderItem<E extends ERenderItemEvent = ERenderItemEvent>
|
||||
/** 不透明度 */
|
||||
alpha: number = 1;
|
||||
|
||||
get x() {
|
||||
return this._transform.x;
|
||||
}
|
||||
get y() {
|
||||
return this._transform.y;
|
||||
}
|
||||
|
||||
/** 该元素的变换矩阵 */
|
||||
private _transform: Transform = new Transform();
|
||||
set transform(value: Transform) {
|
||||
this._transform.bind();
|
||||
this._transform = value;
|
||||
value.bind(this);
|
||||
}
|
||||
get transform() {
|
||||
return this._transform;
|
||||
}
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region 父子关系
|
||||
|
||||
private _parent?: RenderItem;
|
||||
/** 当前元素的父元素 */
|
||||
get parent() {
|
||||
return this._parent;
|
||||
}
|
||||
/** 当前元素是否为根元素 */
|
||||
/** 当前元素是否为根元素,如果是根元素,那么必须实现 `IRenderTreeRoot` 接口 */
|
||||
readonly isRoot: boolean = false;
|
||||
|
||||
/** 该元素的变换矩阵 */
|
||||
transform: Transform = new Transform();
|
||||
private _root?: RenderItem & IRenderTreeRoot;
|
||||
get root() {
|
||||
return this._root;
|
||||
}
|
||||
|
||||
/** 当前元素是否已经连接至任意根元素 */
|
||||
get connected() {
|
||||
return !!this._root;
|
||||
}
|
||||
|
||||
/** 该渲染元素的子元素 */
|
||||
children: Set<RenderItem<ERenderItemEvent>> = new Set();
|
||||
|
||||
get x() {
|
||||
return this.transform.x;
|
||||
}
|
||||
get y() {
|
||||
return this.transform.y;
|
||||
}
|
||||
//#endregion
|
||||
|
||||
//#region 渲染配置与缓存
|
||||
/** 渲染缓存信息 */
|
||||
protected cache: MotaOffscreenCanvas2D = new MotaOffscreenCanvas2D();
|
||||
/** 是否需要更新缓存 */
|
||||
@ -253,6 +314,26 @@ export abstract class RenderItem<E extends ERenderItemEvent = ERenderItemEvent>
|
||||
readonly enableCache: boolean = true;
|
||||
/** 是否启用transform下穿机制,即画布的变换是否会继续作用到下一层画布 */
|
||||
readonly transformFallThrough: boolean = false;
|
||||
//#endregion
|
||||
|
||||
//#region 交互事件
|
||||
|
||||
/** 是否调用了 `ev.stopPropagation` */
|
||||
protected propagationStoped: Map<ActionType, boolean> = new Map();
|
||||
/** 捕获阶段缓存的事件对象 */
|
||||
private cachedEvent: Map<ActionType, IActionEvent> = new Map();
|
||||
/** 下穿模式下当前下穿过来的变换矩阵 */
|
||||
private fallTransform?: Transform;
|
||||
/** 鼠标当前是否覆盖在当前元素上 */
|
||||
private hovered: boolean = false;
|
||||
/** 是否在元素内 */
|
||||
private inElement: boolean = false;
|
||||
/** 鼠标标识符映射,键为按下的鼠标按键类型,值表示本次操作的唯一标识符,在按下、移动、抬起过程中保持一致 */
|
||||
protected mouseId: Map<MouseType, number> = new Map();
|
||||
/** 当前所有的触摸标识符 */
|
||||
protected touchId: Set<number> = new Set();
|
||||
|
||||
//#endregion
|
||||
|
||||
constructor(
|
||||
type: RenderItemPosition,
|
||||
@ -265,26 +346,14 @@ export abstract class RenderItem<E extends ERenderItemEvent = ERenderItemEvent>
|
||||
this.transformFallThrough = transformFallThrough;
|
||||
this.type = type;
|
||||
|
||||
this.transform.bind(this);
|
||||
this._transform.bind(this);
|
||||
this.cache.withGameScale(true);
|
||||
}
|
||||
|
||||
private findRoot() {
|
||||
let ele: RenderItem = this;
|
||||
while (!ele.isRoot) {
|
||||
if (!ele.parent) {
|
||||
return null;
|
||||
} else {
|
||||
ele = ele.parent;
|
||||
}
|
||||
}
|
||||
return ele;
|
||||
}
|
||||
|
||||
/**
|
||||
* 渲染函数
|
||||
* @param canvas 渲染至的画布
|
||||
* @param transform 当前变换矩阵的,渲染时已经进行变换处理,不需要对画布再次进行变换处理
|
||||
* @param transform 当前变换矩阵的,渲染时已经进行变换处理,不需要对画布再次进行变换处理。
|
||||
* 此参数可用于自己对元素进行变换处理,也会用于对子元素的处理。
|
||||
* 例如对于`absolute`类型的元素,同时有对视角改变的需求,就可以通过此参数进行变换。
|
||||
* 样板内置的`Layer`及`Damage`元素就是通过此方式实现的
|
||||
@ -294,25 +363,18 @@ export abstract class RenderItem<E extends ERenderItemEvent = ERenderItemEvent>
|
||||
transform: Transform
|
||||
): void;
|
||||
|
||||
/**
|
||||
* 修改这个对象的大小
|
||||
*/
|
||||
size(width: number, height: number): void {
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
this.cache.size(width, height);
|
||||
this.update(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* 渲染当前对象
|
||||
* @param canvas 渲染至的画布
|
||||
* @param transform 父元素的变换矩阵
|
||||
* @param transform 由父元素传递过来的变换矩阵
|
||||
*/
|
||||
renderContent(canvas: MotaOffscreenCanvas2D, transform: Transform) {
|
||||
if (this.hidden) return;
|
||||
this.emit('beforeRender', transform);
|
||||
const tran = this.transformFallThrough ? transform : this.transform;
|
||||
if (this.transformFallThrough) {
|
||||
this.fallTransform = transform;
|
||||
}
|
||||
const tran = this.transformFallThrough ? transform : this._transform;
|
||||
|
||||
const ax = -this.anchorX * this.width;
|
||||
const ay = -this.anchorY * this.height;
|
||||
@ -320,8 +382,8 @@ export abstract class RenderItem<E extends ERenderItemEvent = ERenderItemEvent>
|
||||
const ctx = canvas.ctx;
|
||||
ctx.save();
|
||||
canvas.setAntiAliasing(this.antiAliasing);
|
||||
if (this.enableCache) canvas.ctx.filter = this.filter;
|
||||
if (this.type === 'static') transformCanvas(canvas, tran);
|
||||
ctx.filter = this.filter;
|
||||
ctx.globalAlpha = this.alpha;
|
||||
ctx.globalCompositeOperation = this.composite;
|
||||
if (this.enableCache) {
|
||||
@ -343,13 +405,25 @@ export abstract class RenderItem<E extends ERenderItemEvent = ERenderItemEvent>
|
||||
this.emit('afterRender', transform);
|
||||
}
|
||||
|
||||
//#region 修改元素属性
|
||||
|
||||
/**
|
||||
* 修改这个对象的大小
|
||||
*/
|
||||
size(width: number, height: number): void {
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
this.cache.size(width, height);
|
||||
this.update(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置这个元素的位置,等效于`transform.setTranslate(x, y)`
|
||||
* @param x 横坐标
|
||||
* @param y 纵坐标
|
||||
*/
|
||||
pos(x: number, y: number) {
|
||||
this.transform.setTranslate(x, y);
|
||||
this._transform.setTranslate(x, y);
|
||||
this.update();
|
||||
}
|
||||
|
||||
@ -380,32 +454,6 @@ export abstract class RenderItem<E extends ERenderItemEvent = ERenderItemEvent>
|
||||
this.update();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前元素的绝对位置(不建议使用,因为应当很少会有获取绝对位置的需求)
|
||||
*/
|
||||
getAbsolutePosition(): LocArr {
|
||||
if (this.type === 'absolute') return [0, 0];
|
||||
const { x, y } = this.transform;
|
||||
if (!this.parent) return [x, y];
|
||||
else {
|
||||
const [px, py] = this.parent.getAbsolutePosition();
|
||||
return [x + px, y + py];
|
||||
}
|
||||
}
|
||||
|
||||
setAnchor(x: number, y: number): void {
|
||||
this.anchorX = x;
|
||||
this.anchorY = y;
|
||||
this.update();
|
||||
}
|
||||
|
||||
update(item: RenderItem<any> = this): void {
|
||||
if (this.cacheDirty) return;
|
||||
this.cacheDirty = true;
|
||||
if (this.hidden) return;
|
||||
this.parent?.update(item);
|
||||
}
|
||||
|
||||
setHD(hd: boolean): void {
|
||||
this.highResolution = hd;
|
||||
this.cache.setHD(hd);
|
||||
@ -423,6 +471,57 @@ export abstract class RenderItem<E extends ERenderItemEvent = ERenderItemEvent>
|
||||
this.parent?.requestSort();
|
||||
}
|
||||
|
||||
setAnchor(x: number, y: number): void {
|
||||
this.anchorX = x;
|
||||
this.anchorY = y;
|
||||
this.update();
|
||||
}
|
||||
|
||||
/**
|
||||
* 隐藏这个元素
|
||||
*/
|
||||
hide() {
|
||||
if (this.hidden) return;
|
||||
this.hidden = true;
|
||||
this.update(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* 显示这个元素
|
||||
*/
|
||||
show() {
|
||||
if (!this.hidden) return;
|
||||
this.hidden = false;
|
||||
this.refreshAllChildren();
|
||||
}
|
||||
|
||||
//#endregion
|
||||
|
||||
/**
|
||||
* 获取当前元素的绝对位置(不建议使用,因为应当很少会有获取绝对位置的需求)
|
||||
*/
|
||||
getAbsolutePosition(x: number = 0, y: number = 0): LocArr {
|
||||
if (this.type === 'absolute') {
|
||||
if (this.parent) return this.parent.getAbsolutePosition(0, 0);
|
||||
else return [0, 0];
|
||||
}
|
||||
const [px, py] = this._transform.transformed(x, y);
|
||||
if (!this.parent) return [px, py];
|
||||
else {
|
||||
const [px, py] = this.parent.getAbsolutePosition();
|
||||
return [x + px, y + py];
|
||||
}
|
||||
}
|
||||
|
||||
update(item: RenderItem<any> = this): void {
|
||||
if (this.cacheDirty) return;
|
||||
this.cacheDirty = true;
|
||||
if (this.hidden) return;
|
||||
this.parent?.update(item);
|
||||
}
|
||||
|
||||
//#region 动画帧与 ticker
|
||||
|
||||
requestBeforeFrame(fn: () => void): void {
|
||||
beforeFrame.push(fn);
|
||||
}
|
||||
@ -466,22 +565,27 @@ export abstract class RenderItem<E extends ERenderItemEvent = ERenderItemEvent>
|
||||
return RenderItem.tickerMap.has(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* 隐藏这个元素
|
||||
*/
|
||||
hide() {
|
||||
if (this.hidden) return;
|
||||
this.hidden = true;
|
||||
this.update(this);
|
||||
}
|
||||
//#endregion
|
||||
|
||||
/**
|
||||
* 显示这个元素
|
||||
*/
|
||||
show() {
|
||||
if (!this.hidden) return;
|
||||
this.hidden = false;
|
||||
this.refreshAllChildren();
|
||||
//#region 父子关系
|
||||
|
||||
checkRoot() {
|
||||
if (this._root) return this._root;
|
||||
if (this.isRoot) return this;
|
||||
let ele: RenderItem = this;
|
||||
while (!ele.isRoot) {
|
||||
if (ele._root) {
|
||||
this._root = ele._root;
|
||||
return this._root;
|
||||
}
|
||||
if (!ele._parent) {
|
||||
return null;
|
||||
} else {
|
||||
ele = ele._parent;
|
||||
}
|
||||
}
|
||||
this._root = ele as RenderItem & IRenderTreeRoot;
|
||||
return ele;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -510,11 +614,8 @@ export abstract class RenderItem<E extends ERenderItemEvent = ERenderItemEvent>
|
||||
this._parent = parent;
|
||||
parent.requestSort();
|
||||
this.update();
|
||||
if (this._id !== '') {
|
||||
const root = this.findRoot();
|
||||
if (!root) return;
|
||||
RenderItem.itemMap.set(this._id, this);
|
||||
}
|
||||
this.checkRoot();
|
||||
this._root?.connect(this);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -529,7 +630,8 @@ export abstract class RenderItem<E extends ERenderItemEvent = ERenderItemEvent>
|
||||
parent.requestSort();
|
||||
parent.update();
|
||||
if (!success) return false;
|
||||
RenderItem.itemMap.delete(this._id);
|
||||
this._root?.disconnect(this);
|
||||
this._root = void 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -556,6 +658,236 @@ export abstract class RenderItem<E extends ERenderItemEvent = ERenderItemEvent>
|
||||
logger.warn(37);
|
||||
}
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region 交互事件
|
||||
|
||||
/**
|
||||
* 根据事件类型和事件阶段获取事件名称
|
||||
* @param type 事件类型
|
||||
* @param progress 事件阶段
|
||||
*/
|
||||
getEventName(
|
||||
type: ActionType,
|
||||
progress: EventProgress
|
||||
): keyof ERenderItemActionEvent {
|
||||
if (progress === EventProgress.Capture) {
|
||||
return `${eventNameMap[type]}Capture` as keyof ERenderItemActionEvent;
|
||||
} else {
|
||||
return eventNameMap[type] as keyof ERenderItemActionEvent;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 传递事件,即将事件传递给父元素或子元素等,可以通过 override 来实现自己的事件传递,
|
||||
* 例如 Container 元素就需要在捕获阶段将事件传递给所有子元素,
|
||||
* 默认行为是,捕获阶段触发自身冒泡,冒泡阶段触发父元素冒泡,适用于大部分不包含子元素的元素
|
||||
* @param type 事件类型
|
||||
* @param progress 事件阶段,捕获阶段或冒泡阶段
|
||||
* @param event 正在处理的事件对象
|
||||
*/
|
||||
protected propagateEvent<T extends ActionType>(
|
||||
type: T,
|
||||
progress: EventProgress,
|
||||
event: ActionEventMap[T]
|
||||
): void {
|
||||
if (progress === EventProgress.Capture) {
|
||||
this.bubbleEvent(type, event);
|
||||
} else {
|
||||
this.parent?.bubbleEvent(type, event);
|
||||
}
|
||||
}
|
||||
|
||||
private handleEvent<T extends ActionType>(
|
||||
type: T,
|
||||
progress: EventProgress,
|
||||
event: ActionEventMap[T]
|
||||
) {
|
||||
const ev = this.processEvent(type, progress, event);
|
||||
if (ev) {
|
||||
const name = this.getEventName(type, progress);
|
||||
this.emit(name, ev);
|
||||
if (!this.propagationStoped.get(type)) {
|
||||
this.propagateEvent(type, progress, ev);
|
||||
}
|
||||
}
|
||||
this.propagationStoped.set(type, false);
|
||||
return ev;
|
||||
}
|
||||
|
||||
/**
|
||||
* 捕获事件
|
||||
* @param type 事件类型
|
||||
* @param event 由父元素传递来的事件
|
||||
*/
|
||||
captureEvent<T extends ActionType>(type: T, event: ActionEventMap[T]) {
|
||||
return this.handleEvent(type, EventProgress.Capture, event);
|
||||
}
|
||||
|
||||
/**
|
||||
* 冒泡事件
|
||||
* @param type 事件类型
|
||||
* @param event 由子元素传递来的事件
|
||||
*/
|
||||
bubbleEvent<T extends ActionType>(type: T, event: ActionEventMap[T]) {
|
||||
return this.handleEvent(type, EventProgress.Bubble, event);
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理事件,用于根据上一级传递的事件内容生成新的事件内容,并执行一些事件的默认行为
|
||||
* @param type 事件类型
|
||||
* @param progress 事件阶段,捕获阶段还是冒泡阶段
|
||||
* @param event 由上一级(捕获阶段的父元素,冒泡阶段的子元素)传递来的事件内容
|
||||
*/
|
||||
protected processEvent<T extends ActionType>(
|
||||
type: T,
|
||||
progress: EventProgress,
|
||||
event: ActionEventMap[T]
|
||||
): ActionEventMap[T] | null {
|
||||
if (progress === EventProgress.Capture) {
|
||||
// 捕获阶段需要计算鼠标位置
|
||||
const tran = this.transformFallThrough
|
||||
? this.fallTransform
|
||||
: this._transform;
|
||||
if (!tran) return null;
|
||||
const [nx, ny] = this.calActionPosition(event, tran);
|
||||
const inElement = this.isActionInElement(nx, ny);
|
||||
// 在元素范围内,执行事件
|
||||
const newEvent: ActionEventMap[T] = {
|
||||
...event,
|
||||
offsetX: nx,
|
||||
offsetY: ny,
|
||||
target: this,
|
||||
stopPropagation: () => {
|
||||
this.propagationStoped.set(type, true);
|
||||
}
|
||||
};
|
||||
this.inElement = inElement;
|
||||
if (!this.processCapture(type, newEvent, inElement)) return null;
|
||||
this.cachedEvent.set(type, newEvent);
|
||||
return newEvent;
|
||||
} else {
|
||||
const newEvent = this.cachedEvent.get(type) as ActionEventMap[T];
|
||||
this.processBubble(type, newEvent, this.inElement);
|
||||
this.cachedEvent.delete(type);
|
||||
return newEvent;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理捕获阶段的事件,可以通过 override 来添加新内容,注意调用 `super.processCapture` 来执行默认行为
|
||||
* @param type 事件类型
|
||||
* @param event 正在处理的事件对象
|
||||
* @param inElement 当前鼠标是否在元素内
|
||||
* @returns 是否继续传递事件
|
||||
*/
|
||||
protected processCapture<T extends ActionType>(
|
||||
type: T,
|
||||
event: ActionEventMap[T],
|
||||
inElement: boolean
|
||||
): boolean {
|
||||
switch (type) {
|
||||
case ActionType.Move: {
|
||||
if (this.hovered && !inElement) {
|
||||
this.hovered = false;
|
||||
this.emit('leaveCapture', event);
|
||||
this.emit('leave', event);
|
||||
return false;
|
||||
} else if (!this.hovered && inElement) {
|
||||
this.hovered = true;
|
||||
this.emit('enterCapture', event);
|
||||
this.emit('enter', event);
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ActionType.Down: {
|
||||
// 记录标识符,用于判定 click
|
||||
if (!inElement) return false;
|
||||
if (event.touch) {
|
||||
this.touchId.add(event.identifier);
|
||||
} else {
|
||||
this.mouseId.set(event.type, event.identifier);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ActionType.Click: {
|
||||
if (!inElement) return false;
|
||||
if (event.touch) {
|
||||
if (!this.touchId.has(event.identifier)) {
|
||||
return false;
|
||||
}
|
||||
this.touchId.delete(event.identifier);
|
||||
} else {
|
||||
if (this.mouseId.get(event.type) !== event.identifier) {
|
||||
this.mouseId.delete(event.type);
|
||||
return false;
|
||||
}
|
||||
this.mouseId.delete(event.type);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return inElement;
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理冒泡阶段的事件,可以通过 override 来添加新内容,注意调用 `super.processBubble` 来执行默认行为
|
||||
* @param type 事件类型
|
||||
* @param event 正在处理的事件对象
|
||||
* @param inElement 当前鼠标是否在元素内
|
||||
* @returns 是否继续传递事件
|
||||
*/
|
||||
protected processBubble<T extends ActionType>(
|
||||
type: T,
|
||||
event: ActionEventMap[T],
|
||||
inElement: boolean
|
||||
): boolean {
|
||||
return inElement;
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算一个点击事件在该元素上的位置
|
||||
* @param event 触发的事件
|
||||
* @param transform 当前的变换矩阵
|
||||
*/
|
||||
protected calActionPosition(
|
||||
event: IActionEvent,
|
||||
transform: Transform
|
||||
): vec3 {
|
||||
const x = event.offsetX + this.anchorX * this.width;
|
||||
const y = event.offsetY + this.anchorY * this.height;
|
||||
return transform.untransformed(x, y);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断一个点击事件是否在元素内,可以通过 override 来修改其行为
|
||||
* @param x 横坐标
|
||||
* @param y 纵坐标
|
||||
*/
|
||||
protected isActionInElement(x: number, y: number) {
|
||||
return x >= 0 && x < this.width && y >= 0 && y < this.height;
|
||||
}
|
||||
|
||||
actionClick() {}
|
||||
|
||||
actionDown() {}
|
||||
|
||||
actionUp() {}
|
||||
|
||||
actionMove() {}
|
||||
|
||||
actionEnter() {}
|
||||
|
||||
actionLeave() {}
|
||||
|
||||
actionWheel() {}
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region vue支持 props处理
|
||||
|
||||
/**
|
||||
* 判断一个prop是否是期望类型
|
||||
* @param value 实际值
|
||||
@ -616,12 +948,12 @@ export abstract class RenderItem<E extends ERenderItemEvent = ERenderItemEvent>
|
||||
switch (key) {
|
||||
case 'x': {
|
||||
if (!this.assertType(nextValue, 'number', key)) return;
|
||||
this.pos(nextValue, this.transform.y);
|
||||
this.pos(nextValue, this._transform.y);
|
||||
return;
|
||||
}
|
||||
case 'y': {
|
||||
if (!this.assertType(nextValue, 'number', key)) return;
|
||||
this.pos(this.transform.x, nextValue);
|
||||
this.pos(this._transform.x, nextValue);
|
||||
return;
|
||||
}
|
||||
case 'anchorX': {
|
||||
@ -707,6 +1039,8 @@ export abstract class RenderItem<E extends ERenderItemEvent = ERenderItemEvent>
|
||||
}
|
||||
}
|
||||
|
||||
//#endregion
|
||||
|
||||
/**
|
||||
* 摧毁这个渲染元素,摧毁后不应继续使用
|
||||
*/
|
||||
@ -715,7 +1049,6 @@ export abstract class RenderItem<E extends ERenderItemEvent = ERenderItemEvent>
|
||||
this.emit('destroy');
|
||||
this.removeAllListeners();
|
||||
this.cache.delete();
|
||||
RenderItem.itemMap.delete(this._id);
|
||||
}
|
||||
}
|
||||
|
||||
@ -740,63 +1073,3 @@ RenderItem.ticker.add(time => {
|
||||
arr.forEach(v => v());
|
||||
}
|
||||
});
|
||||
|
||||
export interface IAnimateFrame {
|
||||
updateFrameAnimate(frame: number, time: number): void;
|
||||
}
|
||||
|
||||
interface RenderEvent {
|
||||
animateFrame: [frame: number, time: number];
|
||||
}
|
||||
|
||||
class RenderEmits extends EventEmitter<RenderEvent> {
|
||||
private framer: Set<IAnimateFrame> = new Set();
|
||||
|
||||
/**
|
||||
* 添加一个可更新帧动画的对象
|
||||
*/
|
||||
addFramer(framer: IAnimateFrame) {
|
||||
this.framer.add(framer);
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除一个可更新帧动画的对象
|
||||
*/
|
||||
removeFramer(framer: IAnimateFrame) {
|
||||
this.framer.delete(framer);
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新所有帧动画
|
||||
* @param frame 帧数
|
||||
* @param time 帧动画时刻
|
||||
*/
|
||||
emitAnimateFrame(frame: number, time: number) {
|
||||
this.framer.forEach(v => v.updateFrameAnimate(frame, time));
|
||||
this.emit('animateFrame', frame, time);
|
||||
}
|
||||
}
|
||||
|
||||
export const renderEmits = new RenderEmits();
|
||||
|
||||
Mota.require('var', 'hook').once('reset', () => {
|
||||
let lastTime = 0;
|
||||
RenderItem.ticker.add(time => {
|
||||
if (!core.isPlaying()) return;
|
||||
if (time - lastTime > core.values.animateSpeed) {
|
||||
RenderItem.animatedFrame++;
|
||||
lastTime = time;
|
||||
renderEmits.emitAnimateFrame(RenderItem.animatedFrame, time);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
export function transformCanvas(
|
||||
canvas: MotaOffscreenCanvas2D,
|
||||
transform: Transform
|
||||
) {
|
||||
const { ctx } = canvas;
|
||||
const mat = transform.mat;
|
||||
const [a, b, , c, d, , e, f] = mat;
|
||||
ctx.transform(a, b, c, d, e, f);
|
||||
}
|
||||
|
@ -1,44 +0,0 @@
|
||||
import { logger } from '../common/logger';
|
||||
import { MotaOffscreenCanvas2D } from '../fx/canvas2d';
|
||||
|
||||
export class CanvasPool {
|
||||
private pool: MotaOffscreenCanvas2D[] = [];
|
||||
private requested: Set<MotaOffscreenCanvas2D> = new Set();
|
||||
|
||||
/**
|
||||
* 申请画布
|
||||
* @param num 要申请多少画布
|
||||
*/
|
||||
requestCanvas(num: number): MotaOffscreenCanvas2D[] {
|
||||
if (this.pool.length < num) {
|
||||
const diff = num - this.pool.length;
|
||||
for (let i = 0; i < diff; i++) {
|
||||
this.pool.push(new MotaOffscreenCanvas2D(false));
|
||||
}
|
||||
}
|
||||
const toProvide = this.pool.splice(0, num);
|
||||
toProvide.forEach(v => this.requested.add(v));
|
||||
return toProvide;
|
||||
}
|
||||
|
||||
/**
|
||||
* 退回画布
|
||||
* @param canvas 要退回多少画布
|
||||
*/
|
||||
returnCanvas(canvas: MotaOffscreenCanvas2D[]) {
|
||||
canvas.forEach(v => {
|
||||
if (!this.requested.has(v)) {
|
||||
logger.warn(40);
|
||||
return;
|
||||
}
|
||||
this.requested.delete(v);
|
||||
this.pool.push(v);
|
||||
v.clear();
|
||||
});
|
||||
}
|
||||
|
||||
destroy() {
|
||||
this.pool.forEach(v => v.delete());
|
||||
this.requested.forEach(v => v.delete());
|
||||
}
|
||||
}
|
@ -1,11 +1,10 @@
|
||||
import { logger } from '@/core/common/logger';
|
||||
import { RenderAdapter } from '../adapter';
|
||||
import { Sprite } from '../sprite';
|
||||
import { HeroRenderer } from './hero';
|
||||
import { ILayerGroupRenderExtends, LayerGroup } from './layer';
|
||||
import { MotaOffscreenCanvas2D } from '@/core/fx/canvas2d';
|
||||
import { ERenderItemEvent, RenderItem, transformCanvas } from '../item';
|
||||
import { ERenderItemEvent, RenderItem } from '../item';
|
||||
import { Transform } from '../transform';
|
||||
import { transformCanvas } from '../utils';
|
||||
|
||||
export class LayerGroupAnimate implements ILayerGroupRenderExtends {
|
||||
static animateList: Set<LayerGroupAnimate> = new Set();
|
||||
|
@ -6,13 +6,7 @@ import {
|
||||
Layer,
|
||||
LayerGroup
|
||||
} from './layer';
|
||||
import { ESpriteEvent, Sprite } from '../sprite';
|
||||
import {
|
||||
BlockCacher,
|
||||
CanvasCacheItem,
|
||||
IBlockCacheable,
|
||||
ICanvasCacheItem
|
||||
} from './block';
|
||||
import { BlockCacher, CanvasCacheItem, ICanvasCacheItem } from './block';
|
||||
import type {
|
||||
DamageEnemy,
|
||||
EnemyCollection,
|
||||
@ -21,10 +15,11 @@ import type {
|
||||
import { MotaOffscreenCanvas2D } from '@/core/fx/canvas2d';
|
||||
import { isNil } from 'lodash-es';
|
||||
import { getDamageColor } from '@/plugin/utils';
|
||||
import { ERenderItemEvent, RenderItem, transformCanvas } from '../item';
|
||||
import { ERenderItemEvent, RenderItem } from '../item';
|
||||
import EventEmitter from 'eventemitter3';
|
||||
import { Transform } from '../transform';
|
||||
import { ElementNamespace, ComponentInternalInstance } from 'vue';
|
||||
import { transformCanvas } from '../utils';
|
||||
|
||||
const ensureFloorDamage = Mota.require('fn', 'ensureFloorDamage');
|
||||
|
||||
@ -75,7 +70,7 @@ export class FloorDamageExtends
|
||||
this.update(floor);
|
||||
};
|
||||
|
||||
private onSetBlock = (x: number, y: number, floor: FloorIds) => {
|
||||
// private onSetBlock = (x: number, y: number, floor: FloorIds) => {
|
||||
// this.sprite.enemy?.once('extract', () => {
|
||||
// if (floor !== this.sprite.enemy?.floorId) return;
|
||||
// this.sprite.updateBlocks();
|
||||
@ -83,14 +78,14 @@ export class FloorDamageExtends
|
||||
// if (!this.floorBinder.bindThisFloor) {
|
||||
// this.sprite.enemy?.extract();
|
||||
// }
|
||||
};
|
||||
// };
|
||||
|
||||
/**
|
||||
* 进行楼层更新监听
|
||||
*/
|
||||
private listen() {
|
||||
this.floorBinder.on('update', this.onUpdate);
|
||||
this.floorBinder.on('setBlock', this.onSetBlock);
|
||||
// this.floorBinder.on('setBlock', this.onSetBlock);
|
||||
}
|
||||
|
||||
awake(group: LayerGroup): void {
|
||||
@ -106,9 +101,9 @@ export class FloorDamageExtends
|
||||
}
|
||||
}
|
||||
|
||||
onDestroy(group: LayerGroup): void {
|
||||
onDestroy(_group: LayerGroup): void {
|
||||
this.floorBinder.off('update', this.onUpdate);
|
||||
this.floorBinder.off('setBlock', this.onSetBlock);
|
||||
// this.floorBinder.off('setBlock', this.onSetBlock);
|
||||
}
|
||||
}
|
||||
|
||||
@ -208,6 +203,7 @@ export class Damage extends RenderItem<EDamageEvent> {
|
||||
this.cellSize = size;
|
||||
this.update();
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新怪物列表。更新后,{@link Damage.enemy} 会丢失原来的怪物列表引用,换为传入的列表引用
|
||||
* @param enemy 怪物列表
|
||||
@ -259,7 +255,7 @@ export class Damage extends RenderItem<EDamageEvent> {
|
||||
blocks.forEach(v => this.dirtyBlocks.add(v));
|
||||
this.emit('updateBlocks', blocks);
|
||||
} else {
|
||||
this.blockData.forEach((v, i) => {
|
||||
this.blockData.forEach((_v, i) => {
|
||||
this.dirtyBlocks.add(i);
|
||||
});
|
||||
this.emit('updateBlocks', new Set(this.blockData.keys()));
|
||||
@ -473,7 +469,6 @@ export class Damage extends RenderItem<EDamageEvent> {
|
||||
// console.time('damage');
|
||||
const { ctx } = canvas;
|
||||
transformCanvas(canvas, transform);
|
||||
// console.trace();
|
||||
|
||||
const render = this.calNeedRender(transform);
|
||||
const block = this.block;
|
||||
|
@ -170,7 +170,7 @@ export class LayerGroupFloorBinder
|
||||
LayerGroupFloorBinder.activedBinder.add(this);
|
||||
}
|
||||
|
||||
onLayerAdd(group: LayerGroup, layer: Layer): void {
|
||||
onLayerAdd(_group: LayerGroup, layer: Layer): void {
|
||||
this.checkLayerExtends(layer);
|
||||
}
|
||||
|
||||
@ -300,7 +300,7 @@ export class LayerFloorBinder implements ILayerRenderExtends {
|
||||
this.checkListen();
|
||||
}
|
||||
|
||||
onDestroy(layer: Layer) {
|
||||
onDestroy(_layer: Layer) {
|
||||
LayerFloorBinder.listenedBinder.delete(this);
|
||||
this.parent?.layerBinders.delete(this);
|
||||
}
|
||||
@ -397,11 +397,11 @@ export class LayerDoorAnimate implements ILayerRenderExtends {
|
||||
doorAdapter.add(this);
|
||||
}
|
||||
|
||||
onMovingUpdate(layer: Layer, renderable: LayerMovingRenderable[]): void {
|
||||
onMovingUpdate(_layer: Layer, renderable: LayerMovingRenderable[]): void {
|
||||
renderable.push(...this.moving);
|
||||
}
|
||||
|
||||
onDestroy(layer: Layer): void {
|
||||
onDestroy(_layer: Layer): void {
|
||||
doorAdapter.remove(this);
|
||||
}
|
||||
}
|
||||
|
@ -79,6 +79,54 @@ export abstract class GraphicItemBase
|
||||
private strokeAndFill: boolean = false;
|
||||
private propFillSet: boolean = false;
|
||||
|
||||
/**
|
||||
* 获取这个元素的绘制路径
|
||||
*/
|
||||
abstract getPath(): Path2D;
|
||||
|
||||
protected render(
|
||||
canvas: MotaOffscreenCanvas2D,
|
||||
_transform: Transform
|
||||
): void {
|
||||
const ctx = canvas.ctx;
|
||||
this.setCanvasState(canvas);
|
||||
const path = this.getPath();
|
||||
switch (this.mode) {
|
||||
case GraphicMode.Fill:
|
||||
ctx.fill(path, this.fillRule);
|
||||
break;
|
||||
case GraphicMode.Stroke:
|
||||
ctx.stroke(path);
|
||||
break;
|
||||
case GraphicMode.FillAndStroke:
|
||||
ctx.fill(path, this.fillRule);
|
||||
ctx.stroke(path);
|
||||
break;
|
||||
case GraphicMode.StrokeAndFill:
|
||||
ctx.stroke(path);
|
||||
ctx.fill(path, this.fillRule);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
protected isActionInElement(x: number, y: number): boolean {
|
||||
const ctx = this.cache.ctx;
|
||||
const path = this.getPath();
|
||||
switch (this.mode) {
|
||||
case GraphicMode.Fill:
|
||||
return ctx.isPointInPath(path, x, y, this.fillRule);
|
||||
case GraphicMode.Stroke:
|
||||
return ctx.isPointInStroke(path, x, y);
|
||||
case GraphicMode.FillAndStroke:
|
||||
case GraphicMode.StrokeAndFill:
|
||||
return (
|
||||
ctx.isPointInPath(path, x, y, this.fillRule) ||
|
||||
ctx.isPointInStroke(path, x, y)
|
||||
);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置描边绘制的信息
|
||||
* @param options 线的信息
|
||||
@ -253,31 +301,10 @@ export abstract class GraphicItemBase
|
||||
}
|
||||
|
||||
export class Rect extends GraphicItemBase {
|
||||
protected render(
|
||||
canvas: MotaOffscreenCanvas2D,
|
||||
_transform: Transform
|
||||
): void {
|
||||
const ctx = canvas.ctx;
|
||||
this.setCanvasState(canvas);
|
||||
ctx.beginPath();
|
||||
ctx.rect(this.x, this.y, this.width, this.height);
|
||||
|
||||
switch (this.mode) {
|
||||
case GraphicMode.Fill:
|
||||
ctx.fill(this.fillRule);
|
||||
break;
|
||||
case GraphicMode.Stroke:
|
||||
ctx.stroke();
|
||||
break;
|
||||
case GraphicMode.FillAndStroke:
|
||||
ctx.fill(this.fillRule);
|
||||
ctx.stroke();
|
||||
break;
|
||||
case GraphicMode.StrokeAndFill:
|
||||
ctx.stroke();
|
||||
ctx.fill(this.fillRule);
|
||||
break;
|
||||
}
|
||||
getPath(): Path2D {
|
||||
const path = new Path2D();
|
||||
path.rect(this.x, this.y, this.width, this.height);
|
||||
return path;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -354,8 +354,8 @@ export class HeroRenderer
|
||||
moveAs(x: number, y: number, time: number, fn: TimingFn<3>): Promise<void> {
|
||||
if (!this.moving) return Promise.resolve();
|
||||
if (!this.renderable) return Promise.resolve();
|
||||
let nowZIndex = fn(0)[2];
|
||||
let startTime = Date.now();
|
||||
const nowZIndex = fn(0)[2];
|
||||
const startTime = Date.now();
|
||||
return new Promise(res => {
|
||||
this.layer.delegateTicker(
|
||||
() => {
|
||||
@ -414,7 +414,7 @@ export class HeroRenderer
|
||||
layer.removeTicker(this.moveId);
|
||||
}
|
||||
|
||||
onMovingUpdate(layer: Layer, renderable: LayerMovingRenderable[]): void {
|
||||
onMovingUpdate(_layer: Layer, renderable: LayerMovingRenderable[]): void {
|
||||
if (this.renderable) {
|
||||
renderable.push(this.renderable);
|
||||
this.emit('append', renderable);
|
||||
|
@ -2,7 +2,7 @@ import { MotaOffscreenCanvas2D } from '@/core/fx/canvas2d';
|
||||
import { Container, EContainerEvent } from '../container';
|
||||
import { Sprite } from '../sprite';
|
||||
import { TimingFn } from 'mutate-animate';
|
||||
import { IAnimateFrame, renderEmits, RenderItem } from '../item';
|
||||
import { RenderItem } from '../item';
|
||||
import { logger } from '@/core/common/logger';
|
||||
import { RenderableData, texture } from '../cache';
|
||||
import { BlockCacher, CanvasCacheItem, ICanvasCacheItem } from './block';
|
||||
@ -11,6 +11,7 @@ import { LayerFloorBinder, LayerGroupFloorBinder } from './floor';
|
||||
import { RenderAdapter } from '../adapter';
|
||||
import { ElementNamespace, ComponentInternalInstance } from 'vue';
|
||||
import { Camera } from '../camera';
|
||||
import { IAnimateFrame, renderEmits } from '../frame';
|
||||
|
||||
export interface ILayerGroupRenderExtends {
|
||||
/** 拓展的唯一标识符 */
|
||||
|
@ -1,17 +1,12 @@
|
||||
import { MotaOffscreenCanvas2D } from '@/core/fx/canvas2d';
|
||||
import {
|
||||
ERenderItemEvent,
|
||||
IAnimateFrame,
|
||||
renderEmits,
|
||||
RenderItem,
|
||||
RenderItemPosition
|
||||
} from '../item';
|
||||
import { ERenderItemEvent, RenderItem, RenderItemPosition } from '../item';
|
||||
import { Transform } from '../transform';
|
||||
import { ElementNamespace, ComponentInternalInstance } from 'vue';
|
||||
import { AutotileRenderable, RenderableData } from '../cache';
|
||||
import { texture } from '../cache';
|
||||
import { isNil } from 'lodash-es';
|
||||
import { logger } from '@/core/common/logger';
|
||||
import { IAnimateFrame, renderEmits } from '../frame';
|
||||
|
||||
type CanvasStyle = string | CanvasGradient | CanvasPattern;
|
||||
|
||||
@ -241,8 +236,8 @@ export class Icon extends RenderItem<EIconEvent> implements IAnimateFrame {
|
||||
const renderable = this.renderable;
|
||||
if (!renderable) return;
|
||||
const [x, y, w, h] = renderable.render[0];
|
||||
const cw = canvas.width;
|
||||
const ch = canvas.height;
|
||||
const cw = this.width;
|
||||
const ch = this.height;
|
||||
const frame = this.animate
|
||||
? RenderItem.animatedFrame % renderable.frame
|
||||
: 0;
|
||||
|
@ -1,4 +1,3 @@
|
||||
import { logger } from '@/core/common/logger';
|
||||
import { HeroRenderer } from './hero';
|
||||
import { ILayerGroupRenderExtends, LayerGroup } from './layer';
|
||||
import { LayerGroupFloorBinder } from './floor';
|
||||
@ -162,7 +161,7 @@ export class FloorViewport implements ILayerGroupRenderExtends {
|
||||
let yStartTime: number = Date.now();
|
||||
let ending: boolean = false;
|
||||
// 这个数等于 sinh(2),用这个数的话,可以正好在刚开始移动的时候达到1的斜率,效果会比较好
|
||||
let transitionTime = this.hero!.speed * 3.626860407847019;
|
||||
const transitionTime = this.hero!.speed * 3.626860407847019;
|
||||
|
||||
const setTargetX = (x: number, time: number) => {
|
||||
if (x === xTarget) return;
|
||||
|
@ -1,15 +1,55 @@
|
||||
import { logger } from '../common/logger';
|
||||
import { MotaOffscreenCanvas2D } from '../fx/canvas2d';
|
||||
import { Container } from './container';
|
||||
import { RenderItem } from './item';
|
||||
import {
|
||||
ActionType,
|
||||
IActionEvent,
|
||||
IWheelEvent,
|
||||
MouseType,
|
||||
WheelType
|
||||
} from './event';
|
||||
import { IRenderTreeRoot, RenderItem } from './item';
|
||||
import { Transform } from './transform';
|
||||
|
||||
export class MotaRenderer extends Container {
|
||||
interface TouchInfo {
|
||||
/** 这次触摸在渲染系统的标识符 */
|
||||
identifier: number;
|
||||
/** 浏览器的 clientX,用于判断这个触点有没有移动 */
|
||||
clientX: number;
|
||||
/** 浏览器的 clientY,用于判断这个触点有没有移动 */
|
||||
clientY: number;
|
||||
/** 是否覆盖在了当前元素上 */
|
||||
hovered: boolean;
|
||||
}
|
||||
|
||||
interface MouseInfo {
|
||||
/** 这个鼠标按键的标识符 */
|
||||
identifier: number;
|
||||
}
|
||||
|
||||
export class MotaRenderer extends Container implements IRenderTreeRoot {
|
||||
static list: Map<string, MotaRenderer> = new Map();
|
||||
|
||||
/** 所有连接到此根元素的渲染元素的 id 到元素自身的映射 */
|
||||
protected idMap: Map<string, RenderItem> = new Map();
|
||||
|
||||
/** 最后一次按下的鼠标按键,用于处理鼠标移动 */
|
||||
private lastMouse: MouseType = MouseType.None;
|
||||
/** 每个触点的信息 */
|
||||
private touchInfo: Map<number, TouchInfo> = new Map();
|
||||
/** 触点列表 */
|
||||
private touchList: Map<number, Touch> = new Map();
|
||||
/** 每个鼠标按键的信息 */
|
||||
private mouseInfo: Map<MouseType, MouseInfo> = new Map();
|
||||
/** 操作的标识符 */
|
||||
private actionIdentifier: number = 0;
|
||||
|
||||
/** 用于终止 document 上的监听 */
|
||||
private abort?: AbortController;
|
||||
|
||||
target!: MotaOffscreenCanvas2D;
|
||||
|
||||
readonly isRoot: boolean = true;
|
||||
readonly isRoot = true;
|
||||
|
||||
constructor(id: string = 'render-main') {
|
||||
super('static', false);
|
||||
@ -38,6 +78,301 @@ export class MotaRenderer extends Container {
|
||||
};
|
||||
|
||||
update();
|
||||
this.listen();
|
||||
}
|
||||
|
||||
private listen() {
|
||||
// 画布监听
|
||||
const canvas = this.target.canvas;
|
||||
canvas.addEventListener('mousedown', ev => {
|
||||
const mouse = this.getMouseType(ev);
|
||||
this.lastMouse = mouse;
|
||||
this.captureEvent(
|
||||
ActionType.Down,
|
||||
this.createMouseAction(ev, ActionType.Down, mouse)
|
||||
);
|
||||
});
|
||||
canvas.addEventListener('mouseup', ev => {
|
||||
const event = this.createMouseAction(ev, ActionType.Up);
|
||||
this.captureEvent(ActionType.Up, event);
|
||||
this.captureEvent(ActionType.Click, event);
|
||||
});
|
||||
canvas.addEventListener('mousemove', ev => {
|
||||
const event = this.createMouseAction(
|
||||
ev,
|
||||
ActionType.Move,
|
||||
this.lastMouse
|
||||
);
|
||||
this.captureEvent(ActionType.Move, event);
|
||||
});
|
||||
canvas.addEventListener('mouseenter', ev => {
|
||||
const event = this.createMouseAction(ev, ActionType.Enter);
|
||||
this.emit('enterCapture', event);
|
||||
this.emit('enter', event);
|
||||
});
|
||||
canvas.addEventListener('mouseleave', ev => {
|
||||
const event = this.createMouseAction(ev, ActionType.Leave);
|
||||
this.emit('leaveCapture', event);
|
||||
this.emit('leave', event);
|
||||
});
|
||||
document.addEventListener('touchstart', ev => {
|
||||
this.createTouchAction(ev, ActionType.Down).forEach(v => {
|
||||
this.captureEvent(ActionType.Down, v);
|
||||
});
|
||||
});
|
||||
document.addEventListener('touchend', ev => {
|
||||
this.createTouchAction(ev, ActionType.Up).forEach(v => {
|
||||
this.captureEvent(ActionType.Up, v);
|
||||
this.captureEvent(ActionType.Click, v);
|
||||
});
|
||||
});
|
||||
document.addEventListener('touchcancel', ev => {
|
||||
this.createTouchAction(ev, ActionType.Up).forEach(v => {
|
||||
this.captureEvent(ActionType.Up, v);
|
||||
});
|
||||
});
|
||||
document.addEventListener('touchmove', ev => {
|
||||
this.createTouchAction(ev, ActionType.Move).forEach(v => {
|
||||
const touch = this.touchInfo.get(v.identifier);
|
||||
if (!touch) return;
|
||||
const inElement = this.isTouchInCanvas(v.offsetX, v.offsetY);
|
||||
if (touch.hovered && !inElement) {
|
||||
this.emit('leaveCapture', v);
|
||||
this.emit('leave', v);
|
||||
}
|
||||
if (!touch.hovered && inElement) {
|
||||
this.emit('enterCapture', v);
|
||||
this.emit('enter', v);
|
||||
}
|
||||
this.captureEvent(ActionType.Move, v);
|
||||
});
|
||||
});
|
||||
canvas.addEventListener('wheel', ev => {
|
||||
this.captureEvent(
|
||||
ActionType.Wheel,
|
||||
this.createWheelAction(ev, ActionType.Wheel)
|
||||
);
|
||||
});
|
||||
// 文档监听
|
||||
const abort = new AbortController();
|
||||
const signal = abort.signal;
|
||||
this.abort = abort;
|
||||
const clear = (ev: MouseEvent) => {
|
||||
const mouse = this.getMouseButtons(ev);
|
||||
for (const button of this.mouseInfo.keys()) {
|
||||
if (!(mouse & button)) {
|
||||
this.mouseInfo.delete(button);
|
||||
}
|
||||
}
|
||||
};
|
||||
document.addEventListener('click', clear, { signal });
|
||||
document.addEventListener('mouseenter', clear, { signal });
|
||||
document.addEventListener('mouseleave', clear, { signal });
|
||||
}
|
||||
|
||||
private isTouchInCanvas(clientX: number, clientY: number) {
|
||||
const rect = this.target.canvas.getBoundingClientRect();
|
||||
const { left, right, top, bottom } = rect;
|
||||
const x = clientX;
|
||||
const y = clientY;
|
||||
return x >= left && x <= right && y >= top && y <= bottom;
|
||||
}
|
||||
|
||||
private getMouseType(ev: MouseEvent): MouseType {
|
||||
switch (ev.button) {
|
||||
case 0:
|
||||
return MouseType.Left;
|
||||
case 1:
|
||||
return MouseType.Middle;
|
||||
case 2:
|
||||
return MouseType.Right;
|
||||
case 3:
|
||||
return MouseType.Back;
|
||||
case 4:
|
||||
return MouseType.Forward;
|
||||
}
|
||||
return MouseType.None;
|
||||
}
|
||||
|
||||
private getActiveMouseIdentifier(mouse: MouseType) {
|
||||
if (this.lastMouse === MouseType.None) {
|
||||
return -1;
|
||||
} else {
|
||||
const info = this.mouseInfo.get(mouse);
|
||||
if (!info) return -1;
|
||||
else return info.identifier;
|
||||
}
|
||||
}
|
||||
|
||||
private getMouseIdentifier(type: ActionType, mouse: MouseType): number {
|
||||
switch (type) {
|
||||
case ActionType.Down: {
|
||||
const id = this.actionIdentifier++;
|
||||
this.mouseInfo.set(mouse, { identifier: id });
|
||||
return id;
|
||||
}
|
||||
case ActionType.Move:
|
||||
case ActionType.Enter:
|
||||
case ActionType.Leave:
|
||||
case ActionType.Wheel: {
|
||||
return this.getActiveMouseIdentifier(mouse);
|
||||
}
|
||||
case ActionType.Up:
|
||||
case ActionType.Click: {
|
||||
const id = this.getActiveMouseIdentifier(mouse);
|
||||
this.mouseInfo.delete(mouse);
|
||||
return id;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private getMouseButtons(event: MouseEvent): number {
|
||||
if (event.buttons === 0) return MouseType.None;
|
||||
let buttons = 0;
|
||||
if (event.buttons & 0b1) buttons |= MouseType.Left;
|
||||
if (event.buttons & 0b10) buttons |= MouseType.Right;
|
||||
if (event.buttons & 0b100) buttons |= MouseType.Middle;
|
||||
if (event.buttons & 0b1000) buttons |= MouseType.Back;
|
||||
if (event.buttons & 0b10000) buttons |= MouseType.Forward;
|
||||
return buttons;
|
||||
}
|
||||
|
||||
private createMouseAction(
|
||||
event: MouseEvent,
|
||||
type: ActionType,
|
||||
mouse: MouseType = this.getMouseType(event)
|
||||
): IActionEvent {
|
||||
const id = this.getMouseIdentifier(type, mouse);
|
||||
const x = event.offsetX / core.domStyle.scale;
|
||||
const y = event.offsetY / core.domStyle.scale;
|
||||
return {
|
||||
target: this,
|
||||
identifier: id,
|
||||
touch: false,
|
||||
offsetX: x,
|
||||
offsetY: y,
|
||||
absoluteX: x,
|
||||
absoluteY: y,
|
||||
type: mouse,
|
||||
buttons: this.getMouseButtons(event),
|
||||
altKey: event.altKey,
|
||||
ctrlKey: event.ctrlKey,
|
||||
shiftKey: event.shiftKey,
|
||||
metaKey: event.metaKey,
|
||||
stopPropagation: () => {
|
||||
this.propagationStoped.set(type, true);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private createWheelAction(
|
||||
event: WheelEvent,
|
||||
type: ActionType,
|
||||
mouse: MouseType = this.getMouseType(event)
|
||||
): IWheelEvent {
|
||||
const ev = this.createMouseAction(event, type, mouse) as IWheelEvent;
|
||||
ev.wheelX = event.deltaX;
|
||||
ev.wheelY = event.deltaY;
|
||||
ev.wheelZ = event.deltaZ;
|
||||
switch (event.deltaMode) {
|
||||
case 0x00:
|
||||
ev.wheelType = WheelType.Pixel;
|
||||
break;
|
||||
case 0x01:
|
||||
ev.wheelType = WheelType.Line;
|
||||
break;
|
||||
case 0x02:
|
||||
ev.wheelType = WheelType.Page;
|
||||
break;
|
||||
default:
|
||||
ev.wheelType = WheelType.None;
|
||||
break;
|
||||
}
|
||||
return ev;
|
||||
}
|
||||
|
||||
private getTouchIdentifier(touch: Touch, type: ActionType) {
|
||||
if (type === ActionType.Down) {
|
||||
const id = this.actionIdentifier++;
|
||||
this.touchInfo.set(touch.identifier, {
|
||||
identifier: id,
|
||||
clientX: touch.clientX,
|
||||
clientY: touch.clientY,
|
||||
hovered: this.isTouchInCanvas(touch.clientX, touch.clientY)
|
||||
});
|
||||
return id;
|
||||
}
|
||||
const info = this.touchInfo.get(touch.identifier);
|
||||
if (!info) return -1;
|
||||
return info.identifier;
|
||||
}
|
||||
|
||||
private createTouch(
|
||||
touch: Touch,
|
||||
type: ActionType,
|
||||
event: TouchEvent,
|
||||
rect: DOMRect
|
||||
): IActionEvent {
|
||||
const x = (touch.clientX - rect.left) / core.domStyle.scale;
|
||||
const y = (touch.clientY - rect.top) / core.domStyle.scale;
|
||||
return {
|
||||
target: this,
|
||||
identifier: this.getTouchIdentifier(touch, type),
|
||||
touch: true,
|
||||
offsetX: x,
|
||||
offsetY: y,
|
||||
absoluteX: x,
|
||||
absoluteY: y,
|
||||
type: MouseType.Left,
|
||||
buttons: MouseType.Left,
|
||||
altKey: event.altKey,
|
||||
ctrlKey: event.ctrlKey,
|
||||
shiftKey: event.shiftKey,
|
||||
metaKey: event.metaKey,
|
||||
stopPropagation: () => {
|
||||
this.propagationStoped.set(type, true);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private createTouchAction(
|
||||
event: TouchEvent,
|
||||
type: ActionType
|
||||
): IActionEvent[] {
|
||||
const list: IActionEvent[] = [];
|
||||
const rect = this.target.canvas.getBoundingClientRect();
|
||||
if (type === ActionType.Up) {
|
||||
// 抬起是一个需要特殊处理的东西,因为 touches 不会包含这个内容,所以需要特殊处理
|
||||
const touches = Array.from(event.touches).map(v => v.identifier);
|
||||
for (const [id, touch] of this.touchList) {
|
||||
if (!touches.includes(id)) {
|
||||
// 如果不包含,才需要触发
|
||||
if (this.isTouchInCanvas(touch.clientX, touch.clientY)) {
|
||||
const ev = this.createTouch(touch, type, event, rect);
|
||||
list.push(ev);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Array.from(event.touches).forEach(v => {
|
||||
const ev = this.createTouch(v, type, event, rect);
|
||||
if (type === ActionType.Move) {
|
||||
const touch = this.touchInfo.get(v.identifier);
|
||||
if (!touch) return;
|
||||
const moveX = touch.clientX - v.clientX;
|
||||
const moveY = touch.clientY - v.clientY;
|
||||
if (moveX !== 0 || moveY !== 0) {
|
||||
list.push(ev);
|
||||
}
|
||||
} else if (type === ActionType.Down) {
|
||||
this.touchList.set(v.identifier, v);
|
||||
if (this.isTouchInCanvas(v.clientX, v.clientY)) {
|
||||
list.push(ev);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
update(_item: RenderItem = this) {
|
||||
@ -56,13 +391,13 @@ export class MotaRenderer extends Container {
|
||||
* @returns
|
||||
*/
|
||||
getElementById(id: string): RenderItem | null {
|
||||
const map = RenderItem.itemMap;
|
||||
const item = map.get(id);
|
||||
if (id.length === 0) return null;
|
||||
const item = this.idMap.get(id);
|
||||
if (item) return item;
|
||||
else {
|
||||
const item = this.searchElement(this, id);
|
||||
if (item) {
|
||||
map.set(id, item);
|
||||
this.idMap.set(id, item);
|
||||
}
|
||||
return item;
|
||||
}
|
||||
@ -79,10 +414,61 @@ export class MotaRenderer extends Container {
|
||||
return null;
|
||||
}
|
||||
|
||||
connect(item: RenderItem): void {
|
||||
if (item.id.length === 0) return;
|
||||
if (this.idMap.has(item.id)) {
|
||||
logger.warn(23, item.id);
|
||||
} else {
|
||||
this.idMap.set(item.id, item);
|
||||
}
|
||||
}
|
||||
|
||||
disconnect(item: RenderItem): void {
|
||||
this.idMap.delete(item.id);
|
||||
}
|
||||
|
||||
modifyId(item: RenderItem, previous: string, current: string): void {
|
||||
this.idMap.delete(previous);
|
||||
if (current.length !== 0) {
|
||||
if (this.idMap.has(item.id)) {
|
||||
logger.warn(23, item.id);
|
||||
} else {
|
||||
this.idMap.set(item.id, item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getCanvas(): HTMLCanvasElement {
|
||||
return this.target.canvas;
|
||||
}
|
||||
|
||||
destroy() {
|
||||
super.destroy();
|
||||
MotaRenderer.list.delete(this.id);
|
||||
this.target.delete();
|
||||
this.abort?.abort();
|
||||
}
|
||||
|
||||
private toTagString(item: RenderItem, space: number, deep: number): string {
|
||||
const name = item.constructor.name;
|
||||
if (item.children.size === 0) {
|
||||
return `${' '.repeat(deep * space)}<${name} id="${item.id}" type="${item.type}"></${name}>\n`;
|
||||
} else {
|
||||
return (
|
||||
`${' '.repeat(deep * space)}<${name} id="${item.id}" type="${item.type}">\n` +
|
||||
`${[...item.children].map(v => this.toTagString(v, space, deep + 1)).join('')}` +
|
||||
`${' '.repeat(deep * space)}</${name}>\n`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 调试功能,将渲染树输出为 XML 标签形式,只包含渲染元素类名,以及元素 id 等基础属性,不包含属性值等
|
||||
* @param space 缩进空格数
|
||||
*/
|
||||
toTagTree(space: number = 4) {
|
||||
if (!import.meta.env.DEV) return '';
|
||||
return this.toTagString(this, space, 0);
|
||||
}
|
||||
|
||||
static get(id: string) {
|
||||
@ -95,3 +481,8 @@ window.addEventListener('resize', () => {
|
||||
v.requestAfterFrame(() => v.refreshAllChildren())
|
||||
);
|
||||
});
|
||||
|
||||
// @ts-expect-error debug
|
||||
window.logTagTree = () => {
|
||||
console.log(MotaRenderer.get('render-main')?.toTagTree());
|
||||
};
|
||||
|
@ -31,6 +31,7 @@ export interface BaseProps {
|
||||
id?: string;
|
||||
alpha?: number;
|
||||
composite?: GlobalCompositeOperation;
|
||||
cursor?: string;
|
||||
}
|
||||
|
||||
export interface SpriteProps extends BaseProps {
|
||||
|
@ -1,6 +1,4 @@
|
||||
import { MotaOffscreenCanvas2D } from '../fx/canvas2d';
|
||||
import { ERenderItemEvent, RenderItem, RenderItemPosition } from './item';
|
||||
import { Transform } from './transform';
|
||||
import { EGL2Event, GL2, GL2Program, IGL2ProgramPrefix } from './gl2';
|
||||
|
||||
const SHADER_PREFIX: IGL2ProgramPrefix = {
|
||||
@ -38,51 +36,26 @@ export interface EShaderEvent extends EGL2Event {}
|
||||
export class Shader<E extends EShaderEvent = EShaderEvent> extends GL2<
|
||||
EShaderEvent | E
|
||||
> {
|
||||
setHD(hd: boolean): void {
|
||||
super.setHD(hd);
|
||||
this.sizeGL(this.width, this.height);
|
||||
}
|
||||
|
||||
size(width: number, height: number): void {
|
||||
super.size(width, height);
|
||||
this.sizeGL(width, height);
|
||||
}
|
||||
|
||||
private sizeGL(width: number, height: number) {
|
||||
const ratio = this.highResolution ? devicePixelRatio : 1;
|
||||
const scale = ratio * core.domStyle.scale;
|
||||
this.canvas.width = width * scale;
|
||||
this.canvas.height = height * scale;
|
||||
}
|
||||
|
||||
protected preDraw(
|
||||
protected drawScene(
|
||||
canvas: MotaOffscreenCanvas2D,
|
||||
transform: Transform,
|
||||
gl: WebGL2RenderingContext,
|
||||
program: GL2Program
|
||||
): boolean {
|
||||
if (!program.modified) return false;
|
||||
): void {
|
||||
if (!program.modified) return;
|
||||
const tex = program.getTexture('u_sampler');
|
||||
if (!tex) return false;
|
||||
if (!tex) return;
|
||||
const c = canvas.canvas;
|
||||
if (tex.width === c.width && tex.height === c.height) {
|
||||
tex.sub(c, 0, 0, c.width, c.height);
|
||||
} else {
|
||||
tex.set(c);
|
||||
}
|
||||
return true;
|
||||
this.draw(gl, program);
|
||||
}
|
||||
|
||||
protected postDraw(
|
||||
canvas: MotaOffscreenCanvas2D,
|
||||
transform: Transform,
|
||||
gl: WebGL2RenderingContext,
|
||||
program: GL2Program
|
||||
): void {}
|
||||
}
|
||||
|
||||
export class ShaderProgram extends GL2Program {
|
||||
protected readonly prefix: IGL2ProgramPrefix = SHADER_PREFIX;
|
||||
protected override readonly prefix: IGL2ProgramPrefix = SHADER_PREFIX;
|
||||
|
||||
constructor(gl2: GL2, vs?: string, fs?: string) {
|
||||
super(gl2, vs, fs);
|
||||
|
@ -7,7 +7,7 @@ import {
|
||||
import { MotaOffscreenCanvas2D } from '../fx/canvas2d';
|
||||
import { Transform } from './transform';
|
||||
import { ElementNamespace, ComponentInternalInstance } from 'vue';
|
||||
import { logger } from '../common/logger';
|
||||
import { ActionType, EventProgress, ActionEventMap } from './event';
|
||||
|
||||
export interface ESpriteEvent extends ERenderItemEvent {}
|
||||
|
||||
@ -43,6 +43,14 @@ export class Sprite<
|
||||
this.update(this);
|
||||
}
|
||||
|
||||
protected propagateEvent<T extends ActionType>(
|
||||
type: T,
|
||||
_progress: EventProgress,
|
||||
event: ActionEventMap[T]
|
||||
): void {
|
||||
this.parent?.bubbleEvent(type, event);
|
||||
}
|
||||
|
||||
patchProp(
|
||||
key: string,
|
||||
prevValue: any,
|
||||
|
@ -205,7 +205,8 @@ export class Transform {
|
||||
* @param x 横坐标
|
||||
* @param y 纵坐标
|
||||
*/
|
||||
transformed(x: number, y: number) {
|
||||
transformed(x: number, y: number): vec3 {
|
||||
if (!this.modified) return [x, y, 1];
|
||||
return multiplyVec3(this.mat, [x, y, 1]);
|
||||
}
|
||||
|
||||
@ -214,7 +215,8 @@ export class Transform {
|
||||
* @param x 横坐标
|
||||
* @param y 纵坐标
|
||||
*/
|
||||
untransformed(x: number, y: number) {
|
||||
untransformed(x: number, y: number): vec3 {
|
||||
if (!this.modified) return [x, y, 1];
|
||||
const invert = mat3.create();
|
||||
mat3.invert(invert, this.mat);
|
||||
return multiplyVec3(invert, [x, y, 1]);
|
||||
|
@ -3,6 +3,8 @@ import { RenderAdapter } from './adapter';
|
||||
import { FloorViewport } from './preset/viewport';
|
||||
import { JSX } from 'vue/jsx-runtime';
|
||||
import { DefineComponent, DefineSetupFnComponent } from 'vue';
|
||||
import { MotaOffscreenCanvas2D } from '../fx/canvas2d';
|
||||
import { Transform } from './transform';
|
||||
|
||||
export type Props<
|
||||
T extends
|
||||
@ -50,3 +52,13 @@ export function isSetEqual<T>(set1: Set<T>, set2: Set<T>) {
|
||||
if (set1 === set2) return true;
|
||||
else return set1.size === set2.size && set1.isSubsetOf(set2);
|
||||
}
|
||||
|
||||
export function transformCanvas(
|
||||
canvas: MotaOffscreenCanvas2D,
|
||||
transform: Transform
|
||||
) {
|
||||
const { ctx } = canvas;
|
||||
const mat = transform.mat;
|
||||
const [a, b, , c, d, , e, f] = mat;
|
||||
ctx.transform(a, b, c, d, e, f);
|
||||
}
|
||||
|
@ -87,6 +87,7 @@
|
||||
"53": "Cannot $1 audio route '$2', since there is not added route named it.",
|
||||
"54": "Missing start tag for '$1', index: $2.",
|
||||
"55": "Unchildable tag '$1' should follow with param.",
|
||||
"56": "Method '$1' is deprecated. Consider using '$2' instead.",
|
||||
"1001": "Item-detail extension needs 'floor-binder' and 'floor-damage' extension as dependency.",
|
||||
"1101": "Cannot add new effect to point effect instance, for there's no more reserve space for it. Please increase the max count of the instance."
|
||||
}
|
||||
|
@ -38,7 +38,6 @@ import type * as Animation from 'mutate-animate';
|
||||
import type * as RenderUtils from '@/core/render/utils';
|
||||
import type { WeatherController } from '@/module/weather/weather';
|
||||
import type { MotaOffscreenCanvas2D } from '@/core/fx/canvas2d';
|
||||
import type { TextboxStore } from '@/core/render';
|
||||
|
||||
interface ClassInterface {
|
||||
// 渲染进程与游戏进程通用
|
||||
@ -115,7 +114,6 @@ interface ModuleInterface {
|
||||
Camera: typeof Camera;
|
||||
MotaOffscreenCanvas2D: typeof MotaOffscreenCanvas2D;
|
||||
Utils: typeof RenderUtils;
|
||||
TextboxStore: typeof TextboxStore;
|
||||
};
|
||||
State: {
|
||||
ItemState: typeof ItemState;
|
||||
|
@ -35,16 +35,6 @@ export class AudioPlayer extends EventEmitter<AudioPlayerEvent> {
|
||||
this.ac = new AudioContext();
|
||||
this.gain = this.ac.createGain();
|
||||
this.gain.connect(this.ac.destination);
|
||||
|
||||
const func = () => {
|
||||
this.ac.resume();
|
||||
document.body.removeEventListener('mousedown', func);
|
||||
document.body.removeEventListener('touchstart', func);
|
||||
document.body.removeEventListener('keydown', func);
|
||||
};
|
||||
document.body.addEventListener('mousedown', func, { capture: true });
|
||||
document.body.addEventListener('touchstart', func, { capture: true });
|
||||
document.body.addEventListener('keydown', func, { capture: true });
|
||||
}
|
||||
|
||||
/**
|
||||
@ -427,9 +417,10 @@ export class AudioRoute
|
||||
* 开始播放这个音频
|
||||
* @param when 从音频的什么时候开始播放,单位秒
|
||||
*/
|
||||
play(when: number = 0) {
|
||||
async play(when: number = 0) {
|
||||
if (this.status === AudioStatus.Playing) return;
|
||||
this.link();
|
||||
await this.player.ac.resume();
|
||||
if (this.effectRoute.length > 0) {
|
||||
const first = this.effectRoute[0];
|
||||
this.source.connect(first);
|
||||
|
@ -4,6 +4,8 @@ import { mainSetting } from '@/core/main/setting';
|
||||
import { sleep } from 'mutate-animate';
|
||||
import { isNil } from 'lodash-es';
|
||||
|
||||
// todo: 添加弃用警告 logger.warn(56)
|
||||
|
||||
export function patchAudio() {
|
||||
const patch = new Patch(PatchClass.Control);
|
||||
|
||||
|
@ -1,9 +1,10 @@
|
||||
import { Patch } from '@/common/patch';
|
||||
import { patchAudio } from './audio';
|
||||
|
||||
patchAudio();
|
||||
import { patchWeather } from './weather';
|
||||
|
||||
export function patchAll() {
|
||||
patchAudio();
|
||||
patchWeather();
|
||||
const loading = Mota.require('var', 'loading');
|
||||
loading.once('coreInit', () => {
|
||||
Patch.patchAll();
|
||||
|
23
src/module/fallback/weather.ts
Normal file
23
src/module/fallback/weather.ts
Normal file
@ -0,0 +1,23 @@
|
||||
import { Patch, PatchClass } from '@/common/patch';
|
||||
import { WeatherController } from '../weather';
|
||||
import { isNil } from 'lodash-es';
|
||||
|
||||
// todo: 添加弃用警告 logger.warn(56)
|
||||
|
||||
export function patchWeather() {
|
||||
const patch = new Patch(PatchClass.Control);
|
||||
let nowWeather: string = '';
|
||||
let nowLevel: number = 0;
|
||||
|
||||
patch.add('setWeather', (type, level) => {
|
||||
const weather = WeatherController.get('main');
|
||||
if (!weather) return;
|
||||
if (type === nowWeather && level === nowLevel) return;
|
||||
weather.clearWeather();
|
||||
if (!isNil(type)) {
|
||||
weather.activate(type, level);
|
||||
nowWeather = type;
|
||||
nowLevel = level ?? 5;
|
||||
}
|
||||
});
|
||||
}
|
@ -1,19 +1,21 @@
|
||||
import { soundPlayer } from './audio';
|
||||
import { patchAll } from './fallback';
|
||||
import { controller } from './weather';
|
||||
import { create } from './render';
|
||||
import { RainWeather } from './weather/rain';
|
||||
import { WeatherController } from './weather/weather';
|
||||
|
||||
patchAll();
|
||||
Mota.register('module', 'Weather', {
|
||||
controller,
|
||||
WeatherController,
|
||||
RainWeather
|
||||
});
|
||||
Mota.register('module', 'Audio', { soundPlayer });
|
||||
Mota.register('module', 'Audio', {
|
||||
soundPlayer
|
||||
});
|
||||
Mota.require('var', 'loading').once('coreInit', create);
|
||||
|
||||
export * from './weather';
|
||||
export * from './audio';
|
||||
export * from './loader';
|
||||
export * from './fallback';
|
||||
export * from './ui';
|
||||
export * from './render';
|
||||
|
@ -1,26 +1,26 @@
|
||||
import { FloorItemDetail } from '@/plugin/fx/itemDetail';
|
||||
import { FloorDamageExtends } from './preset/damage';
|
||||
import { LayerDoorAnimate } from './preset/floor';
|
||||
import { HeroRenderer } from './preset/hero';
|
||||
import { MotaRenderer } from './render';
|
||||
import { LayerShadowExtends } from '../fx/shadow';
|
||||
import { FloorDamageExtends, LayerGroup, Transform } from '@/core/render';
|
||||
import { LayerDoorAnimate } from '@/core/render';
|
||||
import { HeroRenderer } from '@/core/render';
|
||||
import { MotaRenderer } from '@/core/render';
|
||||
import { LayerShadowExtends } from '@/core/fx/shadow';
|
||||
import { LayerGroupFilter } from '@/plugin/fx/gameCanvas';
|
||||
import { LayerGroupAnimate } from './preset/animate';
|
||||
import { LayerGroupAnimate } from '@/core/render';
|
||||
import { LayerGroupPortal } from '@/plugin/fx/portal';
|
||||
import { LayerGroupHalo } from '@/plugin/fx/halo';
|
||||
import { FloorViewport } from './preset/viewport';
|
||||
import { FloorViewport } from '@/core/render';
|
||||
import { PopText } from '@/plugin/fx/pop';
|
||||
import { FloorChange } from '@/plugin/fallback';
|
||||
import { createApp } from './renderer';
|
||||
import { defineComponent } from 'vue';
|
||||
import { Textbox } from '../../module/ui/components';
|
||||
import { ILayerGroupRenderExtends, ILayerRenderExtends } from './preset';
|
||||
import { Props } from './utils';
|
||||
import { createApp } from '@/core/render';
|
||||
import { defineComponent, onMounted, ref } from 'vue';
|
||||
import { Textbox } from './components';
|
||||
import { ILayerGroupRenderExtends, ILayerRenderExtends } from '@/core/render';
|
||||
import { Props } from '@/core/render';
|
||||
import { WeatherController } from '../weather';
|
||||
import { IActionEvent } from '@/core/render/event';
|
||||
|
||||
let main: MotaRenderer;
|
||||
|
||||
Mota.require('var', 'loading').once('coreInit', () => {
|
||||
main = new MotaRenderer();
|
||||
export function create() {
|
||||
const main = new MotaRenderer();
|
||||
|
||||
const App = defineComponent(_props => {
|
||||
const layerGroupExtends: ILayerGroupRenderExtends[] = [
|
||||
@ -57,9 +57,29 @@ Mota.require('var', 'loading').once('coreInit', () => {
|
||||
lineHeight: 6
|
||||
};
|
||||
|
||||
const map = ref<LayerGroup>();
|
||||
const weather = new WeatherController('main');
|
||||
|
||||
onMounted(() => {
|
||||
weather.bind(map.value);
|
||||
});
|
||||
|
||||
const test = (msg: string) => {
|
||||
return (ev: IActionEvent) => {
|
||||
console.log(msg, ev);
|
||||
};
|
||||
};
|
||||
|
||||
return () => (
|
||||
<container id="map-draw" {...mapDrawProps}>
|
||||
<layer-group id="layer-main" ex={layerGroupExtends}>
|
||||
<icon
|
||||
icon={50}
|
||||
zIndex={100}
|
||||
width={240}
|
||||
height={240}
|
||||
cursor="pointer"
|
||||
></icon>
|
||||
<layer-group id="layer-main" ex={layerGroupExtends} ref={map}>
|
||||
<layer layer="bg" zIndex={10}></layer>
|
||||
<layer layer="bg2" zIndex={20}></layer>
|
||||
<layer layer="event" zIndex={30} ex={eventExtends}></layer>
|
||||
@ -75,10 +95,6 @@ Mota.require('var', 'loading').once('coreInit', () => {
|
||||
|
||||
main.hide();
|
||||
createApp(App).mount(main);
|
||||
// render(<Com></Com>, main);
|
||||
|
||||
console.log(main);
|
||||
});
|
||||
|
||||
Mota.require('var', 'hook').on('reset', () => {
|
||||
main.show();
|
||||
@ -88,17 +104,7 @@ Mota.require('var', 'hook').on('restart', () => {
|
||||
main.hide();
|
||||
});
|
||||
|
||||
export * from './preset';
|
||||
export * from './renderer';
|
||||
export * from './adapter';
|
||||
export * from './cache';
|
||||
export * from './camera';
|
||||
export * from './container';
|
||||
export * from './gl2';
|
||||
export * from './item';
|
||||
export * from './render';
|
||||
export * from './shader';
|
||||
export * from './sprite';
|
||||
export * from './transform';
|
||||
export * from './utils';
|
||||
export * from '../../module/ui/components';
|
||||
console.log(main);
|
||||
}
|
||||
|
||||
export * from './components';
|
@ -1 +0,0 @@
|
||||
export * from './components';
|
@ -1,3 +1,11 @@
|
||||
import { RainWeather } from './rain';
|
||||
import { SnowWeather } from './snow';
|
||||
import { SunWeather } from './sun';
|
||||
import { WeatherController } from './weather';
|
||||
|
||||
export const controller = new WeatherController();
|
||||
WeatherController.register('rain', RainWeather);
|
||||
WeatherController.register('sun', SunWeather);
|
||||
WeatherController.register('snow', SnowWeather);
|
||||
|
||||
export * from './weather';
|
||||
export * from './rain';
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { Shader, ShaderProgram } from '@/core/render/shader';
|
||||
import { IWeather, WeatherController } from './weather';
|
||||
import { IWeather } from './weather';
|
||||
import { MotaRenderer } from '@/core/render/render';
|
||||
import { Container } from '@/core/render/container';
|
||||
import { IShaderUniform, UniformType } from '@/core/render/gl2';
|
||||
@ -78,13 +78,18 @@ void main() {
|
||||
/** 雨滴顶点坐标 */
|
||||
const vertex = new Float32Array([-1, -1, 1, -1, -1, 1, 1, 1]);
|
||||
|
||||
Mota.require('var', 'loading').once('coreInit', () => {
|
||||
export class RainWeather implements IWeather {
|
||||
readonly shader: RainShader;
|
||||
readonly program: ShaderProgram;
|
||||
|
||||
private progress: IShaderUniform<UniformType.Uniform1f> | null = null;
|
||||
|
||||
constructor(readonly level: number = 5) {
|
||||
const shader = new RainShader();
|
||||
const gl = shader.gl;
|
||||
shader.size(480, 480);
|
||||
shader.setHD(true);
|
||||
shader.setZIndex(100);
|
||||
RainWeather.shader = shader;
|
||||
const program = shader.createProgram(ShaderProgram);
|
||||
program.fs(rainFs);
|
||||
program.vs(rainVs);
|
||||
@ -95,7 +100,6 @@ Mota.require('var', 'loading').once('coreInit', () => {
|
||||
program.defineUniform('u_progress', shader.UNIFORM_1f);
|
||||
program.defineUniform('u_color', shader.UNIFORM_4f);
|
||||
program.mode(shader.DRAW_ARRAYS_INSTANCED);
|
||||
RainShader.rainProgram = program;
|
||||
shader.useProgram(program);
|
||||
|
||||
if (pos) {
|
||||
@ -103,42 +107,37 @@ Mota.require('var', 'loading').once('coreInit', () => {
|
||||
pos.pointer(2, gl.FLOAT, false, 0, 0);
|
||||
pos.enable();
|
||||
}
|
||||
});
|
||||
|
||||
export class RainWeather implements IWeather {
|
||||
static id: string = 'rain';
|
||||
|
||||
static shader: RainShader;
|
||||
|
||||
private progress: IShaderUniform<UniformType.Uniform1f> | null = null;
|
||||
|
||||
constructor(readonly level: number = 5) {}
|
||||
this.shader = shader;
|
||||
this.program = program;
|
||||
}
|
||||
|
||||
activate(): void {
|
||||
const render = MotaRenderer.get('render-main');
|
||||
const draw = render?.getElementById('map-draw') as Container;
|
||||
if (!draw) return;
|
||||
const shader = RainWeather.shader;
|
||||
const shader = this.shader;
|
||||
shader.append(draw);
|
||||
|
||||
const gl = shader.gl;
|
||||
const program = RainShader.rainProgram;
|
||||
const program = this.program;
|
||||
program.paramArraysInstanced(gl.TRIANGLE_STRIP, 0, 4, 100 * this.level);
|
||||
|
||||
this.progress = program.getUniform<UniformType.Uniform1f>('u_progress');
|
||||
shader.useProgram(program);
|
||||
shader.generateRainPath(
|
||||
this.level * 100,
|
||||
(((Math.random() - 0.5) * Math.PI) / 30) * this.level,
|
||||
(Math.PI / 180) * (12 - this.level)
|
||||
(Math.PI / 180) * (12 - this.level),
|
||||
program
|
||||
);
|
||||
}
|
||||
|
||||
frame(): void {
|
||||
RainWeather.shader.update(RainWeather.shader);
|
||||
this.shader.update(this.shader);
|
||||
const time = 5000 - 400 * this.level;
|
||||
const progress = (Date.now() % time) / time;
|
||||
|
||||
RainWeather.shader.useProgram(RainShader.rainProgram);
|
||||
this.shader.useProgram(this.program);
|
||||
this.progress?.set(progress);
|
||||
}
|
||||
|
||||
@ -147,25 +146,23 @@ export class RainWeather implements IWeather {
|
||||
const draw = render?.getElementById('map-draw') as Container;
|
||||
const layer = draw.children;
|
||||
if (!layer || !draw) return;
|
||||
const shader = RainWeather.shader;
|
||||
const shader = this.shader;
|
||||
draw.appendChild(...layer);
|
||||
shader.remove();
|
||||
}
|
||||
}
|
||||
|
||||
WeatherController.register(RainWeather);
|
||||
|
||||
class RainShader extends Shader {
|
||||
static rainProgram: ShaderProgram;
|
||||
static backProgram: ShaderProgram;
|
||||
|
||||
/**
|
||||
* 生成雨滴
|
||||
* @param num 雨滴数量
|
||||
*/
|
||||
generateRainPath(num: number, angle: number, deviation: number) {
|
||||
const program = RainShader.rainProgram;
|
||||
RainWeather.shader.useProgram(program);
|
||||
generateRainPath(
|
||||
num: number,
|
||||
angle: number,
|
||||
deviation: number,
|
||||
program: ShaderProgram
|
||||
) {
|
||||
const aOffset = program.getAttribArray('a_offset');
|
||||
const aData = program.getAttribArray('a_data');
|
||||
const color = program.getUniform<UniformType.Uniform4f>('u_color');
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { Shader, ShaderProgram } from '@/core/render/shader';
|
||||
import { IWeather, WeatherController } from './weather';
|
||||
import { IWeather } from './weather';
|
||||
import { MotaRenderer } from '@/core/render/render';
|
||||
import { Container } from '@/core/render/container';
|
||||
import { GL2Program, IShaderUniform, UniformType } from '@/core/render/gl2';
|
||||
@ -174,8 +174,6 @@ export class SnowWeather implements IWeather {
|
||||
}
|
||||
}
|
||||
|
||||
WeatherController.register(SnowWeather);
|
||||
|
||||
class SnowShader extends Shader {
|
||||
static snowProgram: ShaderProgram;
|
||||
static backProgram: ShaderProgram;
|
||||
|
@ -1,8 +1,4 @@
|
||||
import { Shader, ShaderProgram } from '@/core/render/shader';
|
||||
import { IWeather, WeatherController } from './weather';
|
||||
import { MotaOffscreenCanvas2D } from '@/core/fx/canvas2d';
|
||||
import { GL2Program } from '@/core/render/gl2';
|
||||
import { Transform } from '@/core/render/transform';
|
||||
import { IWeather } from './weather';
|
||||
|
||||
export class SunWeather implements IWeather {
|
||||
static id: string = 'sun';
|
||||
@ -13,23 +9,3 @@ export class SunWeather implements IWeather {
|
||||
|
||||
deactivate(): void {}
|
||||
}
|
||||
|
||||
WeatherController.register(SunWeather);
|
||||
|
||||
class SunShader extends Shader {
|
||||
protected preDraw(
|
||||
canvas: MotaOffscreenCanvas2D,
|
||||
transform: Transform,
|
||||
gl: WebGL2RenderingContext,
|
||||
program: GL2Program
|
||||
): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
protected postDraw(
|
||||
canvas: MotaOffscreenCanvas2D,
|
||||
transform: Transform,
|
||||
gl: WebGL2RenderingContext,
|
||||
program: GL2Program
|
||||
): void {}
|
||||
}
|
||||
|
@ -1,11 +1,12 @@
|
||||
import { logger } from '@/core/common/logger';
|
||||
import { RenderItem } from '@/core/render';
|
||||
import { Ticker } from 'mutate-animate';
|
||||
|
||||
export interface IWeather {
|
||||
/**
|
||||
* 初始化天气,当天气被添加时会被立刻调用
|
||||
*/
|
||||
activate(): void;
|
||||
activate(item: RenderItem): void;
|
||||
|
||||
/**
|
||||
* 每帧执行的函数
|
||||
@ -15,21 +16,25 @@ export interface IWeather {
|
||||
/**
|
||||
* 摧毁这个天气,当天气被删除时会执行
|
||||
*/
|
||||
deactivate(): void;
|
||||
deactivate(item: RenderItem): void;
|
||||
}
|
||||
|
||||
interface Weather<T extends IWeather = IWeather> {
|
||||
id: string;
|
||||
new (level?: number): T;
|
||||
}
|
||||
type Weather = new (level?: number) => IWeather;
|
||||
|
||||
export class WeatherController {
|
||||
static list: Map<string, Weather> = new Map();
|
||||
static map: Map<string, WeatherController> = new Map();
|
||||
|
||||
/** 当前的所有天气 */
|
||||
active: Set<IWeather> = new Set();
|
||||
ticker: Ticker = new Ticker();
|
||||
|
||||
private binded?: RenderItem;
|
||||
|
||||
constructor(public readonly id: string) {
|
||||
WeatherController.map.set(id, this);
|
||||
}
|
||||
|
||||
private tick = () => {
|
||||
this.active.forEach(v => {
|
||||
v.frame();
|
||||
@ -40,9 +45,11 @@ export class WeatherController {
|
||||
* 清空所有天气
|
||||
*/
|
||||
clearWeather() {
|
||||
if (this.binded) {
|
||||
this.active.forEach(v => {
|
||||
v.deactivate();
|
||||
v.deactivate(this.binded!);
|
||||
});
|
||||
}
|
||||
this.active.clear();
|
||||
}
|
||||
|
||||
@ -50,7 +57,7 @@ export class WeatherController {
|
||||
* 获取一个天气
|
||||
* @param weather 要获取的天气
|
||||
*/
|
||||
getWeather<T extends IWeather>(weather: Weather<T>): T | null {
|
||||
getWeather<T extends IWeather = IWeather>(weather: Weather): T | null {
|
||||
return ([...this.active].find(v => v instanceof weather) as T) ?? null;
|
||||
}
|
||||
|
||||
@ -68,7 +75,9 @@ export class WeatherController {
|
||||
}
|
||||
const weather = new Weather(level);
|
||||
this.active.add(weather);
|
||||
weather.activate();
|
||||
if (this.binded) {
|
||||
weather.activate(this.binded);
|
||||
}
|
||||
if (!this.ticker.funcs.has(this.tick)) {
|
||||
this.ticker.add(this.tick);
|
||||
}
|
||||
@ -84,10 +93,37 @@ export class WeatherController {
|
||||
if (this.active.size === 0) {
|
||||
this.ticker.remove(this.tick);
|
||||
}
|
||||
weather.deactivate();
|
||||
if (this.binded) {
|
||||
weather.deactivate(this.binded);
|
||||
}
|
||||
}
|
||||
|
||||
static register(weather: Weather) {
|
||||
this.list.set(weather.id, weather);
|
||||
/**
|
||||
* 将这个天气控制器绑定至一个渲染元素上
|
||||
* @param item 要绑定的元素,不填表示取消绑定
|
||||
*/
|
||||
bind(item?: RenderItem) {
|
||||
if (this.binded) {
|
||||
this.active.forEach(v => v.deactivate(this.binded!));
|
||||
}
|
||||
this.binded = item;
|
||||
if (item) {
|
||||
this.active.forEach(v => v.activate(item));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 摧毁这个天气控制器
|
||||
*/
|
||||
destroy() {
|
||||
WeatherController.map.delete(this.id);
|
||||
}
|
||||
|
||||
static get(id: string) {
|
||||
return this.map.get(id);
|
||||
}
|
||||
|
||||
static register(id: string, weather: Weather) {
|
||||
this.list.set(id, weather);
|
||||
}
|
||||
}
|
||||
|
@ -103,7 +103,7 @@ export abstract class BarrageBoss extends EventEmitter<BarrageBossEvent> {
|
||||
}
|
||||
}
|
||||
|
||||
export abstract class BossSprite<
|
||||
export class BossSprite<
|
||||
T extends BarrageBoss = BarrageBoss
|
||||
> extends RenderItem {
|
||||
/** 这个sprite所属的boss */
|
||||
@ -115,36 +115,16 @@ export abstract class BossSprite<
|
||||
}
|
||||
|
||||
/**
|
||||
* 在内置渲染函数执行前渲染内容,返回false会阻止内置渲染函数执行
|
||||
* @param canvas 渲染至的画布
|
||||
* @param transform 渲染时的变换矩阵
|
||||
* override 此方法来实现自定义渲染,默认会调用 {@link renderProjectiles} 方法。
|
||||
* 关于本方法,参考 {@link RenderItem.render}
|
||||
* @param canvas 渲染至的目标画布
|
||||
* @param transform 当前画布相对于父元素的变换矩阵
|
||||
*/
|
||||
protected abstract preDraw(
|
||||
canvas: MotaOffscreenCanvas2D,
|
||||
transform: Transform
|
||||
): boolean;
|
||||
|
||||
/**
|
||||
* 在内置渲染函数执行后渲染内容,如果preDraw返回false,也会执行本函数
|
||||
* @param canvas 渲染至的画布
|
||||
* @param transform 渲染时的变换矩阵
|
||||
*/
|
||||
protected abstract postDraw(
|
||||
canvas: MotaOffscreenCanvas2D,
|
||||
transform: Transform
|
||||
): void;
|
||||
|
||||
protected render(
|
||||
canvas: MotaOffscreenCanvas2D,
|
||||
transform: Transform
|
||||
): void {
|
||||
const pre = this.preDraw(canvas, transform);
|
||||
if (!pre) {
|
||||
this.postDraw(canvas, transform);
|
||||
return;
|
||||
}
|
||||
this.renderProjectiles(canvas, transform);
|
||||
this.postDraw(canvas, transform);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -5,7 +5,6 @@ import { MotaRenderer } from '@/core/render/render';
|
||||
import { LayerGroup } from '@/core/render/preset/layer';
|
||||
import { RenderItem } from '@/core/render/item';
|
||||
import { MotaOffscreenCanvas2D } from '@/core/fx/canvas2d';
|
||||
import { Transform } from '@/core/render/transform';
|
||||
import { Animation, hyper, power, sleep, Transition } from 'mutate-animate';
|
||||
import { Container } from '@/core/render/container';
|
||||
import {
|
||||
@ -21,8 +20,8 @@ import {
|
||||
} from './towerBossProjectile';
|
||||
import { IStateDamageable } from '@/game/state/interface';
|
||||
import { HeroRenderer } from '@/core/render/preset/hero';
|
||||
import { controller } from '@/module/weather';
|
||||
import { Pop, PopText } from '../fx/pop';
|
||||
import { Pop } from '../fx/pop';
|
||||
import { WeatherController } from '@/module';
|
||||
|
||||
Mota.require('var', 'loading').once('coreInit', () => {
|
||||
const shader = new Shader();
|
||||
@ -67,7 +66,7 @@ export class TowerBoss extends BarrageBoss {
|
||||
|
||||
readonly hitbox: Hitbox.Rect;
|
||||
readonly state: IStateDamageable;
|
||||
readonly main: BossEffect;
|
||||
readonly main: BossSprite;
|
||||
|
||||
/** 血条显示元素 */
|
||||
private healthBar: HealthBar;
|
||||
@ -127,7 +126,7 @@ export class TowerBoss extends BarrageBoss {
|
||||
|
||||
this.healthBar = new HealthBar('absolute');
|
||||
this.word = new Word('absolute');
|
||||
this.main = new BossEffect('absolute', this);
|
||||
this.main = new BossSprite('absolute', this);
|
||||
const render = MotaRenderer.get('render-main')!;
|
||||
this.group = render.getElementById('layer-main') as LayerGroup;
|
||||
this.mapDraw = render.getElementById('map-draw') as Container;
|
||||
@ -135,7 +134,9 @@ export class TowerBoss extends BarrageBoss {
|
||||
|
||||
this.healthBar.init();
|
||||
this.word.init();
|
||||
this.main.init();
|
||||
this.main.size(480, 480);
|
||||
this.main.setHD(true);
|
||||
this.main.setZIndex(80);
|
||||
|
||||
TowerBoss.effect.setTransform(this.group.camera);
|
||||
|
||||
@ -201,7 +202,7 @@ export class TowerBoss extends BarrageBoss {
|
||||
/**
|
||||
* 用于全局检测,例如受伤、攻击boss等
|
||||
*/
|
||||
check(time: number) {
|
||||
check(_time: number) {
|
||||
this.checkLose();
|
||||
}
|
||||
|
||||
@ -298,7 +299,7 @@ export class TowerBoss extends BarrageBoss {
|
||||
this.stageProgress = 0;
|
||||
}
|
||||
|
||||
private aiPrologue(time: number, frame: number) {
|
||||
private aiPrologue(time: number, _frame: number) {
|
||||
// stageProgress:
|
||||
// 0: 开始; 1: 开始血条动画
|
||||
|
||||
@ -362,7 +363,7 @@ export class TowerBoss extends BarrageBoss {
|
||||
}
|
||||
}
|
||||
|
||||
private aiStage1(time: number, frame: number) {
|
||||
private aiStage1(time: number, _frame: number) {
|
||||
// stageProgress:
|
||||
// 0: 开始; 1,2,3,4: 对应对话
|
||||
|
||||
@ -394,13 +395,14 @@ export class TowerBoss extends BarrageBoss {
|
||||
}
|
||||
}
|
||||
|
||||
private aiDialogue1(time: number, frame: number) {
|
||||
private aiDialogue1(time: number, _frame: number) {
|
||||
this.changeStage(TowerBossStage.Stage2, time);
|
||||
this.attackTime = 3;
|
||||
this.skill4Time = 5;
|
||||
this.skill5Time = 3;
|
||||
core.playBgm('towerBoss2.opus');
|
||||
controller.activate('rain', 6);
|
||||
const weather = WeatherController.get('main');
|
||||
weather?.activate('rain', 6);
|
||||
}
|
||||
|
||||
releaseSkill4() {
|
||||
@ -441,7 +443,7 @@ export class TowerBoss extends BarrageBoss {
|
||||
}
|
||||
}
|
||||
|
||||
private aiStage2(time: number, frame: number) {
|
||||
private aiStage2(time: number, _frame: number) {
|
||||
const skill4Release = this.skill4Time * this.skill4Interval;
|
||||
const skill5Release = this.skill5Time * this.skill5Interval;
|
||||
const attack = this.attackTime * this.attackInterval;
|
||||
@ -496,7 +498,7 @@ export class TowerBoss extends BarrageBoss {
|
||||
core.setBlock(557, 7, n + 1);
|
||||
}
|
||||
|
||||
private aiDialogue2(time: number, frame: number) {
|
||||
private aiDialogue2(time: number, _frame: number) {
|
||||
this.changeStage(TowerBossStage.Stage3, time);
|
||||
this.attackTime = 3;
|
||||
this.terrainClose(1);
|
||||
@ -539,7 +541,7 @@ export class TowerBoss extends BarrageBoss {
|
||||
}
|
||||
}
|
||||
|
||||
private aiStage3(time: number, frame: number) {
|
||||
private aiStage3(time: number, _frame: number) {
|
||||
const skill6Release = this.skill6Time * this.skill6Interval;
|
||||
const skill7Release = this.skill7Time * this.skill7Interval;
|
||||
const attack = this.attackTime * this.attackInterval;
|
||||
@ -567,7 +569,7 @@ export class TowerBoss extends BarrageBoss {
|
||||
}
|
||||
}
|
||||
|
||||
private aiStage4(time: number, frame: number) {
|
||||
private aiStage4(time: number, _frame: number) {
|
||||
const skill6Release = this.skill6Time * this.skill6Interval;
|
||||
const skill7Release = this.skill7Time * this.skill7Interval;
|
||||
const attack = this.attackTime * this.attackInterval;
|
||||
@ -595,7 +597,7 @@ export class TowerBoss extends BarrageBoss {
|
||||
}
|
||||
}
|
||||
|
||||
private aiStage5(time: number, frame: number) {
|
||||
private aiStage5(time: number, _frame: number) {
|
||||
const skill6Release = this.skill6Time * this.skill6Interval;
|
||||
const skill7Release = this.skill7Time * this.skill7Interval;
|
||||
const attack = this.attackTime * this.attackInterval;
|
||||
@ -618,7 +620,7 @@ export class TowerBoss extends BarrageBoss {
|
||||
}
|
||||
}
|
||||
|
||||
private aiEnd(time: number, frame: number) {
|
||||
private aiEnd(_time: number, _frame: number) {
|
||||
this.end();
|
||||
core.insertAction([
|
||||
{ type: 'openDoor', loc: [13, 6], floorId: 'MT19' },
|
||||
@ -630,29 +632,6 @@ export class TowerBoss extends BarrageBoss {
|
||||
}
|
||||
}
|
||||
|
||||
class BossEffect extends BossSprite<TowerBoss> {
|
||||
/**
|
||||
* 初始化
|
||||
*/
|
||||
init() {
|
||||
this.size(480, 480);
|
||||
this.setHD(true);
|
||||
this.setZIndex(80);
|
||||
}
|
||||
|
||||
protected preDraw(
|
||||
canvas: MotaOffscreenCanvas2D,
|
||||
transform: Transform
|
||||
): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
protected postDraw(
|
||||
canvas: MotaOffscreenCanvas2D,
|
||||
transform: Transform
|
||||
): void {}
|
||||
}
|
||||
|
||||
interface TextRenderable {
|
||||
x: number;
|
||||
y: number;
|
||||
@ -747,10 +726,7 @@ class Word extends RenderItem {
|
||||
return res;
|
||||
}
|
||||
|
||||
protected render(
|
||||
canvas: MotaOffscreenCanvas2D,
|
||||
transform: Transform
|
||||
): void {
|
||||
protected render(canvas: MotaOffscreenCanvas2D): void {
|
||||
const data = this.getTextRenerable();
|
||||
const ctx = canvas.ctx;
|
||||
ctx.font = '18px "normal"';
|
||||
@ -832,10 +808,7 @@ class HealthBar extends RenderItem {
|
||||
this.status = HealthBarStatus.End;
|
||||
}
|
||||
|
||||
protected render(
|
||||
canvas: MotaOffscreenCanvas2D,
|
||||
transform: Transform
|
||||
): void {
|
||||
protected render(canvas: MotaOffscreenCanvas2D): void {
|
||||
const ctx = canvas.ctx;
|
||||
|
||||
const hp = this.trans.value.hp;
|
||||
|
@ -142,7 +142,7 @@ export class Chase extends EventEmitter<ChaseEvent> {
|
||||
|
||||
this.emit('frame', nTime, fTime);
|
||||
|
||||
while (1) {
|
||||
while (true) {
|
||||
const time = this.onTimeListener[0];
|
||||
if (!time) break;
|
||||
if (time.time <= nTime) {
|
||||
@ -157,7 +157,7 @@ export class Chase extends EventEmitter<ChaseEvent> {
|
||||
const floor = this.onFloorTimeListener[this.nowFloor];
|
||||
if (!floor) return;
|
||||
|
||||
while (1) {
|
||||
while (true) {
|
||||
const time = floor[0];
|
||||
if (!time) break;
|
||||
if (time.time <= fTime) {
|
||||
|
@ -442,6 +442,7 @@ export function getVitualKeyOnce(
|
||||
assist: number = 0,
|
||||
emittable: KeyCode[] = []
|
||||
): Promise<KeyboardEmits> {
|
||||
// todo: 正确触发后删除监听器
|
||||
return new Promise(res => {
|
||||
const key = Keyboard.get('full')!;
|
||||
key.withAssist(assist);
|
||||
|
Loading…
Reference in New Issue
Block a user