@eva/plugin-trigger 2.1.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/Trigger.ts ADDED
@@ -0,0 +1,127 @@
1
+ import { Component, decorators } from '@eva/eva.js';
2
+ import { getSignalBus, SignalHandle } from '@eva/plugin-signal-bus';
3
+ import type { TriggerParams, TriggerRule, TriggerAction } from './types';
4
+
5
+ declare const mx: any;
6
+
7
+ /**
8
+ * Trigger 组件 — 把信号映射成 DSL 动作。
9
+ *
10
+ * DSL 用法:
11
+ * ```json
12
+ * {
13
+ * "type": "Trigger",
14
+ * "props": {
15
+ * "rules": [
16
+ * { "on": "input:fire:press",
17
+ * "do": [
18
+ * { "type": "emit", "signal": "rocket:spawn" },
19
+ * { "type": "incStore", "key": "shotsFired" }
20
+ * ] },
21
+ * { "on": "rocket:hit",
22
+ * "guard": "ctx.allowedScene === true",
23
+ * "do": [
24
+ * { "type": "incStore", "key": "score" },
25
+ * { "type": "emit", "signal": "monster:hurt" }
26
+ * ] }
27
+ * ]
28
+ * }
29
+ * }
30
+ * ```
31
+ *
32
+ * 这是 plugin-state-machine 的"无状态弟弟":只配 input → output,不维护 state。
33
+ * 适合写 80% 的"按钮按下→记分"业务,大幅减少自定义 Component 数量。
34
+ */
35
+ @decorators.componentObserver({})
36
+ export class Trigger extends Component<TriggerParams> {
37
+ static componentName = 'Trigger';
38
+
39
+ private rules: TriggerRule[] = [];
40
+ private subs: SignalHandle[] = [];
41
+ ctx: Record<string, any> = {};
42
+
43
+ init(params?: TriggerParams) {
44
+ if (!params) return;
45
+ this.rules = params.rules ?? [];
46
+ this.ctx = params.context ?? {};
47
+ }
48
+
49
+ awake() {
50
+ const bus = getSignalBus();
51
+ for (const rule of this.rules) {
52
+ const h = bus.on(rule.on, (payload: any) => {
53
+ if (rule.guard && !this.evalGuard(rule.guard, payload)) return;
54
+ for (const a of rule.do) this.exec(a, payload);
55
+ });
56
+ this.subs.push(h);
57
+ }
58
+ }
59
+
60
+ private exec(action: TriggerAction, payload: any) {
61
+ try {
62
+ switch (action.type) {
63
+ case 'emit':
64
+ getSignalBus().emit(action.signal, action.payload ?? payload);
65
+ break;
66
+ case 'setStore':
67
+ if (typeof mx !== 'undefined' && mx?.store?.update) {
68
+ mx.store.update(action.key, () => action.value);
69
+ }
70
+ break;
71
+ case 'incStore':
72
+ if (typeof mx !== 'undefined' && mx?.store?.update) {
73
+ mx.store.update(action.key, (v: number) => (v ?? 0) + (action.delta ?? 1));
74
+ }
75
+ break;
76
+ case 'log':
77
+ // eslint-disable-next-line no-console
78
+ console.log('[trigger]', action.message, payload);
79
+ break;
80
+ case 'callMethod':
81
+ this.callMethod(action.entity, action.component, action.method, action.args ?? []);
82
+ break;
83
+ }
84
+ } catch (err) {
85
+ // eslint-disable-next-line no-console
86
+ console.warn('[plugin-trigger] action failed', action, err);
87
+ }
88
+ }
89
+
90
+ private callMethod(entity: string, compName: string, method: string, args: any[]) {
91
+ const game: any = (this as any).gameObject?.scene?.game;
92
+ if (!game) return;
93
+ const stack: any[] = [...(game.scene?.gameObjects ?? [])];
94
+ while (stack.length) {
95
+ const go = stack.pop();
96
+ if (!go) continue;
97
+ if (go.name === entity) {
98
+ const comps: any[] = go.components ?? [];
99
+ const c = comps.find((c) => c?.constructor?.componentName === compName);
100
+ if (c && typeof c[method] === 'function') {
101
+ c[method](...args);
102
+ }
103
+ return;
104
+ }
105
+ if (go.transform?.children?.length) {
106
+ for (const ch of go.transform.children) stack.push(ch.gameObject);
107
+ }
108
+ }
109
+ }
110
+
111
+ private evalGuard(guard: string, payload: any): boolean {
112
+ try {
113
+ // eslint-disable-next-line @typescript-eslint/no-implied-eval, no-new-func
114
+ const fn = new Function('payload', 'ctx', `return (${guard});`);
115
+ return Boolean(fn(payload, this.ctx));
116
+ } catch (err) {
117
+ // eslint-disable-next-line no-console
118
+ console.warn(`[plugin-trigger] bad guard "${guard}":`, err);
119
+ return false;
120
+ }
121
+ }
122
+
123
+ onDestroy() {
124
+ for (const h of this.subs) h.dispose();
125
+ this.subs = [];
126
+ }
127
+ }
@@ -0,0 +1,6 @@
1
+ import { System } from '@eva/eva.js';
2
+
3
+ export class TriggerSystem extends System {
4
+ static systemName = 'Trigger';
5
+ readonly name = 'Trigger';
6
+ }
package/lib/index.ts ADDED
@@ -0,0 +1,3 @@
1
+ export { Trigger } from './Trigger';
2
+ export { TriggerSystem } from './TriggerSystem';
3
+ export type { TriggerParams, TriggerRule, TriggerAction } from './types';
package/lib/types.ts ADDED
@@ -0,0 +1,22 @@
1
+ /** 单条 action,DSL 描述时只填 type + 字段 */
2
+ export type TriggerAction =
3
+ | { type: 'emit'; signal: string; payload?: any }
4
+ | { type: 'setStore'; key: string; value: any }
5
+ | { type: 'incStore'; key: string; delta?: number }
6
+ | { type: 'log'; message: string }
7
+ | { type: 'callMethod'; entity: string; component: string; method: string; args?: any[] };
8
+
9
+ export interface TriggerRule {
10
+ /** 监听的信号名 */
11
+ on: string;
12
+ /** 命中后执行的动作列表 */
13
+ do: TriggerAction[];
14
+ /** 可选:JS 表达式;以 (payload, ctx) 为变量,假则跳过 */
15
+ guard?: string;
16
+ }
17
+
18
+ export interface TriggerParams {
19
+ rules: TriggerRule[];
20
+ /** ctx 变量,可在 guard 中读 */
21
+ context?: Record<string, any>;
22
+ }
package/package.json ADDED
@@ -0,0 +1,22 @@
1
+ {
2
+ "name": "@eva/plugin-trigger",
3
+ "version": "2.1.0-beta.1",
4
+ "description": "Trigger — 信号 → DSL 动作的执行器,DSL 配置 \"on signal\" + \"do actions\",emit/setStore/transitionState/playSound 等。",
5
+ "main": "lib/index.ts",
6
+ "module": "lib/index.ts",
7
+ "types": "lib/index.ts",
8
+ "files": [
9
+ "lib"
10
+ ],
11
+ "keywords": [
12
+ "eva.js",
13
+ "plugin",
14
+ "trigger",
15
+ "rules"
16
+ ],
17
+ "license": "MIT",
18
+ "dependencies": {
19
+ "@eva/eva.js": "2.1.0-beta.1",
20
+ "@eva/plugin-signal-bus": "2.1.0-beta.1"
21
+ }
22
+ }