Compare commits
7 Commits
@esengine/
...
@esengine/
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0a88c6f2fc | ||
|
|
b0b95c60b4 | ||
|
|
683ac7a7d4 | ||
|
|
1e240e86f2 | ||
|
|
4d6c2fe7ff | ||
|
|
a42f2412d7 | ||
|
|
fdb19a33fb |
@@ -33,7 +33,6 @@
|
|||||||
"@esengine/physics-rapier2d",
|
"@esengine/physics-rapier2d",
|
||||||
"@esengine/rapier2d",
|
"@esengine/rapier2d",
|
||||||
"@esengine/world-streaming",
|
"@esengine/world-streaming",
|
||||||
"@esengine/network-server",
|
|
||||||
"@esengine/editor-core",
|
"@esengine/editor-core",
|
||||||
"@esengine/editor-runtime",
|
"@esengine/editor-runtime",
|
||||||
"@esengine/editor-app",
|
"@esengine/editor-app",
|
||||||
|
|||||||
@@ -22,44 +22,112 @@ npm install @esengine/network
|
|||||||
npm install @esengine/network-server
|
npm install @esengine/network-server
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Quick Setup with CLI
|
||||||
|
|
||||||
|
We recommend using ESEngine CLI to quickly create a complete game server project:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Create project directory
|
||||||
|
mkdir my-game-server && cd my-game-server
|
||||||
|
npm init -y
|
||||||
|
|
||||||
|
# Initialize Node.js server with CLI
|
||||||
|
npx @esengine/cli init -p nodejs
|
||||||
|
```
|
||||||
|
|
||||||
|
The CLI will generate the following project structure:
|
||||||
|
|
||||||
|
```
|
||||||
|
my-game-server/
|
||||||
|
├── src/
|
||||||
|
│ ├── index.ts # Entry point
|
||||||
|
│ ├── server/
|
||||||
|
│ │ └── GameServer.ts # Network server configuration
|
||||||
|
│ └── game/
|
||||||
|
│ ├── Game.ts # ECS game class
|
||||||
|
│ ├── scenes/
|
||||||
|
│ │ └── MainScene.ts # Main scene
|
||||||
|
│ ├── components/ # ECS components
|
||||||
|
│ │ ├── PositionComponent.ts
|
||||||
|
│ │ └── VelocityComponent.ts
|
||||||
|
│ └── systems/ # ECS systems
|
||||||
|
│ └── MovementSystem.ts
|
||||||
|
├── tsconfig.json
|
||||||
|
├── package.json
|
||||||
|
└── README.md
|
||||||
|
```
|
||||||
|
|
||||||
|
Start the server:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Development mode (hot reload)
|
||||||
|
npm run dev
|
||||||
|
|
||||||
|
# Production mode
|
||||||
|
npm run start
|
||||||
|
```
|
||||||
|
|
||||||
## Quick Start
|
## Quick Start
|
||||||
|
|
||||||
### Client
|
### Client
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
import { World } from '@esengine/ecs-framework';
|
import { Core, Scene } from '@esengine/ecs-framework';
|
||||||
import {
|
import {
|
||||||
NetworkPlugin,
|
NetworkPlugin,
|
||||||
NetworkIdentity,
|
NetworkIdentity,
|
||||||
NetworkTransform
|
NetworkTransform
|
||||||
} from '@esengine/network';
|
} from '@esengine/network';
|
||||||
|
|
||||||
// Create World and install network plugin
|
// Define game scene
|
||||||
const world = new World();
|
class GameScene extends Scene {
|
||||||
const networkPlugin = new NetworkPlugin({
|
initialize(): void {
|
||||||
serverUrl: 'ws://localhost:3000'
|
this.name = 'Game';
|
||||||
});
|
// Network systems are automatically added by NetworkPlugin
|
||||||
networkPlugin.install(world.services);
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize Core
|
||||||
|
Core.create({ debug: false });
|
||||||
|
const scene = new GameScene();
|
||||||
|
Core.setScene(scene);
|
||||||
|
|
||||||
|
// Install network plugin
|
||||||
|
const networkPlugin = new NetworkPlugin();
|
||||||
|
await Core.installPlugin(networkPlugin);
|
||||||
|
|
||||||
// Register prefab factory
|
// Register prefab factory
|
||||||
networkPlugin.registerPrefab('player', (netId, ownerId) => {
|
networkPlugin.registerPrefab('player', (scene, spawn) => {
|
||||||
const entity = world.createEntity(`player_${netId}`);
|
const entity = scene.createEntity(`player_${spawn.netId}`);
|
||||||
entity.addComponent(new NetworkIdentity(netId, ownerId));
|
|
||||||
|
const identity = entity.addComponent(new NetworkIdentity());
|
||||||
|
identity.netId = spawn.netId;
|
||||||
|
identity.ownerId = spawn.ownerId;
|
||||||
|
identity.isLocalPlayer = spawn.ownerId === networkPlugin.networkService.localClientId;
|
||||||
|
|
||||||
entity.addComponent(new NetworkTransform());
|
entity.addComponent(new NetworkTransform());
|
||||||
// Add other components...
|
|
||||||
return entity;
|
return entity;
|
||||||
});
|
});
|
||||||
|
|
||||||
// Connect to server
|
// Connect to server
|
||||||
await networkPlugin.connect('PlayerName');
|
const success = await networkPlugin.connect('ws://localhost:3000', 'PlayerName');
|
||||||
console.log('Connected! Client ID:', networkPlugin.localPlayerId);
|
if (success) {
|
||||||
|
console.log('Connected!');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Game loop
|
||||||
|
function gameLoop(dt: number) {
|
||||||
|
Core.update(dt);
|
||||||
|
}
|
||||||
|
|
||||||
// Disconnect
|
// Disconnect
|
||||||
networkPlugin.disconnect();
|
await networkPlugin.disconnect();
|
||||||
```
|
```
|
||||||
|
|
||||||
### Server
|
### Server
|
||||||
|
|
||||||
|
After creating a server project with CLI, the generated code already configures GameServer:
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
import { GameServer } from '@esengine/network-server';
|
import { GameServer } from '@esengine/network-server';
|
||||||
|
|
||||||
@@ -72,6 +140,7 @@ const server = new GameServer({
|
|||||||
});
|
});
|
||||||
|
|
||||||
await server.start();
|
await server.start();
|
||||||
|
console.log('Server started on ws://localhost:3000');
|
||||||
```
|
```
|
||||||
|
|
||||||
## Core Concepts
|
## Core Concepts
|
||||||
@@ -229,15 +298,19 @@ interface INetworkCallbacks {
|
|||||||
### Prefab Factory
|
### Prefab Factory
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
type PrefabFactory = (netId: number, ownerId: number) => Entity;
|
type PrefabFactory = (scene: Scene, spawn: MsgSpawn) => Entity;
|
||||||
```
|
```
|
||||||
|
|
||||||
Register prefab factories for network entity creation:
|
Register prefab factories for network entity creation:
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
networkPlugin.registerPrefab('enemy', (netId, ownerId) => {
|
networkPlugin.registerPrefab('enemy', (scene, spawn) => {
|
||||||
const entity = world.createEntity(`enemy_${netId}`);
|
const entity = scene.createEntity(`enemy_${spawn.netId}`);
|
||||||
entity.addComponent(new NetworkIdentity(netId, ownerId));
|
|
||||||
|
const identity = entity.addComponent(new NetworkIdentity());
|
||||||
|
identity.netId = spawn.netId;
|
||||||
|
identity.ownerId = spawn.ownerId;
|
||||||
|
|
||||||
entity.addComponent(new NetworkTransform());
|
entity.addComponent(new NetworkTransform());
|
||||||
entity.addComponent(new EnemyComponent());
|
entity.addComponent(new EnemyComponent());
|
||||||
return entity;
|
return entity;
|
||||||
@@ -264,9 +337,12 @@ class NetworkInputSystem extends EntitySystem {
|
|||||||
Usage example:
|
Usage example:
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
const inputSystem = world.getSystem(NetworkInputSystem);
|
// Send input via NetworkPlugin (recommended)
|
||||||
|
networkPlugin.sendMoveInput(0, 1); // Movement
|
||||||
|
networkPlugin.sendActionInput('jump'); // Action
|
||||||
|
|
||||||
// Handle keyboard input
|
// Or use inputSystem directly
|
||||||
|
const inputSystem = networkPlugin.inputSystem;
|
||||||
if (keyboard.isPressed('W')) {
|
if (keyboard.isPressed('W')) {
|
||||||
inputSystem.addMoveInput(0, 1);
|
inputSystem.addMoveInput(0, 1);
|
||||||
}
|
}
|
||||||
@@ -519,74 +595,92 @@ const networkService = services.get(NetworkServiceToken);
|
|||||||
### Complete Multiplayer Client
|
### Complete Multiplayer Client
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
import { World, EntitySystem, Matcher } from '@esengine/ecs-framework';
|
import { Core, Scene, EntitySystem, Matcher, Entity } from '@esengine/ecs-framework';
|
||||||
import {
|
import {
|
||||||
NetworkPlugin,
|
NetworkPlugin,
|
||||||
NetworkIdentity,
|
NetworkIdentity,
|
||||||
NetworkTransform,
|
NetworkTransform
|
||||||
NetworkInputSystem
|
|
||||||
} from '@esengine/network';
|
} from '@esengine/network';
|
||||||
|
|
||||||
// Create game world
|
// Define game scene
|
||||||
const world = new World();
|
class GameScene extends Scene {
|
||||||
|
initialize(): void {
|
||||||
|
this.name = 'MultiplayerGame';
|
||||||
|
// Network systems are automatically added by NetworkPlugin
|
||||||
|
// Add custom systems
|
||||||
|
this.addSystem(new LocalInputHandler());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Configure network plugin
|
// Initialize
|
||||||
const networkPlugin = new NetworkPlugin({
|
async function initGame() {
|
||||||
serverUrl: 'ws://localhost:3000'
|
Core.create({ debug: false });
|
||||||
});
|
|
||||||
networkPlugin.install(world.services);
|
|
||||||
|
|
||||||
// Register player prefab
|
const scene = new GameScene();
|
||||||
networkPlugin.registerPrefab('player', (netId, ownerId) => {
|
Core.setScene(scene);
|
||||||
const entity = world.createEntity(`player_${netId}`);
|
|
||||||
|
|
||||||
const identity = new NetworkIdentity(netId, ownerId);
|
// Install network plugin
|
||||||
entity.addComponent(identity);
|
const networkPlugin = new NetworkPlugin();
|
||||||
entity.addComponent(new NetworkTransform());
|
await Core.installPlugin(networkPlugin);
|
||||||
|
|
||||||
// If local player, add input component
|
// Register player prefab
|
||||||
if (identity.bIsLocalPlayer) {
|
networkPlugin.registerPrefab('player', (scene, spawn) => {
|
||||||
entity.addComponent(new LocalInputComponent());
|
const entity = scene.createEntity(`player_${spawn.netId}`);
|
||||||
|
|
||||||
|
const identity = entity.addComponent(new NetworkIdentity());
|
||||||
|
identity.netId = spawn.netId;
|
||||||
|
identity.ownerId = spawn.ownerId;
|
||||||
|
identity.isLocalPlayer = spawn.ownerId === networkPlugin.networkService.localClientId;
|
||||||
|
|
||||||
|
entity.addComponent(new NetworkTransform());
|
||||||
|
|
||||||
|
// If local player, add input marker
|
||||||
|
if (identity.isLocalPlayer) {
|
||||||
|
entity.addComponent(new LocalInputComponent());
|
||||||
|
}
|
||||||
|
|
||||||
|
return entity;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Connect to server
|
||||||
|
const success = await networkPlugin.connect('ws://localhost:3000', 'Player1');
|
||||||
|
if (success) {
|
||||||
|
console.log('Connected!');
|
||||||
|
} else {
|
||||||
|
console.error('Connection failed');
|
||||||
}
|
}
|
||||||
|
|
||||||
return entity;
|
return networkPlugin;
|
||||||
});
|
|
||||||
|
|
||||||
// Connect to server
|
|
||||||
async function startGame() {
|
|
||||||
try {
|
|
||||||
await networkPlugin.connect('Player1');
|
|
||||||
console.log('Connected! Player ID:', networkPlugin.localPlayerId);
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Connection failed:', error);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Game loop
|
// Game loop
|
||||||
function gameLoop(deltaTime: number) {
|
function gameLoop(deltaTime: number) {
|
||||||
world.update(deltaTime);
|
Core.update(deltaTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
startGame();
|
initGame();
|
||||||
```
|
```
|
||||||
|
|
||||||
### Handling Input
|
### Handling Input
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
class LocalInputHandler extends EntitySystem {
|
class LocalInputHandler extends EntitySystem {
|
||||||
private _inputSystem: NetworkInputSystem;
|
private _networkPlugin: NetworkPlugin | null = null;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super(Matcher.all(NetworkIdentity, LocalInputComponent));
|
super(Matcher.empty().all(NetworkIdentity, LocalInputComponent));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected onAddedToWorld(): void {
|
protected onAddedToScene(): void {
|
||||||
this._inputSystem = this.world.getSystem(NetworkInputSystem);
|
// Get NetworkPlugin reference
|
||||||
|
this._networkPlugin = Core.getPlugin(NetworkPlugin);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected processEntity(entity: Entity, dt: number): void {
|
protected processEntity(entity: Entity, dt: number): void {
|
||||||
const identity = entity.getComponent(NetworkIdentity);
|
if (!this._networkPlugin) return;
|
||||||
if (!identity.bIsLocalPlayer) return;
|
|
||||||
|
const identity = entity.getComponent(NetworkIdentity)!;
|
||||||
|
if (!identity.isLocalPlayer) return;
|
||||||
|
|
||||||
// Read keyboard input
|
// Read keyboard input
|
||||||
let moveX = 0;
|
let moveX = 0;
|
||||||
@@ -598,11 +692,11 @@ class LocalInputHandler extends EntitySystem {
|
|||||||
if (keyboard.isPressed('S')) moveY -= 1;
|
if (keyboard.isPressed('S')) moveY -= 1;
|
||||||
|
|
||||||
if (moveX !== 0 || moveY !== 0) {
|
if (moveX !== 0 || moveY !== 0) {
|
||||||
this._inputSystem.addMoveInput(moveX, moveY);
|
this._networkPlugin.sendMoveInput(moveX, moveY);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (keyboard.isJustPressed('Space')) {
|
if (keyboard.isJustPressed('Space')) {
|
||||||
this._inputSystem.addActionInput('jump');
|
this._networkPlugin.sendActionInput('jump');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,44 +22,112 @@ npm install @esengine/network
|
|||||||
npm install @esengine/network-server
|
npm install @esengine/network-server
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## 使用 CLI 快速创建服务端
|
||||||
|
|
||||||
|
推荐使用 ESEngine CLI 快速创建完整的游戏服务端项目:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 创建项目目录
|
||||||
|
mkdir my-game-server && cd my-game-server
|
||||||
|
npm init -y
|
||||||
|
|
||||||
|
# 使用 CLI 初始化 Node.js 服务端
|
||||||
|
npx @esengine/cli init -p nodejs
|
||||||
|
```
|
||||||
|
|
||||||
|
CLI 会自动生成以下项目结构:
|
||||||
|
|
||||||
|
```
|
||||||
|
my-game-server/
|
||||||
|
├── src/
|
||||||
|
│ ├── index.ts # 入口文件
|
||||||
|
│ ├── server/
|
||||||
|
│ │ └── GameServer.ts # 网络服务器配置
|
||||||
|
│ └── game/
|
||||||
|
│ ├── Game.ts # ECS 游戏主类
|
||||||
|
│ ├── scenes/
|
||||||
|
│ │ └── MainScene.ts # 主场景
|
||||||
|
│ ├── components/ # ECS 组件
|
||||||
|
│ │ ├── PositionComponent.ts
|
||||||
|
│ │ └── VelocityComponent.ts
|
||||||
|
│ └── systems/ # ECS 系统
|
||||||
|
│ └── MovementSystem.ts
|
||||||
|
├── tsconfig.json
|
||||||
|
├── package.json
|
||||||
|
└── README.md
|
||||||
|
```
|
||||||
|
|
||||||
|
启动服务端:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 开发模式(热重载)
|
||||||
|
npm run dev
|
||||||
|
|
||||||
|
# 生产模式
|
||||||
|
npm run start
|
||||||
|
```
|
||||||
|
|
||||||
## 快速开始
|
## 快速开始
|
||||||
|
|
||||||
### 客户端
|
### 客户端
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
import { World } from '@esengine/ecs-framework';
|
import { Core, Scene } from '@esengine/ecs-framework';
|
||||||
import {
|
import {
|
||||||
NetworkPlugin,
|
NetworkPlugin,
|
||||||
NetworkIdentity,
|
NetworkIdentity,
|
||||||
NetworkTransform
|
NetworkTransform
|
||||||
} from '@esengine/network';
|
} from '@esengine/network';
|
||||||
|
|
||||||
// 创建 World 并安装网络插件
|
// 定义游戏场景
|
||||||
const world = new World();
|
class GameScene extends Scene {
|
||||||
const networkPlugin = new NetworkPlugin({
|
initialize(): void {
|
||||||
serverUrl: 'ws://localhost:3000'
|
this.name = 'Game';
|
||||||
});
|
// 网络系统由 NetworkPlugin 自动添加
|
||||||
networkPlugin.install(world.services);
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 初始化 Core
|
||||||
|
Core.create({ debug: false });
|
||||||
|
const scene = new GameScene();
|
||||||
|
Core.setScene(scene);
|
||||||
|
|
||||||
|
// 安装网络插件
|
||||||
|
const networkPlugin = new NetworkPlugin();
|
||||||
|
await Core.installPlugin(networkPlugin);
|
||||||
|
|
||||||
// 注册预制体工厂
|
// 注册预制体工厂
|
||||||
networkPlugin.registerPrefab('player', (netId, ownerId) => {
|
networkPlugin.registerPrefab('player', (scene, spawn) => {
|
||||||
const entity = world.createEntity(`player_${netId}`);
|
const entity = scene.createEntity(`player_${spawn.netId}`);
|
||||||
entity.addComponent(new NetworkIdentity(netId, ownerId));
|
|
||||||
|
const identity = entity.addComponent(new NetworkIdentity());
|
||||||
|
identity.netId = spawn.netId;
|
||||||
|
identity.ownerId = spawn.ownerId;
|
||||||
|
identity.isLocalPlayer = spawn.ownerId === networkPlugin.networkService.localClientId;
|
||||||
|
|
||||||
entity.addComponent(new NetworkTransform());
|
entity.addComponent(new NetworkTransform());
|
||||||
// 添加其他组件...
|
|
||||||
return entity;
|
return entity;
|
||||||
});
|
});
|
||||||
|
|
||||||
// 连接服务器
|
// 连接服务器
|
||||||
await networkPlugin.connect('PlayerName');
|
const success = await networkPlugin.connect('ws://localhost:3000', 'PlayerName');
|
||||||
console.log('Connected! Client ID:', networkPlugin.localPlayerId);
|
if (success) {
|
||||||
|
console.log('Connected!');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 游戏循环
|
||||||
|
function gameLoop(dt: number) {
|
||||||
|
Core.update(dt);
|
||||||
|
}
|
||||||
|
|
||||||
// 断开连接
|
// 断开连接
|
||||||
networkPlugin.disconnect();
|
await networkPlugin.disconnect();
|
||||||
```
|
```
|
||||||
|
|
||||||
### 服务器端
|
### 服务器端
|
||||||
|
|
||||||
|
使用 CLI 创建服务端项目后,默认生成的代码已经配置好了 GameServer:
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
import { GameServer } from '@esengine/network-server';
|
import { GameServer } from '@esengine/network-server';
|
||||||
|
|
||||||
@@ -72,6 +140,7 @@ const server = new GameServer({
|
|||||||
});
|
});
|
||||||
|
|
||||||
await server.start();
|
await server.start();
|
||||||
|
console.log('Server started on ws://localhost:3000');
|
||||||
```
|
```
|
||||||
|
|
||||||
## 核心概念
|
## 核心概念
|
||||||
@@ -229,15 +298,19 @@ interface INetworkCallbacks {
|
|||||||
### 预制体工厂
|
### 预制体工厂
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
type PrefabFactory = (netId: number, ownerId: number) => Entity;
|
type PrefabFactory = (scene: Scene, spawn: MsgSpawn) => Entity;
|
||||||
```
|
```
|
||||||
|
|
||||||
注册预制体工厂用于网络实体的创建:
|
注册预制体工厂用于网络实体的创建:
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
networkPlugin.registerPrefab('enemy', (netId, ownerId) => {
|
networkPlugin.registerPrefab('enemy', (scene, spawn) => {
|
||||||
const entity = world.createEntity(`enemy_${netId}`);
|
const entity = scene.createEntity(`enemy_${spawn.netId}`);
|
||||||
entity.addComponent(new NetworkIdentity(netId, ownerId));
|
|
||||||
|
const identity = entity.addComponent(new NetworkIdentity());
|
||||||
|
identity.netId = spawn.netId;
|
||||||
|
identity.ownerId = spawn.ownerId;
|
||||||
|
|
||||||
entity.addComponent(new NetworkTransform());
|
entity.addComponent(new NetworkTransform());
|
||||||
entity.addComponent(new EnemyComponent());
|
entity.addComponent(new EnemyComponent());
|
||||||
return entity;
|
return entity;
|
||||||
@@ -264,9 +337,12 @@ class NetworkInputSystem extends EntitySystem {
|
|||||||
使用示例:
|
使用示例:
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
const inputSystem = world.getSystem(NetworkInputSystem);
|
// 通过 NetworkPlugin 发送输入(推荐)
|
||||||
|
networkPlugin.sendMoveInput(0, 1); // 移动
|
||||||
|
networkPlugin.sendActionInput('jump'); // 动作
|
||||||
|
|
||||||
// 处理键盘输入
|
// 或直接使用 inputSystem
|
||||||
|
const inputSystem = networkPlugin.inputSystem;
|
||||||
if (keyboard.isPressed('W')) {
|
if (keyboard.isPressed('W')) {
|
||||||
inputSystem.addMoveInput(0, 1);
|
inputSystem.addMoveInput(0, 1);
|
||||||
}
|
}
|
||||||
@@ -519,74 +595,92 @@ const networkService = services.get(NetworkServiceToken);
|
|||||||
### 完整的多人游戏客户端
|
### 完整的多人游戏客户端
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
import { World, EntitySystem, Matcher } from '@esengine/ecs-framework';
|
import { Core, Scene, EntitySystem, Matcher, Entity } from '@esengine/ecs-framework';
|
||||||
import {
|
import {
|
||||||
NetworkPlugin,
|
NetworkPlugin,
|
||||||
NetworkIdentity,
|
NetworkIdentity,
|
||||||
NetworkTransform,
|
NetworkTransform
|
||||||
NetworkInputSystem
|
|
||||||
} from '@esengine/network';
|
} from '@esengine/network';
|
||||||
|
|
||||||
// 创建游戏世界
|
// 定义游戏场景
|
||||||
const world = new World();
|
class GameScene extends Scene {
|
||||||
|
initialize(): void {
|
||||||
|
this.name = 'MultiplayerGame';
|
||||||
|
// 网络系统由 NetworkPlugin 自动添加
|
||||||
|
// 添加自定义系统
|
||||||
|
this.addSystem(new LocalInputHandler());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 配置网络插件
|
// 初始化
|
||||||
const networkPlugin = new NetworkPlugin({
|
async function initGame() {
|
||||||
serverUrl: 'ws://localhost:3000'
|
Core.create({ debug: false });
|
||||||
});
|
|
||||||
networkPlugin.install(world.services);
|
|
||||||
|
|
||||||
// 注册玩家预制体
|
const scene = new GameScene();
|
||||||
networkPlugin.registerPrefab('player', (netId, ownerId) => {
|
Core.setScene(scene);
|
||||||
const entity = world.createEntity(`player_${netId}`);
|
|
||||||
|
|
||||||
const identity = new NetworkIdentity(netId, ownerId);
|
// 安装网络插件
|
||||||
entity.addComponent(identity);
|
const networkPlugin = new NetworkPlugin();
|
||||||
entity.addComponent(new NetworkTransform());
|
await Core.installPlugin(networkPlugin);
|
||||||
|
|
||||||
// 如果是本地玩家,添加输入组件
|
// 注册玩家预制体
|
||||||
if (identity.bIsLocalPlayer) {
|
networkPlugin.registerPrefab('player', (scene, spawn) => {
|
||||||
entity.addComponent(new LocalInputComponent());
|
const entity = scene.createEntity(`player_${spawn.netId}`);
|
||||||
|
|
||||||
|
const identity = entity.addComponent(new NetworkIdentity());
|
||||||
|
identity.netId = spawn.netId;
|
||||||
|
identity.ownerId = spawn.ownerId;
|
||||||
|
identity.isLocalPlayer = spawn.ownerId === networkPlugin.networkService.localClientId;
|
||||||
|
|
||||||
|
entity.addComponent(new NetworkTransform());
|
||||||
|
|
||||||
|
// 如果是本地玩家,添加输入标记
|
||||||
|
if (identity.isLocalPlayer) {
|
||||||
|
entity.addComponent(new LocalInputComponent());
|
||||||
|
}
|
||||||
|
|
||||||
|
return entity;
|
||||||
|
});
|
||||||
|
|
||||||
|
// 连接服务器
|
||||||
|
const success = await networkPlugin.connect('ws://localhost:3000', 'Player1');
|
||||||
|
if (success) {
|
||||||
|
console.log('已连接!');
|
||||||
|
} else {
|
||||||
|
console.error('连接失败');
|
||||||
}
|
}
|
||||||
|
|
||||||
return entity;
|
return networkPlugin;
|
||||||
});
|
|
||||||
|
|
||||||
// 连接服务器
|
|
||||||
async function startGame() {
|
|
||||||
try {
|
|
||||||
await networkPlugin.connect('Player1');
|
|
||||||
console.log('已连接! 玩家 ID:', networkPlugin.localPlayerId);
|
|
||||||
} catch (error) {
|
|
||||||
console.error('连接失败:', error);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 游戏循环
|
// 游戏循环
|
||||||
function gameLoop(deltaTime: number) {
|
function gameLoop(deltaTime: number) {
|
||||||
world.update(deltaTime);
|
Core.update(deltaTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
startGame();
|
initGame();
|
||||||
```
|
```
|
||||||
|
|
||||||
### 处理输入
|
### 处理输入
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
class LocalInputHandler extends EntitySystem {
|
class LocalInputHandler extends EntitySystem {
|
||||||
private _inputSystem: NetworkInputSystem;
|
private _networkPlugin: NetworkPlugin | null = null;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super(Matcher.all(NetworkIdentity, LocalInputComponent));
|
super(Matcher.empty().all(NetworkIdentity, LocalInputComponent));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected onAddedToWorld(): void {
|
protected onAddedToScene(): void {
|
||||||
this._inputSystem = this.world.getSystem(NetworkInputSystem);
|
// 获取 NetworkPlugin 引用
|
||||||
|
this._networkPlugin = Core.getPlugin(NetworkPlugin);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected processEntity(entity: Entity, dt: number): void {
|
protected processEntity(entity: Entity, dt: number): void {
|
||||||
const identity = entity.getComponent(NetworkIdentity);
|
if (!this._networkPlugin) return;
|
||||||
if (!identity.bIsLocalPlayer) return;
|
|
||||||
|
const identity = entity.getComponent(NetworkIdentity)!;
|
||||||
|
if (!identity.isLocalPlayer) return;
|
||||||
|
|
||||||
// 读取键盘输入
|
// 读取键盘输入
|
||||||
let moveX = 0;
|
let moveX = 0;
|
||||||
@@ -598,11 +692,11 @@ class LocalInputHandler extends EntitySystem {
|
|||||||
if (keyboard.isPressed('S')) moveY -= 1;
|
if (keyboard.isPressed('S')) moveY -= 1;
|
||||||
|
|
||||||
if (moveX !== 0 || moveY !== 0) {
|
if (moveX !== 0 || moveY !== 0) {
|
||||||
this._inputSystem.addMoveInput(moveX, moveY);
|
this._networkPlugin.sendMoveInput(moveX, moveY);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (keyboard.isJustPressed('Space')) {
|
if (keyboard.isJustPressed('Space')) {
|
||||||
this._inputSystem.addActionInput('jump');
|
this._networkPlugin.sendActionInput('jump');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
17
packages/network-ext/network-server/CHANGELOG.md
Normal file
17
packages/network-ext/network-server/CHANGELOG.md
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
# @esengine/network-server
|
||||||
|
|
||||||
|
## 1.0.2
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- [#354](https://github.com/esengine/esengine/pull/354) [`1e240e8`](https://github.com/esengine/esengine/commit/1e240e86f2f75672c3609c9d86238a9ec08ebb4e) Thanks [@esengine](https://github.com/esengine)! - feat(cli): 增强 Node.js 服务端适配器
|
||||||
|
|
||||||
|
**@esengine/cli:**
|
||||||
|
- 添加 @esengine/network-server 依赖支持
|
||||||
|
- 生成完整的 ECS 游戏服务器项目结构
|
||||||
|
- 组件使用 @ECSComponent 装饰器注册
|
||||||
|
- tsconfig 启用 experimentalDecorators
|
||||||
|
|
||||||
|
**@esengine/network-server:**
|
||||||
|
- 支持 ESM/CJS 双格式导出
|
||||||
|
- 添加 ws@8.18.0 解决 Node.js 24 兼容性问题
|
||||||
@@ -1,15 +1,21 @@
|
|||||||
{
|
{
|
||||||
"name": "@esengine/network-server",
|
"name": "@esengine/network-server",
|
||||||
"version": "1.0.0",
|
"version": "1.0.2",
|
||||||
"description": "TSRPC-based network server for ESEngine",
|
"description": "TSRPC-based network server for ESEngine",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.cjs",
|
||||||
"module": "dist/index.js",
|
"module": "dist/index.js",
|
||||||
"types": "dist/index.d.ts",
|
"types": "dist/index.d.ts",
|
||||||
"exports": {
|
"exports": {
|
||||||
".": {
|
".": {
|
||||||
"import": "./dist/index.js",
|
"import": {
|
||||||
"types": "./dist/index.d.ts"
|
"types": "./dist/index.d.ts",
|
||||||
|
"default": "./dist/index.js"
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"types": "./dist/index.d.cts",
|
||||||
|
"default": "./dist/index.cjs"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
@@ -22,7 +28,8 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@esengine/network-protocols": "workspace:*",
|
"@esengine/network-protocols": "workspace:*",
|
||||||
"tsrpc": "^3.4.15"
|
"tsrpc": "^3.4.15",
|
||||||
|
"ws": "^8.18.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"tsup": "^8.5.1",
|
"tsup": "^8.5.1",
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { defineConfig } from 'tsup';
|
|||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
entry: ['src/index.ts', 'src/main.ts'],
|
entry: ['src/index.ts', 'src/main.ts'],
|
||||||
format: ['esm'],
|
format: ['esm', 'cjs'],
|
||||||
dts: true,
|
dts: true,
|
||||||
sourcemap: true,
|
sourcemap: true,
|
||||||
clean: true,
|
clean: true,
|
||||||
|
|||||||
@@ -1,5 +1,21 @@
|
|||||||
# @esengine/cli
|
# @esengine/cli
|
||||||
|
|
||||||
|
## 1.3.0
|
||||||
|
|
||||||
|
### Minor Changes
|
||||||
|
|
||||||
|
- [#354](https://github.com/esengine/esengine/pull/354) [`1e240e8`](https://github.com/esengine/esengine/commit/1e240e86f2f75672c3609c9d86238a9ec08ebb4e) Thanks [@esengine](https://github.com/esengine)! - feat(cli): 增强 Node.js 服务端适配器
|
||||||
|
|
||||||
|
**@esengine/cli:**
|
||||||
|
- 添加 @esengine/network-server 依赖支持
|
||||||
|
- 生成完整的 ECS 游戏服务器项目结构
|
||||||
|
- 组件使用 @ECSComponent 装饰器注册
|
||||||
|
- tsconfig 启用 experimentalDecorators
|
||||||
|
|
||||||
|
**@esengine/network-server:**
|
||||||
|
- 支持 ESM/CJS 双格式导出
|
||||||
|
- 添加 ws@8.18.0 解决 Node.js 24 兼容性问题
|
||||||
|
|
||||||
## 1.2.1
|
## 1.2.1
|
||||||
|
|
||||||
### Patch Changes
|
### Patch Changes
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@esengine/cli",
|
"name": "@esengine/cli",
|
||||||
"version": "1.2.1",
|
"version": "1.3.0",
|
||||||
"description": "CLI tool for adding ESEngine ECS framework to existing projects",
|
"description": "CLI tool for adding ESEngine ECS framework to existing projects",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
|
|||||||
@@ -7,11 +7,12 @@ import type { FileEntry, PlatformAdapter, ProjectConfig } from './types.js';
|
|||||||
export const nodejsAdapter: PlatformAdapter = {
|
export const nodejsAdapter: PlatformAdapter = {
|
||||||
id: 'nodejs',
|
id: 'nodejs',
|
||||||
name: 'Node.js',
|
name: 'Node.js',
|
||||||
description: 'Generate standalone Node.js project with ECS (for servers, CLI tools, simulations)',
|
description: 'Generate Node.js game server with ECS and networking',
|
||||||
|
|
||||||
getDependencies() {
|
getDependencies() {
|
||||||
return {
|
return {
|
||||||
'@esengine/ecs-framework': 'latest'
|
'@esengine/ecs-framework': 'latest',
|
||||||
|
'@esengine/network-server': 'latest'
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -33,147 +34,96 @@ export const nodejsAdapter: PlatformAdapter = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
generateFiles(config: ProjectConfig): FileEntry[] {
|
generateFiles(config: ProjectConfig): FileEntry[] {
|
||||||
const files: FileEntry[] = [];
|
return [
|
||||||
|
{ path: 'src/index.ts', content: generateIndex(config) },
|
||||||
files.push({
|
{ path: 'src/server/GameServer.ts', content: generateGameServer(config) },
|
||||||
path: 'src/index.ts',
|
{ path: 'src/game/Game.ts', content: generateGame(config) },
|
||||||
content: generateIndex(config)
|
{ path: 'src/game/scenes/MainScene.ts', content: generateMainScene(config) },
|
||||||
});
|
{ path: 'src/game/components/PositionComponent.ts', content: generatePositionComponent() },
|
||||||
|
{ path: 'src/game/components/VelocityComponent.ts', content: generateVelocityComponent() },
|
||||||
files.push({
|
{ path: 'src/game/systems/MovementSystem.ts', content: generateMovementSystem() },
|
||||||
path: 'src/Game.ts',
|
{ path: 'tsconfig.json', content: generateTsConfig() },
|
||||||
content: generateGame(config)
|
{ path: 'README.md', content: generateReadme(config) }
|
||||||
});
|
];
|
||||||
|
|
||||||
files.push({
|
|
||||||
path: 'src/components/PositionComponent.ts',
|
|
||||||
content: generatePositionComponent()
|
|
||||||
});
|
|
||||||
|
|
||||||
files.push({
|
|
||||||
path: 'src/systems/MovementSystem.ts',
|
|
||||||
content: generateMovementSystem()
|
|
||||||
});
|
|
||||||
|
|
||||||
files.push({
|
|
||||||
path: 'tsconfig.json',
|
|
||||||
content: generateTsConfig()
|
|
||||||
});
|
|
||||||
|
|
||||||
files.push({
|
|
||||||
path: 'README.md',
|
|
||||||
content: generateReadme(config)
|
|
||||||
});
|
|
||||||
|
|
||||||
return files;
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
function generateIndex(config: ProjectConfig): string {
|
function generateIndex(config: ProjectConfig): string {
|
||||||
return `import { Game } from './Game.js';
|
return `import { createGameServer } from './server/GameServer';
|
||||||
|
|
||||||
const game = new Game();
|
const PORT = Number(process.env.PORT) || 3000;
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
const server = createGameServer({ port: PORT });
|
||||||
|
await server.start();
|
||||||
|
|
||||||
|
console.log('========================================');
|
||||||
|
console.log(' ${config.name} Server');
|
||||||
|
console.log('========================================');
|
||||||
|
console.log(\` WebSocket: ws://localhost:\${PORT}\`);
|
||||||
|
console.log(' Press Ctrl+C to stop');
|
||||||
|
console.log('========================================');
|
||||||
|
}
|
||||||
|
|
||||||
// Handle graceful shutdown
|
|
||||||
process.on('SIGINT', () => {
|
process.on('SIGINT', () => {
|
||||||
console.log('\\nShutting down...');
|
console.log('\\nShutting down...');
|
||||||
game.stop();
|
|
||||||
process.exit(0);
|
process.exit(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
process.on('SIGTERM', () => {
|
main().catch(console.error);
|
||||||
game.stop();
|
`;
|
||||||
process.exit(0);
|
}
|
||||||
});
|
|
||||||
|
|
||||||
// Start the game
|
function generateGameServer(config: ProjectConfig): string {
|
||||||
game.start();
|
return `import { GameServer, type IServerConfig } from '@esengine/network-server';
|
||||||
|
import { Game } from '../game/Game';
|
||||||
|
|
||||||
console.log('[${config.name}] Game started. Press Ctrl+C to stop.');
|
/**
|
||||||
|
* @zh 创建游戏服务器
|
||||||
|
* @en Create game server
|
||||||
|
*/
|
||||||
|
export function createGameServer(config: Partial<IServerConfig> = {}): GameServer {
|
||||||
|
const server = new GameServer({
|
||||||
|
port: config.port ?? 3000,
|
||||||
|
roomConfig: {
|
||||||
|
maxPlayers: 16,
|
||||||
|
tickRate: 20,
|
||||||
|
...config.roomConfig
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 初始化 ECS 游戏逻辑
|
||||||
|
const game = new Game();
|
||||||
|
game.start();
|
||||||
|
|
||||||
|
return server;
|
||||||
|
}
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function generateGame(config: ProjectConfig): string {
|
function generateGame(config: ProjectConfig): string {
|
||||||
return `import { Core, Scene, type ICoreConfig } from '@esengine/ecs-framework';
|
return `import { Core, type ICoreConfig } from '@esengine/ecs-framework';
|
||||||
import { MovementSystem } from './systems/MovementSystem.js';
|
import { MainScene } from './scenes/MainScene';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Game configuration options
|
* @zh 游戏主类
|
||||||
*/
|
* @en Main game class
|
||||||
export interface GameOptions {
|
|
||||||
/** @zh 调试模式 @en Debug mode */
|
|
||||||
debug?: boolean;
|
|
||||||
/** @zh 目标帧率 @en Target FPS */
|
|
||||||
targetFPS?: number;
|
|
||||||
/** @zh 远程调试配置 @en Remote debug configuration */
|
|
||||||
remoteDebug?: {
|
|
||||||
/** @zh 启用远程调试 @en Enable remote debugging */
|
|
||||||
enabled: boolean;
|
|
||||||
/** @zh WebSocket地址 @en WebSocket URL */
|
|
||||||
url: string;
|
|
||||||
/** @zh 自动重连 @en Auto reconnect */
|
|
||||||
autoReconnect?: boolean;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Game Scene - Define your game systems here
|
|
||||||
*/
|
|
||||||
class GameScene extends Scene {
|
|
||||||
initialize(): void {
|
|
||||||
this.name = '${config.name}';
|
|
||||||
this.addSystem(new MovementSystem());
|
|
||||||
// Add more systems here...
|
|
||||||
}
|
|
||||||
|
|
||||||
onStart(): void {
|
|
||||||
// Create your initial entities here
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Main game class with ECS game loop
|
|
||||||
*
|
|
||||||
* Features:
|
|
||||||
* - Configurable debug mode and FPS
|
|
||||||
* - Remote debugging via WebSocket
|
|
||||||
* - Fixed timestep game loop
|
|
||||||
* - Graceful start/stop
|
|
||||||
*/
|
*/
|
||||||
export class Game {
|
export class Game {
|
||||||
private readonly _scene: GameScene;
|
private _scene: MainScene;
|
||||||
private readonly _targetFPS: number;
|
|
||||||
private _running = false;
|
private _running = false;
|
||||||
private _tickInterval: ReturnType<typeof setInterval> | null = null;
|
private _tickInterval: ReturnType<typeof setInterval> | null = null;
|
||||||
private _lastTime = 0;
|
private _lastTime = 0;
|
||||||
|
private _targetFPS = 60;
|
||||||
|
|
||||||
get scene() { return this._scene; }
|
constructor(options: { debug?: boolean; targetFPS?: number } = {}) {
|
||||||
get running() { return this._running; }
|
const { debug = false, targetFPS = 60 } = options;
|
||||||
|
|
||||||
constructor(options: GameOptions = {}) {
|
|
||||||
const { debug = false, targetFPS = 60, remoteDebug } = options;
|
|
||||||
this._targetFPS = targetFPS;
|
this._targetFPS = targetFPS;
|
||||||
|
|
||||||
const config: ICoreConfig = { debug };
|
const config: ICoreConfig = { debug };
|
||||||
|
|
||||||
// 配置远程调试
|
|
||||||
if (remoteDebug?.enabled && remoteDebug.url) {
|
|
||||||
config.debugConfig = {
|
|
||||||
enabled: true,
|
|
||||||
websocketUrl: remoteDebug.url,
|
|
||||||
autoReconnect: remoteDebug.autoReconnect ?? true,
|
|
||||||
channels: {
|
|
||||||
entities: true,
|
|
||||||
systems: true,
|
|
||||||
performance: true,
|
|
||||||
components: true,
|
|
||||||
scenes: true
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
Core.create(config);
|
Core.create(config);
|
||||||
this._scene = new GameScene();
|
|
||||||
|
this._scene = new MainScene();
|
||||||
Core.setScene(this._scene);
|
Core.setScene(this._scene);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -203,11 +153,38 @@ export class Game {
|
|||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function generateMainScene(config: ProjectConfig): string {
|
||||||
|
return `import { Scene } from '@esengine/ecs-framework';
|
||||||
|
import { MovementSystem } from '../systems/MovementSystem';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @zh 主场景
|
||||||
|
* @en Main scene
|
||||||
|
*/
|
||||||
|
export class MainScene extends Scene {
|
||||||
|
initialize(): void {
|
||||||
|
this.name = '${config.name}';
|
||||||
|
|
||||||
|
// 注册系统
|
||||||
|
this.addSystem(new MovementSystem());
|
||||||
|
|
||||||
|
// 添加更多系统...
|
||||||
|
}
|
||||||
|
|
||||||
|
onStart(): void {
|
||||||
|
// 创建初始实体
|
||||||
|
console.log('[MainScene] Scene started');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
function generatePositionComponent(): string {
|
function generatePositionComponent(): string {
|
||||||
return `import { Component, ECSComponent } from '@esengine/ecs-framework';
|
return `import { Component, ECSComponent } from '@esengine/ecs-framework';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Position component - stores entity position
|
* @zh 位置组件
|
||||||
|
* @en Position component
|
||||||
*/
|
*/
|
||||||
@ECSComponent('Position')
|
@ECSComponent('Position')
|
||||||
export class PositionComponent extends Component {
|
export class PositionComponent extends Component {
|
||||||
@@ -219,31 +196,61 @@ export class PositionComponent extends Component {
|
|||||||
this.x = x;
|
this.x = x;
|
||||||
this.y = y;
|
this.y = y;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
reset(): void {
|
||||||
|
this.x = 0;
|
||||||
|
this.y = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function generateVelocityComponent(): string {
|
||||||
|
return `import { Component, ECSComponent } from '@esengine/ecs-framework';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @zh 速度组件
|
||||||
|
* @en Velocity component
|
||||||
|
*/
|
||||||
|
@ECSComponent('Velocity')
|
||||||
|
export class VelocityComponent extends Component {
|
||||||
|
vx = 0;
|
||||||
|
vy = 0;
|
||||||
|
|
||||||
|
constructor(vx = 0, vy = 0) {
|
||||||
|
super();
|
||||||
|
this.vx = vx;
|
||||||
|
this.vy = vy;
|
||||||
|
}
|
||||||
|
|
||||||
|
reset(): void {
|
||||||
|
this.vx = 0;
|
||||||
|
this.vy = 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function generateMovementSystem(): string {
|
function generateMovementSystem(): string {
|
||||||
return `import { EntitySystem, Matcher, Entity, Time, ECSSystem } from '@esengine/ecs-framework';
|
return `import { EntitySystem, Matcher, Entity, Time } from '@esengine/ecs-framework';
|
||||||
import { PositionComponent } from '../components/PositionComponent.js';
|
import { PositionComponent } from '../components/PositionComponent';
|
||||||
|
import { VelocityComponent } from '../components/VelocityComponent';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Movement system - processes entities with PositionComponent
|
* @zh 移动系统
|
||||||
*
|
* @en Movement system
|
||||||
* Customize this system for your game logic.
|
|
||||||
*/
|
*/
|
||||||
@ECSSystem('MovementSystem')
|
|
||||||
export class MovementSystem extends EntitySystem {
|
export class MovementSystem extends EntitySystem {
|
||||||
constructor() {
|
constructor() {
|
||||||
super(Matcher.empty().all(PositionComponent));
|
super(Matcher.empty().all(PositionComponent, VelocityComponent));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected process(entities: readonly Entity[]): void {
|
protected processEntity(entity: Entity, dt: number): void {
|
||||||
for (const entity of entities) {
|
const pos = entity.getComponent(PositionComponent)!;
|
||||||
const position = entity.getComponent(PositionComponent)!;
|
const vel = entity.getComponent(VelocityComponent)!;
|
||||||
// Update position using Time.deltaTime
|
|
||||||
// position.x += velocity.dx * Time.deltaTime;
|
pos.x += vel.vx * dt;
|
||||||
}
|
pos.y += vel.vy * dt;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
@@ -253,8 +260,8 @@ function generateTsConfig(): string {
|
|||||||
return `{
|
return `{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"target": "ES2022",
|
"target": "ES2022",
|
||||||
"module": "NodeNext",
|
"module": "CommonJS",
|
||||||
"moduleResolution": "NodeNext",
|
"moduleResolution": "Node",
|
||||||
"lib": ["ES2022"],
|
"lib": ["ES2022"],
|
||||||
"outDir": "./dist",
|
"outDir": "./dist",
|
||||||
"rootDir": "./src",
|
"rootDir": "./src",
|
||||||
@@ -263,7 +270,9 @@ function generateTsConfig(): string {
|
|||||||
"skipLibCheck": true,
|
"skipLibCheck": true,
|
||||||
"forceConsistentCasingInFileNames": true,
|
"forceConsistentCasingInFileNames": true,
|
||||||
"declaration": true,
|
"declaration": true,
|
||||||
"sourceMap": true
|
"sourceMap": true,
|
||||||
|
"experimentalDecorators": true,
|
||||||
|
"emitDecoratorMetadata": true
|
||||||
},
|
},
|
||||||
"include": ["src/**/*"],
|
"include": ["src/**/*"],
|
||||||
"exclude": ["node_modules", "dist"]
|
"exclude": ["node_modules", "dist"]
|
||||||
@@ -274,75 +283,54 @@ function generateTsConfig(): string {
|
|||||||
function generateReadme(config: ProjectConfig): string {
|
function generateReadme(config: ProjectConfig): string {
|
||||||
return `# ${config.name}
|
return `# ${config.name}
|
||||||
|
|
||||||
A Node.js project using ESEngine ECS framework.
|
Node.js 游戏服务器,基于 ESEngine ECS 框架。
|
||||||
|
|
||||||
## Quick Start
|
## 快速开始
|
||||||
|
|
||||||
\`\`\`bash
|
\`\`\`bash
|
||||||
# Install dependencies
|
# 安装依赖
|
||||||
npm install
|
npm install
|
||||||
|
|
||||||
# Run in development mode (with hot reload)
|
# 开发模式(热重载)
|
||||||
npm run dev
|
npm run dev
|
||||||
|
|
||||||
# Build and run
|
# 构建并运行
|
||||||
npm run build:start
|
npm run build:start
|
||||||
\`\`\`
|
\`\`\`
|
||||||
|
|
||||||
## Project Structure
|
## 项目结构
|
||||||
|
|
||||||
\`\`\`
|
\`\`\`
|
||||||
src/
|
src/
|
||||||
├── index.ts # Entry point
|
├── index.ts # 入口文件
|
||||||
├── Game.ts # Game loop and ECS setup
|
├── server/
|
||||||
├── components/ # ECS components (data)
|
│ └── GameServer.ts # 网络服务器配置
|
||||||
│ └── PositionComponent.ts
|
└── game/
|
||||||
└── systems/ # ECS systems (logic)
|
├── Game.ts # ECS 游戏主类
|
||||||
└── MovementSystem.ts
|
├── scenes/
|
||||||
|
│ └── MainScene.ts # 主场景
|
||||||
|
├── components/ # ECS 组件
|
||||||
|
│ ├── PositionComponent.ts
|
||||||
|
│ └── VelocityComponent.ts
|
||||||
|
└── systems/ # ECS 系统
|
||||||
|
└── MovementSystem.ts
|
||||||
\`\`\`
|
\`\`\`
|
||||||
|
|
||||||
## Creating Components
|
## 客户端连接
|
||||||
|
|
||||||
\`\`\`typescript
|
\`\`\`typescript
|
||||||
import { Component } from '@esengine/ecs-framework';
|
import { NetworkPlugin } from '@esengine/network';
|
||||||
|
|
||||||
export class HealthComponent extends Component {
|
const networkPlugin = new NetworkPlugin({
|
||||||
current = 100;
|
serverUrl: 'ws://localhost:3000'
|
||||||
max = 100;
|
});
|
||||||
|
|
||||||
reset(): void {
|
await networkPlugin.connect('PlayerName');
|
||||||
this.current = this.max;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
\`\`\`
|
\`\`\`
|
||||||
|
|
||||||
## Creating Systems
|
## 文档
|
||||||
|
|
||||||
\`\`\`typescript
|
- [ESEngine 文档](https://esengine.github.io/esengine/)
|
||||||
import { EntitySystem, Matcher, Entity } from '@esengine/ecs-framework';
|
- [Network 模块](https://esengine.github.io/esengine/modules/network/)
|
||||||
import { HealthComponent } from '../components/HealthComponent.js';
|
|
||||||
|
|
||||||
export class HealthSystem extends EntitySystem {
|
|
||||||
constructor() {
|
|
||||||
super(Matcher.all(HealthComponent));
|
|
||||||
}
|
|
||||||
|
|
||||||
protected processEntity(entity: Entity, dt: number): void {
|
|
||||||
const health = entity.getComponent(HealthComponent)!;
|
|
||||||
// Your logic here
|
|
||||||
}
|
|
||||||
}
|
|
||||||
\`\`\`
|
|
||||||
|
|
||||||
## Use Cases
|
|
||||||
|
|
||||||
- Game servers
|
|
||||||
- CLI tools with complex logic
|
|
||||||
- Simulations
|
|
||||||
- Automated testing
|
|
||||||
|
|
||||||
## Documentation
|
|
||||||
|
|
||||||
- [ESEngine ECS Framework](https://github.com/esengine/esengine)
|
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|||||||
3
pnpm-lock.yaml
generated
3
pnpm-lock.yaml
generated
@@ -1705,6 +1705,9 @@ importers:
|
|||||||
tsrpc:
|
tsrpc:
|
||||||
specifier: ^3.4.15
|
specifier: ^3.4.15
|
||||||
version: 3.4.21
|
version: 3.4.21
|
||||||
|
ws:
|
||||||
|
specifier: ^8.18.0
|
||||||
|
version: 8.18.3
|
||||||
devDependencies:
|
devDependencies:
|
||||||
tsup:
|
tsup:
|
||||||
specifier: ^8.5.1
|
specifier: ^8.5.1
|
||||||
|
|||||||
Reference in New Issue
Block a user