@eva/plugin-input-action 2.1.0-beta.2 → 2.1.0-beta.4

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.
@@ -0,0 +1,213 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, '__esModule', { value: true });
4
+
5
+ var eva_js = require('@eva/eva.js');
6
+ var pluginSignalBus = require('@eva/plugin-signal-bus');
7
+
8
+ /******************************************************************************
9
+ Copyright (c) Microsoft Corporation.
10
+
11
+ Permission to use, copy, modify, and/or distribute this software for any
12
+ purpose with or without fee is hereby granted.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
15
+ REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
16
+ AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
17
+ INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
18
+ LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
19
+ OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
20
+ PERFORMANCE OF THIS SOFTWARE.
21
+ ***************************************************************************** */
22
+
23
+ function __decorate(decorators, target, key, desc) {
24
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
25
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
26
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
27
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
28
+ }
29
+
30
+ typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
31
+ var e = new Error(message);
32
+ return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
33
+ };
34
+
35
+ /**
36
+ * InputActionMap 组件 — 把原始 input 抽象成语义化 action 并 emit 信号。
37
+ *
38
+ * DSL 用法:
39
+ * ```json
40
+ * {
41
+ * "type": "InputActionMap",
42
+ * "props": {
43
+ * "bindings": [
44
+ * { "action": "fire", "sources": [{ "type": "click" }, { "type": "key", "code": "Space" }] },
45
+ * { "action": "left", "sources": [{ "type": "key", "code": "ArrowLeft" }] }
46
+ * ]
47
+ * }
48
+ * }
49
+ * ```
50
+ *
51
+ * 信号:
52
+ * - 默认 emit `input:fire:press` / `input:fire:release` / `input:fire:hold`(每帧)
53
+ * - 可在 binding 显式指定 pressSignal / releaseSignal / holdSignal 覆盖
54
+ *
55
+ * 设计原则:Component 不直接修改游戏状态,只 emit 语义化信号。
56
+ */
57
+ exports.InputActionMap = class InputActionMap extends eva_js.Component {
58
+ constructor() {
59
+ super(...arguments);
60
+ this.bindings = [];
61
+ this.states = new Map();
62
+ // 注意:不能命名 `listeners`,会与 EventEmitter 基类的 listeners(event) 方法签名冲突
63
+ this.domListeners = [];
64
+ this.pressedKeys = new Set();
65
+ this.pressedButtons = new Set();
66
+ this.touchActive = false;
67
+ }
68
+ init(params) {
69
+ var _a;
70
+ if (!params)
71
+ return;
72
+ this.bindings = (_a = params.bindings) !== null && _a !== void 0 ? _a : [];
73
+ for (const b of this.bindings) {
74
+ this.states.set(b.action, { binding: b, pressed: false });
75
+ }
76
+ if (typeof document === 'undefined')
77
+ return;
78
+ this.root = params.rootSelector
79
+ ? document.querySelector(params.rootSelector) || document
80
+ : document;
81
+ this.bind();
82
+ }
83
+ bind() {
84
+ if (!this.root)
85
+ return;
86
+ const onKD = (e) => {
87
+ if (e.repeat)
88
+ return;
89
+ this.pressedKeys.add(e.code);
90
+ this.evaluate('press', { type: 'key', code: e.code });
91
+ };
92
+ const onKU = (e) => {
93
+ this.pressedKeys.delete(e.code);
94
+ this.evaluate('release', { type: 'key', code: e.code });
95
+ };
96
+ const onMD = (e) => {
97
+ this.pressedButtons.add(e.button);
98
+ this.evaluate('press', { type: 'mouse', button: e.button });
99
+ this.evaluate('press', { type: 'click' });
100
+ };
101
+ const onMU = (e) => {
102
+ this.pressedButtons.delete(e.button);
103
+ this.evaluate('release', { type: 'mouse', button: e.button });
104
+ this.evaluate('release', { type: 'click' });
105
+ };
106
+ const onTS = () => {
107
+ this.touchActive = true;
108
+ this.evaluate('press', { type: 'touch' });
109
+ this.evaluate('press', { type: 'click' });
110
+ };
111
+ const onTE = () => {
112
+ this.touchActive = false;
113
+ this.evaluate('release', { type: 'touch' });
114
+ this.evaluate('release', { type: 'click' });
115
+ };
116
+ const r = this.root;
117
+ r.addEventListener('keydown', onKD);
118
+ r.addEventListener('keyup', onKU);
119
+ r.addEventListener('mousedown', onMD);
120
+ r.addEventListener('mouseup', onMU);
121
+ r.addEventListener('touchstart', onTS);
122
+ r.addEventListener('touchend', onTE);
123
+ this.domListeners = [
124
+ () => r.removeEventListener('keydown', onKD),
125
+ () => r.removeEventListener('keyup', onKU),
126
+ () => r.removeEventListener('mousedown', onMD),
127
+ () => r.removeEventListener('mouseup', onMU),
128
+ () => r.removeEventListener('touchstart', onTS),
129
+ () => r.removeEventListener('touchend', onTE),
130
+ ];
131
+ }
132
+ evaluate(phase, src) {
133
+ var _a, _b;
134
+ const bus = pluginSignalBus.getSignalBus();
135
+ for (const [name, state] of this.states) {
136
+ if (!state.binding.sources.some((s) => this.sourceMatches(s, src)))
137
+ continue;
138
+ if (phase === 'press' && !state.pressed) {
139
+ state.pressed = true;
140
+ const sig = (_a = state.binding.pressSignal) !== null && _a !== void 0 ? _a : `input:${name}:press`;
141
+ bus.emit(sig, { action: name });
142
+ }
143
+ else if (phase === 'release' && state.pressed) {
144
+ // 检查是否所有源都已 release
145
+ if (this.anySourceActive(state.binding))
146
+ continue;
147
+ state.pressed = false;
148
+ const sig = (_b = state.binding.releaseSignal) !== null && _b !== void 0 ? _b : `input:${name}:release`;
149
+ bus.emit(sig, { action: name });
150
+ }
151
+ }
152
+ }
153
+ sourceMatches(s, e) {
154
+ if (s.type !== e.type)
155
+ return false;
156
+ if (s.type === 'key')
157
+ return s.code === e.code;
158
+ if (s.type === 'mouse')
159
+ return s.button == null || s.button === e.button;
160
+ return true;
161
+ }
162
+ anySourceActive(b) {
163
+ for (const s of b.sources) {
164
+ if (s.type === 'key' && this.pressedKeys.has(s.code))
165
+ return true;
166
+ if (s.type === 'mouse' && (s.button == null || this.pressedButtons.has(s.button)))
167
+ return true;
168
+ if (s.type === 'touch' && this.touchActive)
169
+ return true;
170
+ if (s.type === 'click' && (this.touchActive || this.pressedButtons.size > 0))
171
+ return true;
172
+ }
173
+ return false;
174
+ }
175
+ /** 每帧 emit hold 信号,允许移动型 action 用 update 读 */
176
+ update() {
177
+ var _a;
178
+ const bus = pluginSignalBus.getSignalBus();
179
+ for (const [name, state] of this.states) {
180
+ if (!state.pressed)
181
+ continue;
182
+ const sig = (_a = state.binding.holdSignal) !== null && _a !== void 0 ? _a : `input:${name}:hold`;
183
+ bus.emit(sig, { action: name });
184
+ }
185
+ }
186
+ /** 查询某 action 当前是否按下(给非信号场景用) */
187
+ isPressed(action) {
188
+ var _a;
189
+ return !!((_a = this.states.get(action)) === null || _a === void 0 ? void 0 : _a.pressed);
190
+ }
191
+ onDestroy() {
192
+ for (const off of this.domListeners)
193
+ off();
194
+ this.domListeners = [];
195
+ this.states.clear();
196
+ this.pressedKeys.clear();
197
+ this.pressedButtons.clear();
198
+ }
199
+ };
200
+ exports.InputActionMap.componentName = 'InputActionMap';
201
+ exports.InputActionMap = __decorate([
202
+ eva_js.decorators.componentObserver({})
203
+ ], exports.InputActionMap);
204
+
205
+ class InputActionSystem extends eva_js.System {
206
+ constructor() {
207
+ super(...arguments);
208
+ this.name = 'InputAction';
209
+ }
210
+ }
211
+ InputActionSystem.systemName = 'InputAction';
212
+
213
+ exports.InputActionSystem = InputActionSystem;
@@ -0,0 +1 @@
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:!0});var e=require("@eva/eva.js"),t=require("@eva/plugin-signal-bus");"function"==typeof SuppressedError&&SuppressedError,exports.InputActionMap=class extends e.Component{constructor(){super(...arguments),this.bindings=[],this.states=new Map,this.domListeners=[],this.pressedKeys=new Set,this.pressedButtons=new Set,this.touchActive=!1}init(e){var t;if(e){this.bindings=null!==(t=e.bindings)&&void 0!==t?t:[];for(const e of this.bindings)this.states.set(e.action,{binding:e,pressed:!1});"undefined"!=typeof document&&(this.root=e.rootSelector&&document.querySelector(e.rootSelector)||document,this.bind())}}bind(){if(!this.root)return;const e=e=>{e.repeat||(this.pressedKeys.add(e.code),this.evaluate("press",{type:"key",code:e.code}))},t=e=>{this.pressedKeys.delete(e.code),this.evaluate("release",{type:"key",code:e.code})},s=e=>{this.pressedButtons.add(e.button),this.evaluate("press",{type:"mouse",button:e.button}),this.evaluate("press",{type:"click"})},n=e=>{this.pressedButtons.delete(e.button),this.evaluate("release",{type:"mouse",button:e.button}),this.evaluate("release",{type:"click"})},o=()=>{this.touchActive=!0,this.evaluate("press",{type:"touch"}),this.evaluate("press",{type:"click"})},i=()=>{this.touchActive=!1,this.evaluate("release",{type:"touch"}),this.evaluate("release",{type:"click"})},r=this.root;r.addEventListener("keydown",e),r.addEventListener("keyup",t),r.addEventListener("mousedown",s),r.addEventListener("mouseup",n),r.addEventListener("touchstart",o),r.addEventListener("touchend",i),this.domListeners=[()=>r.removeEventListener("keydown",e),()=>r.removeEventListener("keyup",t),()=>r.removeEventListener("mousedown",s),()=>r.removeEventListener("mouseup",n),()=>r.removeEventListener("touchstart",o),()=>r.removeEventListener("touchend",i)]}evaluate(e,s){var n,o;const i=t.getSignalBus();for(const[t,r]of this.states)if(r.binding.sources.some(e=>this.sourceMatches(e,s)))if("press"!==e||r.pressed){if("release"===e&&r.pressed){if(this.anySourceActive(r.binding))continue;r.pressed=!1;const e=null!==(o=r.binding.releaseSignal)&&void 0!==o?o:`input:${t}:release`;i.emit(e,{action:t})}}else{r.pressed=!0;const e=null!==(n=r.binding.pressSignal)&&void 0!==n?n:`input:${t}:press`;i.emit(e,{action:t})}}sourceMatches(e,t){return e.type===t.type&&("key"===e.type?e.code===t.code:"mouse"!==e.type||(null==e.button||e.button===t.button))}anySourceActive(e){for(const t of e.sources){if("key"===t.type&&this.pressedKeys.has(t.code))return!0;if("mouse"===t.type&&(null==t.button||this.pressedButtons.has(t.button)))return!0;if("touch"===t.type&&this.touchActive)return!0;if("click"===t.type&&(this.touchActive||this.pressedButtons.size>0))return!0}return!1}update(){var e;const s=t.getSignalBus();for(const[t,n]of this.states){if(!n.pressed)continue;const o=null!==(e=n.binding.holdSignal)&&void 0!==e?e:`input:${t}:hold`;s.emit(o,{action:t})}}isPressed(e){var t;return!!(null===(t=this.states.get(e))||void 0===t?void 0:t.pressed)}onDestroy(){for(const e of this.domListeners)e();this.domListeners=[],this.states.clear(),this.pressedKeys.clear(),this.pressedButtons.clear()}},exports.InputActionMap.componentName="InputActionMap",exports.InputActionMap=function(e,t,s,n){var o,i=arguments.length,r=i<3?t:null===n?n=Object.getOwnPropertyDescriptor(t,s):n;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)r=Reflect.decorate(e,t,s,n);else for(var u=e.length-1;u>=0;u--)(o=e[u])&&(r=(i<3?o(r):i>3?o(t,s,r):o(t,s))||r);return i>3&&r&&Object.defineProperty(t,s,r),r}([e.decorators.componentObserver({})],exports.InputActionMap);class s extends e.System{constructor(){super(...arguments),this.name="InputAction"}}s.systemName="InputAction",exports.InputActionSystem=s;
@@ -0,0 +1,83 @@
1
+ import { Component } from '@eva/eva.js';
2
+ import { System } from '@eva/eva.js';
3
+
4
+ /** action 配置 */
5
+ export declare interface ActionBinding {
6
+ /** action 名称,emit 信号时使用 (例如 "fire" / "jump") */
7
+ action: string;
8
+ /** 至少一个输入源触发 */
9
+ sources: InputSource[];
10
+ /** 触发哪些信号 (默认 emit `input:{action}`) */
11
+ pressSignal?: string;
12
+ releaseSignal?: string;
13
+ holdSignal?: string;
14
+ }
15
+
16
+ /**
17
+ * InputActionMap 组件 — 把原始 input 抽象成语义化 action 并 emit 信号。
18
+ *
19
+ * DSL 用法:
20
+ * ```json
21
+ * {
22
+ * "type": "InputActionMap",
23
+ * "props": {
24
+ * "bindings": [
25
+ * { "action": "fire", "sources": [{ "type": "click" }, { "type": "key", "code": "Space" }] },
26
+ * { "action": "left", "sources": [{ "type": "key", "code": "ArrowLeft" }] }
27
+ * ]
28
+ * }
29
+ * }
30
+ * ```
31
+ *
32
+ * 信号:
33
+ * - 默认 emit `input:fire:press` / `input:fire:release` / `input:fire:hold`(每帧)
34
+ * - 可在 binding 显式指定 pressSignal / releaseSignal / holdSignal 覆盖
35
+ *
36
+ * 设计原则:Component 不直接修改游戏状态,只 emit 语义化信号。
37
+ */
38
+ export declare class InputActionMap extends Component<InputActionMapParams> {
39
+ static componentName: string;
40
+ private bindings;
41
+ private states;
42
+ private root?;
43
+ private domListeners;
44
+ private pressedKeys;
45
+ private pressedButtons;
46
+ private touchActive;
47
+ init(params?: InputActionMapParams): void;
48
+ private bind;
49
+ private evaluate;
50
+ private sourceMatches;
51
+ private anySourceActive;
52
+ /** 每帧 emit hold 信号,允许移动型 action 用 update 读 */
53
+ update(): void;
54
+ /** 查询某 action 当前是否按下(给非信号场景用) */
55
+ isPressed(action: string): boolean;
56
+ onDestroy(): void;
57
+ }
58
+
59
+ export declare interface InputActionMapParams {
60
+ bindings?: ActionBinding[];
61
+ /** 监听的根元素;不传走 document */
62
+ rootSelector?: string;
63
+ }
64
+
65
+ export declare class InputActionSystem extends System {
66
+ static systemName: string;
67
+ readonly name = "InputAction";
68
+ }
69
+
70
+ /** 输入源 */
71
+ export declare type InputSource = {
72
+ type: 'key';
73
+ code: string;
74
+ } | {
75
+ type: 'mouse';
76
+ button?: number;
77
+ } | {
78
+ type: 'touch';
79
+ } | {
80
+ type: 'click';
81
+ };
82
+
83
+ export { }
@@ -0,0 +1,209 @@
1
+ import { Component, decorators, System } from '@eva/eva.js';
2
+ import { getSignalBus } from '@eva/plugin-signal-bus';
3
+
4
+ /******************************************************************************
5
+ Copyright (c) Microsoft Corporation.
6
+
7
+ Permission to use, copy, modify, and/or distribute this software for any
8
+ purpose with or without fee is hereby granted.
9
+
10
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
11
+ REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
12
+ AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
13
+ INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
14
+ LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
15
+ OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
16
+ PERFORMANCE OF THIS SOFTWARE.
17
+ ***************************************************************************** */
18
+
19
+ function __decorate(decorators, target, key, desc) {
20
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
21
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
22
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
23
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
24
+ }
25
+
26
+ typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
27
+ var e = new Error(message);
28
+ return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
29
+ };
30
+
31
+ /**
32
+ * InputActionMap 组件 — 把原始 input 抽象成语义化 action 并 emit 信号。
33
+ *
34
+ * DSL 用法:
35
+ * ```json
36
+ * {
37
+ * "type": "InputActionMap",
38
+ * "props": {
39
+ * "bindings": [
40
+ * { "action": "fire", "sources": [{ "type": "click" }, { "type": "key", "code": "Space" }] },
41
+ * { "action": "left", "sources": [{ "type": "key", "code": "ArrowLeft" }] }
42
+ * ]
43
+ * }
44
+ * }
45
+ * ```
46
+ *
47
+ * 信号:
48
+ * - 默认 emit `input:fire:press` / `input:fire:release` / `input:fire:hold`(每帧)
49
+ * - 可在 binding 显式指定 pressSignal / releaseSignal / holdSignal 覆盖
50
+ *
51
+ * 设计原则:Component 不直接修改游戏状态,只 emit 语义化信号。
52
+ */
53
+ let InputActionMap = class InputActionMap extends Component {
54
+ constructor() {
55
+ super(...arguments);
56
+ this.bindings = [];
57
+ this.states = new Map();
58
+ // 注意:不能命名 `listeners`,会与 EventEmitter 基类的 listeners(event) 方法签名冲突
59
+ this.domListeners = [];
60
+ this.pressedKeys = new Set();
61
+ this.pressedButtons = new Set();
62
+ this.touchActive = false;
63
+ }
64
+ init(params) {
65
+ var _a;
66
+ if (!params)
67
+ return;
68
+ this.bindings = (_a = params.bindings) !== null && _a !== void 0 ? _a : [];
69
+ for (const b of this.bindings) {
70
+ this.states.set(b.action, { binding: b, pressed: false });
71
+ }
72
+ if (typeof document === 'undefined')
73
+ return;
74
+ this.root = params.rootSelector
75
+ ? document.querySelector(params.rootSelector) || document
76
+ : document;
77
+ this.bind();
78
+ }
79
+ bind() {
80
+ if (!this.root)
81
+ return;
82
+ const onKD = (e) => {
83
+ if (e.repeat)
84
+ return;
85
+ this.pressedKeys.add(e.code);
86
+ this.evaluate('press', { type: 'key', code: e.code });
87
+ };
88
+ const onKU = (e) => {
89
+ this.pressedKeys.delete(e.code);
90
+ this.evaluate('release', { type: 'key', code: e.code });
91
+ };
92
+ const onMD = (e) => {
93
+ this.pressedButtons.add(e.button);
94
+ this.evaluate('press', { type: 'mouse', button: e.button });
95
+ this.evaluate('press', { type: 'click' });
96
+ };
97
+ const onMU = (e) => {
98
+ this.pressedButtons.delete(e.button);
99
+ this.evaluate('release', { type: 'mouse', button: e.button });
100
+ this.evaluate('release', { type: 'click' });
101
+ };
102
+ const onTS = () => {
103
+ this.touchActive = true;
104
+ this.evaluate('press', { type: 'touch' });
105
+ this.evaluate('press', { type: 'click' });
106
+ };
107
+ const onTE = () => {
108
+ this.touchActive = false;
109
+ this.evaluate('release', { type: 'touch' });
110
+ this.evaluate('release', { type: 'click' });
111
+ };
112
+ const r = this.root;
113
+ r.addEventListener('keydown', onKD);
114
+ r.addEventListener('keyup', onKU);
115
+ r.addEventListener('mousedown', onMD);
116
+ r.addEventListener('mouseup', onMU);
117
+ r.addEventListener('touchstart', onTS);
118
+ r.addEventListener('touchend', onTE);
119
+ this.domListeners = [
120
+ () => r.removeEventListener('keydown', onKD),
121
+ () => r.removeEventListener('keyup', onKU),
122
+ () => r.removeEventListener('mousedown', onMD),
123
+ () => r.removeEventListener('mouseup', onMU),
124
+ () => r.removeEventListener('touchstart', onTS),
125
+ () => r.removeEventListener('touchend', onTE),
126
+ ];
127
+ }
128
+ evaluate(phase, src) {
129
+ var _a, _b;
130
+ const bus = getSignalBus();
131
+ for (const [name, state] of this.states) {
132
+ if (!state.binding.sources.some((s) => this.sourceMatches(s, src)))
133
+ continue;
134
+ if (phase === 'press' && !state.pressed) {
135
+ state.pressed = true;
136
+ const sig = (_a = state.binding.pressSignal) !== null && _a !== void 0 ? _a : `input:${name}:press`;
137
+ bus.emit(sig, { action: name });
138
+ }
139
+ else if (phase === 'release' && state.pressed) {
140
+ // 检查是否所有源都已 release
141
+ if (this.anySourceActive(state.binding))
142
+ continue;
143
+ state.pressed = false;
144
+ const sig = (_b = state.binding.releaseSignal) !== null && _b !== void 0 ? _b : `input:${name}:release`;
145
+ bus.emit(sig, { action: name });
146
+ }
147
+ }
148
+ }
149
+ sourceMatches(s, e) {
150
+ if (s.type !== e.type)
151
+ return false;
152
+ if (s.type === 'key')
153
+ return s.code === e.code;
154
+ if (s.type === 'mouse')
155
+ return s.button == null || s.button === e.button;
156
+ return true;
157
+ }
158
+ anySourceActive(b) {
159
+ for (const s of b.sources) {
160
+ if (s.type === 'key' && this.pressedKeys.has(s.code))
161
+ return true;
162
+ if (s.type === 'mouse' && (s.button == null || this.pressedButtons.has(s.button)))
163
+ return true;
164
+ if (s.type === 'touch' && this.touchActive)
165
+ return true;
166
+ if (s.type === 'click' && (this.touchActive || this.pressedButtons.size > 0))
167
+ return true;
168
+ }
169
+ return false;
170
+ }
171
+ /** 每帧 emit hold 信号,允许移动型 action 用 update 读 */
172
+ update() {
173
+ var _a;
174
+ const bus = getSignalBus();
175
+ for (const [name, state] of this.states) {
176
+ if (!state.pressed)
177
+ continue;
178
+ const sig = (_a = state.binding.holdSignal) !== null && _a !== void 0 ? _a : `input:${name}:hold`;
179
+ bus.emit(sig, { action: name });
180
+ }
181
+ }
182
+ /** 查询某 action 当前是否按下(给非信号场景用) */
183
+ isPressed(action) {
184
+ var _a;
185
+ return !!((_a = this.states.get(action)) === null || _a === void 0 ? void 0 : _a.pressed);
186
+ }
187
+ onDestroy() {
188
+ for (const off of this.domListeners)
189
+ off();
190
+ this.domListeners = [];
191
+ this.states.clear();
192
+ this.pressedKeys.clear();
193
+ this.pressedButtons.clear();
194
+ }
195
+ };
196
+ InputActionMap.componentName = 'InputActionMap';
197
+ InputActionMap = __decorate([
198
+ decorators.componentObserver({})
199
+ ], InputActionMap);
200
+
201
+ class InputActionSystem extends System {
202
+ constructor() {
203
+ super(...arguments);
204
+ this.name = 'InputAction';
205
+ }
206
+ }
207
+ InputActionSystem.systemName = 'InputAction';
208
+
209
+ export { InputActionMap, InputActionSystem };
package/index.js ADDED
@@ -0,0 +1,7 @@
1
+ 'use strict';
2
+
3
+ if (process.env.NODE_ENV === 'production') {
4
+ module.exports = require('./dist/plugin-input-action.cjs.prod.js');
5
+ } else {
6
+ module.exports = require('./dist/plugin-input-action.cjs.js');
7
+ }
package/package.json CHANGED
@@ -1,12 +1,13 @@
1
1
  {
2
2
  "name": "@eva/plugin-input-action",
3
- "version": "2.1.0-beta.2",
3
+ "version": "2.1.0-beta.4",
4
4
  "description": "Action map — DSL 把 keyboard/touch/click 映射为语义化 action,emit 到 SignalBus,游戏逻辑只关心 action。",
5
- "main": "lib/index.ts",
6
- "module": "lib/index.ts",
7
- "types": "lib/index.ts",
5
+ "main": "index.js",
6
+ "module": "dist/plugin-input-action.esm.js",
7
+ "types": "dist/plugin-input-action.d.ts",
8
8
  "files": [
9
- "lib"
9
+ "index.js",
10
+ "dist"
10
11
  ],
11
12
  "keywords": [
12
13
  "eva.js",
@@ -16,7 +17,7 @@
16
17
  ],
17
18
  "license": "MIT",
18
19
  "dependencies": {
19
- "@eva/eva.js": "2.1.0-beta.2",
20
- "@eva/plugin-signal-bus": "2.1.0-beta.2"
20
+ "@eva/eva.js": "2.1.0-beta.4",
21
+ "@eva/plugin-signal-bus": "2.1.0-beta.4"
21
22
  }
22
23
  }
