create-esengine-server 1.1.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/LICENSE +21 -0
- package/dist/index.js +511 -0
- package/package.json +38 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 ESEngine Contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,511 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
4
|
+
|
|
5
|
+
// src/index.ts
|
|
6
|
+
import { Command } from "commander";
|
|
7
|
+
import prompts from "prompts";
|
|
8
|
+
import chalk from "chalk";
|
|
9
|
+
import * as fs from "fs";
|
|
10
|
+
import * as path from "path";
|
|
11
|
+
import { execSync } from "child_process";
|
|
12
|
+
import { fileURLToPath } from "url";
|
|
13
|
+
var __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
14
|
+
var VERSION = "1.0.0";
|
|
15
|
+
function printLogo() {
|
|
16
|
+
console.log();
|
|
17
|
+
console.log(chalk.cyan(" \u256D\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256E"));
|
|
18
|
+
console.log(chalk.cyan(" \u2502 \u2502"));
|
|
19
|
+
console.log(chalk.cyan(" \u2502 ") + chalk.bold.white("Create ESEngine Server") + chalk.cyan(" \u2502"));
|
|
20
|
+
console.log(chalk.cyan(" \u2502 \u2502"));
|
|
21
|
+
console.log(chalk.cyan(" \u2570\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256F"));
|
|
22
|
+
console.log();
|
|
23
|
+
}
|
|
24
|
+
__name(printLogo, "printLogo");
|
|
25
|
+
function detectPackageManager() {
|
|
26
|
+
const userAgent = process.env.npm_config_user_agent || "";
|
|
27
|
+
if (userAgent.includes("pnpm")) return "pnpm";
|
|
28
|
+
if (userAgent.includes("yarn")) return "yarn";
|
|
29
|
+
return "npm";
|
|
30
|
+
}
|
|
31
|
+
__name(detectPackageManager, "detectPackageManager");
|
|
32
|
+
function getInstallCommand(pm) {
|
|
33
|
+
return pm === "yarn" ? "yarn" : `${pm} install`;
|
|
34
|
+
}
|
|
35
|
+
__name(getInstallCommand, "getInstallCommand");
|
|
36
|
+
function writeFile(projectPath, relativePath, content) {
|
|
37
|
+
const fullPath = path.join(projectPath, relativePath);
|
|
38
|
+
fs.mkdirSync(path.dirname(fullPath), {
|
|
39
|
+
recursive: true
|
|
40
|
+
});
|
|
41
|
+
fs.writeFileSync(fullPath, content);
|
|
42
|
+
}
|
|
43
|
+
__name(writeFile, "writeFile");
|
|
44
|
+
function generateProject(projectPath, projectName) {
|
|
45
|
+
const packageJson = {
|
|
46
|
+
name: projectName,
|
|
47
|
+
version: "1.0.0",
|
|
48
|
+
type: "module",
|
|
49
|
+
private: true,
|
|
50
|
+
scripts: {
|
|
51
|
+
dev: "tsx watch src/server/main.ts",
|
|
52
|
+
start: "tsx src/server/main.ts",
|
|
53
|
+
build: "tsc",
|
|
54
|
+
"build:start": "tsc && node dist/server/main.js"
|
|
55
|
+
},
|
|
56
|
+
dependencies: {
|
|
57
|
+
"@esengine/server": "latest",
|
|
58
|
+
"@esengine/rpc": "latest"
|
|
59
|
+
},
|
|
60
|
+
devDependencies: {
|
|
61
|
+
"@types/node": "^20.0.0",
|
|
62
|
+
tsx: "^4.0.0",
|
|
63
|
+
typescript: "^5.0.0"
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
writeFile(projectPath, "package.json", JSON.stringify(packageJson, null, 2));
|
|
67
|
+
const tsconfig = {
|
|
68
|
+
compilerOptions: {
|
|
69
|
+
target: "ES2022",
|
|
70
|
+
module: "NodeNext",
|
|
71
|
+
moduleResolution: "NodeNext",
|
|
72
|
+
lib: [
|
|
73
|
+
"ES2022"
|
|
74
|
+
],
|
|
75
|
+
outDir: "./dist",
|
|
76
|
+
rootDir: "./src",
|
|
77
|
+
strict: true,
|
|
78
|
+
esModuleInterop: true,
|
|
79
|
+
skipLibCheck: true,
|
|
80
|
+
forceConsistentCasingInFileNames: true,
|
|
81
|
+
declaration: true,
|
|
82
|
+
sourceMap: true,
|
|
83
|
+
experimentalDecorators: true,
|
|
84
|
+
emitDecoratorMetadata: true
|
|
85
|
+
},
|
|
86
|
+
include: [
|
|
87
|
+
"src/**/*"
|
|
88
|
+
],
|
|
89
|
+
exclude: [
|
|
90
|
+
"node_modules",
|
|
91
|
+
"dist"
|
|
92
|
+
]
|
|
93
|
+
};
|
|
94
|
+
writeFile(projectPath, "tsconfig.json", JSON.stringify(tsconfig, null, 2));
|
|
95
|
+
const protocolTs = `/**
|
|
96
|
+
* \u6E38\u620F\u534F\u8BAE\u5B9A\u4E49
|
|
97
|
+
* Game Protocol Definition
|
|
98
|
+
*
|
|
99
|
+
* \u8FD9\u4E2A\u6587\u4EF6\u5B9A\u4E49\u4E86\u5BA2\u6237\u7AEF\u548C\u670D\u52A1\u7AEF\u5171\u4EAB\u7684\u534F\u8BAE\u7C7B\u578B
|
|
100
|
+
* This file defines protocol types shared between client and server
|
|
101
|
+
*/
|
|
102
|
+
|
|
103
|
+
// ============================================================================
|
|
104
|
+
// \u623F\u95F4 API | Room API
|
|
105
|
+
// ============================================================================
|
|
106
|
+
|
|
107
|
+
/** \u52A0\u5165\u623F\u95F4\u8BF7\u6C42 | Join room request */
|
|
108
|
+
export interface JoinRoomReq {
|
|
109
|
+
roomType: string
|
|
110
|
+
playerName: string
|
|
111
|
+
options?: Record<string, unknown>
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/** \u52A0\u5165\u623F\u95F4\u54CD\u5E94 | Join room response */
|
|
115
|
+
export interface JoinRoomRes {
|
|
116
|
+
roomId: string
|
|
117
|
+
playerId: string
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// ============================================================================
|
|
121
|
+
// \u6E38\u620F\u6D88\u606F | Game Messages
|
|
122
|
+
// ============================================================================
|
|
123
|
+
|
|
124
|
+
/** \u79FB\u52A8\u6D88\u606F | Move message */
|
|
125
|
+
export interface MsgMove {
|
|
126
|
+
x: number
|
|
127
|
+
y: number
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/** \u804A\u5929\u6D88\u606F | Chat message */
|
|
131
|
+
export interface MsgChat {
|
|
132
|
+
text: string
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// ============================================================================
|
|
136
|
+
// \u670D\u52A1\u7AEF\u5E7F\u64AD | Server Broadcasts
|
|
137
|
+
// ============================================================================
|
|
138
|
+
|
|
139
|
+
/** \u73A9\u5BB6\u52A0\u5165\u5E7F\u64AD | Player joined broadcast */
|
|
140
|
+
export interface BroadcastJoined {
|
|
141
|
+
playerId: string
|
|
142
|
+
playerName: string
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/** \u73A9\u5BB6\u79BB\u5F00\u5E7F\u64AD | Player left broadcast */
|
|
146
|
+
export interface BroadcastLeft {
|
|
147
|
+
playerId: string
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/** \u72B6\u6001\u540C\u6B65\u5E7F\u64AD | State sync broadcast */
|
|
151
|
+
export interface BroadcastSync {
|
|
152
|
+
players: PlayerState[]
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// ============================================================================
|
|
156
|
+
// \u5171\u4EAB\u7C7B\u578B | Shared Types
|
|
157
|
+
// ============================================================================
|
|
158
|
+
|
|
159
|
+
/** \u73A9\u5BB6\u72B6\u6001 | Player state */
|
|
160
|
+
export interface PlayerState {
|
|
161
|
+
id: string
|
|
162
|
+
name: string
|
|
163
|
+
x: number
|
|
164
|
+
y: number
|
|
165
|
+
}
|
|
166
|
+
`;
|
|
167
|
+
writeFile(projectPath, "src/shared/protocol.ts", protocolTs);
|
|
168
|
+
const sharedIndexTs = `export * from './protocol.js'
|
|
169
|
+
`;
|
|
170
|
+
writeFile(projectPath, "src/shared/index.ts", sharedIndexTs);
|
|
171
|
+
const serverMainTs = `import { createServer } from '@esengine/server'
|
|
172
|
+
import { GameRoom } from './rooms/GameRoom.js'
|
|
173
|
+
|
|
174
|
+
const PORT = Number(process.env.PORT) || 3000
|
|
175
|
+
|
|
176
|
+
async function main() {
|
|
177
|
+
const server = await createServer({
|
|
178
|
+
port: PORT,
|
|
179
|
+
onConnect(conn) {
|
|
180
|
+
console.log('[Server] Client connected:', conn.id)
|
|
181
|
+
},
|
|
182
|
+
onDisconnect(conn) {
|
|
183
|
+
console.log('[Server] Client disconnected:', conn.id)
|
|
184
|
+
},
|
|
185
|
+
})
|
|
186
|
+
|
|
187
|
+
// \u6CE8\u518C\u623F\u95F4\u7C7B\u578B
|
|
188
|
+
server.define('game', GameRoom)
|
|
189
|
+
|
|
190
|
+
await server.start()
|
|
191
|
+
|
|
192
|
+
console.log('========================================')
|
|
193
|
+
console.log(' ${projectName}')
|
|
194
|
+
console.log('========================================')
|
|
195
|
+
console.log(\` WebSocket: ws://localhost:\${PORT}\`)
|
|
196
|
+
console.log(' Room type: "game"')
|
|
197
|
+
console.log(' Press Ctrl+C to stop')
|
|
198
|
+
console.log('========================================')
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
process.on('SIGINT', () => {
|
|
202
|
+
console.log('\\nShutting down...')
|
|
203
|
+
process.exit(0)
|
|
204
|
+
})
|
|
205
|
+
|
|
206
|
+
main().catch(console.error)
|
|
207
|
+
`;
|
|
208
|
+
writeFile(projectPath, "src/server/main.ts", serverMainTs);
|
|
209
|
+
const gameRoomTs = `import { Room, Player, onMessage } from '@esengine/server'
|
|
210
|
+
import type {
|
|
211
|
+
MsgMove,
|
|
212
|
+
MsgChat,
|
|
213
|
+
PlayerState,
|
|
214
|
+
BroadcastSync,
|
|
215
|
+
BroadcastJoined,
|
|
216
|
+
BroadcastLeft,
|
|
217
|
+
} from '../../shared/index.js'
|
|
218
|
+
|
|
219
|
+
/** \u73A9\u5BB6\u6570\u636E | Player data */
|
|
220
|
+
interface PlayerData {
|
|
221
|
+
name: string
|
|
222
|
+
x: number
|
|
223
|
+
y: number
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* \u6E38\u620F\u623F\u95F4
|
|
228
|
+
* Game Room
|
|
229
|
+
*/
|
|
230
|
+
export class GameRoom extends Room<{ players: PlayerState[] }, PlayerData> {
|
|
231
|
+
// \u914D\u7F6E
|
|
232
|
+
maxPlayers = 8
|
|
233
|
+
tickRate = 20
|
|
234
|
+
|
|
235
|
+
// \u72B6\u6001
|
|
236
|
+
state = {
|
|
237
|
+
players: [] as PlayerState[],
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// ========================================================================
|
|
241
|
+
// \u751F\u547D\u5468\u671F | Lifecycle
|
|
242
|
+
// ========================================================================
|
|
243
|
+
|
|
244
|
+
onCreate() {
|
|
245
|
+
console.log(\`[GameRoom] Room \${this.id} created\`)
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
onJoin(player: Player<PlayerData>) {
|
|
249
|
+
// \u521D\u59CB\u5316\u73A9\u5BB6\u6570\u636E
|
|
250
|
+
player.data.name = 'Player_' + player.id.slice(-4)
|
|
251
|
+
player.data.x = Math.random() * 800
|
|
252
|
+
player.data.y = Math.random() * 600
|
|
253
|
+
|
|
254
|
+
// \u6DFB\u52A0\u5230\u72B6\u6001
|
|
255
|
+
this.state.players.push({
|
|
256
|
+
id: player.id,
|
|
257
|
+
name: player.data.name,
|
|
258
|
+
x: player.data.x,
|
|
259
|
+
y: player.data.y,
|
|
260
|
+
})
|
|
261
|
+
|
|
262
|
+
// \u5E7F\u64AD\u73A9\u5BB6\u52A0\u5165
|
|
263
|
+
this.broadcast<BroadcastJoined>('Joined', {
|
|
264
|
+
playerId: player.id,
|
|
265
|
+
playerName: player.data.name,
|
|
266
|
+
})
|
|
267
|
+
|
|
268
|
+
console.log(\`[GameRoom] \${player.data.name} joined room \${this.id}\`)
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
onLeave(player: Player<PlayerData>) {
|
|
272
|
+
// \u4ECE\u72B6\u6001\u79FB\u9664
|
|
273
|
+
this.state.players = this.state.players.filter(p => p.id !== player.id)
|
|
274
|
+
|
|
275
|
+
// \u5E7F\u64AD\u73A9\u5BB6\u79BB\u5F00
|
|
276
|
+
this.broadcast<BroadcastLeft>('Left', {
|
|
277
|
+
playerId: player.id,
|
|
278
|
+
})
|
|
279
|
+
|
|
280
|
+
console.log(\`[GameRoom] \${player.data.name} left room \${this.id}\`)
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
onTick(_dt: number) {
|
|
284
|
+
// \u5E7F\u64AD\u72B6\u6001\u540C\u6B65
|
|
285
|
+
this.broadcast<BroadcastSync>('Sync', {
|
|
286
|
+
players: this.state.players,
|
|
287
|
+
})
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
onDispose() {
|
|
291
|
+
console.log(\`[GameRoom] Room \${this.id} disposed\`)
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// ========================================================================
|
|
295
|
+
// \u6D88\u606F\u5904\u7406 | Message Handlers
|
|
296
|
+
// ========================================================================
|
|
297
|
+
|
|
298
|
+
@onMessage('Move')
|
|
299
|
+
handleMove(data: MsgMove, player: Player<PlayerData>) {
|
|
300
|
+
player.data.x = data.x
|
|
301
|
+
player.data.y = data.y
|
|
302
|
+
|
|
303
|
+
// \u66F4\u65B0\u72B6\u6001
|
|
304
|
+
const p = this.state.players.find(p => p.id === player.id)
|
|
305
|
+
if (p) {
|
|
306
|
+
p.x = data.x
|
|
307
|
+
p.y = data.y
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
@onMessage('Chat')
|
|
312
|
+
handleChat(data: MsgChat, player: Player<PlayerData>) {
|
|
313
|
+
// \u5E7F\u64AD\u804A\u5929\u6D88\u606F
|
|
314
|
+
this.broadcast('Chat', {
|
|
315
|
+
from: player.data.name,
|
|
316
|
+
text: data.text,
|
|
317
|
+
})
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
`;
|
|
321
|
+
writeFile(projectPath, "src/server/rooms/GameRoom.ts", gameRoomTs);
|
|
322
|
+
const clientIndexTs = `/**
|
|
323
|
+
* \u5BA2\u6237\u7AEF\u793A\u4F8B\u4EE3\u7801
|
|
324
|
+
* Client Example Code
|
|
325
|
+
*
|
|
326
|
+
* \u8FD9\u662F\u4E00\u4E2A\u793A\u4F8B\uFF0C\u5C55\u793A\u5982\u4F55\u4ECE\u5BA2\u6237\u7AEF\u8FDE\u63A5\u670D\u52A1\u5668
|
|
327
|
+
* This is an example showing how to connect to the server from client
|
|
328
|
+
*/
|
|
329
|
+
|
|
330
|
+
import { connect } from '@esengine/rpc/client'
|
|
331
|
+
import type {
|
|
332
|
+
JoinRoomReq,
|
|
333
|
+
JoinRoomRes,
|
|
334
|
+
MsgMove,
|
|
335
|
+
BroadcastSync,
|
|
336
|
+
BroadcastJoined,
|
|
337
|
+
} from '../shared/index.js'
|
|
338
|
+
|
|
339
|
+
async function main() {
|
|
340
|
+
// \u8FDE\u63A5\u670D\u52A1\u5668
|
|
341
|
+
const client = await connect('ws://localhost:3000')
|
|
342
|
+
|
|
343
|
+
// \u52A0\u5165\u623F\u95F4
|
|
344
|
+
const result = await client.call<JoinRoomReq, JoinRoomRes>('JoinRoom', {
|
|
345
|
+
roomType: 'game',
|
|
346
|
+
playerName: 'Alice',
|
|
347
|
+
})
|
|
348
|
+
console.log('Joined room:', result.roomId)
|
|
349
|
+
|
|
350
|
+
// \u76D1\u542C\u5E7F\u64AD
|
|
351
|
+
client.onMessage<BroadcastJoined>('Joined', (data) => {
|
|
352
|
+
console.log('Player joined:', data.playerName)
|
|
353
|
+
})
|
|
354
|
+
|
|
355
|
+
client.onMessage<BroadcastSync>('Sync', (data) => {
|
|
356
|
+
console.log('State update:', data.players.length, 'players')
|
|
357
|
+
})
|
|
358
|
+
|
|
359
|
+
// \u53D1\u9001\u79FB\u52A8\u6D88\u606F
|
|
360
|
+
client.send<MsgMove>('RoomMessage', {
|
|
361
|
+
type: 'Move',
|
|
362
|
+
payload: { x: 100, y: 200 },
|
|
363
|
+
})
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
main().catch(console.error)
|
|
367
|
+
`;
|
|
368
|
+
writeFile(projectPath, "src/client/index.ts", clientIndexTs);
|
|
369
|
+
const gitignore = `node_modules/
|
|
370
|
+
dist/
|
|
371
|
+
*.log
|
|
372
|
+
.DS_Store
|
|
373
|
+
`;
|
|
374
|
+
writeFile(projectPath, ".gitignore", gitignore);
|
|
375
|
+
const readme = `# ${projectName}
|
|
376
|
+
|
|
377
|
+
ESEngine \u6E38\u620F\u670D\u52A1\u5668\u9879\u76EE\u3002
|
|
378
|
+
|
|
379
|
+
## \u9879\u76EE\u7ED3\u6784
|
|
380
|
+
|
|
381
|
+
\`\`\`
|
|
382
|
+
src/
|
|
383
|
+
\u251C\u2500\u2500 shared/ # \u5171\u4EAB\u534F\u8BAE\uFF08\u5BA2\u6237\u7AEF\u670D\u52A1\u7AEF\u90FD\u7528\uFF09
|
|
384
|
+
\u2502 \u251C\u2500\u2500 protocol.ts # \u7C7B\u578B\u5B9A\u4E49
|
|
385
|
+
\u2502 \u2514\u2500\u2500 index.ts
|
|
386
|
+
\u251C\u2500\u2500 server/ # \u670D\u52A1\u7AEF
|
|
387
|
+
\u2502 \u251C\u2500\u2500 main.ts # \u5165\u53E3
|
|
388
|
+
\u2502 \u2514\u2500\u2500 rooms/
|
|
389
|
+
\u2502 \u2514\u2500\u2500 GameRoom.ts # \u6E38\u620F\u623F\u95F4
|
|
390
|
+
\u2514\u2500\u2500 client/ # \u5BA2\u6237\u7AEF\u793A\u4F8B
|
|
391
|
+
\u2514\u2500\u2500 index.ts
|
|
392
|
+
\`\`\`
|
|
393
|
+
|
|
394
|
+
## \u5FEB\u901F\u5F00\u59CB
|
|
395
|
+
|
|
396
|
+
\`\`\`bash
|
|
397
|
+
# \u542F\u52A8\u670D\u52A1\u5668
|
|
398
|
+
npm run dev
|
|
399
|
+
|
|
400
|
+
# \u670D\u52A1\u5668\u5C06\u5728 ws://localhost:3000 \u542F\u52A8
|
|
401
|
+
\`\`\`
|
|
402
|
+
|
|
403
|
+
## \u5BA2\u6237\u7AEF\u8FDE\u63A5
|
|
404
|
+
|
|
405
|
+
\`\`\`typescript
|
|
406
|
+
import { connect } from '@esengine/rpc/client'
|
|
407
|
+
|
|
408
|
+
const client = await connect('ws://localhost:3000')
|
|
409
|
+
|
|
410
|
+
// \u52A0\u5165\u623F\u95F4
|
|
411
|
+
const { roomId } = await client.call('JoinRoom', {
|
|
412
|
+
roomType: 'game',
|
|
413
|
+
playerName: 'Alice',
|
|
414
|
+
})
|
|
415
|
+
|
|
416
|
+
// \u76D1\u542C\u540C\u6B65
|
|
417
|
+
client.onMessage('Sync', (state) => {
|
|
418
|
+
console.log(state.players)
|
|
419
|
+
})
|
|
420
|
+
|
|
421
|
+
// \u53D1\u9001\u6D88\u606F
|
|
422
|
+
client.send('RoomMessage', { type: 'Move', payload: { x: 100, y: 200 } })
|
|
423
|
+
\`\`\`
|
|
424
|
+
`;
|
|
425
|
+
writeFile(projectPath, "README.md", readme);
|
|
426
|
+
}
|
|
427
|
+
__name(generateProject, "generateProject");
|
|
428
|
+
async function main() {
|
|
429
|
+
printLogo();
|
|
430
|
+
const program = new Command();
|
|
431
|
+
program.name("create-esengine-server").description("Create a new ESEngine game server project").version(VERSION).argument("[project-name]", "Name of the project").action(async (projectName) => {
|
|
432
|
+
if (!projectName) {
|
|
433
|
+
const response = await prompts({
|
|
434
|
+
type: "text",
|
|
435
|
+
name: "name",
|
|
436
|
+
message: "Project name:",
|
|
437
|
+
initial: "my-game-server"
|
|
438
|
+
}, {
|
|
439
|
+
onCancel: /* @__PURE__ */ __name(() => {
|
|
440
|
+
console.log(chalk.yellow("\n Cancelled."));
|
|
441
|
+
process.exit(0);
|
|
442
|
+
}, "onCancel")
|
|
443
|
+
});
|
|
444
|
+
projectName = response.name;
|
|
445
|
+
}
|
|
446
|
+
if (!projectName) {
|
|
447
|
+
console.log(chalk.red(" Project name is required."));
|
|
448
|
+
process.exit(1);
|
|
449
|
+
}
|
|
450
|
+
const projectPath = path.resolve(process.cwd(), projectName);
|
|
451
|
+
if (fs.existsSync(projectPath)) {
|
|
452
|
+
const files = fs.readdirSync(projectPath);
|
|
453
|
+
if (files.length > 0) {
|
|
454
|
+
const response = await prompts({
|
|
455
|
+
type: "confirm",
|
|
456
|
+
name: "overwrite",
|
|
457
|
+
message: `Directory "${projectName}" is not empty. Continue?`,
|
|
458
|
+
initial: false
|
|
459
|
+
});
|
|
460
|
+
if (!response.overwrite) {
|
|
461
|
+
console.log(chalk.yellow("\n Cancelled."));
|
|
462
|
+
process.exit(0);
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
} else {
|
|
466
|
+
fs.mkdirSync(projectPath, {
|
|
467
|
+
recursive: true
|
|
468
|
+
});
|
|
469
|
+
}
|
|
470
|
+
console.log();
|
|
471
|
+
console.log(chalk.bold(` Creating project in ${chalk.cyan(projectPath)}...`));
|
|
472
|
+
console.log();
|
|
473
|
+
generateProject(projectPath, projectName);
|
|
474
|
+
console.log(chalk.green(" \u2713 Created project files"));
|
|
475
|
+
const pm = detectPackageManager();
|
|
476
|
+
const installCmd = getInstallCommand(pm);
|
|
477
|
+
console.log(chalk.gray(` Running ${installCmd}...`));
|
|
478
|
+
console.log();
|
|
479
|
+
try {
|
|
480
|
+
execSync(installCmd, {
|
|
481
|
+
cwd: projectPath,
|
|
482
|
+
stdio: "inherit"
|
|
483
|
+
});
|
|
484
|
+
console.log();
|
|
485
|
+
console.log(chalk.green(" \u2713 Dependencies installed"));
|
|
486
|
+
} catch {
|
|
487
|
+
console.log(chalk.yellow(`
|
|
488
|
+
\u26A0 Failed to install. Run "${installCmd}" manually.`));
|
|
489
|
+
}
|
|
490
|
+
console.log();
|
|
491
|
+
console.log(chalk.bold(" Done! Next steps:"));
|
|
492
|
+
console.log();
|
|
493
|
+
console.log(chalk.cyan(` cd ${projectName}`));
|
|
494
|
+
console.log(chalk.cyan(` ${pm} run dev`));
|
|
495
|
+
console.log();
|
|
496
|
+
console.log(chalk.gray(" Project structure:"));
|
|
497
|
+
console.log(chalk.gray(" src/"));
|
|
498
|
+
console.log(chalk.gray(" \u251C\u2500\u2500 shared/ # Shared protocol types"));
|
|
499
|
+
console.log(chalk.gray(" \u2502 \u2514\u2500\u2500 protocol.ts"));
|
|
500
|
+
console.log(chalk.gray(" \u251C\u2500\u2500 server/ # Server code"));
|
|
501
|
+
console.log(chalk.gray(" \u2502 \u251C\u2500\u2500 main.ts"));
|
|
502
|
+
console.log(chalk.gray(" \u2502 \u2514\u2500\u2500 rooms/"));
|
|
503
|
+
console.log(chalk.gray(" \u2502 \u2514\u2500\u2500 GameRoom.ts"));
|
|
504
|
+
console.log(chalk.gray(" \u2514\u2500\u2500 client/ # Client example"));
|
|
505
|
+
console.log(chalk.gray(" \u2514\u2500\u2500 index.ts"));
|
|
506
|
+
console.log();
|
|
507
|
+
});
|
|
508
|
+
program.parse();
|
|
509
|
+
}
|
|
510
|
+
__name(main, "main");
|
|
511
|
+
main().catch(console.error);
|
package/package.json
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "create-esengine-server",
|
|
3
|
+
"version": "1.1.0",
|
|
4
|
+
"description": "Create ESEngine game server projects",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"create-esengine-server": "./dist/index.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"dist",
|
|
11
|
+
"templates"
|
|
12
|
+
],
|
|
13
|
+
"dependencies": {
|
|
14
|
+
"chalk": "^5.3.0",
|
|
15
|
+
"commander": "^11.0.0",
|
|
16
|
+
"prompts": "^2.4.2"
|
|
17
|
+
},
|
|
18
|
+
"devDependencies": {
|
|
19
|
+
"@types/node": "^20.0.0",
|
|
20
|
+
"@types/prompts": "^2.4.9",
|
|
21
|
+
"tsup": "^8.0.0",
|
|
22
|
+
"typescript": "^5.7.0"
|
|
23
|
+
},
|
|
24
|
+
"publishConfig": {
|
|
25
|
+
"access": "public"
|
|
26
|
+
},
|
|
27
|
+
"keywords": [
|
|
28
|
+
"esengine",
|
|
29
|
+
"create",
|
|
30
|
+
"game-server",
|
|
31
|
+
"scaffold",
|
|
32
|
+
"cli"
|
|
33
|
+
],
|
|
34
|
+
"scripts": {
|
|
35
|
+
"build": "tsup",
|
|
36
|
+
"dev": "tsup --watch"
|
|
37
|
+
}
|
|
38
|
+
}
|