mirror of
https://github.com/unanmed/HumanBreak.git
synced 2025-01-19 12:49:25 +08:00
feat: 伤害渲染 & EventEmitter自动补全
This commit is contained in:
parent
fe57adf6af
commit
c916b957c2
8
idea.md
8
idea.md
@ -5,8 +5,8 @@
|
|||||||
[] 同化
|
[] 同化
|
||||||
[] 同化+阻击
|
[] 同化+阻击
|
||||||
[x] 电摇嘲讽:到同行或同列直接怼过去,门和墙撞碎,不消耗钥匙,攻击怪物,捡道具,改变 bgm,可吃补给用
|
[x] 电摇嘲讽:到同行或同列直接怼过去,门和墙撞碎,不消耗钥匙,攻击怪物,捡道具,改变 bgm,可吃补给用
|
||||||
[] 乾坤挪移:平移光环位置
|
[x] 乾坤挪移:平移光环位置
|
||||||
[] 加光环的光环
|
[x] 加光环的光环
|
||||||
|
|
||||||
### Boss
|
### Boss
|
||||||
|
|
||||||
@ -21,8 +21,8 @@
|
|||||||
|
|
||||||
### 机制
|
### 机制
|
||||||
|
|
||||||
[] 苍蓝之殿 1: 红蓝黄门转换
|
[x] 苍蓝之殿 1: 红蓝黄门转换
|
||||||
[] 苍蓝之殿 2: 乾坤挪移、杀戮光环、同化、光环范围扩大等
|
[x] 苍蓝之殿 2: 乾坤挪移、杀戮光环、同化、光环范围扩大等
|
||||||
[] 苍蓝之殿 3: 传送门
|
[] 苍蓝之殿 3: 传送门
|
||||||
[] 苍蓝之殿 4: 地形变换,如平移、翻转、旋转等
|
[] 苍蓝之殿 4: 地形变换,如平移、翻转、旋转等
|
||||||
[] 苍蓝之殿中: 让我们把这些东西结合起来...
|
[] 苍蓝之殿中: 让我们把这些东西结合起来...
|
||||||
|
@ -4347,7 +4347,7 @@ events.prototype._jumpHero_doJump = function (jumpInfo, callback) {
|
|||||||
var cb = function () {
|
var cb = function () {
|
||||||
core.setHeroLoc('x', jumpInfo.ex);
|
core.setHeroLoc('x', jumpInfo.ex);
|
||||||
core.setHeroLoc('y', jumpInfo.ey);
|
core.setHeroLoc('y', jumpInfo.ey);
|
||||||
render.move(false);
|
// render.move(false);
|
||||||
core.status.heroMoving = 0;
|
core.status.heroMoving = 0;
|
||||||
core.drawHero();
|
core.drawHero();
|
||||||
if (callback) callback();
|
if (callback) callback();
|
||||||
|
@ -3296,8 +3296,8 @@ maps.prototype.setBlock = function (number, x, y, floorId, noredraw) {
|
|||||||
x,
|
x,
|
||||||
y,
|
y,
|
||||||
floorId,
|
floorId,
|
||||||
originBlock?.id ?? 0,
|
number,
|
||||||
number
|
originBlock?.id ?? 0
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { EmitableEvent, EventEmitter } from '../common/eventEmitter';
|
import { EventEmitter } from '../common/eventEmitter';
|
||||||
|
|
||||||
const ac = new AudioContext();
|
const ac = new AudioContext();
|
||||||
|
|
||||||
@ -7,7 +7,7 @@ interface BaseNode {
|
|||||||
channel?: number;
|
channel?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface AudioPlayerEvent extends EmitableEvent {
|
interface AudioPlayerEvent {
|
||||||
play: (node: AudioBufferSourceNode) => void;
|
play: (node: AudioBufferSourceNode) => void;
|
||||||
update: (audio: AudioBuffer) => void;
|
update: (audio: AudioBuffer) => void;
|
||||||
end: (node: AudioBufferSourceNode) => void;
|
end: (node: AudioBufferSourceNode) => void;
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { EmitableEvent, EventEmitter } from './eventEmitter';
|
import { EventEmitter } from './eventEmitter';
|
||||||
|
|
||||||
interface DisposableEvent<T> extends EmitableEvent {
|
interface DisposableEvent<T> {
|
||||||
active: (value: T) => void;
|
active: (value: T) => void;
|
||||||
dispose: (value: T) => void;
|
dispose: (value: T) => void;
|
||||||
destroy: () => void;
|
destroy: () => void;
|
||||||
|
@ -2,9 +2,7 @@ function has<T>(value: T): value is NonNullable<T> {
|
|||||||
return value !== null && value !== undefined;
|
return value !== null && value !== undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface EmitableEvent {
|
export type Callable = (...params: any) => any;
|
||||||
[event: string]: (...params: any) => any;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Listener<T extends (...params: any) => any> {
|
export interface Listener<T extends (...params: any) => any> {
|
||||||
fn: T;
|
fn: T;
|
||||||
@ -22,14 +20,16 @@ type EmitFn<F extends (...params: any) => any> = (
|
|||||||
...params: Parameters<F>
|
...params: Parameters<F>
|
||||||
) => any;
|
) => any;
|
||||||
|
|
||||||
export class EventEmitter<T extends EmitableEvent = {}> {
|
type Key = number | string | symbol;
|
||||||
|
|
||||||
|
export class EventEmitter<T extends Record<keyof T, Callable>> {
|
||||||
protected events: {
|
protected events: {
|
||||||
[P in keyof T]?: Listener<T[P]>[];
|
[x: Key]: Listener<Callable>[];
|
||||||
} = {};
|
} = {};
|
||||||
private emitted: (keyof T)[] = [];
|
private emitted: Set<string> = new Set();
|
||||||
|
|
||||||
protected emitter: {
|
protected emitter: {
|
||||||
[P in keyof T]?: EmitFn<T[P]>;
|
[x: Key]: EmitFn<Callable> | undefined;
|
||||||
} = {};
|
} = {};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -42,8 +42,10 @@ export class EventEmitter<T extends EmitableEvent = {}> {
|
|||||||
event: K,
|
event: K,
|
||||||
fn: T[K],
|
fn: T[K],
|
||||||
options?: Partial<ListenerOptions>
|
options?: Partial<ListenerOptions>
|
||||||
) {
|
): void;
|
||||||
if (options?.immediate && this.emitted.includes(event)) {
|
on(event: string, fn: Callable, options?: Partial<ListenerOptions>): void;
|
||||||
|
on(event: string, fn: Callable, options?: Partial<ListenerOptions>): void {
|
||||||
|
if (options?.immediate && this.emitted.has(event)) {
|
||||||
fn();
|
fn();
|
||||||
if (!options.once) {
|
if (!options.once) {
|
||||||
this.events[event] ??= [];
|
this.events[event] ??= [];
|
||||||
@ -65,7 +67,9 @@ export class EventEmitter<T extends EmitableEvent = {}> {
|
|||||||
* @param event 要取消监听的事件类型
|
* @param event 要取消监听的事件类型
|
||||||
* @param fn 要取消监听的函数
|
* @param fn 要取消监听的函数
|
||||||
*/
|
*/
|
||||||
off<K extends keyof T>(event: K, fn: T[K]) {
|
off<K extends keyof T>(event: K, fn: T[K]): void;
|
||||||
|
off(event: string, fn: Callable): void;
|
||||||
|
off(event: string, fn: Callable): void {
|
||||||
const index = this.events[event]?.findIndex(v => v.fn === fn);
|
const index = this.events[event]?.findIndex(v => v.fn === fn);
|
||||||
if (index === -1 || index === void 0) return;
|
if (index === -1 || index === void 0) return;
|
||||||
this.events[event]?.splice(index, 1);
|
this.events[event]?.splice(index, 1);
|
||||||
@ -76,7 +80,9 @@ export class EventEmitter<T extends EmitableEvent = {}> {
|
|||||||
* @param event 要监听的事件
|
* @param event 要监听的事件
|
||||||
* @param fn 监听函数
|
* @param fn 监听函数
|
||||||
*/
|
*/
|
||||||
once<K extends keyof T>(event: K, fn: T[K]) {
|
once<K extends keyof T>(event: K, fn: T[K]): void;
|
||||||
|
once(event: string, fn: Callable): void;
|
||||||
|
once(event: string, fn: Callable): void {
|
||||||
this.on(event, fn, { once: true });
|
this.on(event, fn, { once: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -89,18 +95,18 @@ export class EventEmitter<T extends EmitableEvent = {}> {
|
|||||||
event: K,
|
event: K,
|
||||||
...params: Parameters<T[K]>
|
...params: Parameters<T[K]>
|
||||||
): ReturnType<T[K]>[];
|
): ReturnType<T[K]>[];
|
||||||
|
emit(event: string, ...params: any[]): any[];
|
||||||
emit<K extends keyof T, R>(event: K, ...params: Parameters<T[K]>): R;
|
emit<K extends keyof T, R>(event: K, ...params: Parameters<T[K]>): R;
|
||||||
emit<K extends keyof T>(event: K, ...params: Parameters<T[K]>): any {
|
emit<R>(event: string, ...params: any[]): R;
|
||||||
if (!this.emitted.includes(event)) {
|
emit(event: string, ...params: any[]): any[] {
|
||||||
this.emitted.push(event);
|
this.emitted.add(event);
|
||||||
}
|
|
||||||
const events = (this.events[event] ??= []);
|
const events = (this.events[event] ??= []);
|
||||||
if (!!this.emitter[event]) {
|
if (!!this.emitter[event]) {
|
||||||
const returns = this.emitter[event]!(events, ...params);
|
const returns = this.emitter[event]!(events, ...params);
|
||||||
this.events[event] = events.filter(v => !v.once);
|
this.events[event] = events.filter(v => !v.once);
|
||||||
return returns;
|
return returns;
|
||||||
} else {
|
} else {
|
||||||
const returns: ReturnType<T[K]>[] = [];
|
const returns: ReturnType<Callable>[] = [];
|
||||||
for (let i = 0; i < events.length; i++) {
|
for (let i = 0; i < events.length; i++) {
|
||||||
const e = events[i];
|
const e = events[i];
|
||||||
returns.push(e.fn(...(params as any)));
|
returns.push(e.fn(...(params as any)));
|
||||||
@ -115,7 +121,10 @@ export class EventEmitter<T extends EmitableEvent = {}> {
|
|||||||
* @param event 要设置的事件
|
* @param event 要设置的事件
|
||||||
* @param fn 事件执行器,留空以清除触发器
|
* @param fn 事件执行器,留空以清除触发器
|
||||||
*/
|
*/
|
||||||
setEmitter<K extends keyof T>(event: K, fn?: EmitFn<T[K]>) {
|
// @ts-ignore
|
||||||
|
setEmitter<K extends keyof T>(event: K, fn?: EmitFn<T[K]>): void;
|
||||||
|
setEmitter(event: string, fn?: EmitFn<Callable>): void;
|
||||||
|
setEmitter(event: string, fn?: EmitFn<Callable>): void {
|
||||||
this.emitter[event] = fn;
|
this.emitter[event] = fn;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -128,7 +137,12 @@ export class EventEmitter<T extends EmitableEvent = {}> {
|
|||||||
* @param event 要取消监听的事件
|
* @param event 要取消监听的事件
|
||||||
*/
|
*/
|
||||||
removeAllListeners(event: keyof T): void;
|
removeAllListeners(event: keyof T): void;
|
||||||
removeAllListeners(event?: keyof T) {
|
/**
|
||||||
|
* 取消监听一个事件的所有函数
|
||||||
|
* @param event 要取消监听的事件
|
||||||
|
*/
|
||||||
|
removeAllListeners(event: string): void;
|
||||||
|
removeAllListeners(event?: string | keyof T) {
|
||||||
if (has(event)) this.events[event] = [];
|
if (has(event)) this.events[event] = [];
|
||||||
else this.events = {};
|
else this.events = {};
|
||||||
}
|
}
|
||||||
@ -137,7 +151,7 @@ export class EventEmitter<T extends EmitableEvent = {}> {
|
|||||||
type IndexedSymbol = number | string | symbol;
|
type IndexedSymbol = number | string | symbol;
|
||||||
|
|
||||||
export class IndexedEventEmitter<
|
export class IndexedEventEmitter<
|
||||||
T extends EmitableEvent
|
T extends Record<keyof T, Callable>
|
||||||
> extends EventEmitter<T> {
|
> extends EventEmitter<T> {
|
||||||
private fnMap: {
|
private fnMap: {
|
||||||
[P in keyof T]?: Map<IndexedSymbol, T[P]>;
|
[P in keyof T]?: Map<IndexedSymbol, T[P]>;
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
import { debounce } from 'lodash-es';
|
import { debounce } from 'lodash-es';
|
||||||
|
|
||||||
|
// todo: 使用格式化输出?
|
||||||
|
|
||||||
export const enum LogLevel {
|
export const enum LogLevel {
|
||||||
/** 输出所有,包括日志 */
|
/** 输出所有,包括日志 */
|
||||||
LOG,
|
LOG,
|
||||||
@ -84,7 +86,7 @@ export class Logger {
|
|||||||
logTip.textContent = `Error thrown, please check in console.`;
|
logTip.textContent = `Error thrown, please check in console.`;
|
||||||
hideTipText();
|
hideTipText();
|
||||||
}
|
}
|
||||||
throw `[ERROR Code ${code}] ${text}`;
|
console.error(`[ERROR Code ${code}] ${text}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,7 +2,7 @@ import axios, { AxiosRequestConfig, ResponseType } from 'axios';
|
|||||||
import { Disposable } from './disposable';
|
import { Disposable } from './disposable';
|
||||||
import { logger } from './logger';
|
import { logger } from './logger';
|
||||||
import JSZip from 'jszip';
|
import JSZip from 'jszip';
|
||||||
import { EmitableEvent, EventEmitter } from './eventEmitter';
|
import { EventEmitter } from './eventEmitter';
|
||||||
|
|
||||||
type ProgressFn = (now: number, total: number) => void;
|
type ProgressFn = (now: number, total: number) => void;
|
||||||
|
|
||||||
@ -305,7 +305,7 @@ export const resourceTypeMap = {
|
|||||||
zip: ZipResource
|
zip: ZipResource
|
||||||
};
|
};
|
||||||
|
|
||||||
interface LoadEvent<T extends keyof ResourceType> extends EmitableEvent {
|
interface LoadEvent<T extends keyof ResourceType> {
|
||||||
progress: (
|
progress: (
|
||||||
type: keyof ResourceType,
|
type: keyof ResourceType,
|
||||||
uri: string,
|
uri: string,
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import { parseCss } from '@/plugin/utils';
|
import { parseCss } from '@/plugin/utils';
|
||||||
import { EmitableEvent, EventEmitter } from '../common/eventEmitter';
|
import { EventEmitter } from '../common/eventEmitter';
|
||||||
import { CSSObj } from '../interface';
|
import { CSSObj } from '../interface';
|
||||||
|
|
||||||
interface OffscreenCanvasEvent extends EmitableEvent {
|
interface OffscreenCanvasEvent {
|
||||||
/** 当被动触发resize时(例如core.domStyle.scale变化、窗口大小变化)时触发,使用size函数并不会触发 */
|
/** 当被动触发resize时(例如core.domStyle.scale变化、窗口大小变化)时触发,使用size函数并不会触发 */
|
||||||
resize: () => void;
|
resize: () => void;
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import { Animation, Ticker, hyper } from 'mutate-animate';
|
import { Animation, Ticker, hyper } from 'mutate-animate';
|
||||||
import { EmitableEvent, EventEmitter } from '../common/eventEmitter';
|
import { EventEmitter } from '../common/eventEmitter';
|
||||||
import { ensureArray } from '@/plugin/utils';
|
import { ensureArray } from '@/plugin/utils';
|
||||||
|
|
||||||
interface ShaderEvent extends EmitableEvent {}
|
interface ShaderEvent {}
|
||||||
|
|
||||||
const isWebGLSupported = (() => {
|
const isWebGLSupported = (() => {
|
||||||
return !!document.createElement('canvas').getContext('webgl');
|
return !!document.createElement('canvas').getContext('webgl');
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { EmitableEvent, EventEmitter } from '../common/eventEmitter';
|
import { EventEmitter } from '../common/eventEmitter';
|
||||||
|
|
||||||
interface ResourceControllerEvent<D = any, T = D> extends EmitableEvent {
|
interface ResourceControllerEvent<D = any, T = D> {
|
||||||
add: (uri: string, data: D) => void;
|
add: (uri: string, data: D) => void;
|
||||||
delete: (uri: string, content: T) => void;
|
delete: (uri: string, content: T) => void;
|
||||||
}
|
}
|
||||||
@ -25,6 +25,6 @@ export abstract class ResourceController<
|
|||||||
remove(uri: string) {
|
remove(uri: string) {
|
||||||
const content = this.list[uri];
|
const content = this.list[uri];
|
||||||
delete this.list[uri];
|
delete this.list[uri];
|
||||||
this.emit(uri, content);
|
// this.emit(uri, content);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import BoxAnimate from '@/components/boxAnimate.vue';
|
import BoxAnimate from '@/components/boxAnimate.vue';
|
||||||
import { EmitableEvent, EventEmitter } from '@/core/common/eventEmitter';
|
import { EventEmitter } from '@/core/common/eventEmitter';
|
||||||
import { logger } from '@/core/common/logger';
|
import { logger } from '@/core/common/logger';
|
||||||
import { ResponseBase } from '@/core/interface';
|
import { ResponseBase } from '@/core/interface';
|
||||||
import {
|
import {
|
||||||
@ -52,7 +52,7 @@ interface PostLikeResponse extends ResponseBase {
|
|||||||
liked: boolean;
|
liked: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface DanmakuEvent extends EmitableEvent {
|
interface DanmakuEvent {
|
||||||
showStart: (danmaku: Danmaku) => void;
|
showStart: (danmaku: Danmaku) => void;
|
||||||
showEnd: (danmaku: Danmaku) => void;
|
showEnd: (danmaku: Danmaku) => void;
|
||||||
like: (liked: boolean, danmaku: Danmaku) => void;
|
like: (liked: boolean, danmaku: Danmaku) => void;
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
import { KeyCode } from '@/plugin/keyCodes';
|
import { KeyCode } from '@/plugin/keyCodes';
|
||||||
import { deleteWith, generateBinary, spliceBy } from '@/plugin/utils';
|
import { deleteWith, generateBinary, spliceBy } from '@/plugin/utils';
|
||||||
import { EmitableEvent, EventEmitter } from '../../common/eventEmitter';
|
import { EventEmitter } from '../../common/eventEmitter';
|
||||||
|
|
||||||
// todo: 按下时触发,长按(单次/连续)触发,按下连续触发,按下节流触发,按下加速节流触发
|
// todo: 按下时触发,长按(单次/连续)触发,按下连续触发,按下节流触发,按下加速节流触发
|
||||||
|
|
||||||
interface HotkeyEvent extends EmitableEvent {
|
interface HotkeyEvent {
|
||||||
set: (id: string, key: KeyCode, assist: number) => void;
|
set: (id: string, key: KeyCode, assist: number) => void;
|
||||||
emit: (key: KeyCode, assist: number, type: KeyEmitType) => void;
|
emit: (key: KeyCode, assist: number, type: KeyEmitType) => void;
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,4 @@
|
|||||||
import {
|
import { EventEmitter, Listener } from '@/core/common/eventEmitter';
|
||||||
EmitableEvent,
|
|
||||||
EventEmitter,
|
|
||||||
Listener
|
|
||||||
} from '@/core/common/eventEmitter';
|
|
||||||
import { KeyCode } from '@/plugin/keyCodes';
|
import { KeyCode } from '@/plugin/keyCodes';
|
||||||
import { gameKey } from '../init/hotkey';
|
import { gameKey } from '../init/hotkey';
|
||||||
import { unwarpBinary } from './hotkey';
|
import { unwarpBinary } from './hotkey';
|
||||||
@ -36,7 +32,7 @@ type VirtualKeyEmitFn = (
|
|||||||
ev: VirtualKeyEmit
|
ev: VirtualKeyEmit
|
||||||
) => void;
|
) => void;
|
||||||
|
|
||||||
interface VirtualKeyboardEvent extends EmitableEvent {
|
interface VirtualKeyboardEvent {
|
||||||
add: (item: KeyboardItem) => void;
|
add: (item: KeyboardItem) => void;
|
||||||
remove: (item: KeyboardItem) => void;
|
remove: (item: KeyboardItem) => void;
|
||||||
extend: (extended: Keyboard) => void;
|
extend: (extended: Keyboard) => void;
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { EmitableEvent, EventEmitter } from '@/core/common/eventEmitter';
|
import { EventEmitter } from '@/core/common/eventEmitter';
|
||||||
import { deleteWith, has } from '@/plugin/utils';
|
import { deleteWith, has } from '@/plugin/utils';
|
||||||
import { Component, nextTick, reactive, shallowReactive } from 'vue';
|
import { Component, nextTick, reactive, shallowReactive } from 'vue';
|
||||||
import { fixedUi } from '../init/ui';
|
import { fixedUi } from '../init/ui';
|
||||||
@ -13,7 +13,7 @@ import type {
|
|||||||
} from '../init/toolbar';
|
} from '../init/toolbar';
|
||||||
import { isMobile } from '@/plugin/use';
|
import { isMobile } from '@/plugin/use';
|
||||||
|
|
||||||
interface CustomToolbarEvent extends EmitableEvent {
|
interface CustomToolbarEvent {
|
||||||
add: (item: ValueOf<ToolbarItemMap>) => void;
|
add: (item: ValueOf<ToolbarItemMap>) => void;
|
||||||
delete: (item: ValueOf<ToolbarItemMap>) => void;
|
delete: (item: ValueOf<ToolbarItemMap>) => void;
|
||||||
set: (id: string, data: Partial<SettableItemData>) => void;
|
set: (id: string, data: Partial<SettableItemData>) => void;
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
import { Component, shallowReactive } from 'vue';
|
import { Component, shallowReactive } from 'vue';
|
||||||
import { EmitableEvent, EventEmitter } from '../../common/eventEmitter';
|
import { Callable, EventEmitter } from '../../common/eventEmitter';
|
||||||
import { KeyCode } from '@/plugin/keyCodes';
|
import { KeyCode } from '@/plugin/keyCodes';
|
||||||
import { Hotkey } from './hotkey';
|
import { Hotkey } from './hotkey';
|
||||||
import { generateBinary } from '@/plugin/utils';
|
import { generateBinary } from '@/plugin/utils';
|
||||||
|
|
||||||
interface FocusEvent<T> extends EmitableEvent {
|
interface FocusEvent<T> {
|
||||||
focus: (before: T | null, after: T) => void;
|
focus: (before: T | null, after: T) => void;
|
||||||
unfocus: (before: T | null) => void;
|
unfocus: (before: T | null) => void;
|
||||||
add: (item: T) => void;
|
add: (item: T) => void;
|
||||||
@ -109,7 +109,7 @@ export class Focus<T = any> extends EventEmitter<FocusEvent<T>> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
interface GameUiEvent extends EmitableEvent {
|
interface GameUiEvent {
|
||||||
close: () => void;
|
close: () => void;
|
||||||
open: () => void;
|
open: () => void;
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { FunctionalComponent, reactive } from 'vue';
|
import { FunctionalComponent, reactive } from 'vue';
|
||||||
import { EmitableEvent, EventEmitter } from '../common/eventEmitter';
|
import { EventEmitter } from '../common/eventEmitter';
|
||||||
import { GameStorage } from './storage';
|
import { GameStorage } from './storage';
|
||||||
import { has, triggerFullscreen } from '@/plugin/utils';
|
import { has, triggerFullscreen } from '@/plugin/utils';
|
||||||
import { createSettingComponents } from './init/settings';
|
import { createSettingComponents } from './init/settings';
|
||||||
@ -31,7 +31,7 @@ export interface MotaSettingItem<T extends MotaSettingType = MotaSettingType> {
|
|||||||
display?: (value: T) => string;
|
display?: (value: T) => string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface SettingEvent extends EmitableEvent {
|
interface SettingEvent {
|
||||||
valueChange: <T extends boolean | number>(
|
valueChange: <T extends boolean | number>(
|
||||||
key: string,
|
key: string,
|
||||||
newValue: T,
|
newValue: T,
|
||||||
@ -230,7 +230,7 @@ export class MotaSetting extends EventEmitter<SettingEvent> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
interface SettingDisplayerEvent extends EmitableEvent {
|
interface SettingDisplayerEvent {
|
||||||
update: (stack: string[], display: SettingDisplayInfo[]) => void;
|
update: (stack: string[], display: SettingDisplayInfo[]) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { EmitableEvent, EventEmitter } from '../common/eventEmitter';
|
import { EventEmitter } from '../common/eventEmitter';
|
||||||
import { logger } from '../common/logger';
|
import { logger } from '../common/logger';
|
||||||
import { MotaOffscreenCanvas2D } from '../fx/canvas2d';
|
import { MotaOffscreenCanvas2D } from '../fx/canvas2d';
|
||||||
import { SizedCanvasImageSource } from './preset/misc';
|
import { SizedCanvasImageSource } from './preset/misc';
|
||||||
@ -62,7 +62,7 @@ export interface AutotileRenderable extends RenderableDataBase {
|
|||||||
bigImage: false;
|
bigImage: false;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface TextureCacheEvent extends EmitableEvent {}
|
interface TextureCacheEvent {}
|
||||||
|
|
||||||
class TextureCache extends EventEmitter<TextureCacheEvent> {
|
class TextureCache extends EventEmitter<TextureCacheEvent> {
|
||||||
tileset!: Record<string, HTMLImageElement>;
|
tileset!: Record<string, HTMLImageElement>;
|
||||||
@ -499,10 +499,10 @@ function splitAutotiles(map: IdToNumber): AutotileCaches {
|
|||||||
const ctx = canvas.ctx;
|
const ctx = canvas.ctx;
|
||||||
for (let i = 0; i < frame; i++) {
|
for (let i = 0; i < frame; i++) {
|
||||||
const dx = 32 * i;
|
const dx = 32 * i;
|
||||||
const sx1 = info[0][0];
|
const sx1 = info[0][0] + (i * row * 32) / 2;
|
||||||
const sx2 = info[1][0];
|
const sx2 = info[1][0] + (i * row * 32) / 2;
|
||||||
const sx3 = info[2][0];
|
const sx3 = info[2][0] + (i * row * 32) / 2;
|
||||||
const sx4 = info[3][0];
|
const sx4 = info[3][0] + (i * row * 32) / 2;
|
||||||
const sy1 = info[0][1];
|
const sy1 = info[0][1];
|
||||||
const sy2 = info[1][1];
|
const sy2 = info[1][1];
|
||||||
const sy3 = info[2][1];
|
const sy3 = info[2][1];
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { ReadonlyMat3, ReadonlyVec3, mat3, vec2, vec3 } from 'gl-matrix';
|
import { ReadonlyMat3, ReadonlyVec3, mat3, vec2, vec3 } from 'gl-matrix';
|
||||||
import { EmitableEvent, EventEmitter } from '../common/eventEmitter';
|
import { EventEmitter } from '../common/eventEmitter';
|
||||||
|
|
||||||
interface CameraEvent extends EmitableEvent {}
|
interface CameraEvent {}
|
||||||
|
|
||||||
export class Camera extends EventEmitter<CameraEvent> {
|
export class Camera extends EventEmitter<CameraEvent> {
|
||||||
mat: mat3 = mat3.create();
|
mat: mat3 = mat3.create();
|
||||||
|
@ -2,12 +2,16 @@ import { MotaOffscreenCanvas2D } from '../fx/canvas2d';
|
|||||||
import { Camera } from './camera';
|
import { Camera } from './camera';
|
||||||
import {
|
import {
|
||||||
ICanvasCachedRenderItem,
|
ICanvasCachedRenderItem,
|
||||||
|
IRenderChildable,
|
||||||
RenderItem,
|
RenderItem,
|
||||||
RenderItemPosition,
|
RenderItemPosition,
|
||||||
withCacheRender
|
withCacheRender
|
||||||
} from './item';
|
} from './item';
|
||||||
|
|
||||||
export class Container extends RenderItem implements ICanvasCachedRenderItem {
|
export class Container
|
||||||
|
extends RenderItem
|
||||||
|
implements ICanvasCachedRenderItem, IRenderChildable
|
||||||
|
{
|
||||||
children: RenderItem[] = [];
|
children: RenderItem[] = [];
|
||||||
sortedChildren: RenderItem[] = [];
|
sortedChildren: RenderItem[] = [];
|
||||||
|
|
||||||
@ -27,17 +31,20 @@ export class Container extends RenderItem implements ICanvasCachedRenderItem {
|
|||||||
): void {
|
): void {
|
||||||
this.emit('beforeRender');
|
this.emit('beforeRender');
|
||||||
if (this.needUpdate) {
|
if (this.needUpdate) {
|
||||||
this.cache(this.writing);
|
this.cache(this.using);
|
||||||
this.needUpdate = false;
|
this.needUpdate = false;
|
||||||
}
|
}
|
||||||
withCacheRender(this, canvas, ctx, camera, c => {
|
withCacheRender(this, canvas, ctx, camera, c => {
|
||||||
this.sortedChildren.forEach(v => {
|
this.sortedChildren.forEach(v => {
|
||||||
|
if (v.hidden) return;
|
||||||
|
ctx.save();
|
||||||
if (!v.antiAliasing) {
|
if (!v.antiAliasing) {
|
||||||
ctx.imageSmoothingEnabled = false;
|
ctx.imageSmoothingEnabled = false;
|
||||||
} else {
|
} else {
|
||||||
ctx.imageSmoothingEnabled = true;
|
ctx.imageSmoothingEnabled = true;
|
||||||
}
|
}
|
||||||
v.render(c.canvas, c.ctx, camera);
|
v.render(c.canvas, c.ctx, camera);
|
||||||
|
ctx.restore();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
this.writing = void 0;
|
this.writing = void 0;
|
||||||
@ -61,10 +68,21 @@ export class Container extends RenderItem implements ICanvasCachedRenderItem {
|
|||||||
* 添加子元素到这个容器上,然后在下一个tick执行更新
|
* 添加子元素到这个容器上,然后在下一个tick执行更新
|
||||||
* @param children 要添加的子元素
|
* @param children 要添加的子元素
|
||||||
*/
|
*/
|
||||||
appendChild(children: RenderItem[]) {
|
appendChild(...children: RenderItem[]) {
|
||||||
children.forEach(v => (v.parent = this));
|
children.forEach(v => (v.parent = this));
|
||||||
this.children.push(...children);
|
this.children.push(...children);
|
||||||
this.sortChildren();
|
this.sortChildren();
|
||||||
|
this.update(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
removeChild(...child: RenderItem[]): void {
|
||||||
|
child.forEach(v => {
|
||||||
|
const index = this.children.indexOf(v);
|
||||||
|
if (index === -1) return;
|
||||||
|
this.children.splice(index, 1);
|
||||||
|
});
|
||||||
|
this.sortChildren();
|
||||||
|
this.update(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
sortChildren() {
|
sortChildren() {
|
||||||
@ -84,4 +102,11 @@ export class Container extends RenderItem implements ICanvasCachedRenderItem {
|
|||||||
this.canvas.setAntiAliasing(anti);
|
this.canvas.setAntiAliasing(anti);
|
||||||
this.update(this);
|
this.update(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
destroy(): void {
|
||||||
|
super.destroy();
|
||||||
|
this.children.forEach(v => {
|
||||||
|
v.destroy();
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { Ticker } from 'mutate-animate';
|
import { Ticker } from 'mutate-animate';
|
||||||
import { MotaCanvas2D } from '../fx/canvas2d';
|
import { MotaCanvas2D } from '../fx/canvas2d';
|
||||||
import { EmitableEvent, EventEmitter } from '../common/eventEmitter';
|
import { EventEmitter } from '../common/eventEmitter';
|
||||||
import { debounce } from 'lodash-es';
|
import { debounce } from 'lodash-es';
|
||||||
|
|
||||||
// 重写样板的勇士绘制
|
// 重写样板的勇士绘制
|
||||||
@ -36,7 +36,7 @@ interface HeroDrawing extends HeroDrawItem {
|
|||||||
dir: Dir;
|
dir: Dir;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface HeroRendererEvent extends EmitableEvent {
|
interface HeroRendererEvent {
|
||||||
beforeDraw: () => void;
|
beforeDraw: () => void;
|
||||||
afterDraw: () => void;
|
afterDraw: () => void;
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,8 @@
|
|||||||
import { isNil } from 'lodash-es';
|
import { isNil } from 'lodash-es';
|
||||||
import { EmitableEvent, EventEmitter } from '../common/eventEmitter';
|
import { EventEmitter } from '../common/eventEmitter';
|
||||||
import { MotaOffscreenCanvas2D } from '../fx/canvas2d';
|
import { MotaOffscreenCanvas2D } from '../fx/canvas2d';
|
||||||
import { Camera } from './camera';
|
import { Camera } from './camera';
|
||||||
import { Ticker } from 'mutate-animate';
|
import { Ticker } from 'mutate-animate';
|
||||||
import type { Container } from './container';
|
|
||||||
|
|
||||||
export type RenderFunction = (
|
export type RenderFunction = (
|
||||||
canvas: MotaOffscreenCanvas2D,
|
canvas: MotaOffscreenCanvas2D,
|
||||||
@ -83,14 +82,28 @@ interface IRenderConfig {
|
|||||||
setAntiAliasing(anti: boolean): void;
|
setAntiAliasing(anti: boolean): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IRenderDestroyable {
|
export interface IRenderChildable {
|
||||||
|
children: RenderItem[];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 摧毁这个渲染对象,被摧毁后理应不被继续使用
|
* 向这个元素添加子元素
|
||||||
|
* @param child 添加的元素
|
||||||
*/
|
*/
|
||||||
destroy(): void;
|
appendChild(...child: RenderItem[]): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 移除这个元素中的某个子元素
|
||||||
|
* @param child 要移除的元素
|
||||||
|
*/
|
||||||
|
removeChild(...child: RenderItem[]): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 对子元素进行排序
|
||||||
|
*/
|
||||||
|
sortChildren(): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface RenderItemEvent extends EmitableEvent {
|
interface RenderItemEvent {
|
||||||
beforeUpdate: (item?: RenderItem) => void;
|
beforeUpdate: (item?: RenderItem) => void;
|
||||||
afterUpdate: (item?: RenderItem) => void;
|
afterUpdate: (item?: RenderItem) => void;
|
||||||
beforeRender: () => void;
|
beforeRender: () => void;
|
||||||
@ -127,8 +140,10 @@ export abstract class RenderItem
|
|||||||
highResolution: boolean = true;
|
highResolution: boolean = true;
|
||||||
/** 是否抗锯齿 */
|
/** 是否抗锯齿 */
|
||||||
antiAliasing: boolean = true;
|
antiAliasing: boolean = true;
|
||||||
|
/** 是否被隐藏 */
|
||||||
|
hidden: boolean = false;
|
||||||
|
|
||||||
parent?: RenderItem;
|
parent?: RenderItem & IRenderChildable;
|
||||||
|
|
||||||
protected needUpdate: boolean = false;
|
protected needUpdate: boolean = false;
|
||||||
|
|
||||||
@ -209,10 +224,49 @@ export abstract class RenderItem
|
|||||||
|
|
||||||
setZIndex(zIndex: number) {
|
setZIndex(zIndex: number) {
|
||||||
this.zIndex = zIndex;
|
this.zIndex = zIndex;
|
||||||
(this.parent as Container)?.sortChildren?.();
|
this.parent?.sortChildren?.();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 隐藏这个元素
|
||||||
|
*/
|
||||||
|
hide() {
|
||||||
|
if (this.hidden) return;
|
||||||
|
this.hidden = true;
|
||||||
|
this.update(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 显示这个元素
|
||||||
|
*/
|
||||||
|
show() {
|
||||||
|
if (!this.hidden) return;
|
||||||
|
this.hidden = false;
|
||||||
|
this.update(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从渲染树中移除这个节点
|
||||||
|
*/
|
||||||
|
remove() {
|
||||||
|
this.parent?.removeChild(this);
|
||||||
|
this.parent = void 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 摧毁这个渲染元素,摧毁后不应继续使用
|
||||||
|
*/
|
||||||
|
destroy(): void {
|
||||||
|
this.remove();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface RenderEvent {
|
||||||
|
animateFrame: (frame: number, time: number) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const renderEmits = new EventEmitter<RenderEvent>();
|
||||||
|
|
||||||
Mota.require('var', 'hook').once('reset', () => {
|
Mota.require('var', 'hook').once('reset', () => {
|
||||||
let lastTime = 0;
|
let lastTime = 0;
|
||||||
RenderItem.ticker.add(time => {
|
RenderItem.ticker.add(time => {
|
||||||
@ -220,13 +274,14 @@ Mota.require('var', 'hook').once('reset', () => {
|
|||||||
if (time - lastTime > core.values.animateSpeed) {
|
if (time - lastTime > core.values.animateSpeed) {
|
||||||
RenderItem.animatedFrame++;
|
RenderItem.animatedFrame++;
|
||||||
lastTime = time;
|
lastTime = time;
|
||||||
|
renderEmits.emit('animateFrame', RenderItem.animatedFrame, time);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
export function withCacheRender(
|
export function withCacheRender(
|
||||||
item: RenderItem & ICanvasCachedRenderItem,
|
item: RenderItem & ICanvasCachedRenderItem,
|
||||||
canvas: HTMLCanvasElement,
|
_canvas: HTMLCanvasElement,
|
||||||
ctx: CanvasRenderingContext2D,
|
ctx: CanvasRenderingContext2D,
|
||||||
camera: Camera,
|
camera: Camera,
|
||||||
fn: RenderFunction
|
fn: RenderFunction
|
||||||
@ -264,3 +319,24 @@ export function withCacheRender(
|
|||||||
|
|
||||||
ctx.drawImage(c, item.x - ax, item.y - ay, item.width, item.height);
|
ctx.drawImage(c, item.x - ax, item.y - ay, item.width, item.height);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function transformCanvas(
|
||||||
|
canvas: MotaOffscreenCanvas2D,
|
||||||
|
camera: Camera,
|
||||||
|
clear: boolean = false
|
||||||
|
) {
|
||||||
|
const { canvas: ca, ctx, scale } = canvas;
|
||||||
|
const mat = camera.mat;
|
||||||
|
const a = mat[0] * scale;
|
||||||
|
const b = mat[1] * scale;
|
||||||
|
const c = mat[3] * scale;
|
||||||
|
const d = mat[4] * scale;
|
||||||
|
const e = mat[6] * scale;
|
||||||
|
const f = mat[7] * scale;
|
||||||
|
ctx.setTransform(1, 0, 0, 1, 0, 0);
|
||||||
|
if (clear) {
|
||||||
|
ctx.clearRect(0, 0, ca.width, ca.height);
|
||||||
|
}
|
||||||
|
ctx.translate(ca.width / 2, ca.height / 2);
|
||||||
|
ctx.transform(a, b, c, d, e, f);
|
||||||
|
}
|
||||||
|
205
src/core/render/preset/block.ts
Normal file
205
src/core/render/preset/block.ts
Normal file
@ -0,0 +1,205 @@
|
|||||||
|
import { EventEmitter } from '@/core/common/eventEmitter';
|
||||||
|
import { logger } from '@/core/common/logger';
|
||||||
|
|
||||||
|
interface BlockCacherEvent {
|
||||||
|
split: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface BlockData {
|
||||||
|
/** 横向宽度,包括rest的那一个块 */
|
||||||
|
width: number;
|
||||||
|
/** 纵向宽度,包括rest的那一个块 */
|
||||||
|
height: number;
|
||||||
|
/** 横向最后一个块的宽度 */
|
||||||
|
restWidth: number;
|
||||||
|
/** 纵向最后一个块的高度 */
|
||||||
|
restHeight: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class BlockCacher<T> extends EventEmitter<BlockCacherEvent> {
|
||||||
|
/** 区域宽度 */
|
||||||
|
width: number;
|
||||||
|
/** 区域高度 */
|
||||||
|
height: number;
|
||||||
|
/** 分块大小 */
|
||||||
|
blockSize: number;
|
||||||
|
/** 分块信息 */
|
||||||
|
blockData: BlockData = {
|
||||||
|
width: 0,
|
||||||
|
height: 0,
|
||||||
|
restWidth: 0,
|
||||||
|
restHeight: 0
|
||||||
|
};
|
||||||
|
/** 缓存深度,例如填4的时候表示每格包含4个缓存 */
|
||||||
|
cacheDepth: number = 1;
|
||||||
|
|
||||||
|
/** 缓存内容,计算公式为 (x + y * width) * depth + deep */
|
||||||
|
cache: Map<number, T> = new Map();
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
width: number,
|
||||||
|
height: number,
|
||||||
|
size: number,
|
||||||
|
depth: number = 1
|
||||||
|
) {
|
||||||
|
super();
|
||||||
|
this.width = width;
|
||||||
|
this.height = height;
|
||||||
|
this.blockSize = size;
|
||||||
|
this.cacheDepth = depth;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置区域大小
|
||||||
|
* @param width 区域宽度
|
||||||
|
* @param height 区域高度
|
||||||
|
*/
|
||||||
|
size(width: number, height: number) {
|
||||||
|
this.width = width;
|
||||||
|
this.height = height;
|
||||||
|
this.split();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置分块大小
|
||||||
|
*/
|
||||||
|
setBlockSize(size: number) {
|
||||||
|
this.blockSize = size;
|
||||||
|
this.split();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置缓存深度,设置后会自动将旧缓存移植到新缓存中,最大值为31
|
||||||
|
* @param depth 缓存深度
|
||||||
|
*/
|
||||||
|
setCacheDepth(depth: number) {
|
||||||
|
if (depth > 31) {
|
||||||
|
logger.error(11, `Cache depth cannot larger than 31.`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const old = this.cache;
|
||||||
|
const before = this.cacheDepth;
|
||||||
|
this.cache = new Map();
|
||||||
|
old.forEach((v, k) => {
|
||||||
|
const index = Math.floor(k / before);
|
||||||
|
const deep = k % before;
|
||||||
|
this.cache.set(index * depth + deep, v);
|
||||||
|
});
|
||||||
|
old.clear();
|
||||||
|
this.cacheDepth = depth;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 执行分块
|
||||||
|
*/
|
||||||
|
split() {
|
||||||
|
this.blockData = {
|
||||||
|
width: Math.ceil(this.width / this.blockSize),
|
||||||
|
height: Math.ceil(this.height / this.blockSize),
|
||||||
|
restWidth: this.width % this.blockSize,
|
||||||
|
restHeight: this.height % this.blockSize
|
||||||
|
};
|
||||||
|
this.emit('split');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 清除指定块的索引
|
||||||
|
* @param index 要清除的缓存索引
|
||||||
|
* @param deep 清除哪些深度的缓存,至多31位二进制数,例如填0b111就是清除前三层的索引
|
||||||
|
*/
|
||||||
|
clearCache(index: number, deep: number) {
|
||||||
|
const depth = this.cacheDepth;
|
||||||
|
for (let i = 0; i < depth; i++) {
|
||||||
|
if (deep & (1 << i)) {
|
||||||
|
this.cache.delete(index * this.cacheDepth + i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 清空指定索引的缓存,与 {@link clearCache} 不同的是,这里会直接清空对应索引的缓存,而不是指定分块的缓存
|
||||||
|
*/
|
||||||
|
clearCacheByIndex(index: number) {
|
||||||
|
this.cache.delete(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 清空所有缓存
|
||||||
|
*/
|
||||||
|
clearAllCache() {
|
||||||
|
this.cache.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据分块的横纵坐标获取其索引
|
||||||
|
*/
|
||||||
|
getIndex(x: number, y: number) {
|
||||||
|
return x + y * this.blockData.width;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据元素位置获取分块索引(注意是单个元素的位置,而不是分块的位置)
|
||||||
|
*/
|
||||||
|
getIndexByLoc(x: number, y: number) {
|
||||||
|
return this.getIndex(
|
||||||
|
Math.floor(x / this.blockSize),
|
||||||
|
Math.floor(y / this.blockSize)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据块的索引获取其位置
|
||||||
|
*/
|
||||||
|
getBlockXYByIndex(index: number): LocArr {
|
||||||
|
const width = this.blockData.width;
|
||||||
|
return [index % width, Math.floor(index / width)];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取一个元素位置所在的分块位置(即使它不在任何分块内)
|
||||||
|
*/
|
||||||
|
getBlockXY(x: number, y: number): LocArr {
|
||||||
|
return [Math.floor(x / this.blockSize), Math.floor(y / this.blockSize)];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据分块坐标与deep获取一个分块的精确索引
|
||||||
|
*/
|
||||||
|
getPreciseIndex(x: number, y: number, deep: number) {
|
||||||
|
return (x + y * this.blockSize) * this.cacheDepth + deep;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据元素坐标及deep获取元素所在块的精确索引
|
||||||
|
*/
|
||||||
|
getPreciseIndexByLoc(x: number, y: number, deep: number) {
|
||||||
|
return this.getPreciseIndex(...this.getBlockXY(x, y), deep);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新一个区域内的缓存
|
||||||
|
* @param deep 缓存清除深度,默认全部清空
|
||||||
|
*/
|
||||||
|
updateArea(
|
||||||
|
x: number,
|
||||||
|
y: number,
|
||||||
|
width: number,
|
||||||
|
height: number,
|
||||||
|
deep: number = 2 ** 31 - 1
|
||||||
|
) {
|
||||||
|
const cleared = new Set<number>();
|
||||||
|
const sx = Math.max(x, 0);
|
||||||
|
const sy = Math.max(y, 0);
|
||||||
|
const ex = Math.min(x + width, this.blockData.width);
|
||||||
|
const ey = Math.min(y + height, this.blockData.height);
|
||||||
|
|
||||||
|
for (let nx = sx; nx < ex; nx++) {
|
||||||
|
for (let ny = sy; ny < ey; ny++) {
|
||||||
|
const index = this.getIndexByLoc(nx, ny);
|
||||||
|
if (cleared.has(index)) continue;
|
||||||
|
this.clearCache(index, deep);
|
||||||
|
cleared.add(index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
@ -2,10 +2,10 @@ import { Animation, hyper, sleep } from 'mutate-animate';
|
|||||||
import { MotaCanvas2D, MotaOffscreenCanvas2D } from '../fx/canvas2d';
|
import { MotaCanvas2D, MotaOffscreenCanvas2D } from '../fx/canvas2d';
|
||||||
import { Camera } from './camera';
|
import { Camera } from './camera';
|
||||||
import { Container } from './container';
|
import { Container } from './container';
|
||||||
import { IRenderDestroyable, RenderItem, withCacheRender } from './item';
|
import { RenderItem, transformCanvas, withCacheRender } from './item';
|
||||||
import { Layer } from './preset/layer';
|
import { Layer, LayerGroup } from './preset/layer';
|
||||||
|
|
||||||
export class MotaRenderer extends Container implements IRenderDestroyable {
|
export class MotaRenderer extends Container {
|
||||||
static list: Set<MotaRenderer> = new Set();
|
static list: Set<MotaRenderer> = new Set();
|
||||||
|
|
||||||
canvas: MotaOffscreenCanvas2D;
|
canvas: MotaOffscreenCanvas2D;
|
||||||
@ -71,40 +71,33 @@ export class MotaRenderer extends Container implements IRenderDestroyable {
|
|||||||
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||||
withCacheRender(this, canvas, ctx, camera, canvas => {
|
withCacheRender(this, canvas, ctx, camera, canvas => {
|
||||||
const { canvas: ca, ctx: ct, scale } = canvas;
|
const { canvas: ca, ctx: ct, scale } = canvas;
|
||||||
const mat = camera.mat;
|
|
||||||
const a = mat[0] * scale;
|
|
||||||
const b = mat[1] * scale;
|
|
||||||
const c = mat[3] * scale;
|
|
||||||
const d = mat[4] * scale;
|
|
||||||
const e = mat[6] * scale;
|
|
||||||
const f = mat[7] * scale;
|
|
||||||
this.sortedChildren.forEach(v => {
|
this.sortedChildren.forEach(v => {
|
||||||
|
if (v.hidden) return;
|
||||||
if (v.type === 'absolute') {
|
if (v.type === 'absolute') {
|
||||||
ct.setTransform(scale, 0, 0, scale, 0, 0);
|
ct.setTransform(scale, 0, 0, scale, 0, 0);
|
||||||
} else {
|
} else {
|
||||||
ct.setTransform(1, 0, 0, 1, 0, 0);
|
transformCanvas(canvas, camera, false);
|
||||||
ct.translate(ca.width / 2, ca.height / 2);
|
|
||||||
ct.transform(a, b, c, d, e, f);
|
|
||||||
}
|
}
|
||||||
|
ct.save();
|
||||||
if (!v.antiAliasing) {
|
if (!v.antiAliasing) {
|
||||||
ctx.imageSmoothingEnabled = false;
|
ctx.imageSmoothingEnabled = false;
|
||||||
|
ct.imageSmoothingEnabled = false;
|
||||||
} else {
|
} else {
|
||||||
ctx.imageSmoothingEnabled = true;
|
ctx.imageSmoothingEnabled = true;
|
||||||
|
ct.imageSmoothingEnabled = true;
|
||||||
}
|
}
|
||||||
v.render(ca, ct, camera);
|
v.render(ca, ct, camera);
|
||||||
|
ct.restore();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
this.emit('afterRender');
|
this.emit('afterRender');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 更新渲染,在下一个tick更新
|
|
||||||
*/
|
|
||||||
update(item?: RenderItem) {
|
update(item?: RenderItem) {
|
||||||
if (this.needUpdate) return;
|
if (this.needUpdate) return;
|
||||||
this.needUpdate = true;
|
this.needUpdate = true;
|
||||||
requestAnimationFrame(() => {
|
requestAnimationFrame(() => {
|
||||||
this.cache(this.writing);
|
this.cache(this.using);
|
||||||
this.needUpdate = false;
|
this.needUpdate = false;
|
||||||
this.refresh(item);
|
this.refresh(item);
|
||||||
});
|
});
|
||||||
@ -144,57 +137,15 @@ window.addEventListener('resize', () => {
|
|||||||
|
|
||||||
Mota.require('var', 'hook').once('reset', () => {
|
Mota.require('var', 'hook').once('reset', () => {
|
||||||
const render = new MotaRenderer();
|
const render = new MotaRenderer();
|
||||||
const layer = new Layer();
|
|
||||||
const bgLayer = new Layer();
|
|
||||||
const camera = render.camera;
|
const camera = render.camera;
|
||||||
render.mount();
|
render.mount();
|
||||||
|
|
||||||
layer.setZIndex(2);
|
const layer = new LayerGroup();
|
||||||
bgLayer.setZIndex(1);
|
layer.addDamage();
|
||||||
render.appendChild([layer, bgLayer]);
|
render.appendChild(layer);
|
||||||
layer.bindThis('event');
|
|
||||||
bgLayer.bindThis('bg');
|
|
||||||
bgLayer.setBackground(305);
|
|
||||||
|
|
||||||
const ani = new Animation();
|
|
||||||
|
|
||||||
// ani.ticker.add(() => {
|
|
||||||
// camera.reset();
|
|
||||||
// camera.rotate((ani.angle / 180) * Math.PI);
|
|
||||||
// camera.move(ani.x, ani.y);
|
|
||||||
// camera.scale(ani.size);
|
|
||||||
// render.update(render);
|
|
||||||
// });
|
|
||||||
|
|
||||||
// camera.rotate(Math.PI * 1.23);
|
|
||||||
camera.move(240, 240);
|
camera.move(240, 240);
|
||||||
// camera.scale(0.7);
|
|
||||||
render.update();
|
render.update();
|
||||||
|
|
||||||
// sleep(2000).then(() => {
|
|
||||||
// render.update();
|
|
||||||
// });
|
|
||||||
|
|
||||||
sleep(1000).then(() => {
|
|
||||||
// ani.mode(hyper('sin', 'out'))
|
|
||||||
// .time(100)
|
|
||||||
// .absolute()
|
|
||||||
// .rotate(30)
|
|
||||||
// .move(240, 240);
|
|
||||||
// sleep(100).then(() => {
|
|
||||||
// ani.time(3000).rotate(0);
|
|
||||||
// });
|
|
||||||
// sleep(3100).then(() => {
|
|
||||||
// ani.time(5000)
|
|
||||||
// .mode(hyper('sin', 'in-out'))
|
|
||||||
// .rotate(360)
|
|
||||||
// .move(200, 480)
|
|
||||||
// .scale(0.5);
|
|
||||||
// });
|
|
||||||
// ani.mode(shake2(5, hyper('sin', 'in-out')), true)
|
|
||||||
// .time(5000)
|
|
||||||
// .shake(1, 0);
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log(layer);
|
console.log(layer);
|
||||||
});
|
});
|
||||||
|
@ -26,7 +26,7 @@ export class Sprite extends RenderItem implements ICanvasCachedRenderItem {
|
|||||||
): void {
|
): void {
|
||||||
this.emit('beforeRender');
|
this.emit('beforeRender');
|
||||||
if (this.needUpdate) {
|
if (this.needUpdate) {
|
||||||
this.cache(this.writing);
|
this.cache(this.using);
|
||||||
this.needUpdate = false;
|
this.needUpdate = false;
|
||||||
}
|
}
|
||||||
withCacheRender(this, canvas, ctx, camera, canvas => {
|
withCacheRender(this, canvas, ctx, camera, canvas => {
|
||||||
@ -40,7 +40,6 @@ export class Sprite extends RenderItem implements ICanvasCachedRenderItem {
|
|||||||
this.width = width;
|
this.width = width;
|
||||||
this.height = height;
|
this.height = height;
|
||||||
this.canvas.size(width, height);
|
this.canvas.size(width, height);
|
||||||
this.writing = this.using;
|
|
||||||
this.update(this);
|
this.update(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,12 +1,13 @@
|
|||||||
import { EmitableEvent, EventEmitter } from '../core/common/eventEmitter';
|
import { EventEmitter } from '../core/common/eventEmitter';
|
||||||
import type { DamageEnemy } from './enemy/damage';
|
import type { DamageEnemy } from './enemy/damage';
|
||||||
|
|
||||||
// ----- 加载事件
|
// ----- 加载事件
|
||||||
interface GameLoadEvent extends EmitableEvent {
|
interface GameLoadEvent {
|
||||||
coreLoaded: () => void;
|
coreLoaded: () => void;
|
||||||
autotileLoaded: () => void;
|
autotileLoaded: () => void;
|
||||||
coreInit: () => void;
|
coreInit: () => void;
|
||||||
loaded: () => void;
|
loaded: () => void;
|
||||||
|
materialLoaded: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
class GameLoading extends EventEmitter<GameLoadEvent> {
|
class GameLoading extends EventEmitter<GameLoadEvent> {
|
||||||
@ -64,7 +65,7 @@ class GameLoading extends EventEmitter<GameLoadEvent> {
|
|||||||
|
|
||||||
export const loading = new GameLoading();
|
export const loading = new GameLoading();
|
||||||
|
|
||||||
export interface GameEvent extends EmitableEvent {
|
export interface GameEvent {
|
||||||
/** Emitted in libs/events.js resetGame. */
|
/** Emitted in libs/events.js resetGame. */
|
||||||
reset: () => void;
|
reset: () => void;
|
||||||
/** Emitted in src/App.vue setup. */
|
/** Emitted in src/App.vue setup. */
|
||||||
@ -101,14 +102,14 @@ export interface GameEvent extends EmitableEvent {
|
|||||||
x: number,
|
x: number,
|
||||||
y: number,
|
y: number,
|
||||||
floorId: FloorIds,
|
floorId: FloorIds,
|
||||||
oldBlock: AllNumbers,
|
newBlock: AllNumbers,
|
||||||
newBlock: AllNumbers
|
oldBlock: AllNumbers
|
||||||
) => void;
|
) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const hook = new EventEmitter<GameEvent>();
|
export const hook = new EventEmitter<GameEvent>();
|
||||||
|
|
||||||
interface ListenerEvent extends EmitableEvent {
|
interface ListenerEvent {
|
||||||
// block
|
// block
|
||||||
hoverBlock: (block: Block, ev: MouseEvent) => void;
|
hoverBlock: (block: Block, ev: MouseEvent) => void;
|
||||||
leaveBlock: (block: Block, ev: MouseEvent, leaveGame: boolean) => void;
|
leaveBlock: (block: Block, ev: MouseEvent, leaveGame: boolean) => void;
|
||||||
|
@ -96,3 +96,31 @@ Range.registerRangeType(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
Range.registerRangeType(
|
||||||
|
'rect',
|
||||||
|
(col, { x, y, w, h }) => {
|
||||||
|
const list = col.collection.list;
|
||||||
|
const ex = x + w;
|
||||||
|
const ey = y + h;
|
||||||
|
|
||||||
|
return list.filter(v => {
|
||||||
|
return (
|
||||||
|
has(v.x) &&
|
||||||
|
has(v.y) &&
|
||||||
|
v.x >= x &&
|
||||||
|
v.y >= y &&
|
||||||
|
v.x < ex &&
|
||||||
|
v.y < ey
|
||||||
|
);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
(col, { x, y, w, h }, item) => {
|
||||||
|
const ex = x + w;
|
||||||
|
const ey = y + h;
|
||||||
|
const v = item;
|
||||||
|
|
||||||
|
return (
|
||||||
|
has(v.x) && has(v.y) && v.x >= x && v.y >= y && v.x < ex && v.y < ey
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
@ -43,7 +43,7 @@ export function has<T>(value: T): value is NonNullable<T> {
|
|||||||
* 根据伤害大小获取颜色
|
* 根据伤害大小获取颜色
|
||||||
* @param damage 伤害大小
|
* @param damage 伤害大小
|
||||||
*/
|
*/
|
||||||
export function getDamageColor(damage: number): Color {
|
export function getDamageColor(damage: number): string {
|
||||||
if (typeof damage !== 'number') return '#f00';
|
if (typeof damage !== 'number') return '#f00';
|
||||||
if (damage === 0) return '#2f2';
|
if (damage === 0) return '#2f2';
|
||||||
if (damage < 0) return '#7f7';
|
if (damage < 0) return '#7f7';
|
||||||
|
Loading…
Reference in New Issue
Block a user