@@ -1,163 +0,0 @@
1
- import { Component, decorators } from '@eva/eva.js';
2
- import { getSignalBus } from '@eva/plugin-signal-bus';
3
- import type { InputActionMapParams, ActionBinding } from './types';
4
-
5
- interface ActionState {
6
- binding: ActionBinding;
7
- pressed: boolean;
8
- }
9
-
10
- /**
11
- * InputActionMap 组件 — 把原始 input 抽象成语义化 action 并 emit 信号。
12
- *
13
- * DSL 用法:
14
- * ```json
15
- * {
16
- * "type": "InputActionMap",
17
- * "props": {
18
- * "bindings": [
19
- * { "action": "fire", "sources": [{ "type": "click" }, { "type": "key", "code": "Space" }] },
20
- * { "action": "left", "sources": [{ "type": "key", "code": "ArrowLeft" }] }
21
- * ]
22
- * }
23
- * }
24
- * ```
25
- *
26
- * 信号:
27
- * - 默认 emit `input:fire:press` / `input:fire:release` / `input:fire:hold`(每帧)
28
- * - 可在 binding 显式指定 pressSignal / releaseSignal / holdSignal 覆盖
29
- *
30
- * 设计原则:Component 不直接修改游戏状态,只 emit 语义化信号。
31
- */
32
- @decorators.componentObserver({})
33
- export class InputActionMap extends Component<InputActionMapParams> {
34
- static componentName = 'InputActionMap';
35
-
36
- private bindings: ActionBinding[] = [];
37
- private states = new Map<string, ActionState>();
38
- private root?: HTMLElement | Document;
39
- // 注意:不能命名 `listeners`,会与 EventEmitter 基类的 listeners(event) 方法签名冲突
40
- private domListeners: Array<() => void> = [];
41
- private pressedKeys = new Set<string>();
42
- private pressedButtons = new Set<number>();
43
- private touchActive = false;
44
-
45
- init(params?: InputActionMapParams) {
46
- if (!params) return;
47
- this.bindings = params.bindings ?? [];
48
- for (const b of this.bindings) {
49
- this.states.set(b.action, { binding: b, pressed: false });
50
- }
51
- if (typeof document === 'undefined') return;
52
- this.root = params.rootSelector
53
- ? (document.querySelector(params.rootSelector) as HTMLElement) || document
54
- : document;
55
- this.bind();
56
- }
57
-
58
- private bind() {
59
- if (!this.root) return;
60
- const onKD = (e: KeyboardEvent) => {
61
- if (e.repeat) return;
62
- this.pressedKeys.add(e.code);
63
- this.evaluate('press', { type: 'key', code: e.code });
64
- };
65
- const onKU = (e: KeyboardEvent) => {
66
- this.pressedKeys.delete(e.code);
67
- this.evaluate('release', { type: 'key', code: e.code });
68
- };
69
- const onMD = (e: MouseEvent) => {
70
- this.pressedButtons.add(e.button);
71
- this.evaluate('press', { type: 'mouse', button: e.button });
72
- this.evaluate('press', { type: 'click' });
73
- };
74
- const onMU = (e: MouseEvent) => {
75
- this.pressedButtons.delete(e.button);
76
- this.evaluate('release', { type: 'mouse', button: e.button });
77
- this.evaluate('release', { type: 'click' });
78
- };
79
- const onTS = () => {
80
- this.touchActive = true;
81
- this.evaluate('press', { type: 'touch' });
82
- this.evaluate('press', { type: 'click' });
83
- };
84
- const onTE = () => {
85
- this.touchActive = false;
86
- this.evaluate('release', { type: 'touch' });
87
- this.evaluate('release', { type: 'click' });
88
- };
89
- const r = this.root as any;
90
- r.addEventListener('keydown', onKD);
91
- r.addEventListener('keyup', onKU);
92
- r.addEventListener('mousedown', onMD);
93
- r.addEventListener('mouseup', onMU);
94
- r.addEventListener('touchstart', onTS);
95
- r.addEventListener('touchend', onTE);
96
- this.domListeners = [
97
- () => r.removeEventListener('keydown', onKD),
98
- () => r.removeEventListener('keyup', onKU),
99
- () => r.removeEventListener('mousedown', onMD),
100
- () => r.removeEventListener('mouseup', onMU),
101
- () => r.removeEventListener('touchstart', onTS),
102
- () => r.removeEventListener('touchend', onTE),
103
- ];
104
- }
105
-
106
- private evaluate(phase: 'press' | 'release', src: any) {
107
- const bus = getSignalBus();
108
- for (const [name, state] of this.states) {
109
- if (!state.binding.sources.some((s) => this.sourceMatches(s, src))) continue;
110
- if (phase === 'press' && !state.pressed) {
111
- state.pressed = true;
112
- const sig = state.binding.pressSignal ?? `input:${name}:press`;
113
- bus.emit(sig, { action: name });
114
- } else if (phase === 'release' && state.pressed) {
115
- // 检查是否所有源都已 release
116
- if (this.anySourceActive(state.binding)) continue;
117
- state.pressed = false;
118
- const sig = state.binding.releaseSignal ?? `input:${name}:release`;
119
- bus.emit(sig, { action: name });
120
- }
121
- }
122
- }
123
-
124
- private sourceMatches(s: any, e: any): boolean {
125
- if (s.type !== e.type) return false;
126
- if (s.type === 'key') return s.code === e.code;
127
- if (s.type === 'mouse') return s.button == null || s.button === e.button;
128
- return true;
129
- }
130
-
131
- private anySourceActive(b: ActionBinding): boolean {
132
- for (const s of b.sources) {
133
- if (s.type === 'key' && this.pressedKeys.has(s.code)) return true;
134
- if (s.type === 'mouse' && (s.button == null || this.pressedButtons.has(s.button))) return true;
135
- if (s.type === 'touch' && this.touchActive) return true;
136
- if (s.type === 'click' && (this.touchActive || this.pressedButtons.size > 0)) return true;
137
- }
138
- return false;
139
- }
140
-
141
- /** 每帧 emit hold 信号,允许移动型 action 用 update 读 */
142
- update() {
143
- const bus = getSignalBus();
144
- for (const [name, state] of this.states) {
145
- if (!state.pressed) continue;
146
- const sig = state.binding.holdSignal ?? `input:${name}:hold`;
147
- bus.emit(sig, { action: name });
148
- }
149
- }
150
-
151
- /** 查询某 action 当前是否按下(给非信号场景用) */
152
- isPressed(action: string): boolean {
153
- return !!this.states.get(action)?.pressed;
154
- }
155
-
156
- onDestroy() {
157
- for (const off of this.domListeners) off();
158
- this.domListeners = [];
159
- this.states.clear();
160
- this.pressedKeys.clear();
161
- this.pressedButtons.clear();
162
- }
163
- }
@@ -1,6 +0,0 @@
1
- import { System } from '@eva/eva.js';
2
-
3
- export class InputActionSystem extends System {
4
- static systemName = 'InputAction';
5
- readonly name = 'InputAction';
6
- }
package/lib/index.ts DELETED
@@ -1,3 +0,0 @@
1
- export { InputActionMap } from './InputActionMap';
2
- export { InputActionSystem } from './InputActionSystem';
3
- export type { InputActionMapParams, ActionBinding, InputSource } from './types';
package/lib/types.ts DELETED
@@ -1,24 +0,0 @@
1
- /** 输入源 */
2
- export type InputSource =
3
- | { type: 'key'; code: string } // 例如 "Space" / "ArrowLeft"
4
- | { type: 'mouse'; button?: number } // 0=left
5
- | { type: 'touch' } // 任意触屏按下
6
- | { type: 'click' }; // 任意点击(touch+mouse 合并)
7
-
8
- /** action 配置 */
9
- export interface ActionBinding {
10
- /** action 名称,emit 信号时使用 (例如 "fire" / "jump") */
11
- action: string;
12
- /** 至少一个输入源触发 */
13
- sources: InputSource[];
14
- /** 触发哪些信号 (默认 emit `input:{action}`) */
15
- pressSignal?: string;
16
- releaseSignal?: string;
17
- holdSignal?: string;
18
- }
19
-
20
- export interface InputActionMapParams {
21
- bindings?: ActionBinding[];
22
- /** 监听的根元素;不传走 document */
23
- rootSelector?: string;
24
- }