steamsheep-ts-game-engine 1.0.0
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/README.md +1087 -0
- package/dist/core/index.d.mts +328 -0
- package/dist/core/index.d.ts +328 -0
- package/dist/core/index.js +220 -0
- package/dist/core/index.js.map +1 -0
- package/dist/core/index.mjs +195 -0
- package/dist/core/index.mjs.map +1 -0
- package/dist/index.d.mts +10 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.js +1196 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +1160 -0
- package/dist/index.mjs.map +1 -0
- package/dist/state/index.d.mts +110 -0
- package/dist/state/index.d.ts +110 -0
- package/dist/state/index.js +479 -0
- package/dist/state/index.js.map +1 -0
- package/dist/state/index.mjs +476 -0
- package/dist/state/index.mjs.map +1 -0
- package/dist/store-BP5bpjRr.d.ts +327 -0
- package/dist/store-BeEHel1o.d.mts +327 -0
- package/dist/systems/index.d.mts +318 -0
- package/dist/systems/index.d.ts +318 -0
- package/dist/systems/index.js +347 -0
- package/dist/systems/index.js.map +1 -0
- package/dist/systems/index.mjs +341 -0
- package/dist/systems/index.mjs.map +1 -0
- package/dist/types-CZueoTHl.d.mts +464 -0
- package/dist/types-CZueoTHl.d.ts +464 -0
- package/dist/ui/index.d.mts +56 -0
- package/dist/ui/index.d.ts +56 -0
- package/dist/ui/index.js +412 -0
- package/dist/ui/index.js.map +1 -0
- package/dist/ui/index.mjs +407 -0
- package/dist/ui/index.mjs.map +1 -0
- package/package.json +76 -0
|
@@ -0,0 +1,407 @@
|
|
|
1
|
+
import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
|
|
2
|
+
import { useRef, useEffect, useState } from 'react';
|
|
3
|
+
|
|
4
|
+
var __defProp = Object.defineProperty;
|
|
5
|
+
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
6
|
+
var __publicField = (obj, key, value) => __defNormalProp(obj, key + "" , value);
|
|
7
|
+
var Layout = ({
|
|
8
|
+
sidebar,
|
|
9
|
+
main,
|
|
10
|
+
logs,
|
|
11
|
+
className = ""
|
|
12
|
+
}) => {
|
|
13
|
+
return /* @__PURE__ */ jsxs(
|
|
14
|
+
"div",
|
|
15
|
+
{
|
|
16
|
+
className: `min-h-screen bg-[#0c0c0e] text-[#a1a1aa] font-sans flex overflow-hidden ${className}`,
|
|
17
|
+
children: [
|
|
18
|
+
sidebar && /* @__PURE__ */ jsx("aside", { className: "w-72 bg-[#141417] border-r border-[#27272a] flex flex-col shrink-0 z-10", children: sidebar }),
|
|
19
|
+
/* @__PURE__ */ jsx("main", { className: "flex-1 flex flex-col min-w-0 relative", children: main }),
|
|
20
|
+
logs && /* @__PURE__ */ jsx("aside", { className: "w-80 bg-[#0c0c0e] border-l border-[#27272a] flex flex-col shrink-0 z-10", children: logs })
|
|
21
|
+
]
|
|
22
|
+
}
|
|
23
|
+
);
|
|
24
|
+
};
|
|
25
|
+
var MainContent = ({ header, children }) => /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
26
|
+
header && /* @__PURE__ */ jsx("div", { className: "p-10 border-b border-[#27272a] bg-[#0c0c0e] shrink-0", children: /* @__PURE__ */ jsx("div", { className: "max-w-3xl mx-auto", children: header }) }),
|
|
27
|
+
/* @__PURE__ */ jsx("div", { className: "flex-1 overflow-y-auto p-10 bg-[#101012]", children: /* @__PURE__ */ jsx("div", { className: "max-w-3xl mx-auto", children }) })
|
|
28
|
+
] });
|
|
29
|
+
var LogStream = ({
|
|
30
|
+
logs,
|
|
31
|
+
className = "",
|
|
32
|
+
title = "Chronicle"
|
|
33
|
+
}) => {
|
|
34
|
+
const bottomRef = useRef(null);
|
|
35
|
+
useEffect(() => {
|
|
36
|
+
bottomRef.current?.scrollIntoView({ behavior: "smooth" });
|
|
37
|
+
}, [logs]);
|
|
38
|
+
const getLogStyle = (type) => {
|
|
39
|
+
switch (type) {
|
|
40
|
+
case "warn":
|
|
41
|
+
return "text-amber-500 border-l-2 border-amber-500/50 pl-2";
|
|
42
|
+
case "error":
|
|
43
|
+
return "text-red-500 border-l-2 border-red-500/50 pl-2 font-bold";
|
|
44
|
+
case "success":
|
|
45
|
+
return "text-green-500 border-l-2 border-green-500/50 pl-2";
|
|
46
|
+
case "result":
|
|
47
|
+
return "text-stone-300 border-l-2 border-stone-600/50 pl-2";
|
|
48
|
+
// 行动结果
|
|
49
|
+
case "info":
|
|
50
|
+
default:
|
|
51
|
+
return "text-stone-500 border-l-2 border-transparent pl-2";
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
return /* @__PURE__ */ jsxs(
|
|
55
|
+
"div",
|
|
56
|
+
{
|
|
57
|
+
className: `flex flex-col bg-[#0c0c0e] border-l border-[#27272a] h-full ${className}`,
|
|
58
|
+
children: [
|
|
59
|
+
/* @__PURE__ */ jsx("div", { className: "p-4 border-b border-[#27272a] bg-[#141417] shrink-0", children: /* @__PURE__ */ jsx("h3", { className: "text-xs font-bold text-stone-500 uppercase tracking-wider", children: title }) }),
|
|
60
|
+
/* @__PURE__ */ jsxs("div", { className: "flex-1 overflow-y-auto p-6 space-y-4 font-serif scroll-smooth", children: [
|
|
61
|
+
logs.length === 0 && /* @__PURE__ */ jsx("div", { className: "text-stone-700 text-sm italic text-center mt-10", children: "\u6682\u65E0\u8BB0\u5F55..." }),
|
|
62
|
+
logs.map((log) => /* @__PURE__ */ jsx(
|
|
63
|
+
"div",
|
|
64
|
+
{
|
|
65
|
+
className: `text-sm leading-relaxed transition-all duration-500 animate-in fade-in slide-in-from-bottom-2 ${getLogStyle(
|
|
66
|
+
log.type
|
|
67
|
+
)}`,
|
|
68
|
+
children: log.text
|
|
69
|
+
},
|
|
70
|
+
log.id
|
|
71
|
+
)),
|
|
72
|
+
/* @__PURE__ */ jsx("div", { ref: bottomRef })
|
|
73
|
+
] })
|
|
74
|
+
]
|
|
75
|
+
}
|
|
76
|
+
);
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
// systems/events.ts
|
|
80
|
+
var EventBus = class {
|
|
81
|
+
constructor() {
|
|
82
|
+
/**
|
|
83
|
+
* 事件监听器的内部存储
|
|
84
|
+
* 将事件名映射到监听器回调函数数组
|
|
85
|
+
* 使用 Listener[](默认为 Listener<unknown>[])以允许同一事件名有不同类型的监听器
|
|
86
|
+
* 类型安全在订阅/发射时强制执行
|
|
87
|
+
*/
|
|
88
|
+
__publicField(this, "listeners", /* @__PURE__ */ new Map());
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* 订阅事件
|
|
92
|
+
*
|
|
93
|
+
* @template T - 事件载荷的预期类型
|
|
94
|
+
* @param event - 要订阅的事件名称
|
|
95
|
+
* @param callback - 事件触发时调用的监听器函数
|
|
96
|
+
* @returns 取消订阅的函数
|
|
97
|
+
*
|
|
98
|
+
* @example
|
|
99
|
+
* ```typescript
|
|
100
|
+
* const handler = (data: { count: number }) => console.log(data.count);
|
|
101
|
+
* const unsubscribe = eventBus.on('update', handler);
|
|
102
|
+
* // 稍后取消订阅
|
|
103
|
+
* unsubscribe();
|
|
104
|
+
* ```
|
|
105
|
+
*/
|
|
106
|
+
on(event, callback) {
|
|
107
|
+
if (!this.listeners.has(event)) {
|
|
108
|
+
this.listeners.set(event, []);
|
|
109
|
+
}
|
|
110
|
+
this.listeners.get(event).push(callback);
|
|
111
|
+
return () => this.off(event, callback);
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* 取消订阅事件
|
|
115
|
+
*
|
|
116
|
+
* @template T - 事件载荷的预期类型
|
|
117
|
+
* @param event - 要取消订阅的事件名称
|
|
118
|
+
* @param callback - 要移除的监听器函数
|
|
119
|
+
*
|
|
120
|
+
* @example
|
|
121
|
+
* ```typescript
|
|
122
|
+
* const handler = (data: number) => console.log(data);
|
|
123
|
+
* eventBus.on('count', handler);
|
|
124
|
+
* eventBus.off('count', handler); // 移除监听器
|
|
125
|
+
* ```
|
|
126
|
+
*/
|
|
127
|
+
off(event, callback) {
|
|
128
|
+
const callbacks = this.listeners.get(event);
|
|
129
|
+
if (callbacks) {
|
|
130
|
+
this.listeners.set(
|
|
131
|
+
event,
|
|
132
|
+
callbacks.filter((cb) => cb !== callback)
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* 发射事件并携带可选的类型化载荷数据
|
|
138
|
+
* 该事件的所有已注册监听器都将被调用并传入数据
|
|
139
|
+
* 单个监听器中的错误会被捕获并记录,不会影响其他监听器
|
|
140
|
+
*
|
|
141
|
+
* @template T - 事件载荷的类型
|
|
142
|
+
* @param event - 要发射的事件名称
|
|
143
|
+
* @param data - 传递给监听器的可选载荷数据
|
|
144
|
+
*
|
|
145
|
+
* @example
|
|
146
|
+
* ```typescript
|
|
147
|
+
* eventBus.emit<{ userId: string }>('login', { userId: '123' });
|
|
148
|
+
* eventBus.emit('logout'); // 无载荷
|
|
149
|
+
* ```
|
|
150
|
+
*/
|
|
151
|
+
emit(event, data) {
|
|
152
|
+
const callbacks = this.listeners.get(event);
|
|
153
|
+
if (callbacks) {
|
|
154
|
+
callbacks.forEach((cb) => {
|
|
155
|
+
try {
|
|
156
|
+
cb(data);
|
|
157
|
+
} catch (error) {
|
|
158
|
+
console.error(`Error in event listener for "${event}":`, error);
|
|
159
|
+
}
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* 清空所有事件的所有监听器
|
|
165
|
+
* 用于清理或重置事件系统
|
|
166
|
+
*
|
|
167
|
+
* @example
|
|
168
|
+
* ```typescript
|
|
169
|
+
* eventBus.clear(); // 所有监听器被移除
|
|
170
|
+
* ```
|
|
171
|
+
*/
|
|
172
|
+
clear() {
|
|
173
|
+
this.listeners.clear();
|
|
174
|
+
}
|
|
175
|
+
};
|
|
176
|
+
var gameEvents = new EventBus();
|
|
177
|
+
var EngineEvents = {
|
|
178
|
+
/** 当需要显示瞬时通知时触发(Toast/Modal,不持久化) */
|
|
179
|
+
NOTIFICATION: "engine:notification"};
|
|
180
|
+
|
|
181
|
+
// core/constants.ts
|
|
182
|
+
var DEFAULT_CONFIG = {
|
|
183
|
+
/** 飘字显示时长(毫秒) */
|
|
184
|
+
TOAST_DURATION: 3e3
|
|
185
|
+
};
|
|
186
|
+
function OverlaySystem() {
|
|
187
|
+
const [toasts, setToasts] = useState([]);
|
|
188
|
+
const [modal, setModal] = useState(null);
|
|
189
|
+
useEffect(() => {
|
|
190
|
+
const unsubscribe = gameEvents.on(
|
|
191
|
+
EngineEvents.NOTIFICATION,
|
|
192
|
+
(payload) => {
|
|
193
|
+
switch (payload.notificationType) {
|
|
194
|
+
case "toast": {
|
|
195
|
+
const toastId = `toast-${payload.timestamp}-${Math.random()}`;
|
|
196
|
+
const toast = { ...payload, id: toastId };
|
|
197
|
+
setToasts((prev) => [...prev, toast]);
|
|
198
|
+
setTimeout(() => {
|
|
199
|
+
setToasts((prev) => prev.filter((t) => t.id !== toastId));
|
|
200
|
+
}, DEFAULT_CONFIG.TOAST_DURATION);
|
|
201
|
+
break;
|
|
202
|
+
}
|
|
203
|
+
case "modal": {
|
|
204
|
+
setModal(payload);
|
|
205
|
+
break;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
);
|
|
210
|
+
return unsubscribe;
|
|
211
|
+
}, []);
|
|
212
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
213
|
+
/* @__PURE__ */ jsx("div", { className: "toast-container", children: toasts.map((toast) => /* @__PURE__ */ jsx(
|
|
214
|
+
"div",
|
|
215
|
+
{
|
|
216
|
+
className: `toast toast-${toast.type}`,
|
|
217
|
+
role: "alert",
|
|
218
|
+
children: toast.text
|
|
219
|
+
},
|
|
220
|
+
toast.id
|
|
221
|
+
)) }),
|
|
222
|
+
modal && /* @__PURE__ */ jsx(
|
|
223
|
+
"div",
|
|
224
|
+
{
|
|
225
|
+
className: "modal-overlay",
|
|
226
|
+
onClick: () => setModal(null),
|
|
227
|
+
role: "dialog",
|
|
228
|
+
"aria-modal": "true",
|
|
229
|
+
children: /* @__PURE__ */ jsxs(
|
|
230
|
+
"div",
|
|
231
|
+
{
|
|
232
|
+
className: "modal-content",
|
|
233
|
+
onClick: (e) => e.stopPropagation(),
|
|
234
|
+
children: [
|
|
235
|
+
/* @__PURE__ */ jsx("div", { className: `modal-message modal-${modal.type}`, children: modal.text }),
|
|
236
|
+
/* @__PURE__ */ jsx(
|
|
237
|
+
"button",
|
|
238
|
+
{
|
|
239
|
+
className: "modal-button",
|
|
240
|
+
onClick: () => setModal(null),
|
|
241
|
+
autoFocus: true,
|
|
242
|
+
children: "\u786E\u5B9A"
|
|
243
|
+
}
|
|
244
|
+
)
|
|
245
|
+
]
|
|
246
|
+
}
|
|
247
|
+
)
|
|
248
|
+
}
|
|
249
|
+
),
|
|
250
|
+
/* @__PURE__ */ jsx("style", { children: `
|
|
251
|
+
/* \u98D8\u5B57\u5BB9\u5668 */
|
|
252
|
+
.toast-container {
|
|
253
|
+
position: fixed;
|
|
254
|
+
top: 20px;
|
|
255
|
+
right: 20px;
|
|
256
|
+
z-index: 1000;
|
|
257
|
+
display: flex;
|
|
258
|
+
flex-direction: column;
|
|
259
|
+
gap: 10px;
|
|
260
|
+
pointer-events: none;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/* \u98D8\u5B57\u6837\u5F0F */
|
|
264
|
+
.toast {
|
|
265
|
+
padding: 12px 20px;
|
|
266
|
+
border-radius: 8px;
|
|
267
|
+
background: white;
|
|
268
|
+
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
|
269
|
+
animation: slideIn 0.3s ease-out, fadeOut 0.3s ease-in 2.7s;
|
|
270
|
+
pointer-events: auto;
|
|
271
|
+
max-width: 300px;
|
|
272
|
+
word-wrap: break-word;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
.toast-info {
|
|
276
|
+
border-left: 4px solid #3b82f6;
|
|
277
|
+
color: #1e40af;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
.toast-success {
|
|
281
|
+
border-left: 4px solid #10b981;
|
|
282
|
+
color: #065f46;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
.toast-warn {
|
|
286
|
+
border-left: 4px solid #f59e0b;
|
|
287
|
+
color: #92400e;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
.toast-error {
|
|
291
|
+
border-left: 4px solid #ef4444;
|
|
292
|
+
color: #991b1b;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
.toast-result {
|
|
296
|
+
border-left: 4px solid #6366f1;
|
|
297
|
+
color: #3730a3;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
@keyframes slideIn {
|
|
301
|
+
from {
|
|
302
|
+
transform: translateX(100%);
|
|
303
|
+
opacity: 0;
|
|
304
|
+
}
|
|
305
|
+
to {
|
|
306
|
+
transform: translateX(0);
|
|
307
|
+
opacity: 1;
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
@keyframes fadeOut {
|
|
312
|
+
from {
|
|
313
|
+
opacity: 1;
|
|
314
|
+
}
|
|
315
|
+
to {
|
|
316
|
+
opacity: 0;
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/* \u6A21\u6001\u5F39\u7A97 */
|
|
321
|
+
.modal-overlay {
|
|
322
|
+
position: fixed;
|
|
323
|
+
inset: 0;
|
|
324
|
+
background: rgba(0, 0, 0, 0.5);
|
|
325
|
+
display: flex;
|
|
326
|
+
align-items: center;
|
|
327
|
+
justify-content: center;
|
|
328
|
+
z-index: 2000;
|
|
329
|
+
animation: fadeIn 0.2s ease-out;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
.modal-content {
|
|
333
|
+
background: white;
|
|
334
|
+
padding: 24px;
|
|
335
|
+
border-radius: 12px;
|
|
336
|
+
max-width: 400px;
|
|
337
|
+
min-width: 300px;
|
|
338
|
+
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1);
|
|
339
|
+
animation: scaleIn 0.2s ease-out;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
.modal-message {
|
|
343
|
+
margin-bottom: 20px;
|
|
344
|
+
font-size: 16px;
|
|
345
|
+
line-height: 1.5;
|
|
346
|
+
color: #1f2937;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
.modal-success {
|
|
350
|
+
color: #10b981;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
.modal-warn {
|
|
354
|
+
color: #f59e0b;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
.modal-error {
|
|
358
|
+
color: #ef4444;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
.modal-button {
|
|
362
|
+
width: 100%;
|
|
363
|
+
padding: 10px 20px;
|
|
364
|
+
background: #3b82f6;
|
|
365
|
+
color: white;
|
|
366
|
+
border: none;
|
|
367
|
+
border-radius: 6px;
|
|
368
|
+
font-size: 14px;
|
|
369
|
+
font-weight: 500;
|
|
370
|
+
cursor: pointer;
|
|
371
|
+
transition: background 0.2s;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
.modal-button:hover {
|
|
375
|
+
background: #2563eb;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
.modal-button:active {
|
|
379
|
+
background: #1d4ed8;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
@keyframes fadeIn {
|
|
383
|
+
from {
|
|
384
|
+
opacity: 0;
|
|
385
|
+
}
|
|
386
|
+
to {
|
|
387
|
+
opacity: 1;
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
@keyframes scaleIn {
|
|
392
|
+
from {
|
|
393
|
+
transform: scale(0.9);
|
|
394
|
+
opacity: 0;
|
|
395
|
+
}
|
|
396
|
+
to {
|
|
397
|
+
transform: scale(1);
|
|
398
|
+
opacity: 1;
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
` })
|
|
402
|
+
] });
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
export { Layout, LogStream, MainContent, OverlaySystem };
|
|
406
|
+
//# sourceMappingURL=index.mjs.map
|
|
407
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../ui/Layout.tsx","../../ui/LogStream.tsx","../../systems/events.ts","../../core/constants.ts","../../ui/OverlaySystem.tsx"],"names":["jsxs","jsx","useEffect","Fragment"],"mappings":"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","file":"index.mjs","sourcesContent":["import React from \"react\";\n\ninterface LayoutProps {\n sidebar?: React.ReactNode; // 左侧:状态、背包\n main?: React.ReactNode; // 中间:地点图、动作列表\n logs?: React.ReactNode; // 右侧:日志流\n className?: string;\n}\n\nexport const Layout: React.FC<LayoutProps> = ({\n sidebar,\n main,\n logs,\n className = \"\",\n}) => {\n return (\n <div\n className={`min-h-screen bg-[#0c0c0e] text-[#a1a1aa] font-sans flex overflow-hidden ${className}`}\n >\n {/* 1. 左侧侧边栏 (固定宽度) */}\n {sidebar && (\n <aside className=\"w-72 bg-[#141417] border-r border-[#27272a] flex flex-col shrink-0 z-10\">\n {sidebar}\n </aside>\n )}\n\n {/* 2. 中间主舞台 (自适应宽度) */}\n <main className=\"flex-1 flex flex-col min-w-0 relative\">{main}</main>\n\n {/* 3. 右侧日志栏 (固定宽度) */}\n {logs && (\n <aside className=\"w-80 bg-[#0c0c0e] border-l border-[#27272a] flex flex-col shrink-0 z-10\">\n {logs}\n </aside>\n )}\n </div>\n );\n};\n\n// 同时也导出一个用于 Main 区域内部的容器组件,方便统一内边距\nexport const MainContent: React.FC<{\n header?: React.ReactNode;\n children: React.ReactNode;\n}> = ({ header, children }) => (\n <>\n {/* 顶部标题区域 */}\n {header && (\n <div className=\"p-10 border-b border-[#27272a] bg-[#0c0c0e] shrink-0\">\n <div className=\"max-w-3xl mx-auto\">{header}</div>\n </div>\n )}\n\n {/* 内容滚动区域 */}\n <div className=\"flex-1 overflow-y-auto p-10 bg-[#101012]\">\n <div className=\"max-w-3xl mx-auto\">{children}</div>\n </div>\n </>\n);\n","import React, { useEffect, useRef } from \"react\";\nimport type { LogEntry } from \"../core/types\";\n\ninterface LogStreamProps {\n logs: LogEntry[];\n className?: string;\n title?: string;\n}\n\nexport const LogStream: React.FC<LogStreamProps> = ({\n logs,\n className = \"\",\n title = \"Chronicle\",\n}) => {\n const bottomRef = useRef<HTMLDivElement>(null);\n\n // 自动滚动到底部\n useEffect(() => {\n bottomRef.current?.scrollIntoView({ behavior: \"smooth\" });\n }, [logs]);\n\n // 根据类型返回样式\n const getLogStyle = (type: LogEntry[\"type\"]) => {\n switch (type) {\n case \"warn\":\n return \"text-amber-500 border-l-2 border-amber-500/50 pl-2\";\n case \"error\":\n return \"text-red-500 border-l-2 border-red-500/50 pl-2 font-bold\";\n case \"success\":\n return \"text-green-500 border-l-2 border-green-500/50 pl-2\";\n case \"result\":\n return \"text-stone-300 border-l-2 border-stone-600/50 pl-2\"; // 行动结果\n case \"info\":\n default:\n return \"text-stone-500 border-l-2 border-transparent pl-2\"; // 普通叙事\n }\n };\n\n return (\n <div\n className={`flex flex-col bg-[#0c0c0e] border-l border-[#27272a] h-full ${className}`}\n >\n {/* 标题栏 */}\n <div className=\"p-4 border-b border-[#27272a] bg-[#141417] shrink-0\">\n <h3 className=\"text-xs font-bold text-stone-500 uppercase tracking-wider\">\n {title}\n </h3>\n </div>\n\n {/* 滚动区域 */}\n <div className=\"flex-1 overflow-y-auto p-6 space-y-4 font-serif scroll-smooth\">\n {logs.length === 0 && (\n <div className=\"text-stone-700 text-sm italic text-center mt-10\">\n 暂无记录...\n </div>\n )}\n\n {logs.map((log) => (\n <div\n key={log.id}\n className={`text-sm leading-relaxed transition-all duration-500 animate-in fade-in slide-in-from-bottom-2 ${getLogStyle(\n log.type\n )}`}\n >\n {/* 时间戳 (可选,根据需求显示) */}\n {/* <span className=\"text-[10px] opacity-50 mr-2 font-mono\">[{log.timestamp}]</span> */}\n {log.text}\n </div>\n ))}\n\n {/* 锚点 */}\n <div ref={bottomRef} />\n </div>\n </div>\n );\n};\n","// src/engine/systems/events.ts\n\n/**\n * 类型安全的事件监听器回调函数\n * 使用 `unknown` 作为默认类型以强制类型安全\n * @template T - 监听器期望接收的数据类型\n */\ntype Listener<T = unknown> = (data: T) => void;\n\n/**\n * EventBus - 类型安全的发布-订阅事件系统\n *\n * 提供了一个集中式机制,让组件通过事件进行通信而无需紧密耦合。\n * 支持泛型类型参数以实现类型安全的载荷数据。\n *\n * @example\n * ```typescript\n * // 订阅事件并保证类型安全\n * const unsubscribe = eventBus.on<{ count: number }>('update', (data) => {\n * console.log(data.count); // TypeScript 知道 data 有 count 属性\n * });\n *\n * // 发射事件\n * eventBus.emit('update', { count: 42 });\n *\n * // 取消订阅\n * unsubscribe();\n * ```\n */\nexport class EventBus {\n /**\n * 事件监听器的内部存储\n * 将事件名映射到监听器回调函数数组\n * 使用 Listener[](默认为 Listener<unknown>[])以允许同一事件名有不同类型的监听器\n * 类型安全在订阅/发射时强制执行\n */\n private listeners: Map<string, Listener[]> = new Map();\n\n /**\n * 订阅事件\n *\n * @template T - 事件载荷的预期类型\n * @param event - 要订阅的事件名称\n * @param callback - 事件触发时调用的监听器函数\n * @returns 取消订阅的函数\n *\n * @example\n * ```typescript\n * const handler = (data: { count: number }) => console.log(data.count);\n * const unsubscribe = eventBus.on('update', handler);\n * // 稍后取消订阅\n * unsubscribe();\n * ```\n */\n on<T>(event: string, callback: Listener<T>): () => void {\n if (!this.listeners.has(event)) {\n this.listeners.set(event, []);\n }\n this.listeners.get(event)!.push(callback as Listener);\n\n // 返回 unsubscribe 函数\n return () => this.off(event, callback);\n }\n\n /**\n * 取消订阅事件\n *\n * @template T - 事件载荷的预期类型\n * @param event - 要取消订阅的事件名称\n * @param callback - 要移除的监听器函数\n *\n * @example\n * ```typescript\n * const handler = (data: number) => console.log(data);\n * eventBus.on('count', handler);\n * eventBus.off('count', handler); // 移除监听器\n * ```\n */\n off<T>(event: string, callback: Listener<T>) {\n const callbacks = this.listeners.get(event);\n if (callbacks) {\n this.listeners.set(\n event,\n callbacks.filter((cb) => cb !== (callback as Listener))\n );\n }\n }\n\n /**\n * 发射事件并携带可选的类型化载荷数据\n * 该事件的所有已注册监听器都将被调用并传入数据\n * 单个监听器中的错误会被捕获并记录,不会影响其他监听器\n *\n * @template T - 事件载荷的类型\n * @param event - 要发射的事件名称\n * @param data - 传递给监听器的可选载荷数据\n *\n * @example\n * ```typescript\n * eventBus.emit<{ userId: string }>('login', { userId: '123' });\n * eventBus.emit('logout'); // 无载荷\n * ```\n */\n emit<T>(event: string, data?: T) {\n const callbacks = this.listeners.get(event);\n if (callbacks) {\n callbacks.forEach((cb) => {\n try {\n cb(data);\n } catch (error) {\n console.error(`Error in event listener for \"${event}\":`, error);\n }\n });\n }\n }\n\n /**\n * 清空所有事件的所有监听器\n * 用于清理或重置事件系统\n *\n * @example\n * ```typescript\n * eventBus.clear(); // 所有监听器被移除\n * ```\n */\n clear() {\n this.listeners.clear();\n }\n}\n\n/**\n * 全局单例 EventBus 实例,用于游戏范围内的事件通信\n * 在整个应用中使用此实例以保持一致的事件处理\n *\n * @example\n * ```typescript\n * import { gameEvents, EngineEvents } from './events';\n *\n * gameEvents.on(EngineEvents.STAT_CHANGE, (data) => {\n * console.log(`属性 ${data.stat} 变化了 ${data.delta}`);\n * });\n * ```\n */\nexport const gameEvents = new EventBus();\n\n/**\n * 预定义的系统事件名称常量,用于常见的游戏引擎事件\n * 使用这些常量而不是字符串字面量,以保证类型安全和一致性\n *\n * 事件载荷类型:\n * - STAT_CHANGE: { stat: string, delta: number, current: number }\n * - ITEM_ADD: { item: string }\n * - ITEM_REMOVE: { item: string }\n * - FLAG_CHANGE: { flag: string, value: boolean }\n * - ACTION_EXECUTED: { actionId: string }\n * - TIME_PASS: { day: number, time: number }\n * - NOTIFICATION: { text: string, type: string, notificationType: string, timestamp: number }\n * - CUSTOM: { id: string } (对应 effects.triggerEvent)\n */\nexport const EngineEvents = {\n /** 当角色属性变化时触发 */\n STAT_CHANGE: \"engine:stat_change\",\n /** 当物品添加到背包时触发 */\n ITEM_ADD: \"engine:item_add\",\n /** 当物品从背包移除时触发 */\n ITEM_REMOVE: \"engine:item_remove\",\n /** 当游戏标志变化时触发 */\n FLAG_CHANGE: \"engine:flag_change\",\n /** 当动作执行时触发 */\n ACTION_EXECUTED: \"engine:action_exec\",\n /** 当游戏时间推进时触发 */\n TIME_PASS: \"engine:time_pass\",\n /** 当需要显示瞬时通知时触发(Toast/Modal,不持久化) */\n NOTIFICATION: \"engine:notification\",\n /** 用于自定义触发事件 */\n CUSTOM: \"engine:custom_trigger\",\n} as const;\n","// src/engine/core/constants.ts\n/**\n * 全局常量定义\n */\n\n/**\n * 引擎版本\n */\nexport const ENGINE_VERSION = '1.0.0';\n\n/**\n * 默认配置\n */\nexport const DEFAULT_CONFIG = {\n /** 默认存档键名 */\n DEFAULT_SAVE_KEY: 'game-save',\n /** 最大日志条数 */\n MAX_LOG_ENTRIES: 50,\n /** 最大历史快照数 */\n MAX_HISTORY_SNAPSHOTS: 20,\n /** 飘字显示时长(毫秒) */\n TOAST_DURATION: 3000,\n} as const;\n\n/**\n * 日志类型颜色映射\n */\nexport const LOG_TYPE_COLORS = {\n info: '#3b82f6',\n success: '#10b981',\n warn: '#f59e0b',\n error: '#ef4444',\n result: '#6366f1',\n} as const;\n\n/**\n * 默认属性值\n */\nexport const DEFAULT_STATS = {\n /** 默认生命值 */\n DEFAULT_HP: 100,\n /** 默认金币 */\n DEFAULT_GOLD: 0,\n} as const;\n\n/**\n * 游戏时间常量\n */\nexport const TIME_CONSTANTS = {\n /** 一天的小时数 */\n HOURS_PER_DAY: 24,\n /** 早晨开始时间 */\n MORNING_START: 6,\n /** 中午开始时间 */\n NOON_START: 12,\n /** 傍晚开始时间 */\n EVENING_START: 18,\n /** 夜晚开始时间 */\n NIGHT_START: 22,\n} as const;\n\n/**\n * UI 常量\n */\nexport const UI_CONSTANTS = {\n /** 侧边栏宽度 */\n SIDEBAR_WIDTH: 288, // 72 * 4 = 288px (w-72)\n /** 日志栏宽度 */\n LOG_WIDTH: 320, // 80 * 4 = 320px (w-80)\n /** Toast 容器 z-index */\n TOAST_Z_INDEX: 1000,\n /** Modal 容器 z-index */\n MODAL_Z_INDEX: 2000,\n} as const;\n\n/**\n * 验证常量\n */\nexport const VALIDATION = {\n /** 最小属性值 */\n MIN_STAT_VALUE: 0,\n /** 最大属性值 */\n MAX_STAT_VALUE: 9999,\n /** 最大背包容量 */\n MAX_INVENTORY_SIZE: 100,\n /** 最小动作 ID 长度 */\n MIN_ACTION_ID_LENGTH: 1,\n /** 最大动作 ID 长度 */\n MAX_ACTION_ID_LENGTH: 50,\n} as const;","// src/engine/ui/OverlaySystem.tsx\n/**\n * Overlay System - 覆盖层系统\n * \n * ⚠️ 重要架构说明:\n * \n * 这个系统处理所有瞬时的 UI 通知,与持久化的日志系统完全分离:\n * \n * 1. Logs (日志) - LogStream 组件\n * - 持久化到 localStorage\n * - 页面刷新后重新加载\n * - 永久显示在日志面板\n * - 用于回顾历史\n * \n * 2. Toasts (飘字) - 本组件处理\n * - 瞬时通知,不持久化\n * - 自动消失(3秒)\n * - 不阻塞用户操作\n * - 用于轻量级反馈\n * \n * 3. Modals (弹窗) - 本组件处理\n * - 瞬时通知,不持久化\n * - 需要用户确认\n * - 阻塞式交互\n * - 用于重要信息\n */\n\nimport { useEffect, useState } from 'react';\nimport { gameEvents, EngineEvents } from '../systems/events';\nimport type { NotificationPayload } from '../core/types';\nimport { DEFAULT_CONFIG } from '../core/constants';\n\n/**\n * Toast 通知项\n */\ninterface ToastItem extends NotificationPayload {\n id: string;\n}\n\n/**\n * Overlay System 组件\n * \n * 监听 NOTIFICATION 事件并显示对应的 UI\n */\nexport function OverlaySystem() {\n const [toasts, setToasts] = useState<ToastItem[]>([]);\n const [modal, setModal] = useState<NotificationPayload | null>(null);\n\n useEffect(() => {\n // 监听通知事件\n const unsubscribe = gameEvents.on<NotificationPayload>(\n EngineEvents.NOTIFICATION,\n (payload) => {\n switch (payload.notificationType) {\n case 'toast': {\n // 添加飘字\n const toastId = `toast-${payload.timestamp}-${Math.random()}`;\n const toast: ToastItem = { ...payload, id: toastId };\n \n setToasts((prev) => [...prev, toast]);\n \n // 自动移除(使用配置的时长)\n setTimeout(() => {\n setToasts((prev) => prev.filter((t) => t.id !== toastId));\n }, DEFAULT_CONFIG.TOAST_DURATION);\n break;\n }\n\n case 'modal': {\n // 显示弹窗\n setModal(payload);\n break;\n }\n }\n }\n );\n\n return unsubscribe;\n }, []);\n\n return (\n <>\n {/* 飘字容器 */}\n <div className=\"toast-container\">\n {toasts.map((toast) => (\n <div\n key={toast.id}\n className={`toast toast-${toast.type}`}\n role=\"alert\"\n >\n {toast.text}\n </div>\n ))}\n </div>\n\n {/* 模态弹窗 */}\n {modal && (\n <div\n className=\"modal-overlay\"\n onClick={() => setModal(null)}\n role=\"dialog\"\n aria-modal=\"true\"\n >\n <div\n className=\"modal-content\"\n onClick={(e) => e.stopPropagation()}\n >\n <div className={`modal-message modal-${modal.type}`}>\n {modal.text}\n </div>\n <button\n className=\"modal-button\"\n onClick={() => setModal(null)}\n autoFocus\n >\n 确定\n </button>\n </div>\n </div>\n )}\n\n {/* 样式 */}\n <style>{`\n /* 飘字容器 */\n .toast-container {\n position: fixed;\n top: 20px;\n right: 20px;\n z-index: 1000;\n display: flex;\n flex-direction: column;\n gap: 10px;\n pointer-events: none;\n }\n\n /* 飘字样式 */\n .toast {\n padding: 12px 20px;\n border-radius: 8px;\n background: white;\n box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);\n animation: slideIn 0.3s ease-out, fadeOut 0.3s ease-in 2.7s;\n pointer-events: auto;\n max-width: 300px;\n word-wrap: break-word;\n }\n\n .toast-info {\n border-left: 4px solid #3b82f6;\n color: #1e40af;\n }\n\n .toast-success {\n border-left: 4px solid #10b981;\n color: #065f46;\n }\n\n .toast-warn {\n border-left: 4px solid #f59e0b;\n color: #92400e;\n }\n\n .toast-error {\n border-left: 4px solid #ef4444;\n color: #991b1b;\n }\n\n .toast-result {\n border-left: 4px solid #6366f1;\n color: #3730a3;\n }\n\n @keyframes slideIn {\n from {\n transform: translateX(100%);\n opacity: 0;\n }\n to {\n transform: translateX(0);\n opacity: 1;\n }\n }\n\n @keyframes fadeOut {\n from {\n opacity: 1;\n }\n to {\n opacity: 0;\n }\n }\n\n /* 模态弹窗 */\n .modal-overlay {\n position: fixed;\n inset: 0;\n background: rgba(0, 0, 0, 0.5);\n display: flex;\n align-items: center;\n justify-content: center;\n z-index: 2000;\n animation: fadeIn 0.2s ease-out;\n }\n\n .modal-content {\n background: white;\n padding: 24px;\n border-radius: 12px;\n max-width: 400px;\n min-width: 300px;\n box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1);\n animation: scaleIn 0.2s ease-out;\n }\n\n .modal-message {\n margin-bottom: 20px;\n font-size: 16px;\n line-height: 1.5;\n color: #1f2937;\n }\n\n .modal-success {\n color: #10b981;\n }\n\n .modal-warn {\n color: #f59e0b;\n }\n\n .modal-error {\n color: #ef4444;\n }\n\n .modal-button {\n width: 100%;\n padding: 10px 20px;\n background: #3b82f6;\n color: white;\n border: none;\n border-radius: 6px;\n font-size: 14px;\n font-weight: 500;\n cursor: pointer;\n transition: background 0.2s;\n }\n\n .modal-button:hover {\n background: #2563eb;\n }\n\n .modal-button:active {\n background: #1d4ed8;\n }\n\n @keyframes fadeIn {\n from {\n opacity: 0;\n }\n to {\n opacity: 1;\n }\n }\n\n @keyframes scaleIn {\n from {\n transform: scale(0.9);\n opacity: 0;\n }\n to {\n transform: scale(1);\n opacity: 1;\n }\n }\n `}</style>\n </>\n );\n}\n"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "steamsheep-ts-game-engine",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "通用游戏引擎框架 - 基于 TypeScript 和 Zustand 构建的文字冒险/RPG 游戏引擎",
|
|
5
|
+
"main": "./dist/index.js",
|
|
6
|
+
"module": "./dist/index.mjs",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"import": "./dist/index.mjs",
|
|
11
|
+
"require": "./dist/index.js",
|
|
12
|
+
"types": "./dist/index.d.ts"
|
|
13
|
+
},
|
|
14
|
+
"./core": {
|
|
15
|
+
"import": "./dist/core/index.mjs",
|
|
16
|
+
"require": "./dist/core/index.js",
|
|
17
|
+
"types": "./dist/core/index.d.ts"
|
|
18
|
+
},
|
|
19
|
+
"./state": {
|
|
20
|
+
"import": "./dist/state/index.mjs",
|
|
21
|
+
"require": "./dist/state/index.js",
|
|
22
|
+
"types": "./dist/state/index.d.ts"
|
|
23
|
+
},
|
|
24
|
+
"./systems": {
|
|
25
|
+
"import": "./dist/systems/index.mjs",
|
|
26
|
+
"require": "./dist/systems/index.js",
|
|
27
|
+
"types": "./dist/systems/index.d.ts"
|
|
28
|
+
},
|
|
29
|
+
"./ui": {
|
|
30
|
+
"import": "./dist/ui/index.mjs",
|
|
31
|
+
"require": "./dist/ui/index.js",
|
|
32
|
+
"types": "./dist/ui/index.d.ts"
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
"files": [
|
|
36
|
+
"dist",
|
|
37
|
+
"README.md"
|
|
38
|
+
],
|
|
39
|
+
"scripts": {
|
|
40
|
+
"build": "tsup",
|
|
41
|
+
"dev": "tsup --watch",
|
|
42
|
+
"prepublishOnly": "npm run build"
|
|
43
|
+
},
|
|
44
|
+
"keywords": [
|
|
45
|
+
"game-engine",
|
|
46
|
+
"rpg",
|
|
47
|
+
"text-adventure",
|
|
48
|
+
"zustand",
|
|
49
|
+
"typescript",
|
|
50
|
+
"game-framework",
|
|
51
|
+
"state-management",
|
|
52
|
+
"event-system",
|
|
53
|
+
"steamsheep"
|
|
54
|
+
],
|
|
55
|
+
"author": "Corey.H",
|
|
56
|
+
"license": "MIT",
|
|
57
|
+
"peerDependencies": {
|
|
58
|
+
"react": "^18.0.0",
|
|
59
|
+
"zustand": "^4.0.0"
|
|
60
|
+
},
|
|
61
|
+
"devDependencies": {
|
|
62
|
+
"@types/react": "^18.0.0",
|
|
63
|
+
"react": "^18.0.0",
|
|
64
|
+
"tsup": "^8.0.0",
|
|
65
|
+
"typescript": "^5.0.0",
|
|
66
|
+
"zustand": "^4.0.0"
|
|
67
|
+
},
|
|
68
|
+
"repository": {
|
|
69
|
+
"type": "git",
|
|
70
|
+
"url": "https://github.com/SteamSheepGame/ts-game-engine.git"
|
|
71
|
+
},
|
|
72
|
+
"bugs": {
|
|
73
|
+
"url": "https://github.com/SteamSheepGame/ts-game-engine/issues"
|
|
74
|
+
},
|
|
75
|
+
"homepage": "https://github.com/SteamSheepGame/ts-game-engine#readme"
|
|
76
|
+
}
|