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] 乾坤挪移:平移光环位置
|
||||
[x] 加光环的光环
|
||||
|
||||
### Boss
|
||||
|
||||
@ -21,8 +21,8 @@
|
||||
|
||||
### 机制
|
||||
|
||||
[] 苍蓝之殿 1: 红蓝黄门转换
|
||||
[] 苍蓝之殿 2: 乾坤挪移、杀戮光环、同化、光环范围扩大等
|
||||
[x] 苍蓝之殿 1: 红蓝黄门转换
|
||||
[x] 苍蓝之殿 2: 乾坤挪移、杀戮光环、同化、光环范围扩大等
|
||||
[] 苍蓝之殿 3: 传送门
|
||||
[] 苍蓝之殿 4: 地形变换,如平移、翻转、旋转等
|
||||
[] 苍蓝之殿中: 让我们把这些东西结合起来...
|
||||
|
@ -4347,7 +4347,7 @@ events.prototype._jumpHero_doJump = function (jumpInfo, callback) {
|
||||
var cb = function () {
|
||||
core.setHeroLoc('x', jumpInfo.ex);
|
||||
core.setHeroLoc('y', jumpInfo.ey);
|
||||
render.move(false);
|
||||
// render.move(false);
|
||||
core.status.heroMoving = 0;
|
||||
core.drawHero();
|
||||
if (callback) callback();
|
||||
|
@ -3296,8 +3296,8 @@ maps.prototype.setBlock = function (number, x, y, floorId, noredraw) {
|
||||
x,
|
||||
y,
|
||||
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();
|
||||
|
||||
@ -7,7 +7,7 @@ interface BaseNode {
|
||||
channel?: number;
|
||||
}
|
||||
|
||||
interface AudioPlayerEvent extends EmitableEvent {
|
||||
interface AudioPlayerEvent {
|
||||
play: (node: AudioBufferSourceNode) => void;
|
||||
update: (audio: AudioBuffer) => 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;
|
||||
dispose: (value: T) => void;
|
||||
destroy: () => void;
|
||||
|
@ -2,9 +2,7 @@ function has<T>(value: T): value is NonNullable<T> {
|
||||
return value !== null && value !== undefined;
|
||||
}
|
||||
|
||||
export interface EmitableEvent {
|
||||
[event: string]: (...params: any) => any;
|
||||
}
|
||||
export type Callable = (...params: any) => any;
|
||||
|
||||
export interface Listener<T extends (...params: any) => any> {
|
||||
fn: T;
|
||||
@ -22,14 +20,16 @@ type EmitFn<F extends (...params: any) => any> = (
|
||||
...params: Parameters<F>
|
||||
) => any;
|
||||
|
||||
export class EventEmitter<T extends EmitableEvent = {}> {
|
||||
type Key = number | string | symbol;
|
||||
|
||||
export class EventEmitter<T extends Record<keyof T, Callable>> {
|
||||
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: {
|
||||
[P in keyof T]?: EmitFn<T[P]>;
|
||||
[x: Key]: EmitFn<Callable> | undefined;
|
||||
} = {};
|
||||
|
||||
/**
|
||||
@ -42,8 +42,10 @@ export class EventEmitter<T extends EmitableEvent = {}> {
|
||||
event: K,
|
||||
fn: T[K],
|
||||
options?: Partial<ListenerOptions>
|
||||
) {
|
||||
if (options?.immediate && this.emitted.includes(event)) {
|
||||
): void;
|
||||
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();
|
||||
if (!options.once) {
|
||||
this.events[event] ??= [];
|
||||
@ -65,7 +67,9 @@ export class EventEmitter<T extends EmitableEvent = {}> {
|
||||
* @param event 要取消监听的事件类型
|
||||
* @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);
|
||||
if (index === -1 || index === void 0) return;
|
||||
this.events[event]?.splice(index, 1);
|
||||
@ -76,7 +80,9 @@ export class EventEmitter<T extends EmitableEvent = {}> {
|
||||
* @param event 要监听的事件
|
||||
* @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 });
|
||||
}
|
||||
|
||||
@ -89,18 +95,18 @@ export class EventEmitter<T extends EmitableEvent = {}> {
|
||||
event: K,
|
||||
...params: Parameters<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>(event: K, ...params: Parameters<T[K]>): any {
|
||||
if (!this.emitted.includes(event)) {
|
||||
this.emitted.push(event);
|
||||
}
|
||||
emit<R>(event: string, ...params: any[]): R;
|
||||
emit(event: string, ...params: any[]): any[] {
|
||||
this.emitted.add(event);
|
||||
const events = (this.events[event] ??= []);
|
||||
if (!!this.emitter[event]) {
|
||||
const returns = this.emitter[event]!(events, ...params);
|
||||
this.events[event] = events.filter(v => !v.once);
|
||||
return returns;
|
||||
} else {
|
||||
const returns: ReturnType<T[K]>[] = [];
|
||||
const returns: ReturnType<Callable>[] = [];
|
||||
for (let i = 0; i < events.length; i++) {
|
||||
const e = events[i];
|
||||
returns.push(e.fn(...(params as any)));
|
||||
@ -115,7 +121,10 @@ export class EventEmitter<T extends EmitableEvent = {}> {
|
||||
* @param event 要设置的事件
|
||||
* @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;
|
||||
}
|
||||
|
||||
@ -128,7 +137,12 @@ export class EventEmitter<T extends EmitableEvent = {}> {
|
||||
* @param event 要取消监听的事件
|
||||
*/
|
||||
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] = [];
|
||||
else this.events = {};
|
||||
}
|
||||
@ -137,7 +151,7 @@ export class EventEmitter<T extends EmitableEvent = {}> {
|
||||
type IndexedSymbol = number | string | symbol;
|
||||
|
||||
export class IndexedEventEmitter<
|
||||
T extends EmitableEvent
|
||||
T extends Record<keyof T, Callable>
|
||||
> extends EventEmitter<T> {
|
||||
private fnMap: {
|
||||
[P in keyof T]?: Map<IndexedSymbol, T[P]>;
|
||||
|
@ -1,5 +1,7 @@
|
||||
import { debounce } from 'lodash-es';
|
||||
|
||||
// todo: 使用格式化输出?
|
||||
|
||||
export const enum LogLevel {
|
||||
/** 输出所有,包括日志 */
|
||||
LOG,
|
||||
@ -84,7 +86,7 @@ export class Logger {
|
||||
logTip.textContent = `Error thrown, please check in console.`;
|
||||
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 { logger } from './logger';
|
||||
import JSZip from 'jszip';
|
||||
import { EmitableEvent, EventEmitter } from './eventEmitter';
|
||||
import { EventEmitter } from './eventEmitter';
|
||||
|
||||
type ProgressFn = (now: number, total: number) => void;
|
||||
|
||||
@ -305,7 +305,7 @@ export const resourceTypeMap = {
|
||||
zip: ZipResource
|
||||
};
|
||||
|
||||
interface LoadEvent<T extends keyof ResourceType> extends EmitableEvent {
|
||||
interface LoadEvent<T extends keyof ResourceType> {
|
||||
progress: (
|
||||
type: keyof ResourceType,
|
||||
uri: string,
|
||||
|
@ -1,8 +1,8 @@
|
||||
import { parseCss } from '@/plugin/utils';
|
||||
import { EmitableEvent, EventEmitter } from '../common/eventEmitter';
|
||||
import { EventEmitter } from '../common/eventEmitter';
|
||||
import { CSSObj } from '../interface';
|
||||
|
||||
interface OffscreenCanvasEvent extends EmitableEvent {
|
||||
interface OffscreenCanvasEvent {
|
||||
/** 当被动触发resize时(例如core.domStyle.scale变化、窗口大小变化)时触发,使用size函数并不会触发 */
|
||||
resize: () => void;
|
||||
}
|
||||
|
@ -1,8 +1,8 @@
|
||||
import { Animation, Ticker, hyper } from 'mutate-animate';
|
||||
import { EmitableEvent, EventEmitter } from '../common/eventEmitter';
|
||||
import { EventEmitter } from '../common/eventEmitter';
|
||||
import { ensureArray } from '@/plugin/utils';
|
||||
|
||||
interface ShaderEvent extends EmitableEvent {}
|
||||
interface ShaderEvent {}
|
||||
|
||||
const isWebGLSupported = (() => {
|
||||
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;
|
||||
delete: (uri: string, content: T) => void;
|
||||
}
|
||||
@ -25,6 +25,6 @@ export abstract class ResourceController<
|
||||
remove(uri: string) {
|
||||
const content = 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 { EmitableEvent, EventEmitter } from '@/core/common/eventEmitter';
|
||||
import { EventEmitter } from '@/core/common/eventEmitter';
|
||||
import { logger } from '@/core/common/logger';
|
||||
import { ResponseBase } from '@/core/interface';
|
||||
import {
|
||||
@ -52,7 +52,7 @@ interface PostLikeResponse extends ResponseBase {
|
||||
liked: boolean;
|
||||
}
|
||||
|
||||
interface DanmakuEvent extends EmitableEvent {
|
||||
interface DanmakuEvent {
|
||||
showStart: (danmaku: Danmaku) => void;
|
||||
showEnd: (danmaku: Danmaku) => void;
|
||||
like: (liked: boolean, danmaku: Danmaku) => void;
|
||||
|
@ -1,10 +1,10 @@
|
||||
import { KeyCode } from '@/plugin/keyCodes';
|
||||
import { deleteWith, generateBinary, spliceBy } from '@/plugin/utils';
|
||||
import { EmitableEvent, EventEmitter } from '../../common/eventEmitter';
|
||||
import { EventEmitter } from '../../common/eventEmitter';
|
||||
|
||||
// todo: 按下时触发,长按(单次/连续)触发,按下连续触发,按下节流触发,按下加速节流触发
|
||||
|
||||
interface HotkeyEvent extends EmitableEvent {
|
||||
interface HotkeyEvent {
|
||||
set: (id: string, key: KeyCode, assist: number) => void;
|
||||
emit: (key: KeyCode, assist: number, type: KeyEmitType) => void;
|
||||
}
|
||||
|
@ -1,8 +1,4 @@
|
||||
import {
|
||||
EmitableEvent,
|
||||
EventEmitter,
|
||||
Listener
|
||||
} from '@/core/common/eventEmitter';
|
||||
import { EventEmitter, Listener } from '@/core/common/eventEmitter';
|
||||
import { KeyCode } from '@/plugin/keyCodes';
|
||||
import { gameKey } from '../init/hotkey';
|
||||
import { unwarpBinary } from './hotkey';
|
||||
@ -36,7 +32,7 @@ type VirtualKeyEmitFn = (
|
||||
ev: VirtualKeyEmit
|
||||
) => void;
|
||||
|
||||
interface VirtualKeyboardEvent extends EmitableEvent {
|
||||
interface VirtualKeyboardEvent {
|
||||
add: (item: KeyboardItem) => void;
|
||||
remove: (item: KeyboardItem) => 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 { Component, nextTick, reactive, shallowReactive } from 'vue';
|
||||
import { fixedUi } from '../init/ui';
|
||||
@ -13,7 +13,7 @@ import type {
|
||||
} from '../init/toolbar';
|
||||
import { isMobile } from '@/plugin/use';
|
||||
|
||||
interface CustomToolbarEvent extends EmitableEvent {
|
||||
interface CustomToolbarEvent {
|
||||
add: (item: ValueOf<ToolbarItemMap>) => void;
|
||||
delete: (item: ValueOf<ToolbarItemMap>) => void;
|
||||
set: (id: string, data: Partial<SettableItemData>) => void;
|
||||
|
@ -1,10 +1,10 @@
|
||||
import { Component, shallowReactive } from 'vue';
|
||||
import { EmitableEvent, EventEmitter } from '../../common/eventEmitter';
|
||||
import { Callable, EventEmitter } from '../../common/eventEmitter';
|
||||
import { KeyCode } from '@/plugin/keyCodes';
|
||||
import { Hotkey } from './hotkey';
|
||||
import { generateBinary } from '@/plugin/utils';
|
||||
|
||||
interface FocusEvent<T> extends EmitableEvent {
|
||||
interface FocusEvent<T> {
|
||||
focus: (before: T | null, after: T) => void;
|
||||
unfocus: (before: T | null) => 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;
|
||||
open: () => void;
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { FunctionalComponent, reactive } from 'vue';
|
||||
import { EmitableEvent, EventEmitter } from '../common/eventEmitter';
|
||||
import { EventEmitter } from '../common/eventEmitter';
|
||||
import { GameStorage } from './storage';
|
||||
import { has, triggerFullscreen } from '@/plugin/utils';
|
||||
import { createSettingComponents } from './init/settings';
|
||||
@ -31,7 +31,7 @@ export interface MotaSettingItem<T extends MotaSettingType = MotaSettingType> {
|
||||
display?: (value: T) => string;
|
||||
}
|
||||
|
||||
interface SettingEvent extends EmitableEvent {
|
||||
interface SettingEvent {
|
||||
valueChange: <T extends boolean | number>(
|
||||
key: string,
|
||||
newValue: T,
|
||||
@ -230,7 +230,7 @@ export class MotaSetting extends EventEmitter<SettingEvent> {
|
||||
}
|
||||
}
|
||||
|
||||
interface SettingDisplayerEvent extends EmitableEvent {
|
||||
interface SettingDisplayerEvent {
|
||||
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 { MotaOffscreenCanvas2D } from '../fx/canvas2d';
|
||||
import { SizedCanvasImageSource } from './preset/misc';
|
||||
@ -62,7 +62,7 @@ export interface AutotileRenderable extends RenderableDataBase {
|
||||
bigImage: false;
|
||||
}
|
||||
|
||||
interface TextureCacheEvent extends EmitableEvent {}
|
||||
interface TextureCacheEvent {}
|
||||
|
||||
class TextureCache extends EventEmitter<TextureCacheEvent> {
|
||||
tileset!: Record<string, HTMLImageElement>;
|
||||
@ -499,10 +499,10 @@ function splitAutotiles(map: IdToNumber): AutotileCaches {
|
||||
const ctx = canvas.ctx;
|
||||
for (let i = 0; i < frame; i++) {
|
||||
const dx = 32 * i;
|
||||
const sx1 = info[0][0];
|
||||
const sx2 = info[1][0];
|
||||
const sx3 = info[2][0];
|
||||
const sx4 = info[3][0];
|
||||
const sx1 = info[0][0] + (i * row * 32) / 2;
|
||||
const sx2 = info[1][0] + (i * row * 32) / 2;
|
||||
const sx3 = info[2][0] + (i * row * 32) / 2;
|
||||
const sx4 = info[3][0] + (i * row * 32) / 2;
|
||||
const sy1 = info[0][1];
|
||||
const sy2 = info[1][1];
|
||||
const sy3 = info[2][1];
|
||||
|
@ -1,7 +1,7 @@
|
||||
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> {
|
||||
mat: mat3 = mat3.create();
|
||||
|
@ -2,12 +2,16 @@ import { MotaOffscreenCanvas2D } from '../fx/canvas2d';
|
||||
import { Camera } from './camera';
|
||||
import {
|
||||
ICanvasCachedRenderItem,
|
||||
IRenderChildable,
|
||||
RenderItem,
|
||||
RenderItemPosition,
|
||||
withCacheRender
|
||||
} from './item';
|
||||
|
||||
export class Container extends RenderItem implements ICanvasCachedRenderItem {
|
||||
export class Container
|
||||
extends RenderItem
|
||||
implements ICanvasCachedRenderItem, IRenderChildable
|
||||
{
|
||||
children: RenderItem[] = [];
|
||||
sortedChildren: RenderItem[] = [];
|
||||
|
||||
@ -27,17 +31,20 @@ export class Container extends RenderItem implements ICanvasCachedRenderItem {
|
||||
): void {
|
||||
this.emit('beforeRender');
|
||||
if (this.needUpdate) {
|
||||
this.cache(this.writing);
|
||||
this.cache(this.using);
|
||||
this.needUpdate = false;
|
||||
}
|
||||
withCacheRender(this, canvas, ctx, camera, c => {
|
||||
this.sortedChildren.forEach(v => {
|
||||
if (v.hidden) return;
|
||||
ctx.save();
|
||||
if (!v.antiAliasing) {
|
||||
ctx.imageSmoothingEnabled = false;
|
||||
} else {
|
||||
ctx.imageSmoothingEnabled = true;
|
||||
}
|
||||
v.render(c.canvas, c.ctx, camera);
|
||||
ctx.restore();
|
||||
});
|
||||
});
|
||||
this.writing = void 0;
|
||||
@ -61,10 +68,21 @@ export class Container extends RenderItem implements ICanvasCachedRenderItem {
|
||||
* 添加子元素到这个容器上,然后在下一个tick执行更新
|
||||
* @param children 要添加的子元素
|
||||
*/
|
||||
appendChild(children: RenderItem[]) {
|
||||
appendChild(...children: RenderItem[]) {
|
||||
children.forEach(v => (v.parent = this));
|
||||
this.children.push(...children);
|
||||
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() {
|
||||
@ -84,4 +102,11 @@ export class Container extends RenderItem implements ICanvasCachedRenderItem {
|
||||
this.canvas.setAntiAliasing(anti);
|
||||
this.update(this);
|
||||
}
|
||||
|
||||
destroy(): void {
|
||||
super.destroy();
|
||||
this.children.forEach(v => {
|
||||
v.destroy();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { Ticker } from 'mutate-animate';
|
||||
import { MotaCanvas2D } from '../fx/canvas2d';
|
||||
import { EmitableEvent, EventEmitter } from '../common/eventEmitter';
|
||||
import { EventEmitter } from '../common/eventEmitter';
|
||||
import { debounce } from 'lodash-es';
|
||||
|
||||
// 重写样板的勇士绘制
|
||||
@ -36,7 +36,7 @@ interface HeroDrawing extends HeroDrawItem {
|
||||
dir: Dir;
|
||||
}
|
||||
|
||||
interface HeroRendererEvent extends EmitableEvent {
|
||||
interface HeroRendererEvent {
|
||||
beforeDraw: () => void;
|
||||
afterDraw: () => void;
|
||||
}
|
||||
|
@ -1,9 +1,8 @@
|
||||
import { isNil } from 'lodash-es';
|
||||
import { EmitableEvent, EventEmitter } from '../common/eventEmitter';
|
||||
import { EventEmitter } from '../common/eventEmitter';
|
||||
import { MotaOffscreenCanvas2D } from '../fx/canvas2d';
|
||||
import { Camera } from './camera';
|
||||
import { Ticker } from 'mutate-animate';
|
||||
import type { Container } from './container';
|
||||
|
||||
export type RenderFunction = (
|
||||
canvas: MotaOffscreenCanvas2D,
|
||||
@ -83,14 +82,28 @@ interface IRenderConfig {
|
||||
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;
|
||||
afterUpdate: (item?: RenderItem) => void;
|
||||
beforeRender: () => void;
|
||||
@ -127,8 +140,10 @@ export abstract class RenderItem
|
||||
highResolution: boolean = true;
|
||||
/** 是否抗锯齿 */
|
||||
antiAliasing: boolean = true;
|
||||
/** 是否被隐藏 */
|
||||
hidden: boolean = false;
|
||||
|
||||
parent?: RenderItem;
|
||||
parent?: RenderItem & IRenderChildable;
|
||||
|
||||
protected needUpdate: boolean = false;
|
||||
|
||||
@ -209,10 +224,49 @@ export abstract class RenderItem
|
||||
|
||||
setZIndex(zIndex: number) {
|
||||
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', () => {
|
||||
let lastTime = 0;
|
||||
RenderItem.ticker.add(time => {
|
||||
@ -220,13 +274,14 @@ Mota.require('var', 'hook').once('reset', () => {
|
||||
if (time - lastTime > core.values.animateSpeed) {
|
||||
RenderItem.animatedFrame++;
|
||||
lastTime = time;
|
||||
renderEmits.emit('animateFrame', RenderItem.animatedFrame, time);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
export function withCacheRender(
|
||||
item: RenderItem & ICanvasCachedRenderItem,
|
||||
canvas: HTMLCanvasElement,
|
||||
_canvas: HTMLCanvasElement,
|
||||
ctx: CanvasRenderingContext2D,
|
||||
camera: Camera,
|
||||
fn: RenderFunction
|
||||
@ -264,3 +319,24 @@ export function withCacheRender(
|
||||
|
||||
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 { Camera } from './camera';
|
||||
import { Container } from './container';
|
||||
import { IRenderDestroyable, RenderItem, withCacheRender } from './item';
|
||||
import { Layer } from './preset/layer';
|
||||
import { RenderItem, transformCanvas, withCacheRender } from './item';
|
||||
import { Layer, LayerGroup } from './preset/layer';
|
||||
|
||||
export class MotaRenderer extends Container implements IRenderDestroyable {
|
||||
export class MotaRenderer extends Container {
|
||||
static list: Set<MotaRenderer> = new Set();
|
||||
|
||||
canvas: MotaOffscreenCanvas2D;
|
||||
@ -71,40 +71,33 @@ export class MotaRenderer extends Container implements IRenderDestroyable {
|
||||
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||
withCacheRender(this, canvas, ctx, camera, canvas => {
|
||||
const { canvas: ca, ctx: ct, scale } = canvas;
|
||||
const mat = camera.mat;
|
||||
const a = mat[0] * scale;
|
||||
const b = mat[1] * 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 => {
|
||||
if (v.hidden) return;
|
||||
if (v.type === 'absolute') {
|
||||
ct.setTransform(scale, 0, 0, scale, 0, 0);
|
||||
} else {
|
||||
ct.setTransform(1, 0, 0, 1, 0, 0);
|
||||
ct.translate(ca.width / 2, ca.height / 2);
|
||||
ct.transform(a, b, c, d, e, f);
|
||||
transformCanvas(canvas, camera, false);
|
||||
}
|
||||
ct.save();
|
||||
if (!v.antiAliasing) {
|
||||
ctx.imageSmoothingEnabled = false;
|
||||
ct.imageSmoothingEnabled = false;
|
||||
} else {
|
||||
ctx.imageSmoothingEnabled = true;
|
||||
ct.imageSmoothingEnabled = true;
|
||||
}
|
||||
v.render(ca, ct, camera);
|
||||
ct.restore();
|
||||
});
|
||||
});
|
||||
this.emit('afterRender');
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新渲染,在下一个tick更新
|
||||
*/
|
||||
update(item?: RenderItem) {
|
||||
if (this.needUpdate) return;
|
||||
this.needUpdate = true;
|
||||
requestAnimationFrame(() => {
|
||||
this.cache(this.writing);
|
||||
this.cache(this.using);
|
||||
this.needUpdate = false;
|
||||
this.refresh(item);
|
||||
});
|
||||
@ -144,57 +137,15 @@ window.addEventListener('resize', () => {
|
||||
|
||||
Mota.require('var', 'hook').once('reset', () => {
|
||||
const render = new MotaRenderer();
|
||||
const layer = new Layer();
|
||||
const bgLayer = new Layer();
|
||||
const camera = render.camera;
|
||||
render.mount();
|
||||
|
||||
layer.setZIndex(2);
|
||||
bgLayer.setZIndex(1);
|
||||
render.appendChild([layer, bgLayer]);
|
||||
layer.bindThis('event');
|
||||
bgLayer.bindThis('bg');
|
||||
bgLayer.setBackground(305);
|
||||
const layer = new LayerGroup();
|
||||
layer.addDamage();
|
||||
render.appendChild(layer);
|
||||
|
||||
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.scale(0.7);
|
||||
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);
|
||||
});
|
||||
|
@ -26,7 +26,7 @@ export class Sprite extends RenderItem implements ICanvasCachedRenderItem {
|
||||
): void {
|
||||
this.emit('beforeRender');
|
||||
if (this.needUpdate) {
|
||||
this.cache(this.writing);
|
||||
this.cache(this.using);
|
||||
this.needUpdate = false;
|
||||
}
|
||||
withCacheRender(this, canvas, ctx, camera, canvas => {
|
||||
@ -40,7 +40,6 @@ export class Sprite extends RenderItem implements ICanvasCachedRenderItem {
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
this.canvas.size(width, height);
|
||||
this.writing = this.using;
|
||||
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';
|
||||
|
||||
// ----- 加载事件
|
||||
interface GameLoadEvent extends EmitableEvent {
|
||||
interface GameLoadEvent {
|
||||
coreLoaded: () => void;
|
||||
autotileLoaded: () => void;
|
||||
coreInit: () => void;
|
||||
loaded: () => void;
|
||||
materialLoaded: () => void;
|
||||
}
|
||||
|
||||
class GameLoading extends EventEmitter<GameLoadEvent> {
|
||||
@ -64,7 +65,7 @@ class GameLoading extends EventEmitter<GameLoadEvent> {
|
||||
|
||||
export const loading = new GameLoading();
|
||||
|
||||
export interface GameEvent extends EmitableEvent {
|
||||
export interface GameEvent {
|
||||
/** Emitted in libs/events.js resetGame. */
|
||||
reset: () => void;
|
||||
/** Emitted in src/App.vue setup. */
|
||||
@ -101,14 +102,14 @@ export interface GameEvent extends EmitableEvent {
|
||||
x: number,
|
||||
y: number,
|
||||
floorId: FloorIds,
|
||||
oldBlock: AllNumbers,
|
||||
newBlock: AllNumbers
|
||||
newBlock: AllNumbers,
|
||||
oldBlock: AllNumbers
|
||||
) => void;
|
||||
}
|
||||
|
||||
export const hook = new EventEmitter<GameEvent>();
|
||||
|
||||
interface ListenerEvent extends EmitableEvent {
|
||||
interface ListenerEvent {
|
||||
// block
|
||||
hoverBlock: (block: Block, ev: MouseEvent) => 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 伤害大小
|
||||
*/
|
||||
export function getDamageColor(damage: number): Color {
|
||||
export function getDamageColor(damage: number): string {
|
||||
if (typeof damage !== 'number') return '#f00';
|
||||
if (damage === 0) return '#2f2';
|
||||
if (damage < 0) return '#7f7';
|
||||
|
Loading…
Reference in New Issue
Block a user