docs: restructure documentation with modular sub-pages (#363)

* docs: split Entity docs into sub-modules and fix Starlight CI

- Split monolithic entity.md into 4 focused sub-documents:
  - guide/entity/index.md - Overview and basic concepts
  - guide/entity/component-operations.md - Component API operations
  - guide/entity/entity-handle.md - EntityHandle system for safe references
  - guide/entity/lifecycle.md - Lifecycle and persistence management

- Created bilingual versions (Chinese and English)

- Updated sidebar configuration in astro.config.mjs

- Fixed CI workflow for Starlight migration:
  - Updated docs.yml to upload from docs/dist instead of .vitepress/dist
  - Updated package.json scripts to use pnpm filter for docs
  - Added docs directory to pnpm-workspace.yaml
  - Renamed docs package to @esengine/docs

- Documented missing Entity APIs:
  - createComponent() method
  - addComponents() batch method
  - getComponentByType() with inheritance support
  - markDirty() for change detection

* docs: split Network docs and fix API errors

- Split network module into focused sub-documents:
  - modules/network/index.md - Overview and quick start
  - modules/network/client.md - Client-side usage
  - modules/network/server.md - Server-side GameServer/Room
  - modules/network/sync.md - Interpolation and prediction
  - modules/network/api.md - Complete API reference

- Fixed incorrect API documentation:
  - localClientId → clientId
  - ENetworkState enum values (strings → numbers)
  - connect() method signature
  - Removed non-existent localPlayerId property
  - Fixed onConnected callback signature

- Created bilingual versions (Chinese and English)
- Updated sidebar configuration
- Updated pnpm-lock.yaml for docs workspace

* docs(worker-system): split into focused sub-modules

Split 773-line worker-system.md into 5 focused documents:
- index.md: Core features and quick start
- configuration.md: IWorkerSystemConfig and processing modes
- examples.md: Complete particle physics implementation
- wechat.md: WeChat Mini Game limitations and solutions
- best-practices.md: Performance optimization tips

Updated sidebar config to reflect new structure.
Created both Chinese and English versions.

* docs(scene): split into focused sub-modules

Split 666-line scene.md into 7 focused documents:
- index.md: Overview and quick start
- lifecycle.md: Scene lifecycle methods
- entity-management.md: Entity creation, find, destroy
- system-management.md: System add, remove, control
- events.md: Event system usage
- debugging.md: Stats, performance monitoring
- best-practices.md: Design patterns and examples

Updated sidebar config to reflect new structure.
Created both Chinese and English versions.

* docs(plugin-system): split into focused sub-modules

Split 645-line plugin-system.md into 7 focused documents:
- index.md: Overview and quick start
- development.md: IPlugin interface and lifecycle
- services-systems.md: Register services and add systems
- dependencies.md: Dependency management
- management.md: Plugin management via Core/PluginManager
- examples.md: Complete plugin examples
- best-practices.md: Design guidelines and FAQ

Updated sidebar config to reflect new structure.
Created both Chinese and English versions.

* docs(behavior-tree): add English docs and expand sidebar navigation

- Add 12 English behavior-tree documentation pages
- Update sidebar config to show behavior-tree sub-navigation
- Include: overview, getting-started, core-concepts, custom-actions,
  editor-guide, editor-workflow, asset-management, advanced-usage,
  best-practices, cocos-integration, laya-integration, nodejs-usage

* docs(modules): split spatial and timer module docs

Spatial module (602 lines -> 5 files):
- index.md: Overview and quick start
- spatial-index.md: Grid index, range queries, raycasting API
- aoi.md: Area of Interest management
- examples.md: Combat, MMO sync, AI perception examples
- utilities.md: Geometry detection, performance tips

Timer module (481 lines -> 4 files):
- index.md: Overview and core concepts
- api.md: Complete timer and cooldown API
- examples.md: Skill cooldowns, DOT, buff systems
- best-practices.md: Usage tips, ECS integration

Also includes English versions and sidebar navigation updates.

* docs: split FSM, pathfinding, blueprint, procgen module docs

- FSM: Split into index, api, examples (3 files)
- Pathfinding: Split into index, grid-map, navmesh, smoothing, examples (5 files)
- Blueprint: Split into index, vm, custom-nodes, nodes, composition, examples (6 files)
- Procgen: Split into index, noise, random, sampling, examples (5 files)
- Added English versions for all split modules
- Updated sidebar navigation with sub-menus for all modules
This commit is contained in:
YHH
2025-12-27 20:35:54 +08:00
committed by GitHub
parent d57a007a42
commit 8605888f11
113 changed files with 12958 additions and 6598 deletions

View File

@@ -1,645 +0,0 @@
---
title: "Plugin System"
---
The plugin system allows you to extend ECS Framework functionality in a modular way. Through plugins, you can encapsulate specific features (such as network synchronization, physics engines, debugging tools, etc.) and reuse them across multiple projects.
## Overview
### What is a Plugin
A plugin is a class that implements the `IPlugin` interface and can be dynamically installed into the framework at runtime. Plugins can:
- Register custom services to the service container
- Add systems to scenes
- Register custom components
- Extend framework functionality
### Advantages of Plugins
- **Modular**: Encapsulate functionality as independent modules, improving code maintainability
- **Reusable**: Same plugin can be used across multiple projects
- **Decoupled**: Core framework separated from extended functionality
- **Hot-swappable**: Dynamically install and uninstall plugins at runtime
## Quick Start
### Creating Your First Plugin
Create a simple debug plugin:
```typescript
import { IPlugin, Core, ServiceContainer } from '@esengine/ecs-framework';
class DebugPlugin implements IPlugin {
readonly name = 'debug-plugin';
readonly version = '1.0.0';
install(core: Core, services: ServiceContainer): void {
console.log('Debug plugin installed');
// Can register services, add systems, etc. here
}
uninstall(): void {
console.log('Debug plugin uninstalled');
// Clean up resources
}
}
```
### Installing a Plugin
Use `Core.installPlugin()` to install a plugin:
```typescript
import { Core } from '@esengine/ecs-framework';
// Initialize Core
Core.create({ debug: true });
// Install plugin
await Core.installPlugin(new DebugPlugin());
// Check if plugin is installed
if (Core.isPluginInstalled('debug-plugin')) {
console.log('Debug plugin is running');
}
```
### Uninstalling a Plugin
```typescript
// Uninstall plugin
await Core.uninstallPlugin('debug-plugin');
```
### Getting Plugin Instance
```typescript
// Get installed plugin
const plugin = Core.getPlugin('debug-plugin');
if (plugin) {
console.log(`Plugin version: ${plugin.version}`);
}
```
## Plugin Development
### IPlugin Interface
All plugins must implement the `IPlugin` interface:
```typescript
export interface IPlugin {
// Unique plugin name
readonly name: string;
// Plugin version (semver recommended)
readonly version: string;
// Dependencies on other plugins (optional)
readonly dependencies?: readonly string[];
// Called when plugin is installed
install(core: Core, services: ServiceContainer): void | Promise<void>;
// Called when plugin is uninstalled
uninstall(): void | Promise<void>;
}
```
### Plugin Lifecycle
#### install Method
Called when the plugin is installed, used to initialize the plugin:
```typescript
class MyPlugin implements IPlugin {
readonly name = 'my-plugin';
readonly version = '1.0.0';
install(core: Core, services: ServiceContainer): void {
// 1. Register services
services.registerSingleton(MyService);
// 2. Access current scene
const scene = core.scene;
if (scene) {
// 3. Add systems
scene.addSystem(new MySystem());
}
// 4. Other initialization logic
console.log('Plugin initialized');
}
uninstall(): void {
// Cleanup logic
}
}
```
#### uninstall Method
Called when the plugin is uninstalled, used to clean up resources:
```typescript
class MyPlugin implements IPlugin {
readonly name = 'my-plugin';
readonly version = '1.0.0';
private myService?: MyService;
install(core: Core, services: ServiceContainer): void {
this.myService = new MyService();
services.registerInstance(MyService, this.myService);
}
uninstall(): void {
// Clean up service
if (this.myService) {
this.myService.dispose();
this.myService = undefined;
}
// Remove event listeners
// Release other resources
}
}
```
### Async Plugins
Plugin `install` and `uninstall` methods both support async:
```typescript
class AsyncPlugin implements IPlugin {
readonly name = 'async-plugin';
readonly version = '1.0.0';
async install(core: Core, services: ServiceContainer): Promise<void> {
// Async resource loading
const config = await fetch('/plugin-config.json').then(r => r.json());
// Initialize service with loaded config
const service = new MyService(config);
services.registerInstance(MyService, service);
}
async uninstall(): Promise<void> {
// Async cleanup
await this.saveState();
}
private async saveState() {
// Save plugin state
}
}
// Usage
await Core.installPlugin(new AsyncPlugin());
```
### Registering Services
Plugins can register their own services to the service container:
```typescript
import { IService } from '@esengine/ecs-framework';
class NetworkService implements IService {
connect(url: string) {
console.log(`Connecting to ${url}`);
}
dispose(): void {
console.log('Network service disposed');
}
}
class NetworkPlugin implements IPlugin {
readonly name = 'network-plugin';
readonly version = '1.0.0';
install(core: Core, services: ServiceContainer): void {
// Register network service
services.registerSingleton(NetworkService);
// Resolve and use service
const network = services.resolve(NetworkService);
network.connect('ws://localhost:8080');
}
uninstall(): void {
// Service container automatically calls service's dispose method
}
}
```
### Adding Systems
Plugins can add custom systems to scenes:
```typescript
import { EntitySystem, Matcher } from '@esengine/ecs-framework';
class PhysicsSystem extends EntitySystem {
constructor() {
super(Matcher.empty().all(PhysicsBody));
}
protected process(entities: readonly Entity[]): void {
// Physics simulation logic
}
}
class PhysicsPlugin implements IPlugin {
readonly name = 'physics-plugin';
readonly version = '1.0.0';
private physicsSystem?: PhysicsSystem;
install(core: Core, services: ServiceContainer): void {
const scene = core.scene;
if (scene) {
this.physicsSystem = new PhysicsSystem();
scene.addSystem(this.physicsSystem);
}
}
uninstall(): void {
// Remove system
if (this.physicsSystem) {
const scene = Core.scene;
if (scene) {
scene.removeSystem(this.physicsSystem);
}
this.physicsSystem = undefined;
}
}
}
```
## Dependency Management
### Declaring Dependencies
Plugins can declare dependencies on other plugins:
```typescript
class AdvancedPhysicsPlugin implements IPlugin {
readonly name = 'advanced-physics';
readonly version = '2.0.0';
// Declare dependency on base physics plugin
readonly dependencies = ['physics-plugin'] as const;
install(core: Core, services: ServiceContainer): void {
// Can safely use services provided by physics-plugin
const physicsService = services.resolve(PhysicsService);
// ...
}
uninstall(): void {
// Cleanup
}
}
```
### Dependency Checking
The framework automatically checks dependency relationships and throws errors if dependencies are unmet:
```typescript
// Error: physics-plugin not installed
try {
await Core.installPlugin(new AdvancedPhysicsPlugin());
} catch (error) {
console.error(error); // Plugin advanced-physics has unmet dependencies: physics-plugin
}
// Correct: Install dependency first
await Core.installPlugin(new PhysicsPlugin());
await Core.installPlugin(new AdvancedPhysicsPlugin());
```
### Uninstall Order
The framework checks dependency relationships, preventing uninstallation of plugins that other plugins depend on:
```typescript
await Core.installPlugin(new PhysicsPlugin());
await Core.installPlugin(new AdvancedPhysicsPlugin());
// Error: physics-plugin is required by advanced-physics
try {
await Core.uninstallPlugin('physics-plugin');
} catch (error) {
console.error(error); // Cannot uninstall plugin physics-plugin: it is required by advanced-physics
}
// Correct: Uninstall dependent plugin first
await Core.uninstallPlugin('advanced-physics');
await Core.uninstallPlugin('physics-plugin');
```
## Plugin Management
### Managing via Core
The Core class provides convenient plugin management methods:
```typescript
// Install plugin
await Core.installPlugin(myPlugin);
// Uninstall plugin
await Core.uninstallPlugin('plugin-name');
// Check if plugin is installed
if (Core.isPluginInstalled('plugin-name')) {
// ...
}
// Get plugin instance
const plugin = Core.getPlugin('plugin-name');
```
### Managing via PluginManager
You can also use the PluginManager service directly:
```typescript
const pluginManager = Core.services.resolve(PluginManager);
// Get all plugins
const allPlugins = pluginManager.getAllPlugins();
console.log(`Total plugins: ${allPlugins.length}`);
// Get plugin metadata
const metadata = pluginManager.getMetadata('my-plugin');
if (metadata) {
console.log(`State: ${metadata.state}`);
console.log(`Installed at: ${new Date(metadata.installedAt!)}`);
}
// Get all plugin metadata
const allMetadata = pluginManager.getAllMetadata();
for (const meta of allMetadata) {
console.log(`${meta.name} v${meta.version} - ${meta.state}`);
}
```
## Practical Plugin Examples
### Network Sync Plugin
```typescript
import { IPlugin, IService, Core, ServiceContainer } from '@esengine/ecs-framework';
class NetworkSyncService implements IService {
private ws?: WebSocket;
connect(url: string) {
this.ws = new WebSocket(url);
this.ws.onmessage = (event) => {
const data = JSON.parse(event.data);
this.handleMessage(data);
};
}
private handleMessage(data: any) {
// Handle network messages
}
dispose(): void {
if (this.ws) {
this.ws.close();
this.ws = undefined;
}
}
}
class NetworkSyncPlugin implements IPlugin {
readonly name = 'network-sync';
readonly version = '1.0.0';
install(core: Core, services: ServiceContainer): void {
// Register network service
services.registerSingleton(NetworkSyncService);
// Auto connect
const network = services.resolve(NetworkSyncService);
network.connect('ws://localhost:8080');
}
uninstall(): void {
// Service will auto dispose
}
}
```
### Performance Analysis Plugin
```typescript
class PerformanceAnalysisPlugin implements IPlugin {
readonly name = 'performance-analysis';
readonly version = '1.0.0';
private frameCount = 0;
private totalTime = 0;
install(core: Core, services: ServiceContainer): void {
const monitor = services.resolve(PerformanceMonitor);
monitor.enable();
// Periodically output performance report
const timer = services.resolve(TimerManager);
timer.schedule(5.0, true, null, () => {
this.printReport(monitor);
});
}
uninstall(): void {
// Cleanup
}
private printReport(monitor: PerformanceMonitor) {
console.log('=== Performance Report ===');
console.log(`FPS: ${monitor.getFPS()}`);
console.log(`Memory: ${monitor.getMemoryUsage()} MB`);
}
}
```
## Best Practices
### Naming Convention
- Plugin names use lowercase letters and hyphens: `my-awesome-plugin`
- Version numbers follow semantic versioning: `1.0.0`
```typescript
class MyPlugin implements IPlugin {
readonly name = 'my-awesome-plugin'; // Good
readonly version = '1.0.0'; // Good
}
```
### Resource Cleanup
Always clean up all resources created by the plugin in `uninstall`:
```typescript
class MyPlugin implements IPlugin {
readonly name = 'my-plugin';
readonly version = '1.0.0';
private timerId?: number;
private listener?: () => void;
install(core: Core, services: ServiceContainer): void {
// Add timer
this.timerId = setInterval(() => {
// ...
}, 1000);
// Add event listener
this.listener = () => {};
window.addEventListener('resize', this.listener);
}
uninstall(): void {
// Clean up timer
if (this.timerId) {
clearInterval(this.timerId);
this.timerId = undefined;
}
// Remove event listener
if (this.listener) {
window.removeEventListener('resize', this.listener);
this.listener = undefined;
}
}
}
```
### Error Handling
Handle errors properly in plugins to avoid affecting the entire application:
```typescript
class MyPlugin implements IPlugin {
readonly name = 'my-plugin';
readonly version = '1.0.0';
async install(core: Core, services: ServiceContainer): Promise<void> {
try {
// Operation that might fail
await this.loadConfig();
} catch (error) {
console.error('Failed to load plugin config:', error);
throw error; // Re-throw to let framework know installation failed
}
}
async uninstall(): Promise<void> {
try {
await this.cleanup();
} catch (error) {
console.error('Failed to cleanup plugin:', error);
// Cleanup failure shouldn't block uninstall
}
}
private async loadConfig() {
// Load configuration
}
private async cleanup() {
// Cleanup
}
}
```
### Configuration
Allow users to configure plugin behavior:
```typescript
interface NetworkPluginConfig {
serverUrl: string;
autoReconnect: boolean;
timeout: number;
}
class NetworkPlugin implements IPlugin {
readonly name = 'network-plugin';
readonly version = '1.0.0';
constructor(private config: NetworkPluginConfig) {}
install(core: Core, services: ServiceContainer): void {
const network = new NetworkService(this.config);
services.registerInstance(NetworkService, network);
}
uninstall(): void {
// Cleanup
}
}
// Usage
const plugin = new NetworkPlugin({
serverUrl: 'ws://localhost:8080',
autoReconnect: true,
timeout: 5000
});
await Core.installPlugin(plugin);
```
## Common Issues
### Plugin Installation Failed
**Problem**: Plugin throws error during installation
**Causes**:
- Dependencies not met
- Exception in install method
- Service registration conflict
**Solutions**:
1. Check if dependencies are installed
2. Check error logs
3. Ensure service names don't conflict
### Plugin Still Has Side Effects After Uninstall
**Problem**: After uninstalling plugin, plugin functionality is still running
**Cause**: Resources not properly cleaned up in uninstall method
**Solution**: Ensure cleanup in uninstall:
- Timers
- Event listeners
- WebSocket connections
- System references
### When to Use Plugins
**Good for plugins**:
- Optional features (debug tools, performance analysis)
- Third-party integrations (network libraries, physics engines)
- Functionality modules reused across projects
**Not suitable for plugins**:
- Core game logic
- Simple utility classes
- Project-specific features
## Related Links
- [Service Container](./service-container/) - Using service container in plugins
- [System Architecture](./system/) - Adding systems in plugins
- [Quick Start](./getting-started/) - Core initialization and basic usage

View File

@@ -0,0 +1,151 @@
---
title: "Best Practices"
description: "Plugin design guidelines and common issues"
---
## Naming Convention
```typescript
class MyPlugin implements IPlugin {
// Use lowercase letters and hyphens
readonly name = 'my-awesome-plugin'; // OK
// Follow semantic versioning
readonly version = '1.0.0'; // OK
}
```
## Resource Cleanup
Always clean up all resources created by the plugin in `uninstall`:
```typescript
class MyPlugin implements IPlugin {
readonly name = 'my-plugin';
readonly version = '1.0.0';
private timerId?: number;
private listener?: () => void;
install(core: Core, services: ServiceContainer): void {
// Add timer
this.timerId = setInterval(() => {
// ...
}, 1000);
// Add event listener
this.listener = () => {};
window.addEventListener('resize', this.listener);
}
uninstall(): void {
// Clear timer
if (this.timerId) {
clearInterval(this.timerId);
this.timerId = undefined;
}
// Remove event listener
if (this.listener) {
window.removeEventListener('resize', this.listener);
this.listener = undefined;
}
}
}
```
## Error Handling
```typescript
class MyPlugin implements IPlugin {
readonly name = 'my-plugin';
readonly version = '1.0.0';
async install(core: Core, services: ServiceContainer): Promise<void> {
try {
await this.loadConfig();
} catch (error) {
console.error('Failed to load plugin config:', error);
throw error; // Re-throw to let framework know installation failed
}
}
async uninstall(): Promise<void> {
try {
await this.cleanup();
} catch (error) {
console.error('Failed to cleanup plugin:', error);
// Don't block uninstall even if cleanup fails
}
}
private async loadConfig() { /* ... */ }
private async cleanup() { /* ... */ }
}
```
## Configuration
Allow users to configure plugin behavior:
```typescript
interface NetworkPluginConfig {
serverUrl: string;
autoReconnect: boolean;
timeout: number;
}
class NetworkPlugin implements IPlugin {
readonly name = 'network-plugin';
readonly version = '1.0.0';
constructor(private config: NetworkPluginConfig) {}
install(core: Core, services: ServiceContainer): void {
const network = new NetworkService(this.config);
services.registerInstance(NetworkService, network);
}
uninstall(): void {}
}
// Usage
const plugin = new NetworkPlugin({
serverUrl: 'ws://localhost:8080',
autoReconnect: true,
timeout: 5000
});
await Core.installPlugin(plugin);
```
## Common Issues
### Plugin Installation Failed
**Causes**:
- Dependencies not satisfied
- Exception in install method
- Service registration conflict
**Solutions**:
1. Check if dependencies are installed
2. Review error logs
3. Ensure service names don't conflict
### Side Effects After Uninstall
**Cause**: Resources not properly cleaned in uninstall
**Solution**: Ensure uninstall cleans up:
- Timers
- Event listeners
- WebSocket connections
- System references
### When to Use Plugins
| Good for Plugins | Not Good for Plugins |
|------------------|---------------------|
| Optional features (debug tools, profiling) | Core game logic |
| Third-party integration (network libs, physics) | Simple utilities |
| Cross-project reusable modules | Project-specific features |

View File

@@ -0,0 +1,106 @@
---
title: "Dependency Management"
description: "Declare and check plugin dependencies"
---
## Declaring Dependencies
Plugins can declare dependencies on other plugins:
```typescript
class AdvancedPhysicsPlugin implements IPlugin {
readonly name = 'advanced-physics';
readonly version = '2.0.0';
// Declare dependency on base physics plugin
readonly dependencies = ['physics-plugin'] as const;
install(core: Core, services: ServiceContainer): void {
// Can safely use services from physics-plugin
const physicsService = services.resolve(PhysicsService);
// ...
}
uninstall(): void {
// Cleanup
}
}
```
## Dependency Checking
The framework automatically checks dependencies and throws an error if not satisfied:
```typescript
// Error: physics-plugin not installed
try {
await Core.installPlugin(new AdvancedPhysicsPlugin());
} catch (error) {
console.error(error);
// Plugin advanced-physics has unmet dependencies: physics-plugin
}
// Correct: install dependency first
await Core.installPlugin(new PhysicsPlugin());
await Core.installPlugin(new AdvancedPhysicsPlugin());
```
## Uninstall Order
The framework checks dependencies to prevent uninstalling plugins required by others:
```typescript
await Core.installPlugin(new PhysicsPlugin());
await Core.installPlugin(new AdvancedPhysicsPlugin());
// Error: physics-plugin is required by advanced-physics
try {
await Core.uninstallPlugin('physics-plugin');
} catch (error) {
console.error(error);
// Cannot uninstall plugin physics-plugin: it is required by advanced-physics
}
// Correct: uninstall dependent plugin first
await Core.uninstallPlugin('advanced-physics');
await Core.uninstallPlugin('physics-plugin');
```
## Dependency Graph Example
```
physics-plugin (base)
advanced-physics (depends on physics-plugin)
game-physics (depends on advanced-physics)
```
Install order: `physics-plugin``advanced-physics``game-physics`
Uninstall order: `game-physics``advanced-physics``physics-plugin`
## Multiple Dependencies
```typescript
class GamePlugin implements IPlugin {
readonly name = 'game';
readonly version = '1.0.0';
// Declare multiple dependencies
readonly dependencies = [
'physics-plugin',
'network-plugin',
'audio-plugin'
] as const;
install(core: Core, services: ServiceContainer): void {
// All dependencies are available
const physics = services.resolve(PhysicsService);
const network = services.resolve(NetworkService);
const audio = services.resolve(AudioService);
}
uninstall(): void {}
}
```

View File

@@ -0,0 +1,139 @@
---
title: "Plugin Development"
description: "IPlugin interface and lifecycle"
---
## IPlugin Interface
All plugins must implement the `IPlugin` interface:
```typescript
export interface IPlugin {
// Unique plugin name
readonly name: string;
// Plugin version (semver recommended)
readonly version: string;
// Dependencies on other plugins (optional)
readonly dependencies?: readonly string[];
// Called when plugin is installed
install(core: Core, services: ServiceContainer): void | Promise<void>;
// Called when plugin is uninstalled
uninstall(): void | Promise<void>;
}
```
## Lifecycle Methods
### install Method
Called when the plugin is installed, used for initialization:
```typescript
class MyPlugin implements IPlugin {
readonly name = 'my-plugin';
readonly version = '1.0.0';
install(core: Core, services: ServiceContainer): void {
// 1. Register services
services.registerSingleton(MyService);
// 2. Access current scene
const scene = core.scene;
if (scene) {
// 3. Add systems
scene.addSystem(new MySystem());
}
// 4. Other initialization
console.log('Plugin initialized');
}
uninstall(): void {
// Cleanup logic
}
}
```
### uninstall Method
Called when the plugin is uninstalled, used for cleanup:
```typescript
class MyPlugin implements IPlugin {
readonly name = 'my-plugin';
readonly version = '1.0.0';
private myService?: MyService;
install(core: Core, services: ServiceContainer): void {
this.myService = new MyService();
services.registerInstance(MyService, this.myService);
}
uninstall(): void {
// Cleanup service
if (this.myService) {
this.myService.dispose();
this.myService = undefined;
}
// Remove event listeners
// Release other resources
}
}
```
## Async Plugins
Both `install` and `uninstall` methods support async:
```typescript
class AsyncPlugin implements IPlugin {
readonly name = 'async-plugin';
readonly version = '1.0.0';
async install(core: Core, services: ServiceContainer): Promise<void> {
// Async load resources
const config = await fetch('/plugin-config.json').then(r => r.json());
// Initialize service with loaded config
const service = new MyService(config);
services.registerInstance(MyService, service);
}
async uninstall(): Promise<void> {
// Async cleanup
await this.saveState();
}
private async saveState() {
// Save plugin state
}
}
// Usage
await Core.installPlugin(new AsyncPlugin());
```
## Lifecycle Flow
```
Install: Core.installPlugin(plugin)
Dependency check: Verify dependencies are satisfied
Call install(): Register services, add systems
State update: Mark as installed
Uninstall: Core.uninstallPlugin(name)
Dependency check: Verify not required by other plugins
Call uninstall(): Cleanup resources
State update: Remove from plugin list
```

View File

@@ -0,0 +1,188 @@
---
title: "Example Plugins"
description: "Complete plugin implementation examples"
---
## Network Sync Plugin
```typescript
import { IPlugin, IService, Core, ServiceContainer } from '@esengine/ecs-framework';
class NetworkSyncService implements IService {
private ws?: WebSocket;
connect(url: string) {
this.ws = new WebSocket(url);
this.ws.onmessage = (event) => {
const data = JSON.parse(event.data);
this.handleMessage(data);
};
}
private handleMessage(data: any) {
// Handle network messages
}
dispose(): void {
if (this.ws) {
this.ws.close();
this.ws = undefined;
}
}
}
class NetworkSyncPlugin implements IPlugin {
readonly name = 'network-sync';
readonly version = '1.0.0';
install(core: Core, services: ServiceContainer): void {
// Register network service
services.registerSingleton(NetworkSyncService);
// Auto connect
const network = services.resolve(NetworkSyncService);
network.connect('ws://localhost:8080');
}
uninstall(): void {
// Service will auto-dispose
}
}
```
## Performance Analysis Plugin
```typescript
class PerformanceAnalysisPlugin implements IPlugin {
readonly name = 'performance-analysis';
readonly version = '1.0.0';
private frameCount = 0;
private totalTime = 0;
install(core: Core, services: ServiceContainer): void {
const monitor = services.resolve(PerformanceMonitor);
monitor.enable();
// Periodic performance report
const timer = services.resolve(TimerManager);
timer.schedule(5.0, true, null, () => {
this.printReport(monitor);
});
}
uninstall(): void {
// Cleanup
}
private printReport(monitor: PerformanceMonitor) {
console.log('=== Performance Report ===');
console.log(`FPS: ${monitor.getFPS()}`);
console.log(`Memory: ${monitor.getMemoryUsage()} MB`);
}
}
```
## Debug Tools Plugin
```typescript
class DebugToolsPlugin implements IPlugin {
readonly name = 'debug-tools';
readonly version = '1.0.0';
private debugUI?: DebugUI;
install(core: Core, services: ServiceContainer): void {
// Create debug UI
this.debugUI = new DebugUI();
this.debugUI.mount(document.body);
// Register hotkey
window.addEventListener('keydown', this.handleKeyDown);
// Add debug system
const scene = core.scene;
if (scene) {
scene.addSystem(new DebugRenderSystem());
}
}
uninstall(): void {
// Remove UI
if (this.debugUI) {
this.debugUI.unmount();
this.debugUI = undefined;
}
// Remove event listener
window.removeEventListener('keydown', this.handleKeyDown);
}
private handleKeyDown = (e: KeyboardEvent) => {
if (e.key === 'F12') {
this.debugUI?.toggle();
}
};
}
```
## Audio Plugin
```typescript
class AudioPlugin implements IPlugin {
readonly name = 'audio';
readonly version = '1.0.0';
constructor(private config: { volume: number }) {}
install(core: Core, services: ServiceContainer): void {
const audioService = new AudioService(this.config);
services.registerInstance(AudioService, audioService);
// Add audio system
const scene = core.scene;
if (scene) {
scene.addSystem(new AudioSystem());
}
}
uninstall(): void {
// Stop all audio
const audio = Core.services.resolve(AudioService);
audio.stopAll();
}
}
// Usage
await Core.installPlugin(new AudioPlugin({ volume: 0.8 }));
```
## Input Manager Plugin
```typescript
class InputPlugin implements IPlugin {
readonly name = 'input';
readonly version = '1.0.0';
private inputManager?: InputManager;
install(core: Core, services: ServiceContainer): void {
this.inputManager = new InputManager();
services.registerInstance(InputManager, this.inputManager);
// Bind default keys
this.inputManager.bind('jump', ['Space', 'KeyW']);
this.inputManager.bind('attack', ['MouseLeft', 'KeyJ']);
// Add input system
const scene = core.scene;
if (scene) {
scene.addSystem(new InputSystem());
}
}
uninstall(): void {
if (this.inputManager) {
this.inputManager.dispose();
this.inputManager = undefined;
}
}
}
```

View File

@@ -0,0 +1,85 @@
---
title: "Plugin System"
description: "Extend ECS Framework in a modular way"
---
The plugin system allows you to extend ECS Framework functionality in a modular way. Through plugins, you can encapsulate specific features (like network sync, physics engines, debug tools) and reuse them across multiple projects.
## What is a Plugin
A plugin is a class that implements the `IPlugin` interface and can be dynamically installed into the framework at runtime. Plugins can:
- Register custom services to the service container
- Add systems to scenes
- Register custom components
- Extend framework functionality
## Plugin Benefits
| Benefit | Description |
|---------|-------------|
| **Modular** | Encapsulate functionality as independent modules |
| **Reusable** | Use the same plugin across multiple projects |
| **Decoupled** | Separate core framework from extensions |
| **Hot-swappable** | Dynamically install and uninstall at runtime |
## Quick Start
### Create a Plugin
```typescript
import { IPlugin, Core, ServiceContainer } from '@esengine/ecs-framework';
class DebugPlugin implements IPlugin {
readonly name = 'debug-plugin';
readonly version = '1.0.0';
install(core: Core, services: ServiceContainer): void {
console.log('Debug plugin installed');
}
uninstall(): void {
console.log('Debug plugin uninstalled');
}
}
```
### Install a Plugin
```typescript
import { Core } from '@esengine/ecs-framework';
Core.create({ debug: true });
// Install plugin
await Core.installPlugin(new DebugPlugin());
// Check if plugin is installed
if (Core.isPluginInstalled('debug-plugin')) {
console.log('Debug plugin is running');
}
```
### Uninstall a Plugin
```typescript
await Core.uninstallPlugin('debug-plugin');
```
### Get Plugin Instance
```typescript
const plugin = Core.getPlugin('debug-plugin');
if (plugin) {
console.log(`Plugin version: ${plugin.version}`);
}
```
## Next Steps
- [Development](./development/) - IPlugin interface and lifecycle
- [Services & Systems](./services-systems/) - Register services and add systems
- [Dependencies](./dependencies/) - Declare and check dependencies
- [Management](./management/) - Manage via Core and PluginManager
- [Examples](./examples/) - Complete examples
- [Best Practices](./best-practices/) - Design guidelines

View File

@@ -0,0 +1,93 @@
---
title: "Plugin Management"
description: "Manage plugins via Core and PluginManager"
---
## Via Core
Core class provides convenient plugin management methods:
```typescript
// Install plugin
await Core.installPlugin(myPlugin);
// Uninstall plugin
await Core.uninstallPlugin('plugin-name');
// Check if plugin is installed
if (Core.isPluginInstalled('plugin-name')) {
// ...
}
// Get plugin instance
const plugin = Core.getPlugin('plugin-name');
```
## Via PluginManager
You can also use the PluginManager service directly:
```typescript
const pluginManager = Core.services.resolve(PluginManager);
// Get all plugins
const allPlugins = pluginManager.getAllPlugins();
console.log(`Total plugins: ${allPlugins.length}`);
// Get plugin metadata
const metadata = pluginManager.getMetadata('my-plugin');
if (metadata) {
console.log(`State: ${metadata.state}`);
console.log(`Installed at: ${new Date(metadata.installedAt!)}`);
}
// Get all plugin metadata
const allMetadata = pluginManager.getAllMetadata();
for (const meta of allMetadata) {
console.log(`${meta.name} v${meta.version} - ${meta.state}`);
}
```
## API Reference
### Core Static Methods
| Method | Description |
|--------|-------------|
| `installPlugin(plugin)` | Install plugin |
| `uninstallPlugin(name)` | Uninstall plugin |
| `isPluginInstalled(name)` | Check if installed |
| `getPlugin(name)` | Get plugin instance |
### PluginManager Methods
| Method | Description |
|--------|-------------|
| `getAllPlugins()` | Get all plugins |
| `getMetadata(name)` | Get plugin metadata |
| `getAllMetadata()` | Get all plugin metadata |
## Plugin States
```typescript
enum PluginState {
Pending = 'pending',
Installing = 'installing',
Installed = 'installed',
Uninstalling = 'uninstalling',
Failed = 'failed'
}
```
## Metadata Information
```typescript
interface PluginMetadata {
name: string;
version: string;
state: PluginState;
dependencies?: string[];
installedAt?: number;
error?: Error;
}
```

View File

@@ -0,0 +1,133 @@
---
title: "Services & Systems"
description: "Register services and add systems in plugins"
---
## Registering Services
Plugins can register their own services to the service container:
```typescript
import { IService } from '@esengine/ecs-framework';
class NetworkService implements IService {
connect(url: string) {
console.log(`Connecting to ${url}`);
}
dispose(): void {
console.log('Network service disposed');
}
}
class NetworkPlugin implements IPlugin {
readonly name = 'network-plugin';
readonly version = '1.0.0';
install(core: Core, services: ServiceContainer): void {
// Register network service
services.registerSingleton(NetworkService);
// Resolve and use service
const network = services.resolve(NetworkService);
network.connect('ws://localhost:8080');
}
uninstall(): void {
// Service container will auto-call service's dispose method
}
}
```
## Service Registration Methods
| Method | Description |
|--------|-------------|
| `registerSingleton(Type)` | Register singleton service |
| `registerInstance(Type, instance)` | Register existing instance |
| `registerTransient(Type)` | Create new instance per resolve |
## Adding Systems
Plugins can add custom systems to scenes:
```typescript
import { EntitySystem, Matcher } from '@esengine/ecs-framework';
class PhysicsSystem extends EntitySystem {
constructor() {
super(Matcher.empty().all(PhysicsBody));
}
protected process(entities: readonly Entity[]): void {
// Physics simulation logic
}
}
class PhysicsPlugin implements IPlugin {
readonly name = 'physics-plugin';
readonly version = '1.0.0';
private physicsSystem?: PhysicsSystem;
install(core: Core, services: ServiceContainer): void {
const scene = core.scene;
if (scene) {
this.physicsSystem = new PhysicsSystem();
scene.addSystem(this.physicsSystem);
}
}
uninstall(): void {
// Remove system
if (this.physicsSystem) {
const scene = Core.scene;
if (scene) {
scene.removeSystem(this.physicsSystem);
}
this.physicsSystem = undefined;
}
}
}
```
## Combined Usage
```typescript
class GamePlugin implements IPlugin {
readonly name = 'game-plugin';
readonly version = '1.0.0';
private systems: EntitySystem[] = [];
install(core: Core, services: ServiceContainer): void {
// 1. Register services
services.registerSingleton(ScoreService);
services.registerSingleton(AudioService);
// 2. Add systems
const scene = core.scene;
if (scene) {
const systems = [
new InputSystem(),
new MovementSystem(),
new ScoringSystem()
];
systems.forEach(system => {
scene.addSystem(system);
this.systems.push(system);
});
}
}
uninstall(): void {
// Remove all systems
const scene = Core.scene;
if (scene) {
this.systems.forEach(system => {
scene.removeSystem(system);
});
}
this.systems = [];
}
}
```

View File

@@ -1,368 +0,0 @@
---
title: "scene"
---
# Scene Management
In the ECS architecture, a Scene is a container for the game world, responsible for managing the lifecycle of entities, systems, and components. Scenes provide a complete ECS runtime environment.
## Basic Concepts
Scene is the core container of the ECS framework, providing:
- Entity creation, management, and destruction
- System registration and execution scheduling
- Component storage and querying
- Event system support
- Performance monitoring and debugging information
## Scene Management Options
ECS Framework provides two scene management approaches:
1. **[SceneManager](./scene-manager)** - Suitable for 95% of game applications
- Single-player games, simple multiplayer games, mobile games
- Lightweight, simple and intuitive API
- Supports scene transitions
2. **[WorldManager](./world-manager)** - Suitable for advanced multi-world isolation scenarios
- MMO game servers, game room systems
- Multi-World management, each World can contain multiple scenes
- Completely isolated independent environments
This document focuses on the usage of the Scene class itself. For detailed information about scene managers, please refer to the corresponding documentation.
## Creating a Scene
### Inheriting the Scene Class
**Recommended: Inherit the Scene class to create custom scenes**
```typescript
import { Scene, EntitySystem } from '@esengine/ecs-framework';
class GameScene extends Scene {
protected initialize(): void {
// Set scene name
this.name = "GameScene";
// Add systems
this.addSystem(new MovementSystem());
this.addSystem(new RenderSystem());
this.addSystem(new PhysicsSystem());
// Create initial entities
this.createInitialEntities();
}
private createInitialEntities(): void {
// Create player
const player = this.createEntity("Player");
player.addComponent(new Position(400, 300));
player.addComponent(new Health(100));
player.addComponent(new PlayerController());
// Create enemies
for (let i = 0; i < 5; i++) {
const enemy = this.createEntity(`Enemy_${i}`);
enemy.addComponent(new Position(Math.random() * 800, Math.random() * 600));
enemy.addComponent(new Health(50));
enemy.addComponent(new EnemyAI());
}
}
public onStart(): void {
console.log("Game scene started");
// Logic when scene starts
}
public unload(): void {
console.log("Game scene unloaded");
// Cleanup logic when scene unloads
}
}
```
### Using Scene Configuration
```typescript
import { ISceneConfig } from '@esengine/ecs-framework';
const config: ISceneConfig = {
name: "MainGame",
enableEntityDirectUpdate: false
};
class ConfiguredScene extends Scene {
constructor() {
super(config);
}
}
```
## Scene Lifecycle
Scene provides complete lifecycle management:
```typescript
class ExampleScene extends Scene {
protected initialize(): void {
// Scene initialization: setup systems and initial entities
console.log("Scene initializing");
}
public onStart(): void {
// Scene starts running: game logic begins execution
console.log("Scene starting");
}
public unload(): void {
// Scene unloading: cleanup resources
console.log("Scene unloading");
}
}
// Using scenes (lifecycle automatically managed by framework)
const scene = new ExampleScene();
// Scene's initialize(), begin(), update(), end() are automatically called by the framework
```
**Lifecycle Methods**:
1. `initialize()` - Scene initialization, setup systems and initial entities
2. `begin()` / `onStart()` - Scene starts running
3. `update()` - Per-frame update (called by scene manager)
4. `end()` / `unload()` - Scene unloading, cleanup resources
## Entity Management
### Creating Entities
```typescript
class EntityScene extends Scene {
createGameEntities(): void {
// Create single entity
const player = this.createEntity("Player");
// Batch create entities (high performance)
const bullets = this.createEntities(100, "Bullet");
// Add components to batch-created entities
bullets.forEach((bullet, index) => {
bullet.addComponent(new Position(index * 10, 100));
bullet.addComponent(new Velocity(Math.random() * 200 - 100, -300));
});
}
}
```
### Finding Entities
```typescript
class SearchScene extends Scene {
findEntities(): void {
// Find by name
const player = this.findEntity("Player");
const player2 = this.getEntityByName("Player"); // Alias method
// Find by ID
const entity = this.findEntityById(123);
// Find by tag
const enemies = this.findEntitiesByTag(2);
const enemies2 = this.getEntitiesByTag(2); // Alias method
if (player) {
console.log(`Found player: ${player.name}`);
}
console.log(`Found ${enemies.length} enemies`);
}
}
```
### Destroying Entities
```typescript
class DestroyScene extends Scene {
cleanupEntities(): void {
// Destroy all entities
this.destroyAllEntities();
// Single entity destruction through the entity itself
const enemy = this.findEntity("Enemy_1");
if (enemy) {
enemy.destroy(); // Entity is automatically removed from the scene
}
}
}
```
## System Management
### Adding and Removing Systems
```typescript
class SystemScene extends Scene {
protected initialize(): void {
// Add systems
const movementSystem = new MovementSystem();
this.addSystem(movementSystem);
// Set system update order
movementSystem.updateOrder = 1;
// Add more systems
this.addSystem(new PhysicsSystem());
this.addSystem(new RenderSystem());
}
public removeUnnecessarySystems(): void {
// Get system
const physicsSystem = this.getEntityProcessor(PhysicsSystem);
// Remove system
if (physicsSystem) {
this.removeSystem(physicsSystem);
}
}
}
```
## Event System
Scene has a built-in type-safe event system:
```typescript
class EventScene extends Scene {
protected initialize(): void {
// Listen to events
this.eventSystem.on('player_died', this.onPlayerDied.bind(this));
this.eventSystem.on('enemy_spawned', this.onEnemySpawned.bind(this));
this.eventSystem.on('level_complete', this.onLevelComplete.bind(this));
}
private onPlayerDied(data: any): void {
console.log('Player died event');
// Handle player death
}
private onEnemySpawned(data: any): void {
console.log('Enemy spawned event');
// Handle enemy spawn
}
private onLevelComplete(data: any): void {
console.log('Level complete event');
// Handle level completion
}
public triggerGameEvent(): void {
// Send event (synchronous)
this.eventSystem.emitSync('custom_event', {
message: "This is a custom event",
timestamp: Date.now()
});
// Send event (asynchronous)
this.eventSystem.emit('async_event', {
data: "Async event data"
});
}
}
```
## Best Practices
### 1. Scene Responsibility Separation
```typescript
// Good scene design - clear responsibilities
class MenuScene extends Scene {
// Only handles menu-related logic
}
class GameScene extends Scene {
// Only handles gameplay logic
}
class InventoryScene extends Scene {
// Only handles inventory logic
}
// Avoid this design - mixed responsibilities
class MegaScene extends Scene {
// Contains menu, game, inventory, and all other logic
}
```
### 2. Proper System Organization
```typescript
class OrganizedScene extends Scene {
protected initialize(): void {
// Add systems by function and dependencies
this.addInputSystems();
this.addLogicSystems();
this.addRenderSystems();
}
private addInputSystems(): void {
this.addSystem(new InputSystem());
}
private addLogicSystems(): void {
this.addSystem(new MovementSystem());
this.addSystem(new PhysicsSystem());
this.addSystem(new CollisionSystem());
}
private addRenderSystems(): void {
this.addSystem(new RenderSystem());
this.addSystem(new UISystem());
}
}
```
### 3. Resource Management
```typescript
class ResourceScene extends Scene {
private textures: Map<string, any> = new Map();
private sounds: Map<string, any> = new Map();
protected initialize(): void {
this.loadResources();
}
private loadResources(): void {
// Load resources needed by the scene
this.textures.set('player', this.loadTexture('player.png'));
this.sounds.set('bgm', this.loadSound('bgm.mp3'));
}
public unload(): void {
// Cleanup resources
this.textures.clear();
this.sounds.clear();
console.log('Scene resources cleaned up');
}
private loadTexture(path: string): any {
// Load texture
return null;
}
private loadSound(path: string): any {
// Load sound
return null;
}
}
```
## Next Steps
- Learn about [SceneManager](./scene-manager) - Simple scene management for most games
- Learn about [WorldManager](./world-manager) - For scenarios requiring multi-world isolation
- Learn about [Persistent Entity](./persistent-entity) - Keep entities across scene transitions (v2.3.0+)
Scene is the core container of the ECS framework. Proper scene management makes your game architecture clearer, more modular, and easier to maintain.

View File

@@ -0,0 +1,179 @@
---
title: "Best Practices"
description: "Scene design patterns and complete examples"
---
## Scene Responsibility Separation
```typescript
// Good scene design - clear responsibilities
class MenuScene extends Scene {
// Only handles menu-related logic
}
class GameScene extends Scene {
// Only handles gameplay logic
}
class InventoryScene extends Scene {
// Only handles inventory logic
}
// Avoid this design - mixed responsibilities
class MegaScene extends Scene {
// Contains menu, game, inventory, and all other logic
}
```
## Resource Management
```typescript
class ResourceScene extends Scene {
private textures: Map<string, any> = new Map();
private sounds: Map<string, any> = new Map();
protected initialize(): void {
this.loadResources();
}
private loadResources(): void {
this.textures.set('player', this.loadTexture('player.png'));
this.sounds.set('bgm', this.loadSound('bgm.mp3'));
}
public unload(): void {
// Cleanup resources
this.textures.clear();
this.sounds.clear();
console.log('Scene resources cleaned up');
}
private loadTexture(path: string): any { return null; }
private loadSound(path: string): any { return null; }
}
```
## Initialization Order
```typescript
class ProperInitScene extends Scene {
protected initialize(): void {
// 1. First set scene configuration
this.name = "GameScene";
// 2. Then add systems (by dependency order)
this.addSystem(new InputSystem());
this.addSystem(new MovementSystem());
this.addSystem(new PhysicsSystem());
this.addSystem(new RenderSystem());
// 3. Create entities last
this.createEntities();
// 4. Setup event listeners
this.setupEvents();
}
private createEntities(): void { /* ... */ }
private setupEvents(): void { /* ... */ }
}
```
## Complete Example
```typescript
import { Scene, EntitySystem, Entity, Matcher } from '@esengine/ecs-framework';
// Define components
class Transform {
constructor(public x: number, public y: number) {}
}
class Velocity {
constructor(public vx: number, public vy: number) {}
}
class Health {
constructor(public value: number) {}
}
// Define system
class MovementSystem extends EntitySystem {
constructor() {
super(Matcher.all(Transform, Velocity));
}
process(entities: readonly Entity[]): void {
for (const entity of entities) {
const transform = entity.getComponent(Transform);
const velocity = entity.getComponent(Velocity);
if (transform && velocity) {
transform.x += velocity.vx;
transform.y += velocity.vy;
}
}
}
}
// Define scene
class GameScene extends Scene {
protected initialize(): void {
this.name = "GameScene";
// Add systems
this.addSystem(new MovementSystem());
// Create player
const player = this.createEntity("Player");
player.addComponent(new Transform(400, 300));
player.addComponent(new Velocity(0, 0));
player.addComponent(new Health(100));
// Create enemies
for (let i = 0; i < 5; i++) {
const enemy = this.createEntity(`Enemy_${i}`);
enemy.addComponent(new Transform(
Math.random() * 800,
Math.random() * 600
));
enemy.addComponent(new Velocity(
Math.random() * 100 - 50,
Math.random() * 100 - 50
));
enemy.addComponent(new Health(50));
}
// Setup event listeners
this.eventSystem.on('player_died', () => {
console.log('Player died!');
});
}
public onStart(): void {
console.log('Game scene started');
}
public unload(): void {
console.log('Game scene unloaded');
this.eventSystem.clear();
}
}
// Use scene
import { Core, SceneManager } from '@esengine/ecs-framework';
Core.create({ debug: true });
const sceneManager = Core.services.resolve(SceneManager);
sceneManager.setScene(new GameScene());
```
## Design Principles
| Principle | Description |
|-----------|-------------|
| Single Responsibility | Each scene handles one game state |
| Resource Cleanup | Clean up all resources in `unload()` |
| System Order | Add systems: Input → Logic → Render |
| Event Decoupling | Use event system for scene communication |
| Layered Initialization | Config → Systems → Entities → Events |

View File

@@ -0,0 +1,124 @@
---
title: "Debugging & Monitoring"
description: "Scene statistics, performance monitoring and debugging"
---
Scene includes complete debugging and performance monitoring features.
## Scene Statistics
```typescript
class StatsScene extends Scene {
public showStats(): void {
const stats = this.getStats();
console.log(`Entity count: ${stats.entityCount}`);
console.log(`System count: ${stats.processorCount}`);
console.log('Component storage stats:', stats.componentStorageStats);
}
}
```
## Debug Information
```typescript
public showDebugInfo(): void {
const debugInfo = this.getDebugInfo();
console.log('Scene debug info:', debugInfo);
// Display all entity info
debugInfo.entities.forEach(entity => {
console.log(`Entity ${entity.name}(${entity.id}): ${entity.componentCount} components`);
console.log('Component types:', entity.componentTypes);
});
// Display all system info
debugInfo.processors.forEach(processor => {
console.log(`System ${processor.name}: processing ${processor.entityCount} entities`);
});
}
```
## Performance Monitoring
```typescript
class PerformanceScene extends Scene {
public showPerformance(): void {
// Get performance data
const perfData = this.performanceMonitor?.getPerformanceData();
if (perfData) {
console.log('FPS:', perfData.fps);
console.log('Frame time:', perfData.frameTime);
console.log('Entity update time:', perfData.entityUpdateTime);
console.log('System update time:', perfData.systemUpdateTime);
}
// Get performance report
const report = this.performanceMonitor?.generateReport();
if (report) {
console.log('Performance report:', report);
}
}
}
```
## API Reference
### getStats()
Returns scene statistics:
```typescript
interface SceneStats {
entityCount: number;
processorCount: number;
componentStorageStats: ComponentStorageStats;
}
```
### getDebugInfo()
Returns detailed debug information:
```typescript
interface DebugInfo {
entities: EntityDebugInfo[];
processors: ProcessorDebugInfo[];
}
interface EntityDebugInfo {
id: number;
name: string;
componentCount: number;
componentTypes: string[];
}
interface ProcessorDebugInfo {
name: string;
entityCount: number;
}
```
### performanceMonitor
Performance monitor interface:
```typescript
interface PerformanceMonitor {
getPerformanceData(): PerformanceData;
generateReport(): string;
}
interface PerformanceData {
fps: number;
frameTime: number;
entityUpdateTime: number;
systemUpdateTime: number;
}
```
## Debugging Tips
1. **Debug mode** - Enable with `Core.create({ debug: true })`
2. **Performance analysis** - Call `getStats()` periodically
3. **Memory monitoring** - Check `componentStorageStats` for issues
4. **System performance** - Use `performanceMonitor` to identify slow systems

View File

@@ -0,0 +1,125 @@
---
title: "Entity Management"
description: "Entity creation, finding and destruction in scenes"
---
Scene provides complete entity management APIs for creating, finding, and destroying entities.
## Creating Entities
### Single Entity
```typescript
class EntityScene extends Scene {
createGameEntities(): void {
// Create named entity
const player = this.createEntity("Player");
player.addComponent(new Position(400, 300));
player.addComponent(new Health(100));
}
}
```
### Batch Creation
```typescript
class EntityScene extends Scene {
createBullets(): void {
// Batch create entities (high performance)
const bullets = this.createEntities(100, "Bullet");
// Add components to batch-created entities
bullets.forEach((bullet, index) => {
bullet.addComponent(new Position(index * 10, 100));
bullet.addComponent(new Velocity(Math.random() * 200 - 100, -300));
});
}
}
```
## Finding Entities
### By Name
```typescript
// Find by name (returns first match)
const player = this.findEntity("Player");
const player2 = this.getEntityByName("Player"); // Alias
if (player) {
console.log(`Found player: ${player.name}`);
}
```
### By ID
```typescript
// Find by unique ID
const entity = this.findEntityById(123);
if (entity) {
console.log(`Found entity: ${entity.id}`);
}
```
### By Tag
```typescript
// Find by tag (returns array)
const enemies = this.findEntitiesByTag(2);
const enemies2 = this.getEntitiesByTag(2); // Alias
console.log(`Found ${enemies.length} enemies`);
```
## Destroying Entities
### Single Entity
```typescript
const enemy = this.findEntity("Enemy_1");
if (enemy) {
enemy.destroy(); // Entity is automatically removed from scene
}
```
### All Entities
```typescript
// Destroy all entities in scene
this.destroyAllEntities();
```
## Entity Queries
Scene provides a component query system:
```typescript
class QueryScene extends Scene {
protected initialize(): void {
// Create test entities
for (let i = 0; i < 10; i++) {
const entity = this.createEntity(`Entity_${i}`);
entity.addComponent(new Transform(i * 10, 0));
entity.addComponent(new Velocity(1, 0));
}
}
public queryEntities(): void {
// Query through QuerySystem
const entities = this.querySystem.query([Transform, Velocity]);
console.log(`Found ${entities.length} entities with Transform and Velocity`);
}
}
```
## API Reference
| Method | Returns | Description |
|--------|---------|-------------|
| `createEntity(name)` | `Entity` | Create single entity |
| `createEntities(count, prefix)` | `Entity[]` | Batch create entities |
| `findEntity(name)` | `Entity \| undefined` | Find by name |
| `findEntityById(id)` | `Entity \| undefined` | Find by ID |
| `findEntitiesByTag(tag)` | `Entity[]` | Find by tag |
| `destroyAllEntities()` | `void` | Destroy all entities |

View File

@@ -0,0 +1,122 @@
---
title: "Event System"
description: "Scene's built-in type-safe event system"
---
Scene includes a built-in type-safe event system for decoupled communication within scenes.
## Basic Usage
### Listening to Events
```typescript
class EventScene extends Scene {
protected initialize(): void {
// Listen to events
this.eventSystem.on('player_died', this.onPlayerDied.bind(this));
this.eventSystem.on('enemy_spawned', this.onEnemySpawned.bind(this));
this.eventSystem.on('level_complete', this.onLevelComplete.bind(this));
}
private onPlayerDied(data: any): void {
console.log('Player died event');
}
private onEnemySpawned(data: any): void {
console.log('Enemy spawned event');
}
private onLevelComplete(data: any): void {
console.log('Level complete event');
}
}
```
### Sending Events
```typescript
public triggerGameEvent(): void {
// Send event (synchronous)
this.eventSystem.emitSync('custom_event', {
message: "This is a custom event",
timestamp: Date.now()
});
// Send event (asynchronous)
this.eventSystem.emit('async_event', {
data: "Async event data"
});
}
```
## API Reference
| Method | Description |
|--------|-------------|
| `on(event, callback)` | Listen to event |
| `once(event, callback)` | Listen once (auto-unsubscribe) |
| `off(event, callback)` | Stop listening |
| `emitSync(event, data)` | Send event (synchronous) |
| `emit(event, data)` | Send event (asynchronous) |
| `clear()` | Clear all event listeners |
## Event Handling Patterns
### Centralized Event Management
```typescript
class EventHandlingScene extends Scene {
protected initialize(): void {
this.setupEventListeners();
}
private setupEventListeners(): void {
this.eventSystem.on('game_pause', this.onGamePause.bind(this));
this.eventSystem.on('game_resume', this.onGameResume.bind(this));
this.eventSystem.on('player_input', this.onPlayerInput.bind(this));
}
private onGamePause(): void {
// Pause game logic
this.systems.forEach(system => {
if (system instanceof GameLogicSystem) {
system.enabled = false;
}
});
}
private onGameResume(): void {
// Resume game logic
this.systems.forEach(system => {
if (system instanceof GameLogicSystem) {
system.enabled = true;
}
});
}
private onPlayerInput(data: any): void {
// Handle player input
}
}
```
### Cleanup Event Listeners
Clean up event listeners on scene unload to avoid memory leaks:
```typescript
public unload(): void {
// Clear all event listeners
this.eventSystem.clear();
}
```
## Use Cases
| Category | Example Events |
|----------|----------------|
| Game State | `game_start`, `game_pause`, `game_over` |
| Player Actions | `player_died`, `player_jump`, `player_attack` |
| Enemy Actions | `enemy_spawned`, `enemy_killed` |
| Level Progress | `level_start`, `level_complete` |
| UI Interaction | `button_click`, `menu_open` |

View File

@@ -0,0 +1,91 @@
---
title: "Scene"
description: "Core container of ECS framework, managing entity, system and component lifecycles"
---
In the ECS architecture, a Scene is a container for the game world, responsible for managing the lifecycle of entities, systems, and components.
## Core Features
Scene is the core container of the ECS framework, providing:
- Entity creation, management, and destruction
- System registration and execution scheduling
- Component storage and querying
- Event system support
- Performance monitoring and debugging information
## Scene Management Options
ECS Framework provides two scene management approaches:
| Manager | Use Case | Features |
|---------|----------|----------|
| **SceneManager** | 95% of games | Lightweight, scene transitions |
| **WorldManager** | MMO servers, room systems | Multi-World, full isolation |
## Quick Start
### Inherit Scene Class
```typescript
import { Scene, EntitySystem } from '@esengine/ecs-framework';
class GameScene extends Scene {
protected initialize(): void {
this.name = "GameScene";
// Add systems
this.addSystem(new MovementSystem());
this.addSystem(new RenderSystem());
// Create initial entities
const player = this.createEntity("Player");
player.addComponent(new Position(400, 300));
player.addComponent(new Health(100));
}
public onStart(): void {
console.log("Game scene started");
}
public unload(): void {
console.log("Game scene unloaded");
}
}
```
### Using Scene Configuration
```typescript
import { ISceneConfig } from '@esengine/ecs-framework';
const config: ISceneConfig = {
name: "MainGame",
enableEntityDirectUpdate: false
};
class ConfiguredScene extends Scene {
constructor() {
super(config);
}
}
```
### Running a Scene
```typescript
import { Core, SceneManager } from '@esengine/ecs-framework';
Core.create({ debug: true });
const sceneManager = Core.services.resolve(SceneManager);
sceneManager.setScene(new GameScene());
```
## Next Steps
- [Lifecycle](./lifecycle/) - Scene lifecycle methods
- [Entity Management](./entity-management/) - Create, find, destroy entities
- [System Management](./system-management/) - System control
- [Events](./events/) - Scene event communication
- [Debugging](./debugging/) - Performance and debugging
- [Best Practices](./best-practices/) - Scene design patterns

View File

@@ -0,0 +1,103 @@
---
title: "Scene Lifecycle"
description: "Scene lifecycle methods and execution order"
---
Scene provides complete lifecycle management for proper resource initialization and cleanup.
## Lifecycle Methods
```typescript
class ExampleScene extends Scene {
protected initialize(): void {
// 1. Scene initialization: setup systems and initial entities
console.log("Scene initializing");
}
public onStart(): void {
// 2. Scene starts running: game logic begins execution
console.log("Scene starting");
}
public update(deltaTime: number): void {
// 3. Per-frame update (called by scene manager)
}
public unload(): void {
// 4. Scene unloading: cleanup resources
console.log("Scene unloading");
}
}
```
## Execution Order
| Phase | Method | Description |
|-------|--------|-------------|
| Initialize | `initialize()` | Setup systems and initial entities |
| Start | `begin()` / `onStart()` | Scene starts running |
| Update | `update()` | Per-frame update (auto-called) |
| End | `end()` / `unload()` | Cleanup resources |
## Lifecycle Example
```typescript
class GameScene extends Scene {
private resourcesLoaded = false;
protected initialize(): void {
this.name = "GameScene";
// 1. Add systems (by dependency order)
this.addSystem(new InputSystem());
this.addSystem(new MovementSystem());
this.addSystem(new RenderSystem());
// 2. Create initial entities
this.createPlayer();
this.createEnemies();
// 3. Setup event listeners
this.setupEvents();
}
public onStart(): void {
this.resourcesLoaded = true;
console.log("Scene resources loaded, game starting");
}
public unload(): void {
// Cleanup event listeners
this.eventSystem.clear();
// Cleanup other resources
this.resourcesLoaded = false;
console.log("Scene resources cleaned up");
}
private createPlayer(): void {
const player = this.createEntity("Player");
player.addComponent(new Position(400, 300));
}
private createEnemies(): void {
for (let i = 0; i < 5; i++) {
const enemy = this.createEntity(`Enemy_${i}`);
enemy.addComponent(new Position(Math.random() * 800, Math.random() * 600));
}
}
private setupEvents(): void {
this.eventSystem.on('player_died', () => {
console.log('Player died');
});
}
}
```
## Notes
1. **initialize() called once** - For initial state setup
2. **onStart() on scene activation** - May be called multiple times
3. **unload() must cleanup resources** - Avoid memory leaks
4. **update() managed by framework** - No manual calls needed

View File

@@ -0,0 +1,115 @@
---
title: "System Management"
description: "System addition, removal and control in scenes"
---
Scene manages system registration, execution order, and lifecycle.
## Adding Systems
```typescript
class SystemScene extends Scene {
protected initialize(): void {
// Add system
const movementSystem = new MovementSystem();
this.addSystem(movementSystem);
// Set system update order (lower runs first)
movementSystem.updateOrder = 1;
// Add more systems
this.addSystem(new PhysicsSystem());
this.addSystem(new RenderSystem());
}
}
```
## Getting Systems
```typescript
// Get system of specific type
const physicsSystem = this.getEntityProcessor(PhysicsSystem);
if (physicsSystem) {
console.log("Found physics system");
}
```
## Removing Systems
```typescript
public removeUnnecessarySystems(): void {
const physicsSystem = this.getEntityProcessor(PhysicsSystem);
if (physicsSystem) {
this.removeSystem(physicsSystem);
}
}
```
## Controlling Systems
### Enable/Disable Systems
```typescript
public pausePhysics(): void {
const physicsSystem = this.getEntityProcessor(PhysicsSystem);
if (physicsSystem) {
physicsSystem.enabled = false; // Disable system
}
}
public resumePhysics(): void {
const physicsSystem = this.getEntityProcessor(PhysicsSystem);
if (physicsSystem) {
physicsSystem.enabled = true; // Enable system
}
}
```
### Get All Systems
```typescript
public getAllSystems(): EntitySystem[] {
return this.systems; // Get all registered systems
}
```
## System Organization Best Practice
Group systems by function:
```typescript
class OrganizedScene extends Scene {
protected initialize(): void {
// Add systems by function and dependencies
this.addInputSystems();
this.addLogicSystems();
this.addRenderSystems();
}
private addInputSystems(): void {
this.addSystem(new InputSystem());
}
private addLogicSystems(): void {
this.addSystem(new MovementSystem());
this.addSystem(new PhysicsSystem());
this.addSystem(new CollisionSystem());
}
private addRenderSystems(): void {
this.addSystem(new RenderSystem());
this.addSystem(new UISystem());
}
}
```
## API Reference
| Method | Returns | Description |
|--------|---------|-------------|
| `addSystem(system)` | `void` | Add system to scene |
| `removeSystem(system)` | `void` | Remove system from scene |
| `getEntityProcessor(Type)` | `T \| undefined` | Get system by type |
| `systems` | `EntitySystem[]` | Get all systems |

View File

@@ -1,572 +0,0 @@
---
title: "Worker System"
---
The Worker System (WorkerEntitySystem) is a multi-threaded processing system based on Web Workers in the ECS framework. It's designed for compute-intensive tasks, fully utilizing multi-core CPU performance for true parallel computing.
## Core Features
- **True Parallel Computing**: Execute compute-intensive tasks in background threads using Web Workers
- **Automatic Load Balancing**: Automatically distribute workload based on CPU core count
- **SharedArrayBuffer Optimization**: Zero-copy data sharing for improved large-scale computation performance
- **Graceful Degradation**: Automatic fallback to main thread processing when Workers are not supported
- **Type Safety**: Full TypeScript support and type checking
## Basic Usage
### Simple Physics System Example
```typescript
interface PhysicsData {
id: number;
x: number;
y: number;
vx: number;
vy: number;
mass: number;
radius: number;
}
@ECSSystem('Physics')
class PhysicsWorkerSystem extends WorkerEntitySystem<PhysicsData> {
constructor() {
super(Matcher.all(Position, Velocity, Physics), {
enableWorker: true, // Enable Worker parallel processing
workerCount: 8, // Worker count, auto-limited to hardware capacity
entitiesPerWorker: 100, // Entities per Worker
useSharedArrayBuffer: true, // Enable SharedArrayBuffer optimization
entityDataSize: 7, // Data size per entity
maxEntities: 10000, // Maximum entity count
systemConfig: { // Configuration passed to Worker
gravity: 100,
friction: 0.95
}
});
}
// Data extraction: Convert Entity to serializable data
protected extractEntityData(entity: Entity): PhysicsData {
const position = entity.getComponent(Position);
const velocity = entity.getComponent(Velocity);
const physics = entity.getComponent(Physics);
return {
id: entity.id,
x: position.x,
y: position.y,
vx: velocity.x,
vy: velocity.y,
mass: physics.mass,
radius: physics.radius
};
}
// Worker processing function: Pure function executed in Worker
protected workerProcess(
entities: PhysicsData[],
deltaTime: number,
config: any
): PhysicsData[] {
return entities.map(entity => {
// Apply gravity
entity.vy += config.gravity * deltaTime;
// Update position
entity.x += entity.vx * deltaTime;
entity.y += entity.vy * deltaTime;
// Apply friction
entity.vx *= config.friction;
entity.vy *= config.friction;
return entity;
});
}
// Apply results: Apply Worker processing results back to Entity
protected applyResult(entity: Entity, result: PhysicsData): void {
const position = entity.getComponent(Position);
const velocity = entity.getComponent(Velocity);
position.x = result.x;
position.y = result.y;
velocity.x = result.vx;
velocity.y = result.vy;
}
// SharedArrayBuffer optimization support
protected getDefaultEntityDataSize(): number {
return 7; // id, x, y, vx, vy, mass, radius
}
protected writeEntityToBuffer(entityData: PhysicsData, offset: number): void {
if (!this.sharedFloatArray) return;
this.sharedFloatArray[offset + 0] = entityData.id;
this.sharedFloatArray[offset + 1] = entityData.x;
this.sharedFloatArray[offset + 2] = entityData.y;
this.sharedFloatArray[offset + 3] = entityData.vx;
this.sharedFloatArray[offset + 4] = entityData.vy;
this.sharedFloatArray[offset + 5] = entityData.mass;
this.sharedFloatArray[offset + 6] = entityData.radius;
}
protected readEntityFromBuffer(offset: number): PhysicsData | null {
if (!this.sharedFloatArray) return null;
return {
id: this.sharedFloatArray[offset + 0],
x: this.sharedFloatArray[offset + 1],
y: this.sharedFloatArray[offset + 2],
vx: this.sharedFloatArray[offset + 3],
vy: this.sharedFloatArray[offset + 4],
mass: this.sharedFloatArray[offset + 5],
radius: this.sharedFloatArray[offset + 6]
};
}
}
```
## Configuration Options
The Worker system supports rich configuration options:
```typescript
interface WorkerSystemConfig {
/** Enable Worker parallel processing */
enableWorker?: boolean;
/** Worker count, defaults to CPU core count, auto-limited to system maximum */
workerCount?: number;
/** Entities per Worker for load distribution control */
entitiesPerWorker?: number;
/** System configuration data passed to Worker */
systemConfig?: any;
/** Enable SharedArrayBuffer optimization */
useSharedArrayBuffer?: boolean;
/** Float32 count per entity in SharedArrayBuffer */
entityDataSize?: number;
/** Maximum entity count (for SharedArrayBuffer pre-allocation) */
maxEntities?: number;
/** Pre-compiled Worker script path (for platforms like WeChat Mini Game that don't support dynamic scripts) */
workerScriptPath?: string;
}
```
### Configuration Recommendations
```typescript
constructor() {
super(matcher, {
// Decide based on task complexity
enableWorker: this.shouldUseWorker(),
// Worker count: System auto-limits to hardware capacity
workerCount: 8, // Request 8 Workers, actual count limited by CPU cores
// Entities per Worker (optional)
entitiesPerWorker: 200, // Precise load distribution control
// Enable SharedArrayBuffer for many simple calculations
useSharedArrayBuffer: this.entityCount > 1000,
// Set according to actual data structure
entityDataSize: 8, // Ensure it matches data structure
// Estimated maximum entity count
maxEntities: 10000,
// Global configuration passed to Worker
systemConfig: {
gravity: 9.8,
friction: 0.95,
worldBounds: { width: 1920, height: 1080 }
}
});
}
private shouldUseWorker(): boolean {
// Decide based on entity count and complexity
return this.expectedEntityCount > 100;
}
// Get system info
getSystemInfo() {
const info = this.getWorkerInfo();
console.log(`Worker count: ${info.workerCount}/${info.maxSystemWorkerCount}`);
console.log(`Entities per Worker: ${info.entitiesPerWorker || 'auto'}`);
console.log(`Current mode: ${info.currentMode}`);
}
```
## Processing Modes
The Worker system supports two processing modes:
### 1. Traditional Worker Mode
Data is serialized and passed between main thread and Workers:
```typescript
// Suitable for: Complex computation logic, moderate entity count
constructor() {
super(matcher, {
enableWorker: true,
useSharedArrayBuffer: false, // Use traditional mode
workerCount: 2
});
}
protected workerProcess(entities: EntityData[], deltaTime: number): EntityData[] {
// Complex algorithm logic
return entities.map(entity => {
// AI decisions, pathfinding, etc.
return this.complexAILogic(entity, deltaTime);
});
}
```
### 2. SharedArrayBuffer Mode
Zero-copy data sharing, suitable for many simple calculations:
```typescript
// Suitable for: Many entities with simple calculations
constructor() {
super(matcher, {
enableWorker: true,
useSharedArrayBuffer: true, // Enable shared memory
entityDataSize: 6,
maxEntities: 10000
});
}
protected getSharedArrayBufferProcessFunction(): SharedArrayBufferProcessFunction {
return function(sharedFloatArray: Float32Array, startIndex: number, endIndex: number, deltaTime: number, config: any) {
const entitySize = 6;
for (let i = startIndex; i < endIndex; i++) {
const offset = i * entitySize;
// Read data
let x = sharedFloatArray[offset];
let y = sharedFloatArray[offset + 1];
let vx = sharedFloatArray[offset + 2];
let vy = sharedFloatArray[offset + 3];
// Physics calculations
vy += config.gravity * deltaTime;
x += vx * deltaTime;
y += vy * deltaTime;
// Write back data
sharedFloatArray[offset] = x;
sharedFloatArray[offset + 1] = y;
sharedFloatArray[offset + 2] = vx;
sharedFloatArray[offset + 3] = vy;
}
};
}
```
## Use Cases
The Worker system is particularly suitable for:
### 1. Physics Simulation
- **Gravity systems**: Gravity calculations for many entities
- **Collision detection**: Complex collision algorithms
- **Fluid simulation**: Particle fluid systems
- **Cloth simulation**: Vertex physics calculations
### 2. AI Computation
- **Pathfinding**: A*, Dijkstra algorithms
- **Behavior trees**: Complex AI decision logic
- **Swarm intelligence**: Boid, fish school algorithms
- **Neural networks**: Simple AI inference
### 3. Data Processing
- **Bulk entity updates**: State machines, lifecycle management
- **Statistical calculations**: Game data analysis
- **Image processing**: Texture generation, effect calculations
- **Audio processing**: Sound synthesis, spectrum analysis
## Best Practices
### 1. Worker Function Requirements
```typescript
// Recommended: Worker processing function is a pure function
protected workerProcess(entities: PhysicsData[], deltaTime: number, config: any): PhysicsData[] {
// Only use parameters and standard JavaScript APIs
return entities.map(entity => {
// Pure computation logic, no external state dependencies
entity.y += entity.velocity * deltaTime;
return entity;
});
}
// Avoid: Using external references in Worker function
protected workerProcess(entities: PhysicsData[], deltaTime: number): PhysicsData[] {
// this and external variables are not available in Worker
return entities.map(entity => {
entity.y += this.someProperty; // Error
return entity;
});
}
```
### 2. Data Design
```typescript
// Recommended: Reasonable data design
interface SimplePhysicsData {
x: number;
y: number;
vx: number;
vy: number;
// Keep data structure simple for easy serialization
}
// Avoid: Complex nested objects
interface ComplexData {
transform: {
position: { x: number; y: number };
rotation: { angle: number };
};
// Complex nested structures increase serialization overhead
}
```
### 3. Worker Count Control
```typescript
// Recommended: Flexible Worker configuration
constructor() {
super(matcher, {
// Specify needed Worker count, system auto-limits to hardware capacity
workerCount: 8, // Request 8 Workers
entitiesPerWorker: 100, // 100 entities per Worker
enableWorker: this.shouldUseWorker(), // Conditional enable
});
}
private shouldUseWorker(): boolean {
// Decide based on entity count and complexity
return this.expectedEntityCount > 100;
}
// Get actual Worker info
checkWorkerConfiguration() {
const info = this.getWorkerInfo();
console.log(`Requested Workers: 8`);
console.log(`Actual Workers: ${info.workerCount}`);
console.log(`System maximum: ${info.maxSystemWorkerCount}`);
console.log(`Entities per Worker: ${info.entitiesPerWorker || 'auto'}`);
}
```
### 4. Performance Monitoring
```typescript
// Recommended: Performance monitoring
public getPerformanceMetrics(): WorkerPerformanceMetrics {
return {
...this.getWorkerInfo(),
entityCount: this.entities.length,
averageProcessTime: this.getAverageProcessTime(),
workerUtilization: this.getWorkerUtilization()
};
}
```
## Performance Optimization Tips
### 1. Compute Intensity Assessment
Only use Workers for compute-intensive tasks to avoid thread overhead for simple calculations.
### 2. Data Transfer Optimization
- Use SharedArrayBuffer to reduce serialization overhead
- Keep data structures simple and flat
- Avoid frequent large data transfers
### 3. Degradation Strategy
Always provide main thread fallback to ensure normal operation in environments without Worker support.
### 4. Memory Management
Clean up Worker pools and shared buffers promptly to avoid memory leaks.
### 5. Load Balancing
Use `entitiesPerWorker` parameter to precisely control load distribution, avoiding idle Workers while others are overloaded.
## WeChat Mini Game Support
WeChat Mini Game has special Worker limitations and doesn't support dynamic Worker script creation. ESEngine provides the `@esengine/worker-generator` CLI tool to solve this problem.
### WeChat Mini Game Worker Limitations
| Feature | Browser | WeChat Mini Game |
|---------|---------|------------------|
| Dynamic scripts (Blob URL) | Supported | Not supported |
| Worker count | Multiple | Maximum 1 |
| Script source | Any | Must be in code package |
| SharedArrayBuffer | Requires COOP/COEP | Limited support |
### Using Worker Generator CLI
#### 1. Install the Tool
```bash
pnpm add -D @esengine/worker-generator
```
#### 2. Configure workerScriptPath
Configure `workerScriptPath` in your WorkerEntitySystem subclass:
```typescript
@ECSSystem('Physics')
class PhysicsWorkerSystem extends WorkerEntitySystem<PhysicsData> {
constructor() {
super(Matcher.all(Position, Velocity, Physics), {
enableWorker: true,
workerScriptPath: 'workers/physics-worker.js', // Specify Worker file path
systemConfig: {
gravity: 100,
friction: 0.95
}
});
}
protected workerProcess(
entities: PhysicsData[],
deltaTime: number,
config: any
): PhysicsData[] {
// Physics calculation logic
return entities.map(entity => {
entity.vy += config.gravity * deltaTime;
entity.x += entity.vx * deltaTime;
entity.y += entity.vy * deltaTime;
return entity;
});
}
// ... other methods
}
```
#### 3. Generate Worker Files
Run the CLI tool to automatically extract `workerProcess` functions and generate WeChat Mini Game compatible Worker files:
```bash
# Basic usage
npx esengine-worker-gen --src ./src --wechat
# Full options
npx esengine-worker-gen \
--src ./src \ # Source directory
--wechat \ # Generate WeChat Mini Game compatible code
--mapping \ # Generate worker-mapping.json
--verbose # Verbose output
```
The CLI tool will:
1. Scan source directory for all `WorkerEntitySystem` subclasses
2. Read each class's `workerScriptPath` configuration
3. Extract `workerProcess` method body
4. Convert to ES5 syntax (WeChat Mini Game compatible)
5. Generate to configured path
#### 4. Configure game.json
Configure workers directory in WeChat Mini Game's `game.json`:
```json
{
"deviceOrientation": "portrait",
"workers": "workers"
}
```
#### 5. Project Structure
```
your-game/
├── game.js
├── game.json # Configure "workers": "workers"
├── src/
│ └── systems/
│ └── PhysicsSystem.ts # workerScriptPath: 'workers/physics-worker.js'
└── workers/
├── physics-worker.js # Auto-generated
└── worker-mapping.json # Auto-generated
```
### Temporarily Disabling Workers
If you need to temporarily disable Workers (e.g., for debugging), there are two ways:
#### Method 1: Configuration Disable
```typescript
constructor() {
super(matcher, {
enableWorker: false, // Disable Worker, use main thread processing
// ...
});
}
```
#### Method 2: Platform Adapter Disable
Return Worker not supported in custom platform adapter:
```typescript
class MyPlatformAdapter implements IPlatformAdapter {
isWorkerSupported(): boolean {
return false; // Return false to disable Worker
}
// ...
}
```
### Important Notes
1. **Re-run CLI tool after each `workerProcess` modification** to generate new Worker files
2. **Worker functions must be pure functions**, cannot depend on `this` or external variables:
```typescript
// Correct: Only use parameters
protected workerProcess(entities, deltaTime, config) {
return entities.map(e => {
e.y += config.gravity * deltaTime;
return e;
});
}
// Wrong: Using this
protected workerProcess(entities, deltaTime, config) {
return entities.map(e => {
e.y += this.gravity * deltaTime; // Cannot access this in Worker
return e;
});
}
```
3. **Pass configuration data via `systemConfig`**, not class properties
4. **Developer tool warnings can be ignored**:
- `getNetworkType:fail not support` - WeChat DevTools internal behavior
- `SharedArrayBuffer will require cross-origin isolation` - Development environment warning, won't appear on real devices
## Online Demo
See the complete Worker system demo: [Worker System Demo](https://esengine.github.io/ecs-framework/demos/worker-system/)
The demo showcases:
- Multi-threaded physics computation
- Real-time performance comparison
- SharedArrayBuffer optimization
- Parallel processing of many entities
The Worker system provides powerful parallel computing capabilities for the ECS framework, allowing you to fully utilize modern multi-core processor performance, offering efficient solutions for complex game logic and compute-intensive tasks.

View File

@@ -0,0 +1,44 @@
---
title: "Best Practices"
description: "Worker system performance optimization"
---
## Worker Function Requirements
```typescript
// ✅ Pure function using only parameters
protected workerProcess(entities: PhysicsData[], dt: number, config: any): PhysicsData[] {
return entities.map(e => {
e.y += e.velocity * dt;
return e;
});
}
// ❌ Avoid using this or external variables
protected workerProcess(entities: PhysicsData[], dt: number): PhysicsData[] {
e.y += this.someProperty; // ❌ Can't access this in Worker
}
```
## Data Design
- Use simple, flat data structures
- Avoid complex nested objects
- Keep serialization overhead minimal
## When to Use Workers
| Scenario | Recommendation |
|----------|----------------|
| Entities < 100 | Don't use Worker |
| 100 < Entities < 1000 | Traditional Worker mode |
| Entities > 1000 | SharedArrayBuffer mode |
| Complex AI | Traditional Worker mode |
| Simple physics | SharedArrayBuffer mode |
## Performance Tips
1. Only use Workers for compute-intensive tasks
2. Use SharedArrayBuffer to reduce serialization
3. Keep data structures simple and flat
4. Use `entitiesPerWorker` for load balancing

View File

@@ -0,0 +1,61 @@
---
title: "Configuration"
description: "Worker system configuration and processing modes"
---
## Configuration Interface
```typescript
interface IWorkerSystemConfig {
enableWorker?: boolean;
workerCount?: number;
entitiesPerWorker?: number;
systemConfig?: unknown;
useSharedArrayBuffer?: boolean;
entityDataSize?: number;
maxEntities?: number;
workerScriptPath?: string;
}
```
## Processing Modes
### Traditional Worker Mode
Data serialized between main thread and Workers:
```typescript
constructor() {
super(matcher, {
enableWorker: true,
useSharedArrayBuffer: false,
workerCount: 2
});
}
```
**Use case**: Complex calculations, moderate entity count
### SharedArrayBuffer Mode
Zero-copy data sharing for large-scale simple calculations:
```typescript
constructor() {
super(matcher, {
enableWorker: true,
useSharedArrayBuffer: true,
entityDataSize: 6,
maxEntities: 10000
});
}
```
**Use case**: Many entities with simple calculations
## Get System Info
```typescript
const info = this.getWorkerInfo();
// { enabled, workerCount, maxSystemWorkerCount, currentMode, ... }
```

View File

@@ -0,0 +1,39 @@
---
title: "Examples"
description: "Complete Worker system examples"
---
## Particle Physics System
A complete particle physics system with collision detection. See the [Chinese version](/guide/worker-system/examples/) for the full code example with:
- Gravity and velocity updates
- Boundary collision
- Particle-particle collision with elastic response
- SharedArrayBuffer optimization
- Performance monitoring
## Key Implementation Points
```typescript
@ECSSystem('ParticlePhysics')
class ParticlePhysicsWorkerSystem extends WorkerEntitySystem<ParticleData> {
constructor() {
super(Matcher.all(Position, Velocity, Physics), {
enableWorker: true,
workerCount: 6,
entitiesPerWorker: 150,
useSharedArrayBuffer: true,
entityDataSize: 9,
maxEntities: 5000,
systemConfig: {
gravity: 100,
canvasWidth: 800,
canvasHeight: 600
}
});
}
// Implement required abstract methods...
}
```

View File

@@ -0,0 +1,104 @@
---
title: "Worker System"
description: "Web Worker based multi-threaded parallel processing system"
---
Worker System (WorkerEntitySystem) is a multi-threaded processing system based on Web Workers, designed for compute-intensive tasks.
## Core Features
- **True Parallel Computing**: Execute tasks in background threads via Web Workers
- **Auto Load Balancing**: Distribute workload based on CPU cores
- **SharedArrayBuffer Optimization**: Zero-copy data sharing
- **Fallback Support**: Auto fallback to main thread when Workers unavailable
- **Type Safety**: Full TypeScript support
## Quick Start
```typescript
interface PhysicsData {
id: number;
x: number;
y: number;
vx: number;
vy: number;
}
@ECSSystem('Physics')
class PhysicsWorkerSystem extends WorkerEntitySystem<PhysicsData> {
constructor() {
super(Matcher.all(Position, Velocity), {
enableWorker: true,
workerCount: 4,
systemConfig: { gravity: 100 }
});
}
protected getDefaultEntityDataSize(): number {
return 5;
}
protected extractEntityData(entity: Entity): PhysicsData {
const pos = entity.getComponent(Position);
const vel = entity.getComponent(Velocity);
return { id: entity.id, x: pos.x, y: pos.y, vx: vel.x, vy: vel.y };
}
protected workerProcess(entities: PhysicsData[], dt: number, config: any): PhysicsData[] {
return entities.map(e => {
e.vy += config.gravity * dt;
e.x += e.vx * dt;
e.y += e.vy * dt;
return e;
});
}
protected applyResult(entity: Entity, result: PhysicsData): void {
const pos = entity.getComponent(Position);
const vel = entity.getComponent(Velocity);
pos.x = result.x;
pos.y = result.y;
vel.x = result.vx;
vel.y = result.vy;
}
protected writeEntityToBuffer(data: PhysicsData, offset: number): void {
if (!this.sharedFloatArray) return;
this.sharedFloatArray[offset] = data.id;
this.sharedFloatArray[offset + 1] = data.x;
this.sharedFloatArray[offset + 2] = data.y;
this.sharedFloatArray[offset + 3] = data.vx;
this.sharedFloatArray[offset + 4] = data.vy;
}
protected readEntityFromBuffer(offset: number): PhysicsData | null {
if (!this.sharedFloatArray) return null;
return {
id: this.sharedFloatArray[offset],
x: this.sharedFloatArray[offset + 1],
y: this.sharedFloatArray[offset + 2],
vx: this.sharedFloatArray[offset + 3],
vy: this.sharedFloatArray[offset + 4]
};
}
}
```
## Use Cases
| Scenario | Examples |
|----------|----------|
| **Physics Simulation** | Gravity, collision detection, fluid |
| **AI Computing** | Pathfinding, behavior trees, flocking |
| **Data Processing** | State machines, statistics, image processing |
## Documentation
- [Configuration](/en/guide/worker-system/configuration/) - Options and processing modes
- [Examples](/en/guide/worker-system/examples/) - Complete particle physics example
- [WeChat Mini Game](/en/guide/worker-system/wechat/) - WeChat Worker support
- [Best Practices](/en/guide/worker-system/best-practices/) - Performance optimization
## Live Demo
[Worker System Demo](https://esengine.github.io/ecs-framework/demos/worker-system/)

View File

@@ -0,0 +1,43 @@
---
title: "WeChat Mini Game"
description: "WeChat Worker limitations and solutions"
---
WeChat Mini Game has special Worker restrictions. ESEngine provides CLI tools to solve this.
## Platform Differences
| Feature | Browser | WeChat |
|---------|---------|--------|
| Dynamic scripts | ✅ | ❌ |
| Worker count | Multiple | Max 1 |
| Script source | Any | Package files only |
## Using Worker Generator CLI
```bash
# Install
pnpm add -D @esengine/worker-generator
# Generate Worker files
npx esengine-worker-gen --src ./src --wechat
```
## Configuration
```typescript
class PhysicsSystem extends WorkerEntitySystem<PhysicsData> {
constructor() {
super(matcher, {
enableWorker: true,
workerScriptPath: 'workers/physics-worker.js'
});
}
}
```
## Important Notes
1. Re-run CLI after modifying `workerProcess`
2. Worker functions must be pure (no `this`)
3. Pass config via `systemConfig`

View File

@@ -0,0 +1,121 @@
---
title: "Advanced Usage"
description: "Performance optimization and debugging"
---
## Performance Optimization
### Tick Rate Control
```typescript
// Reduce update frequency for distant entities
const runtime = entity.getComponent(BehaviorTreeRuntimeComponent);
runtime.tickInterval = 100; // Update every 100ms instead of every frame
```
### Conditional Execution
```typescript
// Skip execution based on conditions
class OptimizedSystem extends BehaviorTreeExecutionSystem {
shouldProcess(entity: Entity): boolean {
const distance = getDistanceToPlayer(entity);
return distance < 1000; // Only process nearby entities
}
}
```
### Node Pooling
The framework automatically pools node execution contexts to reduce GC pressure.
## Debugging
### Runtime Inspection
```typescript
const runtime = entity.getComponent(BehaviorTreeRuntimeComponent);
// Current state
console.log('State:', runtime.state);
console.log('Current node:', runtime.currentNodeId);
// Blackboard
console.log('Health:', runtime.getBlackboardValue('health'));
// Execution history
console.log('Last nodes:', runtime.executionHistory);
```
### Event Logging
```typescript
runtime.onNodeEnter = (nodeId) => {
console.log(`Entering: ${nodeId}`);
};
runtime.onNodeExit = (nodeId, status) => {
console.log(`Exiting: ${nodeId} with ${status}`);
};
```
### Visual Debugging
```typescript
// Enable visual debugging
runtime.debug = true;
// Draw current execution path
BehaviorTreeDebugger.draw(entity);
```
## State Persistence
### Save State
```typescript
const state = runtime.serialize();
localStorage.setItem('ai-state', JSON.stringify(state));
```
### Restore State
```typescript
const state = JSON.parse(localStorage.getItem('ai-state'));
runtime.deserialize(state);
```
## Multi-Tree Entities
```typescript
// An entity can have multiple behavior trees
const combatAI = BehaviorTreeBuilder.create('Combat').build();
const dialogAI = BehaviorTreeBuilder.create('Dialog').build();
// Start both
BehaviorTreeStarter.start(entity, combatAI, 'combat');
BehaviorTreeStarter.start(entity, dialogAI, 'dialog');
// Control individually
const combatRuntime = entity.getComponent(BehaviorTreeRuntimeComponent, 'combat');
combatRuntime.pause();
```
## Custom Execution
Override the default execution system:
```typescript
class CustomExecutionSystem extends BehaviorTreeExecutionSystem {
protected processEntity(entity: Entity, dt: number): void {
// Custom pre-processing
this.updateBlackboard(entity);
// Standard execution
super.processEntity(entity, dt);
// Custom post-processing
this.handleResults(entity);
}
}
```

View File

@@ -0,0 +1,94 @@
---
title: "Asset Management"
description: "Loading, managing, and reusing behavior tree assets"
---
## Loading Trees
### From JSON
```typescript
import { BehaviorTreeLoader } from '@esengine/behavior-tree';
// Load from URL
const tree = await BehaviorTreeLoader.load('assets/enemy-ai.json');
// Load from object
const tree = BehaviorTreeLoader.fromData(jsonData);
```
### Using Asset Manager
```typescript
import { AssetManager } from '@esengine/asset-system';
// Register loader
AssetManager.registerLoader('btree', BehaviorTreeLoader);
// Load asset
const tree = await AssetManager.load<BehaviorTreeData>('enemy-ai.btree');
```
## Subtrees
Reuse behavior trees as subtrees:
```typescript
// Create a reusable patrol behavior
const patrolTree = BehaviorTreeBuilder.create('PatrolBehavior')
.sequence('Patrol')
.action('moveToWaypoint')
.wait(2000)
.action('nextWaypoint')
.end()
.build();
// Use as subtree in main AI
const enemyAI = BehaviorTreeBuilder.create('EnemyAI')
.selector('Main')
.sequence('Combat')
.condition('hasTarget')
.action('attack')
.end()
// Include patrol subtree
.subtree(patrolTree)
.end()
.build();
```
## Asset References
Reference external trees by ID:
```typescript
const tree = BehaviorTreeBuilder.create('MainAI')
.selector('Root')
.subtreeRef('combat-behavior') // References another tree
.subtreeRef('patrol-behavior')
.end()
.build();
```
## Caching
```typescript
// Trees are cached automatically
const cache = BehaviorTreeLoader.getCache();
// Clear specific tree
cache.remove('enemy-ai');
// Clear all
cache.clear();
```
## Hot Reloading
During development:
```typescript
// Enable hot reload
BehaviorTreeLoader.enableHotReload();
// Trees will automatically update when files change
```

View File

@@ -0,0 +1,174 @@
---
title: "Best Practices"
description: "Design patterns and tips for behavior trees"
---
## Tree Structure
### Keep Trees Shallow
```typescript
// Good - flat structure
.selector('Main')
.sequence('Combat')
.sequence('Patrol')
.sequence('Idle')
.end()
// Avoid - deep nesting
.selector('Main')
.selector('Level1')
.selector('Level2')
.selector('Level3')
// ...
```
### Use Subtrees
Break complex behaviors into reusable subtrees:
```typescript
// Define reusable behaviors
const combatBehavior = createCombatTree();
const patrolBehavior = createPatrolTree();
// Compose main AI
const enemyAI = BehaviorTreeBuilder.create('EnemyAI')
.selector('Main')
.subtree(combatBehavior)
.subtree(patrolBehavior)
.end()
.build();
```
## Blackboard Design
### Use Clear Naming
```typescript
// Good
.defineBlackboardVariable('targetEntity', null)
.defineBlackboardVariable('lastKnownPosition', null)
.defineBlackboardVariable('alertLevel', 0)
// Avoid
.defineBlackboardVariable('t', null)
.defineBlackboardVariable('pos', null)
.defineBlackboardVariable('a', 0)
```
### Group Related Variables
```typescript
// Combat-related
combatTarget: Entity
combatRange: number
attackCooldown: number
// Movement-related
moveTarget: Vector2
moveSpeed: number
pathNodes: Vector2[]
```
## Action Design
### Single Responsibility
```typescript
// Good - focused actions
class MoveToTarget implements INodeExecutor { }
class AttackTarget implements INodeExecutor { }
class PlayAnimation implements INodeExecutor { }
// Avoid - do-everything actions
class CombatAction implements INodeExecutor {
// Moves, attacks, plays animation, etc.
}
```
### Stateless Executors
```typescript
// Good - use context for state
class WaitAction implements INodeExecutor {
execute(context: NodeExecutionContext): TaskStatus {
const elapsed = context.runtime.getNodeState(context.node.id, 'elapsed') ?? 0;
// ...
}
}
// Avoid - instance state
class WaitAction implements INodeExecutor {
private elapsed = 0; // Don't do this!
}
```
## Debugging Tips
### Add Log Nodes
```typescript
.sequence('AttackSequence')
.log('Starting attack sequence', 'Debug')
.action('findTarget')
.log('Target found', 'Debug')
.action('attack')
.log('Attack complete', 'Debug')
.end()
```
### Use Meaningful Node Names
```typescript
// Good
.sequence('ApproachAndAttackEnemy')
.condition('IsEnemyInRange')
.action('PerformMeleeAttack')
// Avoid
.sequence('Seq1')
.condition('Cond1')
.action('Action1')
```
## Performance Tips
1. **Reduce tick rate** for distant entities
2. **Use conditions early** to fail fast
3. **Cache expensive calculations** in blackboard
4. **Limit subtree depth** to reduce traversal cost
5. **Profile** your trees in real gameplay
## Common Patterns
### Guard Pattern
```typescript
.sequence('GuardedAction')
.condition('canPerformAction') // Guard condition
.action('performAction') // Actual action
.end()
```
### Cooldown Pattern
```typescript
.sequence('CooldownAttack')
.condition('isCooldownReady')
.action('attack')
.action('startCooldown')
.end()
```
### Memory Pattern
```typescript
.selector('RememberAndAct')
.sequence('UseMemory')
.condition('hasLastKnownPosition')
.action('moveToLastKnownPosition')
.end()
.action('search')
.end()
```

View File

@@ -0,0 +1,147 @@
---
title: "Cocos Creator Integration"
description: "Using behavior trees with Cocos Creator"
---
## Setup
### Installation
```bash
npm install @esengine/behavior-tree @esengine/ecs-framework
```
### Project Configuration
Add to your Cocos Creator project's `tsconfig.json`:
```json
{
"compilerOptions": {
"experimentalDecorators": true,
"emitDecoratorMetadata": true
}
}
```
## Basic Integration
```typescript
import { _decorator, Component } from 'cc';
import { Core, Scene } from '@esengine/ecs-framework';
import { BehaviorTreePlugin, BehaviorTreeBuilder, BehaviorTreeStarter } from '@esengine/behavior-tree';
const { ccclass, property } = _decorator;
@ccclass('GameManager')
export class GameManager extends Component {
private scene: Scene;
private plugin: BehaviorTreePlugin;
async start() {
// Initialize ECS
Core.create();
this.plugin = new BehaviorTreePlugin();
await Core.installPlugin(this.plugin);
this.scene = new Scene();
this.plugin.setupScene(this.scene);
Core.setScene(this.scene);
}
update(dt: number) {
// Update ECS
this.scene?.update(dt * 1000);
}
}
```
## Creating AI Components
```typescript
@ccclass('EnemyAI')
export class EnemyAI extends Component {
@property
public aggroRange: number = 200;
private entity: Entity;
start() {
const tree = this.createBehaviorTree();
this.entity = GameManager.instance.scene.createEntity('Enemy');
BehaviorTreeStarter.start(this.entity, tree);
}
private createBehaviorTree() {
return BehaviorTreeBuilder.create('EnemyAI')
.defineBlackboardVariable('target', null)
.defineBlackboardVariable('ccNode', this.node)
.selector('Main')
.sequence('Attack')
.condition('hasTarget')
.action('attack')
.end()
.action('patrol')
.end()
.build();
}
}
```
## Custom Actions for Cocos
```typescript
@NodeExecutorMetadata({
implementationType: 'CCMoveToTarget',
nodeType: NodeType.Action,
displayName: 'Move To Target',
category: 'Cocos'
})
export class CCMoveToTarget implements INodeExecutor {
execute(context: NodeExecutionContext): TaskStatus {
const ccNode = context.runtime.getBlackboardValue<Node>('ccNode');
const target = context.runtime.getBlackboardValue<Vec3>('targetPosition');
if (!ccNode || !target) return TaskStatus.Failure;
const pos = ccNode.position;
const direction = new Vec3();
Vec3.subtract(direction, target, pos);
if (direction.length() < 10) {
return TaskStatus.Success;
}
direction.normalize();
const speed = BindingHelper.getValue<number>(context, 'speed', 100);
const delta = new Vec3();
Vec3.multiplyScalar(delta, direction, speed * context.deltaTime / 1000);
ccNode.position = pos.add(delta);
return TaskStatus.Running;
}
}
```
## Loading Tree Assets
```typescript
// Load from Cocos resources
import { resources, JsonAsset } from 'cc';
async function loadBehaviorTree(path: string): Promise<BehaviorTreeData> {
return new Promise((resolve, reject) => {
resources.load(path, JsonAsset, (err, asset) => {
if (err) reject(err);
else resolve(BehaviorTreeLoader.fromData(asset.json));
});
});
}
```
## Best Practices
1. **Sync positions** between Cocos nodes and ECS entities
2. **Use blackboard** to store Cocos-specific references
3. **Update ECS** in Cocos update loop
4. **Handle cleanup** when destroying components

View File

@@ -0,0 +1,179 @@
---
title: "Core Concepts"
description: "Understanding behavior tree fundamentals"
---
## Node Types
### Composite Nodes
Control the execution flow of child nodes.
#### Sequence
Executes children in order. Fails if any child fails.
```typescript
.sequence('AttackSequence')
.condition('hasTarget') // If false, sequence fails
.action('moveToTarget') // If fails, sequence fails
.action('attack') // If succeeds, sequence succeeds
.end()
```
#### Selector
Tries children until one succeeds. Fails if all children fail.
```typescript
.selector('FindTarget')
.action('findNearestEnemy') // Try first
.action('findNearestItem') // Try if first fails
.action('wander') // Fallback
.end()
```
#### Parallel
Executes all children simultaneously.
```typescript
.parallel('CombatActions', {
successPolicy: 'all', // 'all' | 'any'
failurePolicy: 'any' // 'all' | 'any'
})
.action('playAttackAnimation')
.action('dealDamage')
.action('playSound')
.end()
```
### Leaf Nodes
#### Action
Performs a specific task.
```typescript
.action('attack', { damage: 10 })
```
#### Condition
Checks a condition without side effects.
```typescript
.condition('isHealthLow', { threshold: 30 })
```
### Decorator Nodes
Modify child behavior.
```typescript
// Invert result
.inverter()
.condition('isEnemy')
.end()
// Repeat until failure
.repeatUntilFail()
.action('patrol')
.end()
// Timeout
.timeout(5000)
.action('searchForTarget')
.end()
```
## Task Status
Every node returns one of these statuses:
| Status | Description |
|--------|-------------|
| `Success` | Task completed successfully |
| `Failure` | Task failed |
| `Running` | Task still in progress |
```typescript
import { TaskStatus } from '@esengine/behavior-tree';
class MyAction implements INodeExecutor {
execute(context: NodeExecutionContext): TaskStatus {
if (/* completed */) return TaskStatus.Success;
if (/* failed */) return TaskStatus.Failure;
return TaskStatus.Running; // Still working
}
}
```
## Blackboard System
### Local vs Global
```typescript
// Local blackboard - per behavior tree instance
runtime.setBlackboardValue('localVar', value);
// Global blackboard - shared across all trees
runtime.setGlobalBlackboardValue('globalVar', value);
```
### Variable Access in Executors
```typescript
class PatrolAction implements INodeExecutor {
execute(context: NodeExecutionContext): TaskStatus {
// Read from blackboard
const target = context.runtime.getBlackboardValue('patrolTarget');
const speed = context.runtime.getBlackboardValue('moveSpeed');
// Write to blackboard
context.runtime.setBlackboardValue('lastPosition', currentPos);
return TaskStatus.Success;
}
}
```
## Execution Context
```typescript
interface NodeExecutionContext {
readonly node: IBehaviorTreeNode; // Current node data
readonly runtime: BehaviorTreeRuntimeComponent; // Runtime state
readonly entity: Entity; // Owner entity
readonly scene: Scene; // Current scene
readonly deltaTime: number; // Frame delta time
}
```
## Architecture Overview
```
BehaviorTreeData (Pure Data)
├── Serializable JSON structure
├── Node definitions
└── Blackboard schema
BehaviorTreeRuntimeComponent (State)
├── Current execution state
├── Blackboard values
└── Node status cache
BehaviorTreeExecutionSystem (Logic)
├── Drives tree execution
├── Manages node traversal
└── Calls INodeExecutor.execute()
INodeExecutor (Behavior)
├── Stateless design
├── Receives context
└── Returns TaskStatus
```

View File

@@ -0,0 +1,197 @@
---
title: "Custom Actions"
description: "Create custom behavior tree nodes"
---
## Creating a Custom Executor
```typescript
import {
INodeExecutor,
NodeExecutionContext,
NodeExecutorMetadata,
TaskStatus,
NodeType,
BindingHelper
} from '@esengine/behavior-tree';
@NodeExecutorMetadata({
implementationType: 'AttackAction',
nodeType: NodeType.Action,
displayName: 'Attack',
description: 'Attack the target',
category: 'Combat',
configSchema: {
damage: {
type: 'number',
default: 10,
supportBinding: true
},
range: {
type: 'number',
default: 50
}
}
})
export class AttackAction implements INodeExecutor {
execute(context: NodeExecutionContext): TaskStatus {
const damage = BindingHelper.getValue<number>(context, 'damage', 10);
const target = context.runtime.getBlackboardValue('target');
if (!target) {
return TaskStatus.Failure;
}
// Perform attack logic
console.log(`Dealing ${damage} damage`);
return TaskStatus.Success;
}
}
```
## Decorator Metadata
```typescript
@NodeExecutorMetadata({
implementationType: 'MoveToTarget',
nodeType: NodeType.Action,
displayName: 'Move To Target',
description: 'Move entity towards target position',
category: 'Movement',
icon: 'move',
color: '#4CAF50',
configSchema: {
speed: {
type: 'number',
default: 100,
min: 0,
max: 1000,
supportBinding: true,
description: 'Movement speed'
},
arrivalDistance: {
type: 'number',
default: 10,
description: 'Distance to consider arrived'
}
}
})
```
## Config Schema Types
| Type | Properties |
|------|------------|
| `number` | `min`, `max`, `step`, `default` |
| `string` | `default`, `maxLength` |
| `boolean` | `default` |
| `select` | `options`, `default` |
| `vector2` | `default: { x, y }` |
```typescript
configSchema: {
mode: {
type: 'select',
options: ['aggressive', 'defensive', 'passive'],
default: 'aggressive'
},
offset: {
type: 'vector2',
default: { x: 0, y: 0 }
}
}
```
## Using BindingHelper
```typescript
import { BindingHelper } from '@esengine/behavior-tree';
execute(context: NodeExecutionContext): TaskStatus {
// Get value with fallback
const speed = BindingHelper.getValue<number>(context, 'speed', 100);
// Get bound value from blackboard
const target = BindingHelper.getBoundValue<Entity>(context, 'target');
// Check if value is bound
if (BindingHelper.isBound(context, 'target')) {
// Value comes from blackboard
}
return TaskStatus.Success;
}
```
## Async Actions
For actions that span multiple frames:
```typescript
@NodeExecutorMetadata({
implementationType: 'WaitAction',
nodeType: NodeType.Action,
displayName: 'Wait',
category: 'Timing',
configSchema: {
duration: { type: 'number', default: 1000 }
}
})
export class WaitAction implements INodeExecutor {
execute(context: NodeExecutionContext): TaskStatus {
const duration = BindingHelper.getValue<number>(context, 'duration', 1000);
// Get or initialize state
let elapsed = context.runtime.getNodeState<number>(context.node.id, 'elapsed') ?? 0;
elapsed += context.deltaTime;
if (elapsed >= duration) {
// Clear state and complete
context.runtime.clearNodeState(context.node.id);
return TaskStatus.Success;
}
// Save state and continue
context.runtime.setNodeState(context.node.id, 'elapsed', elapsed);
return TaskStatus.Running;
}
}
```
## Condition Nodes
```typescript
@NodeExecutorMetadata({
implementationType: 'IsHealthLow',
nodeType: NodeType.Condition,
displayName: 'Is Health Low',
category: 'Conditions',
configSchema: {
threshold: { type: 'number', default: 30 }
}
})
export class IsHealthLow implements INodeExecutor {
execute(context: NodeExecutionContext): TaskStatus {
const threshold = BindingHelper.getValue<number>(context, 'threshold', 30);
const health = context.runtime.getBlackboardValue<number>('health') ?? 100;
return health <= threshold
? TaskStatus.Success
: TaskStatus.Failure;
}
}
```
## Registering Custom Executors
Executors are auto-registered via the decorator. To manually register:
```typescript
import { NodeExecutorRegistry } from '@esengine/behavior-tree';
// Register
NodeExecutorRegistry.register('CustomAction', CustomAction);
// Get executor
const executor = NodeExecutorRegistry.get('CustomAction');
```

View File

@@ -0,0 +1,73 @@
---
title: "Editor Guide"
description: "Visual behavior tree creation"
---
## Opening the Editor
The behavior tree editor provides a visual interface for creating and managing behavior trees.
## Creating a New Tree
1. Open the behavior tree editor
2. Click "New Behavior Tree"
3. Enter a name for your tree
4. Start adding nodes
## Node Operations
### Adding Nodes
- Drag nodes from the palette to the canvas
- Right-click on canvas to open context menu
- Use keyboard shortcuts for quick access
### Connecting Nodes
- Click and drag from a node's output port
- Drop on another node's input port
- Connections show execution flow
### Configuring Nodes
- Select a node to view its properties
- Configure parameters in the property panel
- Use bindings to connect to blackboard variables
## Blackboard Panel
The blackboard panel allows you to:
- Define variables used by the tree
- Set initial values
- Specify variable types
```
Variables:
- health: number = 100
- target: Entity = null
- isAlert: boolean = false
```
## Saving and Loading
- **Save**: Ctrl+S or File → Save
- **Export**: Export as JSON for runtime use
- **Import**: Load existing tree files
## Debugging
- Use the play button to test execution
- Watch node status in real-time
- View blackboard values during execution
## Keyboard Shortcuts
| Shortcut | Action |
|----------|--------|
| Ctrl+S | Save |
| Ctrl+Z | Undo |
| Ctrl+Y | Redo |
| Delete | Remove selected |
| Ctrl+C | Copy |
| Ctrl+V | Paste |

View File

@@ -0,0 +1,88 @@
---
title: "Editor Workflow"
description: "Complete development workflow with the editor"
---
## Workflow Overview
1. **Design** - Plan your AI behavior
2. **Create** - Build the tree in the editor
3. **Configure** - Set up blackboard and properties
4. **Test** - Debug in the editor
5. **Export** - Generate runtime assets
6. **Integrate** - Use in your game
## Design Phase
Before opening the editor, plan your AI:
```
Enemy AI Design:
├── If health > 50%
│ ├── Find target
│ ├── Move to target
│ └── Attack
└── Else
└── Flee to safety
```
## Create Phase
Translate your design to nodes:
1. Start with a Selector (main decision)
2. Add Sequences for each branch
3. Add Conditions and Actions
4. Configure node properties
## Configure Phase
### Blackboard Setup
Define variables your tree needs:
| Variable | Type | Default | Description |
|----------|------|---------|-------------|
| health | number | 100 | Current health |
| target | Entity | null | Attack target |
| homePosition | Vector2 | (0,0) | Safe position |
### Property Bindings
Connect node properties to blackboard:
```
Attack Node:
damage: @blackboard.attackPower
target: @blackboard.target
```
## Test Phase
1. Click Play in the editor
2. Watch node execution
3. Monitor blackboard values
4. Step through execution
5. Fix issues and repeat
## Export Phase
Export your tree for runtime use:
```typescript
// The exported JSON can be loaded at runtime
const treeData = await loadBehaviorTree('assets/enemy-ai.json');
```
## Integration Phase
```typescript
import { BehaviorTreeLoader, BehaviorTreeStarter } from '@esengine/behavior-tree';
// Load exported tree
const treeData = await BehaviorTreeLoader.load('enemy-ai.json');
// Attach to entity
const enemy = scene.createEntity('Enemy');
BehaviorTreeStarter.start(enemy, treeData);
```

View File

@@ -0,0 +1,105 @@
---
title: "Getting Started"
description: "Quick start guide for behavior trees"
---
## Installation
```bash
npm install @esengine/behavior-tree
```
## Basic Setup
```typescript
import { Core, Scene } from '@esengine/ecs-framework';
import { BehaviorTreePlugin } from '@esengine/behavior-tree';
// Initialize Core
Core.create();
// Install behavior tree plugin
const plugin = new BehaviorTreePlugin();
await Core.installPlugin(plugin);
// Create and setup scene
const scene = new Scene();
plugin.setupScene(scene);
Core.setScene(scene);
```
## Creating Your First Behavior Tree
### Using Builder API
```typescript
import { BehaviorTreeBuilder, BehaviorTreeStarter } from '@esengine/behavior-tree';
// Build behavior tree
const patrolAI = BehaviorTreeBuilder.create('PatrolAI')
.defineBlackboardVariable('targetPosition', null)
.sequence('PatrolSequence')
.log('Start patrol', 'Patrol')
.wait(2000)
.log('Move to next point', 'Patrol')
.end()
.build();
// Attach to entity
const entity = scene.createEntity('Guard');
BehaviorTreeStarter.start(entity, patrolAI);
```
### Node Types
| Node Type | Description |
|-----------|-------------|
| **Sequence** | Executes children in order until one fails |
| **Selector** | Tries children until one succeeds |
| **Parallel** | Executes all children simultaneously |
| **Action** | Performs a specific action |
| **Condition** | Checks a condition |
| **Decorator** | Modifies child behavior |
## Blackboard Variables
The blackboard is a shared data store for behavior tree nodes:
```typescript
const tree = BehaviorTreeBuilder.create('EnemyAI')
// Define variables
.defineBlackboardVariable('health', 100)
.defineBlackboardVariable('target', null)
.defineBlackboardVariable('isAlert', false)
.selector('Main')
// Use blackboard in conditions
.sequence('AttackBranch')
.blackboardCompare('health', 30, 'greater')
.blackboardCondition('target', (t) => t !== null)
.log('Attacking', 'Combat')
.end()
.log('Retreating', 'Combat')
.end()
.build();
```
## Running the Behavior Tree
```typescript
// The behavior tree runs automatically via ECS system
// Just create the entity and start the tree
const enemy = scene.createEntity('Enemy');
BehaviorTreeStarter.start(enemy, patrolAI);
// Access runtime for debugging
const runtime = enemy.getComponent(BehaviorTreeRuntimeComponent);
console.log('Current state:', runtime.state);
```
## Next Steps
- [Core Concepts](./core-concepts/) - Understand nodes and execution
- [Custom Actions](./custom-actions/) - Create your own nodes
- [Editor Guide](./editor-guide/) - Visual tree creation

View File

@@ -0,0 +1,95 @@
---
title: "Behavior Tree System"
description: "AI behavior trees with runtime executor architecture"
---
Behavior Tree is a powerful tool for game AI and automation control. This framework provides a behavior tree system based on Runtime Executor Architecture, featuring high performance, type safety, and easy extensibility.
## What is a Behavior Tree?
A behavior tree is a hierarchical task execution structure composed of multiple nodes, each responsible for specific tasks. Behavior trees are especially suitable for:
- Game AI (enemies, NPC behavior)
- Alternative to state machines
- Complex decision logic
- Visual behavior design
## Core Features
### Runtime Executor Architecture
- Data and logic separation
- Stateless executor design
- High-performance execution
- Type safety
### Visual Editor
- Graphical node editing
- Real-time preview and debugging
- Drag-and-drop node creation
- Property connections and bindings
### Flexible Blackboard System
- Local blackboard (single behavior tree)
- Global blackboard (shared across all trees)
- Type-safe variable access
- Property binding support
## Quick Example
```typescript
import { Core, Scene } from '@esengine/ecs-framework';
import {
BehaviorTreeBuilder,
BehaviorTreeStarter,
BehaviorTreePlugin
} from '@esengine/behavior-tree';
// Initialize
Core.create();
const plugin = new BehaviorTreePlugin();
await Core.installPlugin(plugin);
const scene = new Scene();
plugin.setupScene(scene);
Core.setScene(scene);
// Create behavior tree
const enemyAI = BehaviorTreeBuilder.create('EnemyAI')
.defineBlackboardVariable('health', 100)
.defineBlackboardVariable('target', null)
.selector('MainBehavior')
// If health is high, attack
.sequence('AttackBranch')
.blackboardCompare('health', 50, 'greater')
.log('Attack player', 'Attack')
.end()
// Otherwise flee
.log('Flee from combat', 'Flee')
.end()
.build();
// Start AI
const entity = scene.createEntity('Enemy');
BehaviorTreeStarter.start(entity, enemyAI);
```
## Documentation
### Getting Started
- **[Getting Started](./getting-started/)** - 5-minute quickstart
- **[Core Concepts](./core-concepts/)** - Understanding behavior tree fundamentals
### Editor
- **[Editor Guide](./editor-guide/)** - Visual behavior tree creation
- **[Editor Workflow](./editor-workflow/)** - Complete development workflow
### Advanced
- **[Asset Management](./asset-management/)** - Loading, managing, and reusing assets
- **[Custom Actions](./custom-actions/)** - Create custom behavior nodes
- **[Advanced Usage](./advanced-usage/)** - Performance optimization
- **[Best Practices](./best-practices/)** - Design patterns and tips
### Engine Integration
- **[Cocos Creator](./cocos-integration/)** - Using with Cocos Creator
- **[Laya Engine](./laya-integration/)** - Using with Laya
- **[Node.js Server](./nodejs-usage/)** - Server-side usage

View File

@@ -0,0 +1,118 @@
---
title: "Laya Engine Integration"
description: "Using behavior trees with Laya Engine"
---
## Setup
### Installation
```bash
npm install @esengine/behavior-tree @esengine/ecs-framework
```
## Basic Integration
```typescript
import { Laya, Script } from 'laya/Laya';
import { Core, Scene } from '@esengine/ecs-framework';
import { BehaviorTreePlugin, BehaviorTreeBuilder, BehaviorTreeStarter } from '@esengine/behavior-tree';
export class GameMain {
private scene: Scene;
private plugin: BehaviorTreePlugin;
async initialize() {
// Initialize ECS
Core.create();
this.plugin = new BehaviorTreePlugin();
await Core.installPlugin(this.plugin);
this.scene = new Scene();
this.plugin.setupScene(this.scene);
Core.setScene(this.scene);
// Start game loop
Laya.timer.frameLoop(1, this, this.update);
}
update() {
const dt = Laya.timer.delta;
this.scene?.update(dt);
}
}
```
## AI Script Component
```typescript
export class EnemyAI extends Script {
private entity: Entity;
onAwake() {
const tree = this.createBehaviorTree();
this.entity = GameMain.instance.scene.createEntity('Enemy');
// Store Laya node reference
const runtime = this.entity.getComponent(BehaviorTreeRuntimeComponent);
runtime.setBlackboardValue('layaNode', this.owner);
BehaviorTreeStarter.start(this.entity, tree);
}
private createBehaviorTree() {
return BehaviorTreeBuilder.create('EnemyAI')
.selector('Main')
.sequence('Chase')
.condition('canSeePlayer')
.action('moveToPlayer')
.end()
.action('idle')
.end()
.build();
}
onDestroy() {
this.entity?.destroy();
}
}
```
## Custom Actions for Laya
```typescript
@NodeExecutorMetadata({
implementationType: 'LayaMoveAction',
nodeType: NodeType.Action,
displayName: 'Laya Move',
category: 'Laya'
})
export class LayaMoveAction implements INodeExecutor {
execute(context: NodeExecutionContext): TaskStatus {
const layaNode = context.runtime.getBlackboardValue<Sprite>('layaNode');
const target = context.runtime.getBlackboardValue<{x: number, y: number}>('target');
if (!layaNode || !target) return TaskStatus.Failure;
const dx = target.x - layaNode.x;
const dy = target.y - layaNode.y;
const distance = Math.sqrt(dx * dx + dy * dy);
if (distance < 5) return TaskStatus.Success;
const speed = BindingHelper.getValue<number>(context, 'speed', 100);
const step = speed * context.deltaTime / 1000;
layaNode.x += (dx / distance) * step;
layaNode.y += (dy / distance) * step;
return TaskStatus.Running;
}
}
```
## Best Practices
1. Use `Laya.timer.delta` for consistent timing
2. Store Laya nodes in blackboard for access in executors
3. Clean up entities when Laya components are destroyed

View File

@@ -0,0 +1,183 @@
---
title: "Node.js Server Usage"
description: "Using behavior trees in server-side applications"
---
## Use Cases
- Game server AI (NPCs, enemies)
- Chatbots and conversational AI
- Task automation and workflows
- Decision-making systems
## Setup
```bash
npm install @esengine/behavior-tree @esengine/ecs-framework
```
## Basic Server Setup
```typescript
import { Core, Scene } from '@esengine/ecs-framework';
import { BehaviorTreePlugin, BehaviorTreeBuilder, BehaviorTreeStarter } from '@esengine/behavior-tree';
async function initializeAI() {
Core.create();
const plugin = new BehaviorTreePlugin();
await Core.installPlugin(plugin);
const scene = new Scene();
plugin.setupScene(scene);
Core.setScene(scene);
return scene;
}
// Game loop
function startGameLoop(scene: Scene) {
const TICK_RATE = 20; // 20 ticks per second
const TICK_INTERVAL = 1000 / TICK_RATE;
setInterval(() => {
scene.update(TICK_INTERVAL);
}, TICK_INTERVAL);
}
```
## NPC AI Example
```typescript
const npcAI = BehaviorTreeBuilder.create('NPCAI')
.defineBlackboardVariable('playerId', null)
.defineBlackboardVariable('questState', 'idle')
.selector('MainBehavior')
// Handle combat
.sequence('Combat')
.condition('isUnderAttack')
.action('defendSelf')
.end()
// Handle quests
.sequence('QuestInteraction')
.condition('playerNearby')
.action('checkQuestState')
.selector('QuestActions')
.sequence('GiveQuest')
.blackboardCompare('questState', 'idle', 'equals')
.action('offerQuest')
.end()
.sequence('CompleteQuest')
.blackboardCompare('questState', 'complete', 'equals')
.action('giveReward')
.end()
.end()
.end()
// Default idle
.action('idle')
.end()
.build();
```
## Chatbot Example
```typescript
const chatbotAI = BehaviorTreeBuilder.create('ChatbotAI')
.defineBlackboardVariable('userInput', '')
.defineBlackboardVariable('context', {})
.selector('ProcessInput')
// Handle greetings
.sequence('Greeting')
.condition('isGreeting')
.action('respondWithGreeting')
.end()
// Handle questions
.sequence('Question')
.condition('isQuestion')
.action('searchKnowledgeBase')
.action('generateAnswer')
.end()
// Handle commands
.sequence('Command')
.condition('isCommand')
.action('executeCommand')
.end()
// Fallback
.action('respondWithFallback')
.end()
.build();
// Process message
function handleMessage(userId: string, message: string) {
const entity = getOrCreateUserEntity(userId);
const runtime = entity.getComponent(BehaviorTreeRuntimeComponent);
runtime.setBlackboardValue('userInput', message);
scene.update(0); // Process immediately
return runtime.getBlackboardValue('response');
}
```
## Server Integration
### Express.js
```typescript
import express from 'express';
const app = express();
app.use(express.json());
app.post('/api/ai/process', (req, res) => {
const { entityId, action, data } = req.body;
const entity = scene.findEntityById(entityId);
if (!entity) {
return res.status(404).json({ error: 'Entity not found' });
}
const runtime = entity.getComponent(BehaviorTreeRuntimeComponent);
runtime.setBlackboardValue('action', action);
runtime.setBlackboardValue('data', data);
scene.update(0);
const result = runtime.getBlackboardValue('result');
res.json({ result });
});
```
### WebSocket
```typescript
import { WebSocket } from 'ws';
wss.on('connection', (ws) => {
const entity = scene.createEntity('Player');
BehaviorTreeStarter.start(entity, playerAI);
ws.on('message', (data) => {
const message = JSON.parse(data.toString());
const runtime = entity.getComponent(BehaviorTreeRuntimeComponent);
runtime.setBlackboardValue('input', message);
});
ws.on('close', () => {
entity.destroy();
});
});
```
## Performance Tips
1. **Batch updates** - Process multiple entities per tick
2. **Adjust tick rate** - Use lower rates for less time-critical AI
3. **Pool entities** - Reuse entities instead of creating/destroying
4. **Profile** - Monitor CPU usage and optimize hot paths

View File

@@ -0,0 +1,133 @@
---
title: "Blueprint Composition"
description: "Fragments, composer, and triggers"
---
## Blueprint Fragments
Encapsulate reusable logic as fragments:
```typescript
import { createFragment } from '@esengine/blueprint';
const healthFragment = createFragment('HealthSystem', {
inputs: [
{ name: 'damage', type: 'number', internalNodeId: 'input1', internalPinName: 'value' }
],
outputs: [
{ name: 'isDead', type: 'boolean', internalNodeId: 'output1', internalPinName: 'value' }
],
graph: {
nodes: [...],
connections: [...],
variables: [...]
}
});
```
## Composing Blueprints
```typescript
import { createComposer, FragmentRegistry } from '@esengine/blueprint';
// Register fragments
FragmentRegistry.instance.register('health', healthFragment);
FragmentRegistry.instance.register('movement', movementFragment);
// Create composer
const composer = createComposer('PlayerBlueprint');
// Add fragments to slots
composer.addFragment(healthFragment, 'slot1', { position: { x: 0, y: 0 } });
composer.addFragment(movementFragment, 'slot2', { position: { x: 400, y: 0 } });
// Connect slots
composer.connect('slot1', 'onDeath', 'slot2', 'disable');
// Validate
const validation = composer.validate();
if (!validation.isValid) {
console.error(validation.errors);
}
// Compile to blueprint
const blueprint = composer.compile();
```
## Fragment Registry
```typescript
import { FragmentRegistry } from '@esengine/blueprint';
const registry = FragmentRegistry.instance;
// Register fragment
registry.register('health', healthFragment);
// Get fragment
const fragment = registry.get('health');
// Get all fragments
const allFragments = registry.getAll();
// Get by category
const combatFragments = registry.getByCategory('combat');
```
## Trigger System
### Defining Trigger Conditions
```typescript
import { TriggerCondition, TriggerDispatcher } from '@esengine/blueprint';
const lowHealthCondition: TriggerCondition = {
type: 'comparison',
left: { type: 'variable', name: 'health' },
operator: '<',
right: { type: 'constant', value: 20 }
};
```
### Using Trigger Dispatcher
```typescript
const dispatcher = new TriggerDispatcher();
// Register trigger
dispatcher.register('lowHealth', lowHealthCondition, (context) => {
context.triggerEvent('OnLowHealth');
});
// Evaluate each frame
dispatcher.evaluate(context);
```
### Compound Conditions
```typescript
const complexCondition: TriggerCondition = {
type: 'and',
conditions: [
{
type: 'comparison',
left: { type: 'variable', name: 'health' },
operator: '<',
right: { type: 'constant', value: 50 }
},
{
type: 'comparison',
left: { type: 'variable', name: 'inCombat' },
operator: '==',
right: { type: 'constant', value: true }
}
]
};
```
## Fragment Best Practices
1. **Single Responsibility** - Each fragment does one thing
2. **Clear Interface** - Name input/output pins clearly
3. **Documentation** - Add descriptions and usage examples
4. **Version Control** - Maintain backward compatibility when updating

View File

@@ -0,0 +1,128 @@
---
title: "Custom Nodes"
description: "Creating custom blueprint nodes"
---
## Defining Node Template
```typescript
import { BlueprintNodeTemplate } from '@esengine/blueprint';
const MyNodeTemplate: BlueprintNodeTemplate = {
type: 'MyCustomNode',
title: 'My Custom Node',
category: 'custom',
description: 'A custom node example',
keywords: ['custom', 'example'],
inputs: [
{ name: 'exec', type: 'exec', direction: 'input', isExec: true },
{ name: 'value', type: 'number', direction: 'input', defaultValue: 0 }
],
outputs: [
{ name: 'exec', type: 'exec', direction: 'output', isExec: true },
{ name: 'result', type: 'number', direction: 'output' }
]
};
```
## Implementing Node Executor
```typescript
import { INodeExecutor, RegisterNode } from '@esengine/blueprint';
@RegisterNode(MyNodeTemplate)
class MyNodeExecutor implements INodeExecutor {
execute(node: BlueprintNode, context: ExecutionContext): ExecutionResult {
// Get input
const value = context.getInput<number>(node.id, 'value');
// Execute logic
const result = value * 2;
// Return result
return {
outputs: { result },
nextExec: 'exec' // Continue execution
};
}
}
```
## Registration Methods
```typescript
// Method 1: Using decorator
@RegisterNode(MyNodeTemplate)
class MyNodeExecutor implements INodeExecutor { ... }
// Method 2: Manual registration
NodeRegistry.instance.register(MyNodeTemplate, new MyNodeExecutor());
```
## Node Registry
```typescript
import { NodeRegistry } from '@esengine/blueprint';
// Get singleton
const registry = NodeRegistry.instance;
// Get all templates
const allTemplates = registry.getAllTemplates();
// Get by category
const mathNodes = registry.getTemplatesByCategory('math');
// Search nodes
const results = registry.searchTemplates('add');
// Check existence
if (registry.has('MyCustomNode')) { ... }
```
## Pure Nodes
Pure nodes have no side effects and their outputs are cached:
```typescript
const PureNodeTemplate: BlueprintNodeTemplate = {
type: 'GetDistance',
title: 'Get Distance',
category: 'math',
isPure: true, // Mark as pure node
inputs: [
{ name: 'a', type: 'vector2', direction: 'input' },
{ name: 'b', type: 'vector2', direction: 'input' }
],
outputs: [
{ name: 'distance', type: 'number', direction: 'output' }
]
};
```
## Example: Input Handler Node
```typescript
const InputMoveTemplate: BlueprintNodeTemplate = {
type: 'InputMove',
title: 'Get Movement Input',
category: 'input',
inputs: [],
outputs: [
{ name: 'direction', type: 'vector2', direction: 'output' }
],
isPure: true
};
@RegisterNode(InputMoveTemplate)
class InputMoveExecutor implements INodeExecutor {
execute(node: BlueprintNode, context: ExecutionContext): ExecutionResult {
const input = context.scene.services.get(InputServiceToken);
const direction = {
x: input.getAxis('horizontal'),
y: input.getAxis('vertical')
};
return { outputs: { direction } };
}
}
```

View File

@@ -0,0 +1,149 @@
---
title: "Examples"
description: "ECS integration and best practices"
---
## Player Control Blueprint
```typescript
// Define input handling node
const InputMoveTemplate: BlueprintNodeTemplate = {
type: 'InputMove',
title: 'Get Movement Input',
category: 'input',
inputs: [],
outputs: [
{ name: 'direction', type: 'vector2', direction: 'output' }
],
isPure: true
};
@RegisterNode(InputMoveTemplate)
class InputMoveExecutor implements INodeExecutor {
execute(node: BlueprintNode, context: ExecutionContext): ExecutionResult {
const input = context.scene.services.get(InputServiceToken);
const direction = {
x: input.getAxis('horizontal'),
y: input.getAxis('vertical')
};
return { outputs: { direction } };
}
}
```
## State Switching Logic
```typescript
// Implement state machine logic in blueprint
const stateBlueprint = createEmptyBlueprint('PlayerState');
// Add state variable
stateBlueprint.variables.push({
name: 'currentState',
type: 'string',
defaultValue: 'idle',
scope: 'instance'
});
// Check state transitions in Tick event
// ... implemented via node connections
```
## Damage Handling System
```typescript
// Custom damage node
const ApplyDamageTemplate: BlueprintNodeTemplate = {
type: 'ApplyDamage',
title: 'Apply Damage',
category: 'combat',
inputs: [
{ name: 'exec', type: 'exec', direction: 'input', isExec: true },
{ name: 'target', type: 'entity', direction: 'input' },
{ name: 'amount', type: 'number', direction: 'input', defaultValue: 10 }
],
outputs: [
{ name: 'exec', type: 'exec', direction: 'output', isExec: true },
{ name: 'killed', type: 'boolean', direction: 'output' }
]
};
@RegisterNode(ApplyDamageTemplate)
class ApplyDamageExecutor implements INodeExecutor {
execute(node: BlueprintNode, context: ExecutionContext): ExecutionResult {
const target = context.getInput<Entity>(node.id, 'target');
const amount = context.getInput<number>(node.id, 'amount');
const health = target.getComponent(HealthComponent);
if (health) {
health.current -= amount;
const killed = health.current <= 0;
return {
outputs: { killed },
nextExec: 'exec'
};
}
return { outputs: { killed: false }, nextExec: 'exec' };
}
}
```
## Best Practices
### 1. Use Fragments for Reusable Logic
```typescript
// Encapsulate common logic as fragments
const movementFragment = createFragment('Movement', {
inputs: [{ name: 'speed', type: 'number', ... }],
outputs: [{ name: 'position', type: 'vector2', ... }],
graph: { ... }
});
// Build complex blueprints via composer
const composer = createComposer('Player');
composer.addFragment(movementFragment, 'movement');
composer.addFragment(combatFragment, 'combat');
```
### 2. Use Variable Scopes Appropriately
```typescript
// local: Temporary calculation results
{ name: 'tempValue', scope: 'local' }
// instance: Entity state (e.g., health)
{ name: 'health', scope: 'instance' }
// global: Game-wide state
{ name: 'score', scope: 'global' }
```
### 3. Avoid Infinite Loops
```typescript
// VM has max steps per frame limit (default 1000)
// Use Delay nodes to break long execution chains
vm.maxStepsPerFrame = 1000;
```
### 4. Debugging Tips
```typescript
// Enable debug mode for execution logs
vm.debug = true;
// Use Print nodes for intermediate values
// Set breakpoints in editor
```
### 5. Performance Optimization
```typescript
// Pure node outputs are cached
{ isPure: true }
// Avoid heavy computation in Tick
// Use event-driven instead of polling
```

View File

@@ -404,3 +404,11 @@ async function loadBlueprint(path: string): Promise<BlueprintAsset> {
5. **Performance optimization**
- Pure nodes (`isPure: true`) cache outputs
- Avoid heavy computation in Tick
## Documentation
- [Virtual Machine API](./vm) - BlueprintVM execution and context
- [Custom Nodes](./custom-nodes) - Creating custom nodes
- [Built-in Nodes](./nodes) - Built-in node reference
- [Blueprint Composition](./composition) - Fragments and composer
- [Examples](./examples) - ECS integration and best practices

View File

@@ -0,0 +1,107 @@
---
title: "Built-in Nodes"
description: "Blueprint built-in node reference"
---
## Event Nodes
| Node | Description |
|------|-------------|
| `EventBeginPlay` | Triggered when blueprint starts |
| `EventTick` | Triggered each frame |
| `EventEndPlay` | Triggered when blueprint stops |
| `EventCollision` | Triggered on collision |
| `EventInput` | Triggered on input event |
| `EventTimer` | Triggered by timer |
| `EventMessage` | Triggered by custom message |
## Flow Control Nodes
| Node | Description |
|------|-------------|
| `Branch` | Conditional branch (if/else) |
| `Sequence` | Execute multiple outputs in sequence |
| `ForLoop` | Loop execution |
| `WhileLoop` | Conditional loop |
| `DoOnce` | Execute only once |
| `FlipFlop` | Alternate between two branches |
| `Gate` | Toggleable execution gate |
## Time Nodes
| Node | Description |
|------|-------------|
| `Delay` | Delay execution |
| `GetDeltaTime` | Get frame delta time |
| `GetTime` | Get runtime |
| `SetTimer` | Set timer |
| `ClearTimer` | Clear timer |
## Math Nodes
| Node | Description |
|------|-------------|
| `Add` | Addition |
| `Subtract` | Subtraction |
| `Multiply` | Multiplication |
| `Divide` | Division |
| `Abs` | Absolute value |
| `Clamp` | Clamp to range |
| `Lerp` | Linear interpolation |
| `Min` / `Max` | Minimum/Maximum |
| `Sin` / `Cos` | Trigonometric functions |
| `Sqrt` | Square root |
| `Power` | Power |
## Logic Nodes
| Node | Description |
|------|-------------|
| `And` | Logical AND |
| `Or` | Logical OR |
| `Not` | Logical NOT |
| `Equal` | Equality comparison |
| `NotEqual` | Inequality comparison |
| `Greater` | Greater than comparison |
| `Less` | Less than comparison |
## Vector Nodes
| Node | Description |
|------|-------------|
| `MakeVector2` | Create 2D vector |
| `BreakVector2` | Break 2D vector |
| `VectorAdd` | Vector addition |
| `VectorSubtract` | Vector subtraction |
| `VectorMultiply` | Vector multiplication |
| `VectorLength` | Vector length |
| `VectorNormalize` | Vector normalization |
| `VectorDistance` | Vector distance |
## Entity Nodes
| Node | Description |
|------|-------------|
| `GetSelf` | Get current entity |
| `GetComponent` | Get component |
| `HasComponent` | Check component |
| `AddComponent` | Add component |
| `RemoveComponent` | Remove component |
| `SpawnEntity` | Create entity |
| `DestroyEntity` | Destroy entity |
## Variable Nodes
| Node | Description |
|------|-------------|
| `GetVariable` | Get variable value |
| `SetVariable` | Set variable value |
## Debug Nodes
| Node | Description |
|------|-------------|
| `Print` | Print to console |
| `DrawDebugLine` | Draw debug line |
| `DrawDebugPoint` | Draw debug point |
| `Breakpoint` | Debug breakpoint |

View File

@@ -0,0 +1,133 @@
---
title: "Virtual Machine API"
description: "BlueprintVM execution and context"
---
## BlueprintVM
The blueprint virtual machine executes blueprint graphs:
```typescript
import { BlueprintVM } from '@esengine/blueprint';
// Create VM
const vm = new BlueprintVM(blueprintAsset, entity, scene);
// Start (triggers BeginPlay)
vm.start();
// Update each frame (triggers Tick)
vm.tick(deltaTime);
// Stop (triggers EndPlay)
vm.stop();
// Pause/Resume
vm.pause();
vm.resume();
// Trigger events
vm.triggerEvent('EventCollision', { other: otherEntity });
vm.triggerCustomEvent('OnDamage', { amount: 50 });
// Debug mode
vm.debug = true;
```
## Execution Context
```typescript
interface ExecutionContext {
blueprint: BlueprintAsset; // Blueprint asset
entity: Entity; // Current entity
scene: IScene; // Current scene
deltaTime: number; // Frame delta time
time: number; // Total runtime
// Get input value
getInput<T>(nodeId: string, pinName: string): T;
// Set output value
setOutput(nodeId: string, pinName: string, value: unknown): void;
// Variable access
getVariable<T>(name: string): T;
setVariable(name: string, value: unknown): void;
}
```
## Execution Result
```typescript
interface ExecutionResult {
outputs?: Record<string, unknown>; // Output values
nextExec?: string | null; // Next execution pin
delay?: number; // Delay execution (ms)
yield?: boolean; // Pause until next frame
error?: string; // Error message
}
```
## ECS Integration
### Using Blueprint System
```typescript
import { createBlueprintSystem } from '@esengine/blueprint';
class GameScene {
private blueprintSystem: BlueprintSystem;
initialize() {
this.blueprintSystem = createBlueprintSystem(this.scene);
}
update(dt: number) {
// Process all entities with blueprint components
this.blueprintSystem.process(this.entities, dt);
}
}
```
### Triggering Blueprint Events
```typescript
import { triggerBlueprintEvent, triggerCustomBlueprintEvent } from '@esengine/blueprint';
// Trigger built-in event
triggerBlueprintEvent(entity, 'Collision', { other: otherEntity });
// Trigger custom event
triggerCustomBlueprintEvent(entity, 'OnPickup', { item: itemEntity });
```
## Serialization
### Saving Blueprints
```typescript
import { validateBlueprintAsset } from '@esengine/blueprint';
function saveBlueprint(blueprint: BlueprintAsset, path: string): void {
if (!validateBlueprintAsset(blueprint)) {
throw new Error('Invalid blueprint structure');
}
const json = JSON.stringify(blueprint, null, 2);
fs.writeFileSync(path, json);
}
```
### Loading Blueprints
```typescript
async function loadBlueprint(path: string): Promise<BlueprintAsset> {
const json = await fs.readFile(path, 'utf-8');
const asset = JSON.parse(json);
if (!validateBlueprintAsset(asset)) {
throw new Error('Invalid blueprint file');
}
return asset;
}
```

View File

@@ -0,0 +1,158 @@
---
title: "API Reference"
description: "Complete FSM API documentation"
---
## createStateMachine
```typescript
function createStateMachine<TContext = unknown>(
config: StateMachineConfig<TContext>
): StateMachine<TContext>
```
### Configuration
```typescript
interface StateMachineConfig<TContext> {
initial: string;
context?: TContext;
states: Record<string, StateDefinition<TContext>>;
onTransition?: (from: string, to: string, event: string) => void;
}
```
## StateMachine Instance
### Properties
```typescript
// Current state
fsm.current: string
// Context object
fsm.context: TContext
// Get state definition
fsm.getState(name: string): StateDefinition | undefined
// Check if in specific state
fsm.is(state: string): boolean
```
### Methods
```typescript
// Send event to trigger transition
fsm.send(event: string): boolean
// Force transition (no guards, no event matching)
fsm.transitionTo(state: string): void
// Update current state (call onUpdate if exists)
fsm.update(dt: number): void
// Reset to initial state
fsm.reset(): void
```
## State Definition
```typescript
interface StateDefinition<TContext> {
// Called on entering state
onEnter?: (context: TContext) => void;
// Called on exiting state
onExit?: (context: TContext) => void;
// Called on each update
onUpdate?: (dt: number, context: TContext) => void;
// Valid transitions from this state
transitions: Record<string, string | TransitionConfig>;
}
```
## Transition Configuration
```typescript
interface TransitionConfig {
target: string;
guard?: (context: TContext) => boolean;
action?: (context: TContext) => void;
}
```
## Complete Example
```typescript
interface PlayerContext {
health: number;
stamina: number;
isGrounded: boolean;
}
const playerFSM = createStateMachine<PlayerContext>({
initial: 'idle',
context: {
health: 100,
stamina: 100,
isGrounded: true
},
states: {
idle: {
onEnter: (ctx) => console.log('Idle'),
onUpdate: (dt, ctx) => {
ctx.stamina = Math.min(100, ctx.stamina + dt * 10);
},
transitions: {
move: 'walking',
jump: {
target: 'jumping',
guard: (ctx) => ctx.isGrounded && ctx.stamina >= 20
},
attack: {
target: 'attacking',
guard: (ctx) => ctx.stamina >= 30
}
}
},
walking: {
onUpdate: (dt, ctx) => {
ctx.stamina = Math.max(0, ctx.stamina - dt * 5);
},
transitions: {
stop: 'idle',
jump: {
target: 'jumping',
guard: (ctx) => ctx.isGrounded
}
}
},
jumping: {
onEnter: (ctx) => {
ctx.isGrounded = false;
ctx.stamina -= 20;
},
transitions: {
land: {
target: 'idle',
action: (ctx) => { ctx.isGrounded = true; }
}
}
},
attacking: {
onEnter: (ctx) => {
ctx.stamina -= 30;
},
transitions: {
finish: 'idle'
}
}
},
onTransition: (from, to, event) => {
console.log(`${from} -> ${to} (${event})`);
}
});
```

View File

@@ -0,0 +1,253 @@
---
title: "Examples"
description: "Character FSM, AI behavior, ECS integration"
---
## Character State Machine
```typescript
interface CharacterContext {
health: number;
stamina: number;
isGrounded: boolean;
velocity: { x: number; y: number };
}
const characterFSM = createStateMachine<CharacterContext>({
initial: 'idle',
context: {
health: 100,
stamina: 100,
isGrounded: true,
velocity: { x: 0, y: 0 }
},
states: {
idle: {
onEnter: (ctx) => {
ctx.velocity.x = 0;
},
onUpdate: (dt, ctx) => {
// Recover stamina
ctx.stamina = Math.min(100, ctx.stamina + dt * 10);
},
transitions: {
move: 'running',
jump: { target: 'jumping', guard: ctx => ctx.isGrounded },
hurt: 'hit',
die: { target: 'dead', guard: ctx => ctx.health <= 0 }
}
},
running: {
onUpdate: (dt, ctx) => {
ctx.stamina = Math.max(0, ctx.stamina - dt * 5);
},
transitions: {
stop: 'idle',
jump: { target: 'jumping', guard: ctx => ctx.isGrounded },
hurt: 'hit',
exhaust: { target: 'idle', guard: ctx => ctx.stamina <= 0 }
}
},
jumping: {
onEnter: (ctx) => {
ctx.velocity.y = -10;
ctx.isGrounded = false;
ctx.stamina -= 20;
},
transitions: {
land: { target: 'idle', action: ctx => { ctx.isGrounded = true; } },
hurt: 'hit'
}
},
hit: {
onEnter: (ctx) => {
// Play hit animation
},
transitions: {
recover: 'idle',
die: { target: 'dead', guard: ctx => ctx.health <= 0 }
}
},
dead: {
onEnter: (ctx) => {
ctx.velocity = { x: 0, y: 0 };
// Play death animation
},
transitions: {
respawn: {
target: 'idle',
action: (ctx) => {
ctx.health = 100;
ctx.stamina = 100;
}
}
}
}
}
});
```
## ECS Integration
```typescript
import { System } from '@esengine/ecs-framework';
import { createStateMachine } from '@esengine/fsm';
// FSM Component
class FSMComponent extends Component {
fsm: StateMachine<any>;
}
// FSM System
class FSMSystem extends System {
query = this.world.query([FSMComponent]);
update(dt: number): void {
for (const entity of this.query.entities) {
const fsm = entity.getComponent(FSMComponent).fsm;
fsm.update(dt);
}
}
}
// Usage
const entity = world.createEntity();
const fsmComp = entity.addComponent(FSMComponent);
fsmComp.fsm = createStateMachine({
initial: 'patrol',
states: {
patrol: { /* ... */ },
chase: { /* ... */ },
attack: { /* ... */ }
}
});
```
## AI Behavior Example
```typescript
interface EnemyAIContext {
entity: Entity;
target: Entity | null;
alertLevel: number;
lastKnownPosition: { x: number; y: number } | null;
}
const enemyAI = createStateMachine<EnemyAIContext>({
initial: 'patrol',
context: {
entity: enemyEntity,
target: null,
alertLevel: 0,
lastKnownPosition: null
},
states: {
patrol: {
onUpdate: (dt, ctx) => {
// Patrol logic
if (detectPlayer(ctx.entity)) {
ctx.target = player;
ctx.alertLevel = 100;
}
},
transitions: {
detect: { target: 'chase', guard: ctx => ctx.target !== null }
}
},
chase: {
onUpdate: (dt, ctx) => {
if (ctx.target) {
moveToward(ctx.entity, ctx.target.position);
ctx.lastKnownPosition = { ...ctx.target.position };
if (distanceTo(ctx.entity, ctx.target) < 2) {
ctx.entity.fsm.send('inRange');
}
}
ctx.alertLevel -= dt * 10;
},
transitions: {
inRange: 'attack',
lostTarget: {
target: 'search',
guard: ctx => !canSee(ctx.entity, ctx.target)
},
giveUp: {
target: 'patrol',
guard: ctx => ctx.alertLevel <= 0
}
}
},
attack: {
onEnter: (ctx) => {
performAttack(ctx.entity);
},
transitions: {
cooldown: 'chase',
targetDead: {
target: 'patrol',
guard: ctx => ctx.target?.getComponent(Health)?.value <= 0
}
}
},
search: {
onUpdate: (dt, ctx) => {
if (ctx.lastKnownPosition) {
moveToward(ctx.entity, ctx.lastKnownPosition);
}
ctx.alertLevel -= dt * 5;
},
transitions: {
found: { target: 'chase', guard: ctx => canSee(ctx.entity, ctx.target) },
giveUp: { target: 'patrol', guard: ctx => ctx.alertLevel <= 0 }
}
}
}
});
```
## Animation State Machine
```typescript
const animationFSM = createStateMachine({
initial: 'idle',
states: {
idle: {
onEnter: () => playAnimation('idle', { loop: true }),
transitions: {
walk: 'walking',
run: 'running',
jump: 'jumping'
}
},
walking: {
onEnter: () => playAnimation('walk', { loop: true }),
transitions: {
stop: 'idle',
run: 'running',
jump: 'jumping'
}
},
running: {
onEnter: () => playAnimation('run', { loop: true }),
transitions: {
stop: 'idle',
walk: 'walking',
jump: 'jumping'
}
},
jumping: {
onEnter: () => playAnimation('jump', { loop: false }),
transitions: {
land: 'landing'
}
},
landing: {
onEnter: () => playAnimation('land', { loop: false }),
transitions: {
complete: 'idle'
}
}
}
});
```

View File

@@ -316,3 +316,8 @@ The FSM module provides blueprint nodes for visual scripting:
- `GetStateDuration` - Get state duration
- `EvaluateTransitions` - Evaluate transition conditions
- `ResetStateMachine` - Reset state machine
## Documentation
- [API Reference](./api) - Complete API documentation
- [Examples](./examples) - Character FSM, AI behavior, ECS integration

View File

@@ -0,0 +1,164 @@
---
title: "Examples"
description: "Game movement, dynamic obstacles, hierarchical pathfinding"
---
## Game Character Movement
```typescript
class MovementSystem {
private grid: GridMap;
private pathfinder: AStarPathfinder;
private smoother: CombinedSmoother;
constructor(width: number, height: number) {
this.grid = createGridMap(width, height);
this.pathfinder = createAStarPathfinder(this.grid);
this.smoother = createCombinedSmoother();
}
findPath(from: IPoint, to: IPoint): IPoint[] | null {
const result = this.pathfinder.findPath(
from.x, from.y,
to.x, to.y
);
if (!result.found) {
return null;
}
// Smooth path
return this.smoother.smooth(result.path, this.grid);
}
setObstacle(x: number, y: number): void {
this.grid.setWalkable(x, y, false);
}
setTerrain(x: number, y: number, cost: number): void {
this.grid.setCost(x, y, cost);
}
}
```
## Dynamic Obstacles
```typescript
class DynamicPathfinding {
private grid: GridMap;
private pathfinder: AStarPathfinder;
private dynamicObstacles: Set<string> = new Set();
addDynamicObstacle(x: number, y: number): void {
const key = `${x},${y}`;
if (!this.dynamicObstacles.has(key)) {
this.dynamicObstacles.add(key);
this.grid.setWalkable(x, y, false);
}
}
removeDynamicObstacle(x: number, y: number): void {
const key = `${x},${y}`;
if (this.dynamicObstacles.has(key)) {
this.dynamicObstacles.delete(key);
this.grid.setWalkable(x, y, true);
}
}
findPath(from: IPoint, to: IPoint): IPathResult {
return this.pathfinder.findPath(from.x, from.y, to.x, to.y);
}
}
```
## Different Terrain Costs
```typescript
// Set different terrain movement costs
const grid = createGridMap(50, 50);
// Normal ground - cost 1 (default)
// Sand - cost 2
for (let y = 10; y < 20; y++) {
for (let x = 0; x < 50; x++) {
grid.setCost(x, y, 2);
}
}
// Swamp - cost 4
for (let y = 30; y < 35; y++) {
for (let x = 20; x < 30; x++) {
grid.setCost(x, y, 4);
}
}
// Pathfinding automatically considers terrain costs
const result = pathfinder.findPath(0, 0, 49, 49);
```
## Hierarchical Pathfinding
For large maps, use hierarchical pathfinding:
```typescript
class HierarchicalPathfinding {
private coarseGrid: GridMap; // Coarse grid
private fineGrid: GridMap; // Fine grid
private coarsePathfinder: AStarPathfinder;
private finePathfinder: AStarPathfinder;
private cellSize = 10;
findPath(from: IPoint, to: IPoint): IPoint[] {
// 1. Pathfind on coarse grid
const coarseFrom = this.toCoarse(from);
const coarseTo = this.toCoarse(to);
const coarseResult = this.coarsePathfinder.findPath(
coarseFrom.x, coarseFrom.y,
coarseTo.x, coarseTo.y
);
if (!coarseResult.found) {
return [];
}
// 2. Fine pathfind within each coarse cell
const finePath: IPoint[] = [];
// ... detailed implementation
return finePath;
}
private toCoarse(p: IPoint): IPoint {
return {
x: Math.floor(p.x / this.cellSize),
y: Math.floor(p.y / this.cellSize)
};
}
}
```
## Performance Optimization
1. **Limit Search Range**
```typescript
pathfinder.findPath(x1, y1, x2, y2, { maxNodes: 1000 });
```
2. **Use Heuristic Weight**
```typescript
// Weight > 1 is faster but may not be optimal
pathfinder.findPath(x1, y1, x2, y2, { heuristicWeight: 1.5 });
```
3. **Reuse Pathfinder Instance**
```typescript
// Create once, use many times
const pathfinder = createAStarPathfinder(grid);
```
4. **Use Navigation Mesh**
- For complex terrain, NavMesh is more efficient than grid pathfinding
- Polygon count is much less than grid cell count
5. **Choose Appropriate Heuristic**
- 4-direction movement: use `manhattanDistance`
- 8-direction movement: use `octileDistance` (default)

View File

@@ -0,0 +1,112 @@
---
title: "Grid Map API"
description: "Grid operations and A* pathfinder"
---
## createGridMap
```typescript
function createGridMap(
width: number,
height: number,
options?: IGridMapOptions
): GridMap
```
**Configuration Options:**
| Property | Type | Default | Description |
|----------|------|---------|-------------|
| `allowDiagonal` | `boolean` | `true` | Allow diagonal movement |
| `diagonalCost` | `number` | `√2` | Diagonal movement cost |
| `avoidCorners` | `boolean` | `true` | Avoid corner cutting |
| `heuristic` | `HeuristicFunction` | `octileDistance` | Heuristic function |
## Map Operations
```typescript
// Check/set walkability
grid.isWalkable(x, y);
grid.setWalkable(x, y, false);
// Set movement cost (e.g., swamp, sand)
grid.setCost(x, y, 2); // Cost of 2 (default 1)
// Set rectangular area
grid.setRectWalkable(0, 0, 5, 5, false);
// Load from array (0=walkable, non-0=obstacle)
grid.loadFromArray([
[0, 0, 0, 1, 0],
[0, 1, 0, 1, 0],
[0, 1, 0, 0, 0]
]);
// Load from string (.=walkable, #=obstacle)
grid.loadFromString(`
.....
.#.#.
.#...
`);
// Export as string
console.log(grid.toString());
// Reset all nodes to walkable
grid.reset();
```
## A* Pathfinder
### createAStarPathfinder
```typescript
function createAStarPathfinder(map: IPathfindingMap): AStarPathfinder
```
### findPath
```typescript
const result = pathfinder.findPath(
startX, startY,
endX, endY,
{
maxNodes: 5000, // Limit search nodes
heuristicWeight: 1.5 // Faster but may not be optimal
}
);
```
### Reusing Pathfinder
```typescript
// Pathfinder is reusable, clears state automatically
pathfinder.findPath(0, 0, 10, 10);
pathfinder.findPath(5, 5, 15, 15);
// Manual clear (optional)
pathfinder.clear();
```
## Direction Constants
```typescript
import { DIRECTIONS_4, DIRECTIONS_8 } from '@esengine/pathfinding';
// 4 directions (up, down, left, right)
DIRECTIONS_4 // [{ dx: 0, dy: -1 }, { dx: 1, dy: 0 }, ...]
// 8 directions (includes diagonals)
DIRECTIONS_8 // [{ dx: 0, dy: -1 }, { dx: 1, dy: -1 }, ...]
```
## Heuristic Functions
```typescript
import { manhattanDistance, octileDistance } from '@esengine/pathfinding';
// Custom heuristic
const grid = createGridMap(20, 20, {
heuristic: manhattanDistance // Use Manhattan distance
});
```

View File

@@ -299,3 +299,10 @@ for (let y = 30; y < 35; y++) {
| Precision | Grid-aligned | Continuous coordinates |
| Dynamic Updates | Easy | Requires rebuild |
| Setup Complexity | Simple | More complex |
## Documentation
- [Grid Map API](./grid-map) - Grid operations and A* pathfinder
- [Navigation Mesh API](./navmesh) - NavMesh building and querying
- [Path Smoothing](./smoothing) - Line of sight and curve smoothing
- [Examples](./examples) - Game movement, dynamic obstacles, hierarchical pathfinding

View File

@@ -0,0 +1,67 @@
---
title: "Navigation Mesh API"
description: "NavMesh building and querying"
---
## createNavMesh
```typescript
function createNavMesh(): NavMesh
```
## Building Navigation Mesh
```typescript
const navmesh = createNavMesh();
// Add convex polygons
const id1 = navmesh.addPolygon([
{ x: 0, y: 0 }, { x: 10, y: 0 },
{ x: 10, y: 10 }, { x: 0, y: 10 }
]);
const id2 = navmesh.addPolygon([
{ x: 10, y: 0 }, { x: 20, y: 0 },
{ x: 20, y: 10 }, { x: 10, y: 10 }
]);
// Method 1: Auto-detect shared edges and build connections
navmesh.build();
// Method 2: Manually set connections
navmesh.setConnection(id1, id2, {
left: { x: 10, y: 0 },
right: { x: 10, y: 10 }
});
```
## Querying and Pathfinding
```typescript
// Find polygon containing point
const polygon = navmesh.findPolygonAt(5, 5);
// Check if position is walkable
navmesh.isWalkable(5, 5);
// Pathfinding (uses funnel algorithm internally)
const result = navmesh.findPath(1, 1, 18, 8);
```
## Use Cases
Navigation mesh is suitable for:
- Complex irregular terrain
- Scenarios requiring precise paths
- Large maps where polygon count is much less than grid cells
```typescript
// Load navigation mesh data from editor
const navData = await loadNavMeshData('level1.navmesh');
const navmesh = createNavMesh();
for (const poly of navData.polygons) {
navmesh.addPolygon(poly.vertices);
}
navmesh.build();
```

View File

@@ -0,0 +1,67 @@
---
title: "Path Smoothing"
description: "Line of sight simplification and curve smoothing"
---
## Line of Sight Simplification
Remove unnecessary intermediate points:
```typescript
import { createLineOfSightSmoother } from '@esengine/pathfinding';
const smoother = createLineOfSightSmoother();
const smoothedPath = smoother.smooth(result.path, grid);
// Original path: [(0,0), (1,1), (2,2), (3,3), (4,4)]
// Simplified: [(0,0), (4,4)]
```
## Curve Smoothing
Using Catmull-Rom splines:
```typescript
import { createCatmullRomSmoother } from '@esengine/pathfinding';
const smoother = createCatmullRomSmoother(
5, // segments - interpolation points per segment
0.5 // tension - (0-1)
);
const curvedPath = smoother.smooth(result.path, grid);
```
## Combined Smoothing
Simplify first, then curve smooth:
```typescript
import { createCombinedSmoother } from '@esengine/pathfinding';
const smoother = createCombinedSmoother(5, 0.5);
const finalPath = smoother.smooth(result.path, grid);
```
## Line of Sight Functions
```typescript
import { bresenhamLineOfSight, raycastLineOfSight } from '@esengine/pathfinding';
// Bresenham algorithm (fast, grid-aligned)
const hasLOS = bresenhamLineOfSight(x1, y1, x2, y2, grid);
// Raycast (precise, supports floating point coordinates)
const hasLOS = raycastLineOfSight(x1, y1, x2, y2, grid, 0.5);
```
## Blueprint Nodes
- `FindPath` - Find path
- `FindPathSmooth` - Find and smooth path
- `IsWalkable` - Check if position is walkable
- `GetPathLength` - Get number of path points
- `GetPathDistance` - Get total path distance
- `GetPathPoint` - Get point at index
- `MoveAlongPath` - Move along path
- `HasLineOfSight` - Check line of sight

View File

@@ -0,0 +1,230 @@
---
title: "Examples"
description: "Terrain, loot, enemies, and level generation"
---
## Procedural Terrain Generation
```typescript
import { createPerlinNoise, createFBM } from '@esengine/procgen';
class TerrainGenerator {
private fbm: FBM;
private moistureFbm: FBM;
constructor(seed: number) {
const heightNoise = createPerlinNoise(seed);
const moistureNoise = createPerlinNoise(seed + 1000);
this.fbm = createFBM(heightNoise, {
octaves: 8,
persistence: 0.5,
frequency: 0.01
});
this.moistureFbm = createFBM(moistureNoise, {
octaves: 4,
persistence: 0.6,
frequency: 0.02
});
}
getHeight(x: number, y: number): number {
// Base height
let height = this.fbm.noise2D(x, y);
// Add mountains
height += this.fbm.ridged2D(x * 0.5, y * 0.5) * 0.3;
return (height + 1) * 0.5; // Normalize to [0, 1]
}
getBiome(x: number, y: number): string {
const height = this.getHeight(x, y);
const moisture = (this.moistureFbm.noise2D(x, y) + 1) * 0.5;
if (height < 0.3) return 'water';
if (height < 0.4) return 'beach';
if (height > 0.8) return 'mountain';
if (moisture < 0.3) return 'desert';
if (moisture > 0.7) return 'forest';
return 'grassland';
}
}
```
## Loot System
```typescript
import { createSeededRandom, createWeightedRandom, pickOne } from '@esengine/procgen';
interface LootItem {
id: string;
rarity: string;
}
class LootSystem {
private rng: SeededRandom;
private raritySelector: WeightedRandom<string>;
private lootTables: Map<string, LootItem[]> = new Map();
constructor(seed: number) {
this.rng = createSeededRandom(seed);
this.raritySelector = createWeightedRandom([
{ value: 'common', weight: 60 },
{ value: 'uncommon', weight: 25 },
{ value: 'rare', weight: 10 },
{ value: 'legendary', weight: 5 }
]);
// Initialize loot tables
this.lootTables.set('common', [/* ... */]);
this.lootTables.set('rare', [/* ... */]);
}
generateLoot(count: number): LootItem[] {
const loot: LootItem[] = [];
for (let i = 0; i < count; i++) {
const rarity = this.raritySelector.pick(this.rng);
const table = this.lootTables.get(rarity)!;
const item = pickOne(table, this.rng);
loot.push(item);
}
return loot;
}
// Ensure reproducibility
setSeed(seed: number): void {
this.rng = createSeededRandom(seed);
}
}
```
## Procedural Enemy Placement
```typescript
import { createSeededRandom } from '@esengine/procgen';
class EnemySpawner {
private rng: SeededRandom;
constructor(seed: number) {
this.rng = createSeededRandom(seed);
}
spawnEnemiesInArea(
centerX: number,
centerY: number,
radius: number,
count: number
): Array<{ x: number; y: number; type: string }> {
const enemies: Array<{ x: number; y: number; type: string }> = [];
for (let i = 0; i < count; i++) {
// Generate position in circle
const pos = this.rng.nextPointInCircle(radius);
// Randomly select enemy type
const type = this.rng.nextBool(0.2) ? 'elite' : 'normal';
enemies.push({
x: centerX + pos.x,
y: centerY + pos.y,
type
});
}
return enemies;
}
}
```
## Procedural Level Layout
```typescript
import { createSeededRandom, shuffle } from '@esengine/procgen';
interface Room {
x: number;
y: number;
width: number;
height: number;
type: 'start' | 'combat' | 'treasure' | 'boss';
}
class DungeonGenerator {
private rng: SeededRandom;
constructor(seed: number) {
this.rng = createSeededRandom(seed);
}
generate(roomCount: number): Room[] {
const rooms: Room[] = [];
// Generate rooms
for (let i = 0; i < roomCount; i++) {
rooms.push({
x: this.rng.nextInt(0, 100),
y: this.rng.nextInt(0, 100),
width: this.rng.nextInt(5, 15),
height: this.rng.nextInt(5, 15),
type: 'combat'
});
}
// Randomly assign special rooms
shuffle(rooms, this.rng);
rooms[0].type = 'start';
rooms[1].type = 'treasure';
rooms[rooms.length - 1].type = 'boss';
return rooms;
}
}
```
## Blueprint Nodes
Procgen module provides blueprint nodes for visual scripting:
### Noise Nodes
- `SampleNoise2D` - Sample 2D noise
- `SampleFBM` - Sample FBM noise
### Random Nodes
- `SeededRandom` - Generate random float
- `SeededRandomInt` - Generate random integer
- `WeightedPick` - Weighted random selection
- `ShuffleArray` - Shuffle array
- `PickRandom` - Pick random element
- `SampleArray` - Sample from array
- `RandomPointInCircle` - Random point in circle
## Best Practices
1. **Use seeds for reproducibility**
```typescript
const seed = Date.now();
const rng = createSeededRandom(seed);
saveSeed(seed);
```
2. **Precompute weighted selectors** - Avoid repeated creation
3. **Choose appropriate noise**
- Perlin: Smooth terrain, clouds
- Simplex: Performance-critical
- Worley: Cells, stone textures
- FBM: Multi-layer natural effects
4. **Tune FBM parameters**
- `octaves`: More = richer detail, higher cost
- `persistence`: 0.5 is common, higher = more high-frequency detail
- `lacunarity`: Usually 2, controls frequency growth

View File

@@ -396,3 +396,10 @@ class LootSystem {
- `octaves`: More = richer detail, higher cost
- `persistence`: 0.5 is common, higher = more high-frequency detail
- `lacunarity`: Usually 2, controls frequency growth
## Documentation
- [Noise Functions](./noise) - Perlin, Simplex, Worley, FBM
- [Seeded Random](./random) - SeededRandom API and distribution methods
- [Sampling Utilities](./sampling) - Weighted random, shuffle, sampling
- [Examples](./examples) - Terrain, loot, level generation

View File

@@ -0,0 +1,97 @@
---
title: "Noise Functions"
description: "Perlin, Simplex, Worley, and FBM"
---
## Perlin Noise
Classic gradient noise, output range [-1, 1]:
```typescript
import { createPerlinNoise } from '@esengine/procgen';
const perlin = createPerlinNoise(seed);
// 2D noise
const value2D = perlin.noise2D(x, y);
// 3D noise
const value3D = perlin.noise3D(x, y, z);
```
## Simplex Noise
Faster than Perlin with less directional bias:
```typescript
import { createSimplexNoise } from '@esengine/procgen';
const simplex = createSimplexNoise(seed);
const value = simplex.noise2D(x, y);
```
## Worley Noise
Cell-based noise, suitable for stone, cell textures:
```typescript
import { createWorleyNoise } from '@esengine/procgen';
const worley = createWorleyNoise(seed);
// Returns distance to nearest point
const distance = worley.noise2D(x, y);
```
## FBM (Fractal Brownian Motion)
Layer multiple noise octaves for richer detail:
```typescript
import { createPerlinNoise, createFBM } from '@esengine/procgen';
const baseNoise = createPerlinNoise(seed);
const fbm = createFBM(baseNoise, {
octaves: 6, // Layer count (more = richer detail)
lacunarity: 2.0, // Frequency multiplier
persistence: 0.5, // Amplitude decay factor
frequency: 1.0, // Initial frequency
amplitude: 1.0 // Initial amplitude
});
// Standard FBM
const value = fbm.noise2D(x, y);
// Ridged FBM (for mountains)
const ridged = fbm.ridged2D(x, y);
// Turbulence
const turb = fbm.turbulence2D(x, y);
// Billowed (for clouds)
const cloud = fbm.billowed2D(x, y);
```
## FBM Parameter Guide
| Parameter | Description | Recommended |
|-----------|-------------|-------------|
| `octaves` | Layer count, more = richer detail | 4-8 |
| `lacunarity` | Frequency multiplier | 2.0 |
| `persistence` | Amplitude decay factor | 0.5 |
| `frequency` | Initial frequency | 0.01-0.1 |
| `amplitude` | Initial amplitude | 1.0 |
## Choosing the Right Noise
| Noise Type | Use Case |
|------------|----------|
| Perlin | Smooth terrain transitions, clouds |
| Simplex | Performance-critical scenarios |
| Worley | Cells, stone, crack textures |
| FBM | Multi-layer natural detail effects |
| Ridged FBM | Mountains, ridged terrain |
| Turbulence | Flame, smoke effects |
| Billowed FBM | Clouds, soft puffy effects |

View File

@@ -0,0 +1,92 @@
---
title: "Seeded Random"
description: "SeededRandom API and distribution methods"
---
## SeededRandom
Deterministic PRNG based on xorshift128+ algorithm:
```typescript
import { createSeededRandom } from '@esengine/procgen';
const rng = createSeededRandom(42);
```
## Basic Methods
```typescript
// [0, 1) float
rng.next();
// [min, max] integer
rng.nextInt(1, 10);
// [min, max) float
rng.nextFloat(0, 100);
// Boolean (with optional probability)
rng.nextBool(); // 50%
rng.nextBool(0.3); // 30%
// Reset to initial state
rng.reset();
```
## Distribution Methods
```typescript
// Normal distribution (Gaussian)
rng.nextGaussian(); // mean 0, stdDev 1
rng.nextGaussian(100, 15); // mean 100, stdDev 15
// Exponential distribution
rng.nextExponential(); // λ = 1
rng.nextExponential(0.5); // λ = 0.5
```
## Geometry Methods
```typescript
// Uniform point in circle
const point = rng.nextPointInCircle(50); // { x, y }
// Point on circle edge
const edge = rng.nextPointOnCircle(50); // { x, y }
// Uniform point in sphere
const point3D = rng.nextPointInSphere(50); // { x, y, z }
// Random direction vector
const dir = rng.nextDirection2D(); // { x, y }, length 1
```
## Determinism Guarantee
Same seed always produces same sequence:
```typescript
const rng1 = createSeededRandom(12345);
const rng2 = createSeededRandom(12345);
// These sequences are identical
console.log(rng1.next()); // 0.xxx
console.log(rng2.next()); // 0.xxx (same)
console.log(rng1.nextInt(1, 100)); // N
console.log(rng2.nextInt(1, 100)); // N (same)
```
## Saving and Restoring State
```typescript
// Save seed for reproducibility
const seed = Date.now();
const rng = createSeededRandom(seed);
saveSeed(seed);
// Later, restore with same seed
const savedSeed = loadSeed();
const rng2 = createSeededRandom(savedSeed);
// Will produce identical sequence
```

View File

@@ -0,0 +1,135 @@
---
title: "Sampling Utilities"
description: "Weighted random, shuffle, and sampling functions"
---
## Weighted Random API
### WeightedRandom
Precomputed cumulative weights for efficient selection:
```typescript
import { createWeightedRandom } from '@esengine/procgen';
const selector = createWeightedRandom([
{ value: 'apple', weight: 5 },
{ value: 'banana', weight: 3 },
{ value: 'cherry', weight: 2 }
]);
// Use with seeded random
const result = selector.pick(rng);
// Use with Math.random
const result2 = selector.pickRandom();
// Get probability
console.log(selector.getProbability(0)); // 0.5 (5/10)
console.log(selector.size); // 3
console.log(selector.totalWeight); // 10
```
### Convenience Functions
```typescript
import { weightedPick, weightedPickFromMap } from '@esengine/procgen';
// Pick from array
const item = weightedPick([
{ value: 'a', weight: 1 },
{ value: 'b', weight: 2 }
], rng);
// Pick from object
const item2 = weightedPickFromMap({
'common': 60,
'rare': 30,
'epic': 10
}, rng);
```
## Shuffle API
### shuffle / shuffleCopy
Fisher-Yates shuffle algorithm:
```typescript
import { shuffle, shuffleCopy } from '@esengine/procgen';
const arr = [1, 2, 3, 4, 5];
// In-place shuffle
shuffle(arr, rng);
// Create shuffled copy (original unchanged)
const shuffled = shuffleCopy(arr, rng);
```
### pickOne
Randomly select one element:
```typescript
import { pickOne } from '@esengine/procgen';
const items = ['a', 'b', 'c', 'd'];
const item = pickOne(items, rng);
```
## Sampling API
### sample / sampleWithReplacement
```typescript
import { sample, sampleWithReplacement } from '@esengine/procgen';
const arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
// Sample 3 unique elements
const unique = sample(arr, 3, rng);
// Sample 5 (with possible repeats)
const withRep = sampleWithReplacement(arr, 5, rng);
```
### randomIntegers
Generate random integer array within range:
```typescript
import { randomIntegers } from '@esengine/procgen';
// 5 unique random integers from 1-100
const nums = randomIntegers(1, 100, 5, rng);
```
### weightedSample
Sample by weight (no replacement):
```typescript
import { weightedSample } from '@esengine/procgen';
const items = ['A', 'B', 'C', 'D', 'E'];
const weights = [10, 8, 6, 4, 2];
// Select 3 by weight
const selected = weightedSample(items, weights, 3, rng);
```
## Performance Tips
```typescript
// Good: Create once, use many times
const selector = createWeightedRandom(items);
for (let i = 0; i < 1000; i++) {
selector.pick(rng);
}
// Bad: Create every time
for (let i = 0; i < 1000; i++) {
weightedPick(items, rng);
}
```

View File

@@ -0,0 +1,166 @@
---
title: "AOI (Area of Interest)"
description: "View management and enter/exit events"
---
AOI (Area of Interest) tracks visibility relationships between entities, commonly used for MMO synchronization and NPC AI perception.
## createGridAOI
```typescript
function createGridAOI<T>(cellSize?: number): GridAOI<T>
```
Creates a grid-based AOI manager.
**Parameters:**
- `cellSize` - Grid cell size (recommended: 1-2x average view range)
## Observer Management
### addObserver
Add an observer:
```typescript
aoi.addObserver(player, position, {
viewRange: 200, // View range
observable: true // Can be seen by other observers (default true)
});
// NPC that only observes but cannot be observed
aoi.addObserver(camera, position, {
viewRange: 500,
observable: false
});
```
### removeObserver
Remove an observer:
```typescript
aoi.removeObserver(player);
```
### updatePosition
Update position (automatically triggers enter/exit events):
```typescript
aoi.updatePosition(player, newPosition);
```
### updateViewRange
Update view range:
```typescript
// View range expanded after buff
aoi.updateViewRange(player, 300);
```
## Query Methods
### getEntitiesInView
Get all entities within an observer's view:
```typescript
const visible = aoi.getEntitiesInView(player);
for (const entity of visible) {
updateEntityForPlayer(player, entity);
}
```
### getObserversOf
Get all observers who can see a specific entity:
```typescript
const observers = aoi.getObserversOf(monster);
for (const observer of observers) {
notifyMonsterMoved(observer, monster);
}
```
### canSee
Check visibility:
```typescript
if (aoi.canSee(player, enemy)) {
enemy.showHealthBar();
}
```
## Event System
### Global Event Listener
```typescript
aoi.addListener((event) => {
switch (event.type) {
case 'enter':
console.log(`${event.observer} sees ${event.target}`);
break;
case 'exit':
console.log(`${event.target} left ${event.observer}'s view`);
break;
}
});
```
### Entity-Specific Event Listener
```typescript
// Only listen to a specific player's view events
aoi.addEntityListener(player, (event) => {
if (event.type === 'enter') {
sendToClient(player, 'entity_enter', event.target);
} else if (event.type === 'exit') {
sendToClient(player, 'entity_exit', event.target);
}
});
```
### Event Types
```typescript
interface IAOIEvent<T> {
type: 'enter' | 'exit' | 'update';
observer: T; // Observer (who saw the change)
target: T; // Target (object that changed)
position: IVector2; // Target position
}
```
## Blueprint Nodes
- `GetEntitiesInView` - Get entities in view
- `GetObserversOf` - Get observers
- `CanSee` - Check visibility
- `OnEntityEnterView` - Enter view event
- `OnEntityExitView` - Exit view event
## Service Tokens
For dependency injection scenarios:
```typescript
import {
SpatialIndexToken,
SpatialQueryToken,
AOIManagerToken,
createGridSpatialIndex,
createGridAOI
} from '@esengine/spatial';
// Register services
services.register(SpatialIndexToken, createGridSpatialIndex(100));
services.register(AOIManagerToken, createGridAOI(100));
// Get services
const spatialIndex = services.get(SpatialIndexToken);
const aoiManager = services.get(AOIManagerToken);
```

View File

@@ -0,0 +1,184 @@
---
title: "Examples"
description: "Area attacks, MMO sync, AI perception and more"
---
## Area Attack Detection
```typescript
class CombatSystem {
private spatialIndex: ISpatialIndex<Entity>;
dealAreaDamage(center: IVector2, radius: number, damage: number): void {
const targets = this.spatialIndex.findInRadius(
center,
radius,
(entity) => entity.hasComponent(HealthComponent)
);
for (const target of targets) {
const health = target.getComponent(HealthComponent);
health.takeDamage(damage);
}
}
findNearestEnemy(position: IVector2, team: string): Entity | null {
return this.spatialIndex.findNearest(
position,
undefined, // No distance limit
(entity) => {
const teamComp = entity.getComponent(TeamComponent);
return teamComp && teamComp.team !== team;
}
);
}
}
```
## MMO Sync System
```typescript
class SyncSystem {
private aoi: IAOIManager<Player>;
constructor() {
this.aoi = createGridAOI<Player>(100);
// Listen for enter/exit events
this.aoi.addListener((event) => {
const packet = this.createSyncPacket(event);
this.sendToPlayer(event.observer, packet);
});
}
onPlayerJoin(player: Player): void {
this.aoi.addObserver(player, player.position, {
viewRange: player.viewRange
});
}
onPlayerMove(player: Player, newPosition: IVector2): void {
this.aoi.updatePosition(player, newPosition);
}
onPlayerLeave(player: Player): void {
this.aoi.removeObserver(player);
}
// Broadcast to all players who can see a specific player
broadcastToObservers(player: Player, packet: Packet): void {
const observers = this.aoi.getObserversOf(player);
for (const observer of observers) {
this.sendToPlayer(observer, packet);
}
}
}
```
## NPC AI Perception
```typescript
class AIPerceptionSystem {
private aoi: IAOIManager<Entity>;
constructor() {
this.aoi = createGridAOI<Entity>(50);
}
setupNPC(npc: Entity): void {
const perception = npc.getComponent(PerceptionComponent);
this.aoi.addObserver(npc, npc.position, {
viewRange: perception.range
});
// Listen to this NPC's perception events
this.aoi.addEntityListener(npc, (event) => {
const ai = npc.getComponent(AIComponent);
if (event.type === 'enter') {
ai.onTargetDetected(event.target);
} else if (event.type === 'exit') {
ai.onTargetLost(event.target);
}
});
}
update(): void {
// Update all NPC positions
for (const npc of this.npcs) {
this.aoi.updatePosition(npc, npc.position);
}
}
}
```
## Skill Target Selection
```typescript
class TargetingSystem {
private spatialIndex: ISpatialIndex<Entity>;
// Cone-shaped skill
findTargetsInCone(
origin: IVector2,
direction: IVector2,
range: number,
angle: number
): Entity[] {
// First use circular range for rough filtering
const candidates = this.spatialIndex.findInRadius(origin, range);
// Then precisely filter targets within the cone
return candidates.filter(entity => {
const toEntity = normalize(subtract(entity.position, origin));
const dot = dotProduct(direction, toEntity);
const entityAngle = Math.acos(dot);
return entityAngle <= angle / 2;
});
}
// Piercing ray skill
findTargetsOnLine(
origin: IVector2,
direction: IVector2,
maxDistance: number,
maxTargets: number
): Entity[] {
const hits = this.spatialIndex.raycast(origin, direction, maxDistance);
return hits.slice(0, maxTargets).map(hit => hit.target);
}
}
```
## Dynamic Obstacle Avoidance
```typescript
class ObstacleAvoidanceSystem {
private spatialIndex: ISpatialIndex<Entity>;
calculateAvoidanceForce(entity: Entity, velocity: IVector2): IVector2 {
const position = entity.position;
const lookAhead = 50; // Forward detection distance
// Detect obstacles ahead
const hit = this.spatialIndex.raycastFirst(
position,
normalize(velocity),
lookAhead,
(e) => e.hasComponent(ObstacleComponent)
);
if (!hit) return { x: 0, y: 0 };
// Calculate avoidance force
const avoidDirection = normalize({
x: hit.normal.y,
y: -hit.normal.x
});
const urgency = 1 - (hit.distance / lookAhead);
return scale(avoidDirection, urgency * 100);
}
}
```

View File

@@ -1,5 +1,6 @@
---
title: "Spatial Index System"
description: "Efficient spatial queries and AOI management"
---
`@esengine/spatial` provides efficient spatial querying and indexing, including range queries, nearest neighbor queries, raycasting, and AOI (Area of Interest) management.
@@ -76,7 +77,9 @@ const visible = aoi.getEntitiesInView(player);
| Direction | One-way query | Two-way tracking |
| Use Cases | Collision, range attacks | MMO sync, NPC AI perception |
### IBounds
### Core Interfaces
#### IBounds
```typescript
interface IBounds {
@@ -87,7 +90,7 @@ interface IBounds {
}
```
### IRaycastHit
#### IRaycastHit
```typescript
interface IRaycastHit<T> {
@@ -98,227 +101,9 @@ interface IRaycastHit<T> {
}
```
## Spatial Index API
## Documentation
### createGridSpatialIndex
```typescript
function createGridSpatialIndex<T>(cellSize?: number): GridSpatialIndex<T>
```
**Choosing cellSize:**
- Too small: High memory, reduced query efficiency
- Too large: Many objects per cell, slow iteration
- Recommended: 1-2x average object spacing
### Management Methods
```typescript
spatialIndex.insert(entity, position);
spatialIndex.remove(entity);
spatialIndex.update(entity, newPosition);
spatialIndex.clear();
```
### Query Methods
#### findInRadius
```typescript
const enemies = spatialIndex.findInRadius(
{ x: 100, y: 200 },
50,
(entity) => entity.type === 'enemy' // Optional filter
);
```
#### findInRect
```typescript
import { createBounds } from '@esengine/spatial';
const bounds = createBounds(0, 0, 200, 200);
const entities = spatialIndex.findInRect(bounds);
```
#### findNearest
```typescript
const nearest = spatialIndex.findNearest(
playerPosition,
500, // maxDistance
(entity) => entity.type === 'enemy'
);
```
#### findKNearest
```typescript
const nearestEnemies = spatialIndex.findKNearest(
playerPosition,
5, // k
500, // maxDistance
(entity) => entity.type === 'enemy'
);
```
#### raycast / raycastFirst
```typescript
const hits = spatialIndex.raycast(origin, direction, maxDistance);
const firstHit = spatialIndex.raycastFirst(origin, direction, maxDistance);
```
## AOI API
### createGridAOI
```typescript
function createGridAOI<T>(cellSize?: number): GridAOI<T>
```
### Observer Management
```typescript
// Add observer
aoi.addObserver(player, position, {
viewRange: 200,
observable: true // Can be seen by others
});
// Remove observer
aoi.removeObserver(player);
// Update position
aoi.updatePosition(player, newPosition);
// Update view range
aoi.updateViewRange(player, 300);
```
### Query Methods
```typescript
// Get entities in observer's view
const visible = aoi.getEntitiesInView(player);
// Get observers who can see entity
const observers = aoi.getObserversOf(monster);
// Check visibility
if (aoi.canSee(player, enemy)) { ... }
```
### Event System
```typescript
// Global event listener
aoi.addListener((event) => {
switch (event.type) {
case 'enter': /* entered view */ break;
case 'exit': /* left view */ break;
}
});
// Entity-specific listener
aoi.addEntityListener(player, (event) => {
if (event.type === 'enter') {
sendToClient(player, 'entity_enter', event.target);
}
});
```
## Utility Functions
### Bounds Creation
```typescript
import {
createBounds,
createBoundsFromCenter,
createBoundsFromCircle
} from '@esengine/spatial';
const bounds1 = createBounds(0, 0, 100, 100);
const bounds2 = createBoundsFromCenter({ x: 50, y: 50 }, 100, 100);
const bounds3 = createBoundsFromCircle({ x: 50, y: 50 }, 50);
```
### Geometry Checks
```typescript
import {
isPointInBounds,
boundsIntersect,
boundsIntersectsCircle,
distance,
distanceSquared
} from '@esengine/spatial';
if (isPointInBounds(point, bounds)) { ... }
if (boundsIntersect(boundsA, boundsB)) { ... }
if (boundsIntersectsCircle(bounds, center, radius)) { ... }
const dist = distance(pointA, pointB);
const distSq = distanceSquared(pointA, pointB); // Faster
```
## Practical Examples
### Range Attack Detection
```typescript
class CombatSystem {
private spatialIndex: ISpatialIndex<Entity>;
dealAreaDamage(center: IVector2, radius: number, damage: number): void {
const targets = this.spatialIndex.findInRadius(
center, radius,
(entity) => entity.hasComponent(HealthComponent)
);
for (const target of targets) {
target.getComponent(HealthComponent).takeDamage(damage);
}
}
}
```
### MMO Sync System
```typescript
class SyncSystem {
private aoi: IAOIManager<Player>;
constructor() {
this.aoi = createGridAOI<Player>(100);
this.aoi.addListener((event) => {
const packet = this.createSyncPacket(event);
this.sendToPlayer(event.observer, packet);
});
}
onPlayerMove(player: Player, newPosition: IVector2): void {
this.aoi.updatePosition(player, newPosition);
}
}
```
## Blueprint Nodes
### Spatial Query Nodes
- `FindInRadius`, `FindInRect`, `FindNearest`, `FindKNearest`
- `Raycast`, `RaycastFirst`
### AOI Nodes
- `GetEntitiesInView`, `GetObserversOf`, `CanSee`
- `OnEntityEnterView`, `OnEntityExitView`
## Service Tokens
```typescript
import { SpatialIndexToken, AOIManagerToken } from '@esengine/spatial';
services.register(SpatialIndexToken, createGridSpatialIndex(100));
services.register(AOIManagerToken, createGridAOI(100));
```
- [Spatial Index API](./spatial-index) - Grid index, range queries, raycasting
- [AOI (Area of Interest)](./aoi) - View management, enter/exit events
- [Examples](./examples) - Area attacks, MMO sync, AI perception
- [Utilities & Optimization](./utilities) - Geometry detection, performance tips

View File

@@ -0,0 +1,159 @@
---
title: "Spatial Index API"
description: "Grid index, range queries, raycasting"
---
## createGridSpatialIndex
```typescript
function createGridSpatialIndex<T>(cellSize?: number): GridSpatialIndex<T>
```
Creates a uniform grid-based spatial index.
**Parameters:**
- `cellSize` - Grid cell size (default 100)
**Choosing cellSize:**
- Too small: High memory usage, reduced query efficiency
- Too large: Too many objects per cell, slow iteration
- Recommended: 1-2x average object spacing
## Management Methods
### insert
Insert an object into the index:
```typescript
spatialIndex.insert(enemy, { x: 100, y: 200 });
```
### remove
Remove an object:
```typescript
spatialIndex.remove(enemy);
```
### update
Update object position:
```typescript
spatialIndex.update(enemy, { x: 150, y: 250 });
```
### clear
Clear the index:
```typescript
spatialIndex.clear();
```
## Query Methods
### findInRadius
Find all objects within a circular area:
```typescript
// Find all enemies within radius 50 of point (100, 200)
const enemies = spatialIndex.findInRadius(
{ x: 100, y: 200 },
50,
(entity) => entity.type === 'enemy' // Optional filter
);
```
### findInRect
Find all objects within a rectangular area:
```typescript
import { createBounds } from '@esengine/spatial';
const bounds = createBounds(0, 0, 200, 200);
const entities = spatialIndex.findInRect(bounds);
```
### findNearest
Find the nearest object:
```typescript
// Find nearest enemy (max search distance 500)
const nearest = spatialIndex.findNearest(
playerPosition,
500, // maxDistance
(entity) => entity.type === 'enemy'
);
if (nearest) {
attackTarget(nearest);
}
```
### findKNearest
Find the K nearest objects:
```typescript
// Find 5 nearest enemies
const nearestEnemies = spatialIndex.findKNearest(
playerPosition,
5, // k
500, // maxDistance
(entity) => entity.type === 'enemy'
);
```
### raycast
Raycast (returns all hits):
```typescript
const hits = spatialIndex.raycast(
origin, // Ray origin
direction, // Ray direction (should be normalized)
maxDistance, // Maximum detection distance
filter // Optional filter
);
// hits are sorted by distance
for (const hit of hits) {
console.log(`Hit ${hit.target} at ${hit.point}, distance ${hit.distance}`);
}
```
### raycastFirst
Raycast (returns only the first hit):
```typescript
const hit = spatialIndex.raycastFirst(origin, direction, 1000);
if (hit) {
dealDamage(hit.target, calculateDamage(hit.distance));
}
```
## Properties
```typescript
// Get number of objects in index
console.log(spatialIndex.count);
// Get all objects
const all = spatialIndex.getAll();
```
## Blueprint Nodes
- `FindInRadius` - Find objects within radius
- `FindInRect` - Find objects within rectangle
- `FindNearest` - Find nearest object
- `FindKNearest` - Find K nearest objects
- `Raycast` - Raycast (all hits)
- `RaycastFirst` - Raycast (first hit only)

View File

@@ -0,0 +1,149 @@
---
title: "Utilities & Optimization"
description: "Geometry detection functions and performance tips"
---
## Bounds Creation
```typescript
import {
createBounds,
createBoundsFromCenter,
createBoundsFromCircle
} from '@esengine/spatial';
// Create from corners
const bounds1 = createBounds(0, 0, 100, 100);
// Create from center point and size
const bounds2 = createBoundsFromCenter({ x: 50, y: 50 }, 100, 100);
// Create from circle (bounding box)
const bounds3 = createBoundsFromCircle({ x: 50, y: 50 }, 50);
```
## Geometry Detection
```typescript
import {
isPointInBounds,
boundsIntersect,
boundsIntersectsCircle,
distance,
distanceSquared
} from '@esengine/spatial';
// Point inside bounds?
if (isPointInBounds(point, bounds)) { ... }
// Two bounds intersect?
if (boundsIntersect(boundsA, boundsB)) { ... }
// Bounds intersects circle?
if (boundsIntersectsCircle(bounds, center, radius)) { ... }
// Distance calculation
const dist = distance(pointA, pointB);
const distSq = distanceSquared(pointA, pointB); // Faster, avoids sqrt
```
## Performance Optimization
### 1. Choose the Right cellSize
- **Too small**: High memory usage, many cells
- **Too large**: Many objects per cell, slow iteration
- **Rule of thumb**: 1-2x average object spacing
```typescript
// Scene with objects spaced about 50 units apart
const spatialIndex = createGridSpatialIndex(75); // 1.5x
```
### 2. Use Filters to Reduce Results
```typescript
// Filter during spatial query, not afterward
spatialIndex.findInRadius(center, radius, (e) => e.type === 'enemy');
// Avoid this pattern
const all = spatialIndex.findInRadius(center, radius);
const enemies = all.filter(e => e.type === 'enemy'); // Extra iteration
```
### 3. Use distanceSquared Instead of distance
```typescript
// Avoid sqrt calculation
const thresholdSq = threshold * threshold;
if (distanceSquared(a, b) < thresholdSq) {
// Within range
}
```
### 4. Batch Update Optimization
```typescript
// When updating many objects at once
// Consider disabling/enabling events around batch updates
aoi.disableEvents();
for (const entity of entities) {
aoi.updatePosition(entity, entity.position);
}
aoi.enableEvents();
aoi.flushEvents(); // Send all events at once
```
### 5. Layered Indexing
For very large scenes, use multiple spatial indexes:
```typescript
// Static objects use large grid (queried less frequently)
const staticIndex = createGridSpatialIndex(500);
// Dynamic objects use small grid (updated frequently)
const dynamicIndex = createGridSpatialIndex(50);
// Merge results when querying
function findInRadius(center: IVector2, radius: number): Entity[] {
return [
...staticIndex.findInRadius(center, radius),
...dynamicIndex.findInRadius(center, radius)
];
}
```
### 6. Reduce Query Frequency
```typescript
class AISystem {
private lastQueryTime = new Map<Entity, number>();
private queryInterval = 100; // Query every 100ms
update(dt: number): void {
const now = performance.now();
for (const entity of this.entities) {
const lastTime = this.lastQueryTime.get(entity) ?? 0;
if (now - lastTime >= this.queryInterval) {
this.updateAIPerception(entity);
this.lastQueryTime.set(entity, now);
}
}
}
}
```
## Memory Management
```typescript
// Remove destroyed entities promptly
spatialIndex.remove(destroyedEntity);
// Clear completely when switching scenes
spatialIndex.clear();
aoi.clear();
```

View File

@@ -0,0 +1,218 @@
---
title: "API Reference"
description: "Complete timer and cooldown system API"
---
## createTimerService
```typescript
function createTimerService(config?: TimerServiceConfig): ITimerService
```
**Configuration:**
| Property | Type | Default | Description |
|----------|------|---------|-------------|
| `maxTimers` | `number` | `0` | Maximum timer count (0 = unlimited) |
| `maxCooldowns` | `number` | `0` | Maximum cooldown count (0 = unlimited) |
## Timer API
### schedule
Schedule a one-time timer:
```typescript
const handle = timerService.schedule('explosion', 2000, () => {
createExplosion();
});
// Cancel early
handle.cancel();
```
### scheduleRepeating
Schedule a repeating timer:
```typescript
// Execute every second
timerService.scheduleRepeating('regen', 1000, () => {
player.hp += 5;
});
// Execute immediately once, then repeat every second
timerService.scheduleRepeating('tick', 1000, () => {
console.log('Tick');
}, true); // immediate = true
```
### cancel / cancelById
Cancel timers:
```typescript
// Cancel by handle
handle.cancel();
// or
timerService.cancel(handle);
// Cancel by ID
timerService.cancelById('regen');
```
### hasTimer
Check if timer exists:
```typescript
if (timerService.hasTimer('explosion')) {
console.log('Explosion is pending');
}
```
### getTimerInfo
Get timer information:
```typescript
const info = timerService.getTimerInfo('explosion');
if (info) {
console.log(`Remaining: ${info.remaining}ms`);
console.log(`Repeating: ${info.repeating}`);
}
```
## Cooldown API
### startCooldown
Start a cooldown:
```typescript
timerService.startCooldown('skill_fireball', 5000);
```
### isCooldownReady / isOnCooldown
Check cooldown status:
```typescript
if (timerService.isCooldownReady('skill_fireball')) {
castFireball();
timerService.startCooldown('skill_fireball', 5000);
} else {
console.log('Skill still on cooldown');
}
// or use isOnCooldown
if (timerService.isOnCooldown('skill_fireball')) {
console.log('On cooldown...');
}
```
### getCooldownProgress / getCooldownRemaining
Get cooldown progress:
```typescript
// Progress 0-1 (0=started, 1=complete)
const progress = timerService.getCooldownProgress('skill_fireball');
console.log(`Progress: ${(progress * 100).toFixed(0)}%`);
// Remaining time (ms)
const remaining = timerService.getCooldownRemaining('skill_fireball');
console.log(`Remaining: ${(remaining / 1000).toFixed(1)}s`);
```
### getCooldownInfo
Get complete cooldown info:
```typescript
const info = timerService.getCooldownInfo('skill_fireball');
if (info) {
console.log(`Duration: ${info.duration}ms`);
console.log(`Remaining: ${info.remaining}ms`);
console.log(`Progress: ${info.progress}`);
console.log(`Ready: ${info.isReady}`);
}
```
### resetCooldown / clearAllCooldowns
Reset cooldowns:
```typescript
// Reset single cooldown
timerService.resetCooldown('skill_fireball');
// Clear all cooldowns (e.g., on respawn)
timerService.clearAllCooldowns();
```
## Lifecycle
### update
Update timer service (call every frame):
```typescript
function gameLoop(deltaTime: number) {
timerService.update(deltaTime); // deltaTime in ms
}
```
### clear
Clear all timers and cooldowns:
```typescript
timerService.clear();
```
## Debug Properties
```typescript
// Get active timer count
console.log(timerService.activeTimerCount);
// Get active cooldown count
console.log(timerService.activeCooldownCount);
// Get all active timer IDs
const timerIds = timerService.getActiveTimerIds();
// Get all active cooldown IDs
const cooldownIds = timerService.getActiveCooldownIds();
```
## Blueprint Nodes
### Cooldown Nodes
- `StartCooldown` - Start cooldown
- `IsCooldownReady` - Check if cooldown is ready
- `GetCooldownProgress` - Get cooldown progress
- `GetCooldownInfo` - Get cooldown info
- `ResetCooldown` - Reset cooldown
### Timer Nodes
- `HasTimer` - Check if timer exists
- `CancelTimer` - Cancel timer
- `GetTimerRemaining` - Get timer remaining time
## Service Token
For dependency injection:
```typescript
import { TimerServiceToken, createTimerService } from '@esengine/timer';
// Register service
services.register(TimerServiceToken, createTimerService());
// Get service
const timerService = services.get(TimerServiceToken);
```

View File

@@ -0,0 +1,223 @@
---
title: "Best Practices"
description: "Usage tips and ECS integration"
---
## Usage Tips
### 1. Use Meaningful IDs
Use descriptive IDs for easier debugging and management:
```typescript
// Good
timerService.startCooldown('skill_fireball', 5000);
timerService.schedule('explosion_wave_1', 1000, callback);
// Bad
timerService.startCooldown('cd1', 5000);
timerService.schedule('t1', 1000, callback);
```
### 2. Avoid Duplicate IDs
Timers with the same ID will overwrite previous ones. Use unique IDs:
```typescript
// Use unique IDs
const uniqueId = `explosion_${entity.id}_${Date.now()}`;
timerService.schedule(uniqueId, 1000, callback);
// Or use a counter
let timerCounter = 0;
const timerId = `timer_${++timerCounter}`;
```
### 3. Clean Up Promptly
Clean up timers and cooldowns at appropriate times:
```typescript
class Entity {
private timerId: string;
onDestroy(): void {
// Clean up timer when entity is destroyed
this.timerService.cancelById(this.timerId);
}
}
class Scene {
onUnload(): void {
// Clear all when scene unloads
this.timerService.clear();
}
}
```
### 4. Configure Limits
Consider setting maximum limits in production:
```typescript
const timerService = createTimerService({
maxTimers: 1000,
maxCooldowns: 500
});
```
### 5. Batch Management
Use prefixes to manage related timers:
```typescript
// Use unified prefix for all timers of an entity
const prefix = `entity_${entityId}_`;
timerService.schedule(`${prefix}explosion`, 1000, callback1);
timerService.schedule(`${prefix}effect`, 2000, callback2);
// Clean up by iterating with prefix
function clearEntityTimers(entityId: number): void {
const prefix = `entity_${entityId}_`;
const ids = timerService.getActiveTimerIds();
for (const id of ids) {
if (id.startsWith(prefix)) {
timerService.cancelById(id);
}
}
}
```
## ECS Integration
### Timer Component
```typescript
import { Component, EntitySystem, Matcher } from '@esengine/ecs-framework';
import { createTimerService, type ITimerService } from '@esengine/timer';
// Timer component
class TimerComponent extends Component {
timerService: ITimerService;
constructor() {
super();
this.timerService = createTimerService();
}
}
// Timer system
class TimerSystem extends EntitySystem {
constructor() {
super(Matcher.all(TimerComponent));
}
protected processEntity(entity: Entity, dt: number): void {
const timer = entity.getComponent(TimerComponent);
timer.timerService.update(dt);
}
}
```
### Cooldown Component (Shared Cooldowns)
```typescript
// Cooldown component for shared cooldowns
class CooldownComponent extends Component {
constructor(public timerService: ITimerService) {
super();
}
}
// Multiple entities share the same cooldown service
const sharedCooldowns = createTimerService();
entity1.addComponent(new CooldownComponent(sharedCooldowns));
entity2.addComponent(new CooldownComponent(sharedCooldowns));
```
### Global Timer Service
```typescript
// Use service container for global timer
import { TimerServiceToken, createTimerService } from '@esengine/timer';
// Register global service
services.register(TimerServiceToken, createTimerService());
// Use in systems
class EffectSystem extends EntitySystem {
private timerService: ITimerService;
constructor(services: ServiceContainer) {
super(Matcher.all(EffectComponent));
this.timerService = services.get(TimerServiceToken);
}
applyEffect(entity: Entity, effect: Effect): void {
const id = `effect_${entity.id}_${effect.id}`;
this.timerService.schedule(id, effect.duration, () => {
entity.removeComponent(effect);
});
}
}
```
## Performance Optimization
### Consolidate Updates
If you have multiple independent timer services, consider merging them:
```typescript
// Not recommended: each entity has its own timer service
class BadEntity {
private timerService = createTimerService(); // Memory waste
}
// Recommended: share timer service
class GoodSystem {
private timerService = createTimerService();
addTimer(entityId: number, callback: () => void): void {
this.timerService.schedule(`entity_${entityId}`, 1000, callback);
}
}
```
### Avoid Frequent Creation
Reuse timer IDs instead of creating new ones:
```typescript
// Not recommended: create new timer every time
function onHit(): void {
timerService.schedule(`hit_${Date.now()}`, 100, showHitEffect);
}
// Recommended: cancel old timer and reuse ID
function onHit(): void {
timerService.cancelById('hit_effect');
timerService.schedule('hit_effect', 100, showHitEffect);
}
```
### Use Cooldowns Instead of Timers
For scenarios without callbacks, cooldowns are more efficient:
```typescript
// Use cooldown to limit attack frequency
if (timerService.isCooldownReady('attack')) {
attack();
timerService.startCooldown('attack', 1000);
}
// Instead of
timerService.schedule('attack_cooldown', 1000, () => {
canAttack = true;
});
```

View File

@@ -0,0 +1,235 @@
---
title: "Examples"
description: "Skill cooldowns, DOT effects, buff systems and more"
---
## Skill Cooldown System
```typescript
import { createTimerService, type ITimerService } from '@esengine/timer';
class SkillSystem {
private timerService: ITimerService;
private skills: Map<string, SkillData> = new Map();
constructor() {
this.timerService = createTimerService();
}
registerSkill(id: string, data: SkillData): void {
this.skills.set(id, data);
}
useSkill(skillId: string): boolean {
const skill = this.skills.get(skillId);
if (!skill) return false;
// Check cooldown
if (!this.timerService.isCooldownReady(skillId)) {
const remaining = this.timerService.getCooldownRemaining(skillId);
console.log(`Skill ${skillId} on cooldown, ${remaining}ms remaining`);
return false;
}
// Use skill
this.executeSkill(skill);
// Start cooldown
this.timerService.startCooldown(skillId, skill.cooldown);
return true;
}
getSkillCooldownProgress(skillId: string): number {
return this.timerService.getCooldownProgress(skillId);
}
update(dt: number): void {
this.timerService.update(dt);
}
}
interface SkillData {
cooldown: number;
// ... other properties
}
```
## Delayed and Timed Effects
```typescript
class EffectSystem {
private timerService: ITimerService;
constructor(timerService: ITimerService) {
this.timerService = timerService;
}
// Delayed explosion
scheduleExplosion(position: { x: number; y: number }, delay: number): void {
this.timerService.schedule(`explosion_${Date.now()}`, delay, () => {
this.createExplosion(position);
});
}
// DOT damage (damage every second)
applyDOT(target: Entity, damage: number, duration: number): void {
const dotId = `dot_${target.id}_${Date.now()}`;
let elapsed = 0;
this.timerService.scheduleRepeating(dotId, 1000, () => {
elapsed += 1000;
target.takeDamage(damage);
if (elapsed >= duration) {
this.timerService.cancelById(dotId);
}
});
}
// BUFF effect (lasts for a duration)
applyBuff(target: Entity, buffId: string, duration: number): void {
target.addBuff(buffId);
this.timerService.schedule(`buff_expire_${buffId}`, duration, () => {
target.removeBuff(buffId);
});
}
}
```
## Combo System
```typescript
class ComboSystem {
private timerService: ITimerService;
private comboCount = 0;
private comboWindowId = 'combo_window';
constructor(timerService: ITimerService) {
this.timerService = timerService;
}
onAttack(): void {
// Increase combo count
this.comboCount++;
// Cancel previous combo window
this.timerService.cancelById(this.comboWindowId);
// Start new combo window (reset if no action within 2 seconds)
this.timerService.schedule(this.comboWindowId, 2000, () => {
this.comboCount = 0;
console.log('Combo reset');
});
console.log(`Combo: ${this.comboCount}x`);
}
getComboMultiplier(): number {
return 1 + this.comboCount * 0.1;
}
}
```
## Auto-Save System
```typescript
class AutoSaveSystem {
private timerService: ITimerService;
constructor(timerService: ITimerService) {
this.timerService = timerService;
this.startAutoSave();
}
private startAutoSave(): void {
// Auto-save every 5 minutes
this.timerService.scheduleRepeating('autosave', 5 * 60 * 1000, () => {
this.saveGame();
console.log('Game auto-saved');
});
}
private saveGame(): void {
// Save logic
}
stopAutoSave(): void {
this.timerService.cancelById('autosave');
}
}
```
## Charge Skill System
```typescript
class ChargeSkillSystem {
private timerService: ITimerService;
private chargeStartTime = 0;
private maxChargeTime = 3000; // 3 seconds max charge
constructor(timerService: ITimerService) {
this.timerService = timerService;
}
startCharge(): void {
this.chargeStartTime = performance.now();
// Auto-release when fully charged
this.timerService.schedule('charge_complete', this.maxChargeTime, () => {
this.releaseSkill();
});
}
releaseSkill(): void {
this.timerService.cancelById('charge_complete');
const chargeTime = performance.now() - this.chargeStartTime;
const chargePercent = Math.min(chargeTime / this.maxChargeTime, 1);
const damage = 100 + chargePercent * 200; // 100-300 damage
console.log(`Release skill with ${damage} damage (${(chargePercent * 100).toFixed(0)}% charge)`);
}
}
```
## Quest Timer
```typescript
class QuestTimerSystem {
private timerService: ITimerService;
constructor(timerService: ITimerService) {
this.timerService = timerService;
}
startTimedQuest(questId: string, timeLimit: number): void {
this.timerService.schedule(`quest_${questId}_timeout`, timeLimit, () => {
this.failQuest(questId);
});
// UI update for remaining time
this.timerService.scheduleRepeating(`quest_${questId}_tick`, 1000, () => {
const info = this.timerService.getTimerInfo(`quest_${questId}_timeout`);
if (info) {
this.updateQuestTimerUI(questId, info.remaining);
}
});
}
completeQuest(questId: string): void {
this.timerService.cancelById(`quest_${questId}_timeout`);
this.timerService.cancelById(`quest_${questId}_tick`);
console.log(`Quest ${questId} completed!`);
}
private failQuest(questId: string): void {
this.timerService.cancelById(`quest_${questId}_tick`);
console.log(`Quest ${questId} failed - time's up!`);
}
private updateQuestTimerUI(questId: string, remaining: number): void {
// Update UI
}
}
```

View File

@@ -1,5 +1,6 @@
---
title: "Timer System"
description: "Flexible timer and cooldown system"
---
`@esengine/timer` provides a flexible timer and cooldown system for delayed execution, repeating tasks, skill cooldowns, and more.
@@ -67,8 +68,6 @@ interface TimerHandle {
### TimerInfo
Timer information object:
```typescript
interface TimerInfo {
readonly id: string; // Timer ID
@@ -80,8 +79,6 @@ interface TimerInfo {
### CooldownInfo
Cooldown information object:
```typescript
interface CooldownInfo {
readonly id: string; // Cooldown ID
@@ -92,263 +89,8 @@ interface CooldownInfo {
}
```
## API Reference
## Documentation
### createTimerService
```typescript
function createTimerService(config?: TimerServiceConfig): ITimerService
```
**Configuration:**
| Property | Type | Default | Description |
|----------|------|---------|-------------|
| `maxTimers` | `number` | `0` | Maximum timer count (0 = unlimited) |
| `maxCooldowns` | `number` | `0` | Maximum cooldown count (0 = unlimited) |
### Timer API
#### schedule
Schedule a one-time timer:
```typescript
const handle = timerService.schedule('explosion', 2000, () => {
createExplosion();
});
// Cancel early
handle.cancel();
```
#### scheduleRepeating
Schedule a repeating timer:
```typescript
// Execute every second
timerService.scheduleRepeating('regen', 1000, () => {
player.hp += 5;
});
// Execute immediately once, then repeat every second
timerService.scheduleRepeating('tick', 1000, () => {
console.log('Tick');
}, true); // immediate = true
```
#### cancel / cancelById
Cancel timers:
```typescript
// Cancel by handle
handle.cancel();
// or
timerService.cancel(handle);
// Cancel by ID
timerService.cancelById('regen');
```
#### hasTimer
Check if timer exists:
```typescript
if (timerService.hasTimer('explosion')) {
console.log('Explosion is pending');
}
```
#### getTimerInfo
Get timer information:
```typescript
const info = timerService.getTimerInfo('explosion');
if (info) {
console.log(`Remaining: ${info.remaining}ms`);
console.log(`Repeating: ${info.repeating}`);
}
```
### Cooldown API
#### startCooldown
Start a cooldown:
```typescript
timerService.startCooldown('skill_fireball', 5000);
```
#### isCooldownReady / isOnCooldown
Check cooldown status:
```typescript
if (timerService.isCooldownReady('skill_fireball')) {
castFireball();
timerService.startCooldown('skill_fireball', 5000);
}
if (timerService.isOnCooldown('skill_fireball')) {
console.log('On cooldown...');
}
```
#### getCooldownProgress / getCooldownRemaining
Get cooldown progress:
```typescript
// Progress 0-1 (0=started, 1=complete)
const progress = timerService.getCooldownProgress('skill_fireball');
console.log(`Progress: ${(progress * 100).toFixed(0)}%`);
// Remaining time (ms)
const remaining = timerService.getCooldownRemaining('skill_fireball');
console.log(`Remaining: ${(remaining / 1000).toFixed(1)}s`);
```
#### getCooldownInfo
Get complete cooldown info:
```typescript
const info = timerService.getCooldownInfo('skill_fireball');
if (info) {
console.log(`Duration: ${info.duration}ms`);
console.log(`Remaining: ${info.remaining}ms`);
console.log(`Progress: ${info.progress}`);
console.log(`Ready: ${info.isReady}`);
}
```
#### resetCooldown / clearAllCooldowns
Reset cooldowns:
```typescript
// Reset single cooldown
timerService.resetCooldown('skill_fireball');
// Clear all cooldowns (e.g., on respawn)
timerService.clearAllCooldowns();
```
### Lifecycle
#### update
Update timer service (call every frame):
```typescript
function gameLoop(deltaTime: number) {
timerService.update(deltaTime); // deltaTime in ms
}
```
#### clear
Clear all timers and cooldowns:
```typescript
timerService.clear();
```
### Debug Properties
```typescript
console.log(timerService.activeTimerCount);
console.log(timerService.activeCooldownCount);
const timerIds = timerService.getActiveTimerIds();
const cooldownIds = timerService.getActiveCooldownIds();
```
## Practical Examples
### Skill Cooldown System
```typescript
import { createTimerService, type ITimerService } from '@esengine/timer';
class SkillSystem {
private timerService: ITimerService;
private skills: Map<string, SkillData> = new Map();
constructor() {
this.timerService = createTimerService();
}
useSkill(skillId: string): boolean {
const skill = this.skills.get(skillId);
if (!skill) return false;
if (!this.timerService.isCooldownReady(skillId)) {
const remaining = this.timerService.getCooldownRemaining(skillId);
console.log(`Skill ${skillId} on cooldown, ${remaining}ms remaining`);
return false;
}
this.executeSkill(skill);
this.timerService.startCooldown(skillId, skill.cooldown);
return true;
}
update(dt: number): void {
this.timerService.update(dt);
}
}
```
### DOT Effects
```typescript
class EffectSystem {
private timerService: ITimerService;
applyDOT(target: Entity, damage: number, duration: number): void {
const dotId = `dot_${target.id}_${Date.now()}`;
let elapsed = 0;
this.timerService.scheduleRepeating(dotId, 1000, () => {
elapsed += 1000;
target.takeDamage(damage);
if (elapsed >= duration) {
this.timerService.cancelById(dotId);
}
});
}
}
```
## Blueprint Nodes
### Cooldown Nodes
- `StartCooldown` - Start cooldown
- `IsCooldownReady` - Check if cooldown is ready
- `GetCooldownProgress` - Get cooldown progress
- `GetCooldownInfo` - Get cooldown info
- `ResetCooldown` - Reset cooldown
### Timer Nodes
- `HasTimer` - Check if timer exists
- `CancelTimer` - Cancel timer
- `GetTimerRemaining` - Get timer remaining time
## Service Token
For dependency injection:
```typescript
import { TimerServiceToken, createTimerService } from '@esengine/timer';
services.register(TimerServiceToken, createTimerService());
const timerService = services.get(TimerServiceToken);
```
- [API Reference](./api) - Complete timer and cooldown API
- [Examples](./examples) - Skill cooldowns, DOT effects, buff systems
- [Best Practices](./best-practices) - Usage tips and ECS integration

View File

@@ -1,645 +0,0 @@
---
title: "插件系统"
---
插件系统允许你以模块化的方式扩展 ECS Framework 的功能。通过插件,你可以封装特定功能(如网络同步、物理引擎、调试工具等),并在多个项目中复用。
## 概述
### 什么是插件
插件是实现了 `IPlugin` 接口的类,可以在运行时动态安装到框架中。插件可以:
- 注册自定义服务到服务容器
- 添加系统到场景
- 注册自定义组件
- 扩展框架功能
### 插件的优势
- **模块化**: 将功能封装为独立模块,提高代码可维护性
- **可复用**: 同一个插件可以在多个项目中使用
- **解耦**: 核心框架与扩展功能分离
- **热插拔**: 运行时动态安装和卸载插件
## 快速开始
### 创建第一个插件
创建一个简单的调试插件:
```typescript
import { IPlugin, Core, ServiceContainer } from '@esengine/ecs-framework';
class DebugPlugin implements IPlugin {
readonly name = 'debug-plugin';
readonly version = '1.0.0';
install(core: Core, services: ServiceContainer): void {
console.log('Debug plugin installed');
// 可以在这里注册服务、添加系统等
}
uninstall(): void {
console.log('Debug plugin uninstalled');
// 清理资源
}
}
```
### 安装插件
使用 `Core.installPlugin()` 安装插件:
```typescript
import { Core } from '@esengine/ecs-framework';
// 初始化Core
Core.create({ debug: true });
// 安装插件
await Core.installPlugin(new DebugPlugin());
// 检查插件是否已安装
if (Core.isPluginInstalled('debug-plugin')) {
console.log('Debug plugin is running');
}
```
### 卸载插件
```typescript
// 卸载插件
await Core.uninstallPlugin('debug-plugin');
```
### 获取插件实例
```typescript
// 获取已安装的插件
const plugin = Core.getPlugin('debug-plugin');
if (plugin) {
console.log(`Plugin version: ${plugin.version}`);
}
```
## 插件开发
### IPlugin 接口
所有插件必须实现 `IPlugin` 接口:
```typescript
export interface IPlugin {
// 插件唯一名称
readonly name: string;
// 插件版本建议遵循semver规范
readonly version: string;
// 依赖的其他插件(可选)
readonly dependencies?: readonly string[];
// 安装插件时调用
install(core: Core, services: ServiceContainer): void | Promise<void>;
// 卸载插件时调用
uninstall(): void | Promise<void>;
}
```
### 插件生命周期
#### install 方法
在插件安装时调用,用于初始化插件:
```typescript
class MyPlugin implements IPlugin {
readonly name = 'my-plugin';
readonly version = '1.0.0';
install(core: Core, services: ServiceContainer): void {
// 1. 注册服务
services.registerSingleton(MyService);
// 2. 访问当前场景
const scene = core.scene;
if (scene) {
// 3. 添加系统
scene.addSystem(new MySystem());
}
// 4. 其他初始化逻辑
console.log('Plugin initialized');
}
uninstall(): void {
// 清理逻辑
}
}
```
#### uninstall 方法
在插件卸载时调用,用于清理资源:
```typescript
class MyPlugin implements IPlugin {
readonly name = 'my-plugin';
readonly version = '1.0.0';
private myService?: MyService;
install(core: Core, services: ServiceContainer): void {
this.myService = new MyService();
services.registerInstance(MyService, this.myService);
}
uninstall(): void {
// 清理服务
if (this.myService) {
this.myService.dispose();
this.myService = undefined;
}
// 移除事件监听器
// 释放其他资源
}
}
```
### 异步插件
插件的 `install``uninstall` 方法都支持异步:
```typescript
class AsyncPlugin implements IPlugin {
readonly name = 'async-plugin';
readonly version = '1.0.0';
async install(core: Core, services: ServiceContainer): Promise<void> {
// 异步加载资源
const config = await fetch('/plugin-config.json').then(r => r.json());
// 使用加载的配置初始化服务
const service = new MyService(config);
services.registerInstance(MyService, service);
}
async uninstall(): Promise<void> {
// 异步清理
await this.saveState();
}
private async saveState() {
// 保存插件状态
}
}
// 使用
await Core.installPlugin(new AsyncPlugin());
```
### 注册服务
插件可以向服务容器注册自己的服务:
```typescript
import { IService } from '@esengine/ecs-framework';
class NetworkService implements IService {
connect(url: string) {
console.log(`Connecting to ${url}`);
}
dispose(): void {
console.log('Network service disposed');
}
}
class NetworkPlugin implements IPlugin {
readonly name = 'network-plugin';
readonly version = '1.0.0';
install(core: Core, services: ServiceContainer): void {
// 注册网络服务
services.registerSingleton(NetworkService);
// 解析并使用服务
const network = services.resolve(NetworkService);
network.connect('ws://localhost:8080');
}
uninstall(): void {
// 服务容器会自动调用服务的dispose方法
}
}
```
### 添加系统
插件可以向场景添加自定义系统:
```typescript
import { EntitySystem, Matcher } from '@esengine/ecs-framework';
class PhysicsSystem extends EntitySystem {
constructor() {
super(Matcher.empty().all(PhysicsBody));
}
protected process(entities: readonly Entity[]): void {
// 物理模拟逻辑
}
}
class PhysicsPlugin implements IPlugin {
readonly name = 'physics-plugin';
readonly version = '1.0.0';
private physicsSystem?: PhysicsSystem;
install(core: Core, services: ServiceContainer): void {
const scene = core.scene;
if (scene) {
this.physicsSystem = new PhysicsSystem();
scene.addSystem(this.physicsSystem);
}
}
uninstall(): void {
// 移除系统
if (this.physicsSystem) {
const scene = Core.scene;
if (scene) {
scene.removeSystem(this.physicsSystem);
}
this.physicsSystem = undefined;
}
}
}
```
## 依赖管理
### 声明依赖
插件可以声明对其他插件的依赖:
```typescript
class AdvancedPhysicsPlugin implements IPlugin {
readonly name = 'advanced-physics';
readonly version = '2.0.0';
// 声明依赖基础物理插件
readonly dependencies = ['physics-plugin'] as const;
install(core: Core, services: ServiceContainer): void {
// 可以安全地使用physics-plugin提供的服务
const physicsService = services.resolve(PhysicsService);
// ...
}
uninstall(): void {
// 清理
}
}
```
### 依赖检查
框架会自动检查依赖关系,如果依赖未满足会抛出错误:
```typescript
// 错误physics-plugin 未安装
try {
await Core.installPlugin(new AdvancedPhysicsPlugin());
} catch (error) {
console.error(error); // Plugin advanced-physics has unmet dependencies: physics-plugin
}
// 正确:先安装依赖
await Core.installPlugin(new PhysicsPlugin());
await Core.installPlugin(new AdvancedPhysicsPlugin());
```
### 卸载顺序
框架会检查依赖关系,防止卸载被其他插件依赖的插件:
```typescript
await Core.installPlugin(new PhysicsPlugin());
await Core.installPlugin(new AdvancedPhysicsPlugin());
// 错误physics-plugin 被 advanced-physics 依赖
try {
await Core.uninstallPlugin('physics-plugin');
} catch (error) {
console.error(error); // Cannot uninstall plugin physics-plugin: it is required by advanced-physics
}
// 正确:先卸载依赖它的插件
await Core.uninstallPlugin('advanced-physics');
await Core.uninstallPlugin('physics-plugin');
```
## 插件管理
### 通过 Core 管理
Core 类提供了便捷的插件管理方法:
```typescript
// 安装插件
await Core.installPlugin(myPlugin);
// 卸载插件
await Core.uninstallPlugin('plugin-name');
// 检查插件是否已安装
if (Core.isPluginInstalled('plugin-name')) {
// ...
}
// 获取插件实例
const plugin = Core.getPlugin('plugin-name');
```
### 通过 PluginManager 管理
也可以直接使用 PluginManager 服务:
```typescript
const pluginManager = Core.services.resolve(PluginManager);
// 获取所有插件
const allPlugins = pluginManager.getAllPlugins();
console.log(`Total plugins: ${allPlugins.length}`);
// 获取插件元数据
const metadata = pluginManager.getMetadata('my-plugin');
if (metadata) {
console.log(`State: ${metadata.state}`);
console.log(`Installed at: ${new Date(metadata.installedAt!)}`);
}
// 获取所有插件元数据
const allMetadata = pluginManager.getAllMetadata();
for (const meta of allMetadata) {
console.log(`${meta.name} v${meta.version} - ${meta.state}`);
}
```
## 实用插件示例
### 网络同步插件
```typescript
import { IPlugin, IService, Core, ServiceContainer } from '@esengine/ecs-framework';
class NetworkSyncService implements IService {
private ws?: WebSocket;
connect(url: string) {
this.ws = new WebSocket(url);
this.ws.onmessage = (event) => {
const data = JSON.parse(event.data);
this.handleMessage(data);
};
}
private handleMessage(data: any) {
// 处理网络消息
}
dispose(): void {
if (this.ws) {
this.ws.close();
this.ws = undefined;
}
}
}
class NetworkSyncPlugin implements IPlugin {
readonly name = 'network-sync';
readonly version = '1.0.0';
install(core: Core, services: ServiceContainer): void {
// 注册网络服务
services.registerSingleton(NetworkSyncService);
// 自动连接
const network = services.resolve(NetworkSyncService);
network.connect('ws://localhost:8080');
}
uninstall(): void {
// 服务会自动dispose
}
}
```
### 性能分析插件
```typescript
class PerformanceAnalysisPlugin implements IPlugin {
readonly name = 'performance-analysis';
readonly version = '1.0.0';
private frameCount = 0;
private totalTime = 0;
install(core: Core, services: ServiceContainer): void {
const monitor = services.resolve(PerformanceMonitor);
monitor.enable();
// 定期输出性能报告
const timer = services.resolve(TimerManager);
timer.schedule(5.0, true, null, () => {
this.printReport(monitor);
});
}
uninstall(): void {
// 清理
}
private printReport(monitor: PerformanceMonitor) {
console.log('=== Performance Report ===');
console.log(`FPS: ${monitor.getFPS()}`);
console.log(`Memory: ${monitor.getMemoryUsage()} MB`);
}
}
```
## 最佳实践
### 命名规范
- 插件名称使用小写字母和连字符:`my-awesome-plugin`
- 版本号遵循语义化版本规范:`1.0.0`
```typescript
class MyPlugin implements IPlugin {
readonly name = 'my-awesome-plugin'; // 好
readonly version = '1.0.0'; // 好
}
```
### 清理资源
始终在 `uninstall` 中清理插件创建的所有资源:
```typescript
class MyPlugin implements IPlugin {
readonly name = 'my-plugin';
readonly version = '1.0.0';
private timerId?: number;
private listener?: () => void;
install(core: Core, services: ServiceContainer): void {
// 添加定时器
this.timerId = setInterval(() => {
// ...
}, 1000);
// 添加事件监听
this.listener = () => {};
window.addEventListener('resize', this.listener);
}
uninstall(): void {
// 清理定时器
if (this.timerId) {
clearInterval(this.timerId);
this.timerId = undefined;
}
// 移除事件监听
if (this.listener) {
window.removeEventListener('resize', this.listener);
this.listener = undefined;
}
}
}
```
### 错误处理
在插件中妥善处理错误,避免影响整个应用:
```typescript
class MyPlugin implements IPlugin {
readonly name = 'my-plugin';
readonly version = '1.0.0';
async install(core: Core, services: ServiceContainer): Promise<void> {
try {
// 可能失败的操作
await this.loadConfig();
} catch (error) {
console.error('Failed to load plugin config:', error);
throw error; // 重新抛出,让框架知道安装失败
}
}
async uninstall(): Promise<void> {
try {
await this.cleanup();
} catch (error) {
console.error('Failed to cleanup plugin:', error);
// 即使清理失败也不应该阻止卸载
}
}
private async loadConfig() {
// 加载配置
}
private async cleanup() {
// 清理
}
}
```
### 配置化
允许用户配置插件行为:
```typescript
interface NetworkPluginConfig {
serverUrl: string;
autoReconnect: boolean;
timeout: number;
}
class NetworkPlugin implements IPlugin {
readonly name = 'network-plugin';
readonly version = '1.0.0';
constructor(private config: NetworkPluginConfig) {}
install(core: Core, services: ServiceContainer): void {
const network = new NetworkService(this.config);
services.registerInstance(NetworkService, network);
}
uninstall(): void {
// 清理
}
}
// 使用
const plugin = new NetworkPlugin({
serverUrl: 'ws://localhost:8080',
autoReconnect: true,
timeout: 5000
});
await Core.installPlugin(plugin);
```
## 常见问题
### 插件安装失败
**问题**: 插件安装时抛出错误
**原因**:
- 依赖未满足
- install 方法中有异常
- 服务注册冲突
**解决**:
1. 检查依赖是否已安装
2. 查看错误日志
3. 确保服务名称不冲突
### 插件卸载后仍有副作用
**问题**: 卸载插件后,插件的功能仍在运行
**原因**: uninstall 方法中未正确清理资源
**解决**: 确保在 uninstall 中清理:
- 定时器
- 事件监听器
- WebSocket连接
- 系统引用
### 何时使用插件
**适合使用插件**:
- 可选功能(调试工具、性能分析)
- 第三方集成(网络库、物理引擎)
- 跨项目复用的功能模块
**不适合使用插件**:
- 核心游戏逻辑
- 简单的工具类
- 项目特定的功能
## 相关链接
- [服务容器](./service-container/) - 在插件中使用服务容器
- [系统架构](./system/) - 在插件中添加系统
- [快速开始](./getting-started/) - Core 初始化和基础使用

View File

@@ -0,0 +1,151 @@
---
title: "最佳实践"
description: "插件设计规范和常见问题"
---
## 命名规范
```typescript
class MyPlugin implements IPlugin {
// 使用小写字母和连字符
readonly name = 'my-awesome-plugin'; // ✅
// 遵循语义化版本
readonly version = '1.0.0'; // ✅
}
```
## 清理资源
始终在 `uninstall` 中清理插件创建的所有资源:
```typescript
class MyPlugin implements IPlugin {
readonly name = 'my-plugin';
readonly version = '1.0.0';
private timerId?: number;
private listener?: () => void;
install(core: Core, services: ServiceContainer): void {
// 添加定时器
this.timerId = setInterval(() => {
// ...
}, 1000);
// 添加事件监听
this.listener = () => {};
window.addEventListener('resize', this.listener);
}
uninstall(): void {
// 清理定时器
if (this.timerId) {
clearInterval(this.timerId);
this.timerId = undefined;
}
// 移除事件监听
if (this.listener) {
window.removeEventListener('resize', this.listener);
this.listener = undefined;
}
}
}
```
## 错误处理
```typescript
class MyPlugin implements IPlugin {
readonly name = 'my-plugin';
readonly version = '1.0.0';
async install(core: Core, services: ServiceContainer): Promise<void> {
try {
await this.loadConfig();
} catch (error) {
console.error('Failed to load plugin config:', error);
throw error; // 重新抛出,让框架知道安装失败
}
}
async uninstall(): Promise<void> {
try {
await this.cleanup();
} catch (error) {
console.error('Failed to cleanup plugin:', error);
// 即使清理失败也不应该阻止卸载
}
}
private async loadConfig() { /* ... */ }
private async cleanup() { /* ... */ }
}
```
## 配置化
允许用户配置插件行为:
```typescript
interface NetworkPluginConfig {
serverUrl: string;
autoReconnect: boolean;
timeout: number;
}
class NetworkPlugin implements IPlugin {
readonly name = 'network-plugin';
readonly version = '1.0.0';
constructor(private config: NetworkPluginConfig) {}
install(core: Core, services: ServiceContainer): void {
const network = new NetworkService(this.config);
services.registerInstance(NetworkService, network);
}
uninstall(): void {}
}
// 使用
const plugin = new NetworkPlugin({
serverUrl: 'ws://localhost:8080',
autoReconnect: true,
timeout: 5000
});
await Core.installPlugin(plugin);
```
## 常见问题
### 插件安装失败
**原因**:
- 依赖未满足
- install 方法中有异常
- 服务注册冲突
**解决**:
1. 检查依赖是否已安装
2. 查看错误日志
3. 确保服务名称不冲突
### 插件卸载后仍有副作用
**原因**: uninstall 方法中未正确清理资源
**解决**: 确保在 uninstall 中清理:
- 定时器
- 事件监听器
- WebSocket 连接
- 系统引用
### 何时使用插件
| 适合使用插件 | 不适合使用插件 |
|-------------|---------------|
| 可选功能(调试工具、性能分析) | 核心游戏逻辑 |
| 第三方集成(网络库、物理引擎) | 简单的工具类 |
| 跨项目复用的功能模块 | 项目特定的功能 |

View File

@@ -0,0 +1,106 @@
---
title: "依赖管理"
description: "声明和检查插件依赖"
---
## 声明依赖
插件可以声明对其他插件的依赖:
```typescript
class AdvancedPhysicsPlugin implements IPlugin {
readonly name = 'advanced-physics';
readonly version = '2.0.0';
// 声明依赖基础物理插件
readonly dependencies = ['physics-plugin'] as const;
install(core: Core, services: ServiceContainer): void {
// 可以安全地使用physics-plugin提供的服务
const physicsService = services.resolve(PhysicsService);
// ...
}
uninstall(): void {
// 清理
}
}
```
## 依赖检查
框架会自动检查依赖关系,如果依赖未满足会抛出错误:
```typescript
// 错误physics-plugin 未安装
try {
await Core.installPlugin(new AdvancedPhysicsPlugin());
} catch (error) {
console.error(error);
// Plugin advanced-physics has unmet dependencies: physics-plugin
}
// 正确:先安装依赖
await Core.installPlugin(new PhysicsPlugin());
await Core.installPlugin(new AdvancedPhysicsPlugin());
```
## 卸载顺序
框架会检查依赖关系,防止卸载被其他插件依赖的插件:
```typescript
await Core.installPlugin(new PhysicsPlugin());
await Core.installPlugin(new AdvancedPhysicsPlugin());
// 错误physics-plugin 被 advanced-physics 依赖
try {
await Core.uninstallPlugin('physics-plugin');
} catch (error) {
console.error(error);
// Cannot uninstall plugin physics-plugin: it is required by advanced-physics
}
// 正确:先卸载依赖它的插件
await Core.uninstallPlugin('advanced-physics');
await Core.uninstallPlugin('physics-plugin');
```
## 依赖图示例
```
physics-plugin (基础)
advanced-physics (依赖 physics-plugin)
game-physics (依赖 advanced-physics)
```
安装顺序:`physics-plugin``advanced-physics``game-physics`
卸载顺序:`game-physics``advanced-physics``physics-plugin`
## 多依赖
```typescript
class GamePlugin implements IPlugin {
readonly name = 'game';
readonly version = '1.0.0';
// 声明多个依赖
readonly dependencies = [
'physics-plugin',
'network-plugin',
'audio-plugin'
] as const;
install(core: Core, services: ServiceContainer): void {
// 所有依赖都已可用
const physics = services.resolve(PhysicsService);
const network = services.resolve(NetworkService);
const audio = services.resolve(AudioService);
}
uninstall(): void {}
}
```

View File

@@ -0,0 +1,139 @@
---
title: "插件开发"
description: "IPlugin 接口和生命周期"
---
## IPlugin 接口
所有插件必须实现 `IPlugin` 接口:
```typescript
export interface IPlugin {
// 插件唯一名称
readonly name: string;
// 插件版本建议遵循semver规范
readonly version: string;
// 依赖的其他插件(可选)
readonly dependencies?: readonly string[];
// 安装插件时调用
install(core: Core, services: ServiceContainer): void | Promise<void>;
// 卸载插件时调用
uninstall(): void | Promise<void>;
}
```
## 生命周期方法
### install 方法
在插件安装时调用,用于初始化插件:
```typescript
class MyPlugin implements IPlugin {
readonly name = 'my-plugin';
readonly version = '1.0.0';
install(core: Core, services: ServiceContainer): void {
// 1. 注册服务
services.registerSingleton(MyService);
// 2. 访问当前场景
const scene = core.scene;
if (scene) {
// 3. 添加系统
scene.addSystem(new MySystem());
}
// 4. 其他初始化逻辑
console.log('Plugin initialized');
}
uninstall(): void {
// 清理逻辑
}
}
```
### uninstall 方法
在插件卸载时调用,用于清理资源:
```typescript
class MyPlugin implements IPlugin {
readonly name = 'my-plugin';
readonly version = '1.0.0';
private myService?: MyService;
install(core: Core, services: ServiceContainer): void {
this.myService = new MyService();
services.registerInstance(MyService, this.myService);
}
uninstall(): void {
// 清理服务
if (this.myService) {
this.myService.dispose();
this.myService = undefined;
}
// 移除事件监听器
// 释放其他资源
}
}
```
## 异步插件
插件的 `install``uninstall` 方法都支持异步:
```typescript
class AsyncPlugin implements IPlugin {
readonly name = 'async-plugin';
readonly version = '1.0.0';
async install(core: Core, services: ServiceContainer): Promise<void> {
// 异步加载资源
const config = await fetch('/plugin-config.json').then(r => r.json());
// 使用加载的配置初始化服务
const service = new MyService(config);
services.registerInstance(MyService, service);
}
async uninstall(): Promise<void> {
// 异步清理
await this.saveState();
}
private async saveState() {
// 保存插件状态
}
}
// 使用
await Core.installPlugin(new AsyncPlugin());
```
## 生命周期流程
```
安装: Core.installPlugin(plugin)
依赖检查: 检查 dependencies 是否满足
调用 install(): 注册服务、添加系统
状态更新: 标记为已安装
卸载: Core.uninstallPlugin(name)
依赖检查: 检查是否被其他插件依赖
调用 uninstall(): 清理资源
状态更新: 从插件列表移除
```

View File

@@ -0,0 +1,188 @@
---
title: "示例插件"
description: "完整的插件实现示例"
---
## 网络同步插件
```typescript
import { IPlugin, IService, Core, ServiceContainer } from '@esengine/ecs-framework';
class NetworkSyncService implements IService {
private ws?: WebSocket;
connect(url: string) {
this.ws = new WebSocket(url);
this.ws.onmessage = (event) => {
const data = JSON.parse(event.data);
this.handleMessage(data);
};
}
private handleMessage(data: any) {
// 处理网络消息
}
dispose(): void {
if (this.ws) {
this.ws.close();
this.ws = undefined;
}
}
}
class NetworkSyncPlugin implements IPlugin {
readonly name = 'network-sync';
readonly version = '1.0.0';
install(core: Core, services: ServiceContainer): void {
// 注册网络服务
services.registerSingleton(NetworkSyncService);
// 自动连接
const network = services.resolve(NetworkSyncService);
network.connect('ws://localhost:8080');
}
uninstall(): void {
// 服务会自动dispose
}
}
```
## 性能分析插件
```typescript
class PerformanceAnalysisPlugin implements IPlugin {
readonly name = 'performance-analysis';
readonly version = '1.0.0';
private frameCount = 0;
private totalTime = 0;
install(core: Core, services: ServiceContainer): void {
const monitor = services.resolve(PerformanceMonitor);
monitor.enable();
// 定期输出性能报告
const timer = services.resolve(TimerManager);
timer.schedule(5.0, true, null, () => {
this.printReport(monitor);
});
}
uninstall(): void {
// 清理
}
private printReport(monitor: PerformanceMonitor) {
console.log('=== Performance Report ===');
console.log(`FPS: ${monitor.getFPS()}`);
console.log(`Memory: ${monitor.getMemoryUsage()} MB`);
}
}
```
## 调试工具插件
```typescript
class DebugToolsPlugin implements IPlugin {
readonly name = 'debug-tools';
readonly version = '1.0.0';
private debugUI?: DebugUI;
install(core: Core, services: ServiceContainer): void {
// 创建调试UI
this.debugUI = new DebugUI();
this.debugUI.mount(document.body);
// 注册快捷键
window.addEventListener('keydown', this.handleKeyDown);
// 添加调试系统
const scene = core.scene;
if (scene) {
scene.addSystem(new DebugRenderSystem());
}
}
uninstall(): void {
// 移除UI
if (this.debugUI) {
this.debugUI.unmount();
this.debugUI = undefined;
}
// 移除事件监听
window.removeEventListener('keydown', this.handleKeyDown);
}
private handleKeyDown = (e: KeyboardEvent) => {
if (e.key === 'F12') {
this.debugUI?.toggle();
}
};
}
```
## 音频插件
```typescript
class AudioPlugin implements IPlugin {
readonly name = 'audio';
readonly version = '1.0.0';
constructor(private config: { volume: number }) {}
install(core: Core, services: ServiceContainer): void {
const audioService = new AudioService(this.config);
services.registerInstance(AudioService, audioService);
// 添加音频系统
const scene = core.scene;
if (scene) {
scene.addSystem(new AudioSystem());
}
}
uninstall(): void {
// 停止所有音频
const audio = Core.services.resolve(AudioService);
audio.stopAll();
}
}
// 使用
await Core.installPlugin(new AudioPlugin({ volume: 0.8 }));
```
## 输入管理插件
```typescript
class InputPlugin implements IPlugin {
readonly name = 'input';
readonly version = '1.0.0';
private inputManager?: InputManager;
install(core: Core, services: ServiceContainer): void {
this.inputManager = new InputManager();
services.registerInstance(InputManager, this.inputManager);
// 绑定默认按键
this.inputManager.bind('jump', ['Space', 'KeyW']);
this.inputManager.bind('attack', ['MouseLeft', 'KeyJ']);
// 添加输入系统
const scene = core.scene;
if (scene) {
scene.addSystem(new InputSystem());
}
}
uninstall(): void {
if (this.inputManager) {
this.inputManager.dispose();
this.inputManager = undefined;
}
}
}
```

View File

@@ -0,0 +1,85 @@
---
title: "插件系统"
description: "以模块化方式扩展 ECS Framework"
---
插件系统允许你以模块化的方式扩展 ECS Framework 的功能。通过插件,你可以封装特定功能(如网络同步、物理引擎、调试工具等),并在多个项目中复用。
## 什么是插件
插件是实现了 `IPlugin` 接口的类,可以在运行时动态安装到框架中。插件可以:
- 注册自定义服务到服务容器
- 添加系统到场景
- 注册自定义组件
- 扩展框架功能
## 插件的优势
| 优势 | 说明 |
|------|------|
| **模块化** | 将功能封装为独立模块,提高代码可维护性 |
| **可复用** | 同一个插件可以在多个项目中使用 |
| **解耦** | 核心框架与扩展功能分离 |
| **热插拔** | 运行时动态安装和卸载插件 |
## 快速开始
### 创建插件
```typescript
import { IPlugin, Core, ServiceContainer } from '@esengine/ecs-framework';
class DebugPlugin implements IPlugin {
readonly name = 'debug-plugin';
readonly version = '1.0.0';
install(core: Core, services: ServiceContainer): void {
console.log('Debug plugin installed');
}
uninstall(): void {
console.log('Debug plugin uninstalled');
}
}
```
### 安装插件
```typescript
import { Core } from '@esengine/ecs-framework';
Core.create({ debug: true });
// 安装插件
await Core.installPlugin(new DebugPlugin());
// 检查插件是否已安装
if (Core.isPluginInstalled('debug-plugin')) {
console.log('Debug plugin is running');
}
```
### 卸载插件
```typescript
await Core.uninstallPlugin('debug-plugin');
```
### 获取插件实例
```typescript
const plugin = Core.getPlugin('debug-plugin');
if (plugin) {
console.log(`Plugin version: ${plugin.version}`);
}
```
## 下一步
- [插件开发](./development/) - IPlugin 接口和生命周期
- [服务与系统](./services-systems/) - 注册服务和添加系统
- [依赖管理](./dependencies/) - 声明和检查依赖
- [插件管理](./management/) - 通过 Core 和 PluginManager 管理
- [示例插件](./examples/) - 完整示例
- [最佳实践](./best-practices/) - 设计规范

View File

@@ -0,0 +1,93 @@
---
title: "插件管理"
description: "通过 Core 和 PluginManager 管理插件"
---
## 通过 Core 管理
Core 类提供了便捷的插件管理方法:
```typescript
// 安装插件
await Core.installPlugin(myPlugin);
// 卸载插件
await Core.uninstallPlugin('plugin-name');
// 检查插件是否已安装
if (Core.isPluginInstalled('plugin-name')) {
// ...
}
// 获取插件实例
const plugin = Core.getPlugin('plugin-name');
```
## 通过 PluginManager 管理
也可以直接使用 PluginManager 服务:
```typescript
const pluginManager = Core.services.resolve(PluginManager);
// 获取所有插件
const allPlugins = pluginManager.getAllPlugins();
console.log(`Total plugins: ${allPlugins.length}`);
// 获取插件元数据
const metadata = pluginManager.getMetadata('my-plugin');
if (metadata) {
console.log(`State: ${metadata.state}`);
console.log(`Installed at: ${new Date(metadata.installedAt!)}`);
}
// 获取所有插件元数据
const allMetadata = pluginManager.getAllMetadata();
for (const meta of allMetadata) {
console.log(`${meta.name} v${meta.version} - ${meta.state}`);
}
```
## API 参考
### Core 静态方法
| 方法 | 说明 |
|------|------|
| `installPlugin(plugin)` | 安装插件 |
| `uninstallPlugin(name)` | 卸载插件 |
| `isPluginInstalled(name)` | 检查是否已安装 |
| `getPlugin(name)` | 获取插件实例 |
### PluginManager 方法
| 方法 | 说明 |
|------|------|
| `getAllPlugins()` | 获取所有插件 |
| `getMetadata(name)` | 获取插件元数据 |
| `getAllMetadata()` | 获取所有插件元数据 |
## 插件状态
```typescript
enum PluginState {
Pending = 'pending',
Installing = 'installing',
Installed = 'installed',
Uninstalling = 'uninstalling',
Failed = 'failed'
}
```
## 元数据信息
```typescript
interface PluginMetadata {
name: string;
version: string;
state: PluginState;
dependencies?: string[];
installedAt?: number;
error?: Error;
}
```

View File

@@ -0,0 +1,133 @@
---
title: "服务与系统"
description: "在插件中注册服务和添加系统"
---
## 注册服务
插件可以向服务容器注册自己的服务:
```typescript
import { IService } from '@esengine/ecs-framework';
class NetworkService implements IService {
connect(url: string) {
console.log(`Connecting to ${url}`);
}
dispose(): void {
console.log('Network service disposed');
}
}
class NetworkPlugin implements IPlugin {
readonly name = 'network-plugin';
readonly version = '1.0.0';
install(core: Core, services: ServiceContainer): void {
// 注册网络服务
services.registerSingleton(NetworkService);
// 解析并使用服务
const network = services.resolve(NetworkService);
network.connect('ws://localhost:8080');
}
uninstall(): void {
// 服务容器会自动调用服务的dispose方法
}
}
```
## 服务注册方式
| 方法 | 说明 |
|------|------|
| `registerSingleton(Type)` | 注册单例服务 |
| `registerInstance(Type, instance)` | 注册现有实例 |
| `registerTransient(Type)` | 每次解析创建新实例 |
## 添加系统
插件可以向场景添加自定义系统:
```typescript
import { EntitySystem, Matcher } from '@esengine/ecs-framework';
class PhysicsSystem extends EntitySystem {
constructor() {
super(Matcher.empty().all(PhysicsBody));
}
protected process(entities: readonly Entity[]): void {
// 物理模拟逻辑
}
}
class PhysicsPlugin implements IPlugin {
readonly name = 'physics-plugin';
readonly version = '1.0.0';
private physicsSystem?: PhysicsSystem;
install(core: Core, services: ServiceContainer): void {
const scene = core.scene;
if (scene) {
this.physicsSystem = new PhysicsSystem();
scene.addSystem(this.physicsSystem);
}
}
uninstall(): void {
// 移除系统
if (this.physicsSystem) {
const scene = Core.scene;
if (scene) {
scene.removeSystem(this.physicsSystem);
}
this.physicsSystem = undefined;
}
}
}
```
## 组合使用
```typescript
class GamePlugin implements IPlugin {
readonly name = 'game-plugin';
readonly version = '1.0.0';
private systems: EntitySystem[] = [];
install(core: Core, services: ServiceContainer): void {
// 1. 注册服务
services.registerSingleton(ScoreService);
services.registerSingleton(AudioService);
// 2. 添加系统
const scene = core.scene;
if (scene) {
const systems = [
new InputSystem(),
new MovementSystem(),
new ScoringSystem()
];
systems.forEach(system => {
scene.addSystem(system);
this.systems.push(system);
});
}
}
uninstall(): void {
// 移除所有系统
const scene = Core.scene;
if (scene) {
this.systems.forEach(system => {
scene.removeSystem(system);
});
}
this.systems = [];
}
}
```

View File

@@ -1,666 +0,0 @@
---
title: "scene"
---
# 场景管理
在 ECS 架构中场景Scene是游戏世界的容器负责管理实体、系统和组件的生命周期。场景提供了完整的 ECS 运行环境。
## 基本概念
场景是 ECS 框架的核心容器,提供:
- 实体的创建、管理和销毁
- 系统的注册和执行调度
- 组件的存储和查询
- 事件系统支持
- 性能监控和调试信息
## 场景管理方式
ECS Framework 提供了两种场景管理方式:
1. **[SceneManager](./scene-manager/)** - 适用于 95% 的游戏应用
- 单人游戏、简单多人游戏、移动游戏
- 轻量级,简单直观的 API
- 支持场景切换
2. **[WorldManager](./world-manager/)** - 适用于高级多世界隔离场景
- MMO 游戏服务器、游戏房间系统
- 多 World 管理,每个 World 可包含多个场景
- 完全隔离的独立环境
本文档重点介绍 Scene 类本身的使用方法。关于场景管理器的详细信息,请查看对应的文档。
## 创建场景
### 继承 Scene 类
**推荐做法:继承 Scene 类来创建自定义场景**
```typescript
import { Scene, EntitySystem } from '@esengine/ecs-framework';
class GameScene extends Scene {
protected initialize(): void {
// 设置场景名称
this.name = "GameScene";
// 添加系统
this.addSystem(new MovementSystem());
this.addSystem(new RenderSystem());
this.addSystem(new PhysicsSystem());
// 创建初始实体
this.createInitialEntities();
}
private createInitialEntities(): void {
// 创建玩家
const player = this.createEntity("Player");
player.addComponent(new Position(400, 300));
player.addComponent(new Health(100));
player.addComponent(new PlayerController());
// 创建敌人
for (let i = 0; i < 5; i++) {
const enemy = this.createEntity(`Enemy_${i}`);
enemy.addComponent(new Position(Math.random() * 800, Math.random() * 600));
enemy.addComponent(new Health(50));
enemy.addComponent(new EnemyAI());
}
}
public onStart(): void {
console.log("游戏场景已启动");
// 场景启动时的逻辑
}
public unload(): void {
console.log("游戏场景已卸载");
// 场景卸载时的清理逻辑
}
}
```
### 使用场景配置
```typescript
import { ISceneConfig } from '@esengine/ecs-framework';
const config: ISceneConfig = {
name: "MainGame",
enableEntityDirectUpdate: false
};
class ConfiguredScene extends Scene {
constructor() {
super(config);
}
}
```
## 场景生命周期
场景提供了完整的生命周期管理:
```typescript
class ExampleScene extends Scene {
protected initialize(): void {
// 场景初始化:设置系统和初始实体
console.log("场景初始化");
}
public onStart(): void {
// 场景开始运行:游戏逻辑开始执行
console.log("场景开始运行");
}
public unload(): void {
// 场景卸载:清理资源
console.log("场景卸载");
}
}
// 使用场景(由框架自动管理生命周期)
const scene = new ExampleScene();
// 场景的 initialize(), begin(), update(), end() 由框架自动调用
```
**生命周期方法**
1. `initialize()` - 场景初始化,设置系统和初始实体
2. `begin()` / `onStart()` - 场景开始运行
3. `update()` - 每帧更新(由场景管理器调用)
4. `end()` / `unload()` - 场景卸载,清理资源
## 实体管理
### 创建实体
```typescript
class EntityScene extends Scene {
createGameEntities(): void {
// 创建单个实体
const player = this.createEntity("Player");
// 批量创建实体(高性能)
const bullets = this.createEntities(100, "Bullet");
// 为批量创建的实体添加组件
bullets.forEach((bullet, index) => {
bullet.addComponent(new Position(index * 10, 100));
bullet.addComponent(new Velocity(Math.random() * 200 - 100, -300));
});
}
}
```
### 查找实体
```typescript
class SearchScene extends Scene {
findEntities(): void {
// 按名称查找
const player = this.findEntity("Player");
const player2 = this.getEntityByName("Player"); // 别名方法
// 按 ID 查找
const entity = this.findEntityById(123);
// 按标签查找
const enemies = this.findEntitiesByTag(2);
const enemies2 = this.getEntitiesByTag(2); // 别名方法
if (player) {
console.log(`找到玩家: ${player.name}`);
}
console.log(`找到 ${enemies.length} 个敌人`);
}
}
```
### 销毁实体
```typescript
class DestroyScene extends Scene {
cleanupEntities(): void {
// 销毁所有实体
this.destroyAllEntities();
// 单个实体的销毁通过实体本身
const enemy = this.findEntity("Enemy_1");
if (enemy) {
enemy.destroy(); // 实体会自动从场景中移除
}
}
}
```
## 系统管理
### 添加和移除系统
```typescript
class SystemScene extends Scene {
protected initialize(): void {
// 添加系统
const movementSystem = new MovementSystem();
this.addSystem(movementSystem);
// 设置系统更新顺序
movementSystem.updateOrder = 1;
// 添加更多系统
this.addSystem(new PhysicsSystem());
this.addSystem(new RenderSystem());
}
public removeUnnecessarySystems(): void {
// 获取系统
const physicsSystem = this.getEntityProcessor(PhysicsSystem);
// 移除系统
if (physicsSystem) {
this.removeSystem(physicsSystem);
}
}
}
```
### 系统访问
```typescript
class SystemAccessScene extends Scene {
public pausePhysics(): void {
const physicsSystem = this.getEntityProcessor(PhysicsSystem);
if (physicsSystem) {
physicsSystem.enabled = false;
}
}
public getAllSystems(): EntitySystem[] {
return this.systems; // 获取所有系统
}
}
```
## 事件系统
场景内置了类型安全的事件系统:
```typescript
class EventScene extends Scene {
protected initialize(): void {
// 监听事件
this.eventSystem.on('player_died', this.onPlayerDied.bind(this));
this.eventSystem.on('enemy_spawned', this.onEnemySpawned.bind(this));
this.eventSystem.on('level_complete', this.onLevelComplete.bind(this));
}
private onPlayerDied(data: any): void {
console.log('玩家死亡事件');
// 处理玩家死亡
}
private onEnemySpawned(data: any): void {
console.log('敌人生成事件');
// 处理敌人生成
}
private onLevelComplete(data: any): void {
console.log('关卡完成事件');
// 处理关卡完成
}
public triggerGameEvent(): void {
// 发送事件(同步)
this.eventSystem.emitSync('custom_event', {
message: "这是自定义事件",
timestamp: Date.now()
});
// 发送事件(异步)
this.eventSystem.emit('async_event', {
data: "异步事件数据"
});
}
}
```
### 事件系统 API
```typescript
// 监听事件
this.eventSystem.on('event_name', callback);
// 监听一次(自动取消订阅)
this.eventSystem.once('event_name', callback);
// 取消监听
this.eventSystem.off('event_name', callback);
// 同步发送事件
this.eventSystem.emitSync('event_name', data);
// 异步发送事件
this.eventSystem.emit('event_name', data);
// 清除所有事件监听
this.eventSystem.clear();
```
## 场景统计和调试
### 获取场景统计
```typescript
class StatsScene extends Scene {
public showStats(): void {
const stats = this.getStats();
console.log(`实体数量: ${stats.entityCount}`);
console.log(`系统数量: ${stats.processorCount}`);
console.log('组件存储统计:', stats.componentStorageStats);
}
public showDebugInfo(): void {
const debugInfo = this.getDebugInfo();
console.log('场景调试信息:', debugInfo);
// 显示所有实体信息
debugInfo.entities.forEach(entity => {
console.log(`实体 ${entity.name}(${entity.id}): ${entity.componentCount} 个组件`);
console.log('组件类型:', entity.componentTypes);
});
// 显示所有系统信息
debugInfo.processors.forEach(processor => {
console.log(`系统 ${processor.name}: 处理 ${processor.entityCount} 个实体`);
});
}
}
```
## 组件查询
Scene 提供了强大的组件查询系统:
```typescript
class QueryScene extends Scene {
protected initialize(): void {
// 创建一些实体
for (let i = 0; i < 10; i++) {
const entity = this.createEntity(`Entity_${i}`);
entity.addComponent(new Transform(i * 10, 0));
entity.addComponent(new Velocity(1, 0));
if (i % 2 === 0) {
entity.addComponent(new Renderer());
}
}
}
public queryEntities(): void {
// 通过 QuerySystem 查询
const entities = this.querySystem.query([Transform, Velocity]);
console.log(`找到 ${entities.length} 个有 Transform 和 Velocity 的实体`);
// 使用 ECS 流式 API如果通过 SceneManager
// const api = sceneManager.api;
// const entities = api?.find(Transform, Velocity);
}
}
```
## 性能监控
Scene 内置了性能监控功能:
```typescript
class PerformanceScene extends Scene {
public showPerformance(): void {
// 获取性能数据
const perfData = this.performanceMonitor?.getPerformanceData();
if (perfData) {
console.log('FPS:', perfData.fps);
console.log('帧时间:', perfData.frameTime);
console.log('实体更新时间:', perfData.entityUpdateTime);
console.log('系统更新时间:', perfData.systemUpdateTime);
}
// 获取性能报告
const report = this.performanceMonitor?.generateReport();
if (report) {
console.log('性能报告:', report);
}
}
}
```
## 最佳实践
### 1. 场景职责分离
```typescript
// 好的场景设计 - 职责清晰
class MenuScene extends Scene {
// 只处理菜单相关逻辑
}
class GameScene extends Scene {
// 只处理游戏玩法逻辑
}
class InventoryScene extends Scene {
// 只处理物品栏逻辑
}
// 避免的场景设计 - 职责混乱
class MegaScene extends Scene {
// 包含菜单、游戏、物品栏等所有逻辑
}
```
### 2. 合理的系统组织
```typescript
class OrganizedScene extends Scene {
protected initialize(): void {
// 按功能和依赖关系添加系统
this.addInputSystems();
this.addLogicSystems();
this.addRenderSystems();
}
private addInputSystems(): void {
this.addSystem(new InputSystem());
}
private addLogicSystems(): void {
this.addSystem(new MovementSystem());
this.addSystem(new PhysicsSystem());
this.addSystem(new CollisionSystem());
}
private addRenderSystems(): void {
this.addSystem(new RenderSystem());
this.addSystem(new UISystem());
}
}
```
### 3. 资源管理
```typescript
class ResourceScene extends Scene {
private textures: Map<string, any> = new Map();
private sounds: Map<string, any> = new Map();
protected initialize(): void {
this.loadResources();
}
private loadResources(): void {
// 加载场景所需资源
this.textures.set('player', this.loadTexture('player.png'));
this.sounds.set('bgm', this.loadSound('bgm.mp3'));
}
public unload(): void {
// 清理资源
this.textures.clear();
this.sounds.clear();
console.log('场景资源已清理');
}
private loadTexture(path: string): any {
// 加载纹理
return null;
}
private loadSound(path: string): any {
// 加载音效
return null;
}
}
```
### 4. 事件处理规范
```typescript
class EventHandlingScene extends Scene {
protected initialize(): void {
// 集中管理事件监听
this.setupEventListeners();
}
private setupEventListeners(): void {
this.eventSystem.on('game_pause', this.onGamePause.bind(this));
this.eventSystem.on('game_resume', this.onGameResume.bind(this));
this.eventSystem.on('player_input', this.onPlayerInput.bind(this));
}
private onGamePause(): void {
// 暂停游戏逻辑
this.systems.forEach(system => {
if (system instanceof GameLogicSystem) {
system.enabled = false;
}
});
}
private onGameResume(): void {
// 恢复游戏逻辑
this.systems.forEach(system => {
if (system instanceof GameLogicSystem) {
system.enabled = true;
}
});
}
private onPlayerInput(data: any): void {
// 处理玩家输入
}
public unload(): void {
// 清理事件监听
this.eventSystem.clear();
}
}
```
### 5. 初始化顺序
```typescript
class ProperInitScene extends Scene {
protected initialize(): void {
// 1. 首先设置场景配置
this.name = "GameScene";
// 2. 然后添加系统(按依赖顺序)
this.addSystem(new InputSystem());
this.addSystem(new MovementSystem());
this.addSystem(new PhysicsSystem());
this.addSystem(new RenderSystem());
// 3. 最后创建实体
this.createEntities();
// 4. 设置事件监听
this.setupEvents();
}
private createEntities(): void {
// 创建实体
}
private setupEvents(): void {
// 设置事件监听
}
}
```
## 完整示例
```typescript
import { Scene, EntitySystem, Entity, Matcher } from '@esengine/ecs-framework';
// 定义组件
class Transform {
constructor(public x: number, public y: number) {}
}
class Velocity {
constructor(public vx: number, public vy: number) {}
}
class Health {
constructor(public value: number) {}
}
// 定义系统
class MovementSystem extends EntitySystem {
constructor() {
super(Matcher.all(Transform, Velocity));
}
process(entities: readonly Entity[]): void {
for (const entity of entities) {
const transform = entity.getComponent(Transform);
const velocity = entity.getComponent(Velocity);
if (transform && velocity) {
transform.x += velocity.vx;
transform.y += velocity.vy;
}
}
}
}
// 定义场景
class GameScene extends Scene {
protected initialize(): void {
this.name = "GameScene";
// 添加系统
this.addSystem(new MovementSystem());
// 创建玩家
const player = this.createEntity("Player");
player.addComponent(new Transform(400, 300));
player.addComponent(new Velocity(0, 0));
player.addComponent(new Health(100));
// 创建敌人
for (let i = 0; i < 5; i++) {
const enemy = this.createEntity(`Enemy_${i}`);
enemy.addComponent(new Transform(
Math.random() * 800,
Math.random() * 600
));
enemy.addComponent(new Velocity(
Math.random() * 100 - 50,
Math.random() * 100 - 50
));
enemy.addComponent(new Health(50));
}
// 设置事件监听
this.eventSystem.on('player_died', () => {
console.log('玩家死亡!');
});
}
public onStart(): void {
console.log('游戏场景启动');
}
public unload(): void {
console.log('游戏场景卸载');
this.eventSystem.clear();
}
}
// 使用场景
// 方式1通过 SceneManager推荐
import { Core, SceneManager } from '@esengine/ecs-framework';
Core.create({ debug: true });
const sceneManager = Core.services.resolve(SceneManager);
sceneManager.setScene(new GameScene());
// 方式2通过 WorldManager高级用例
import { WorldManager } from '@esengine/ecs-framework';
const worldManager = Core.services.resolve(WorldManager);
const world = worldManager.createWorld('game');
world.createScene('main', new GameScene());
world.setSceneActive('main', true);
```
## 下一步
- 了解 [SceneManager](./scene-manager/) - 适用于大多数游戏的简单场景管理
- 了解 [WorldManager](./world-manager/) - 适用于需要多世界隔离的高级场景
- 了解 [持久化实体](./persistent-entity/) - 让实体跨场景保持状态v2.3.0+
场景是 ECS 框架的核心容器,正确使用场景管理能让你的游戏架构更加清晰、模块化和易于维护。

View File

@@ -0,0 +1,179 @@
---
title: "最佳实践"
description: "场景设计模式和完整示例"
---
## 场景职责分离
```typescript
// 好的场景设计 - 职责清晰
class MenuScene extends Scene {
// 只处理菜单相关逻辑
}
class GameScene extends Scene {
// 只处理游戏玩法逻辑
}
class InventoryScene extends Scene {
// 只处理物品栏逻辑
}
// 避免的场景设计 - 职责混乱
class MegaScene extends Scene {
// 包含菜单、游戏、物品栏等所有逻辑 ❌
}
```
## 资源管理
```typescript
class ResourceScene extends Scene {
private textures: Map<string, any> = new Map();
private sounds: Map<string, any> = new Map();
protected initialize(): void {
this.loadResources();
}
private loadResources(): void {
this.textures.set('player', this.loadTexture('player.png'));
this.sounds.set('bgm', this.loadSound('bgm.mp3'));
}
public unload(): void {
// 清理资源
this.textures.clear();
this.sounds.clear();
console.log('场景资源已清理');
}
private loadTexture(path: string): any { return null; }
private loadSound(path: string): any { return null; }
}
```
## 初始化顺序
```typescript
class ProperInitScene extends Scene {
protected initialize(): void {
// 1. 首先设置场景配置
this.name = "GameScene";
// 2. 然后添加系统(按依赖顺序)
this.addSystem(new InputSystem());
this.addSystem(new MovementSystem());
this.addSystem(new PhysicsSystem());
this.addSystem(new RenderSystem());
// 3. 最后创建实体
this.createEntities();
// 4. 设置事件监听
this.setupEvents();
}
private createEntities(): void { /* ... */ }
private setupEvents(): void { /* ... */ }
}
```
## 完整示例
```typescript
import { Scene, EntitySystem, Entity, Matcher } from '@esengine/ecs-framework';
// 定义组件
class Transform {
constructor(public x: number, public y: number) {}
}
class Velocity {
constructor(public vx: number, public vy: number) {}
}
class Health {
constructor(public value: number) {}
}
// 定义系统
class MovementSystem extends EntitySystem {
constructor() {
super(Matcher.all(Transform, Velocity));
}
process(entities: readonly Entity[]): void {
for (const entity of entities) {
const transform = entity.getComponent(Transform);
const velocity = entity.getComponent(Velocity);
if (transform && velocity) {
transform.x += velocity.vx;
transform.y += velocity.vy;
}
}
}
}
// 定义场景
class GameScene extends Scene {
protected initialize(): void {
this.name = "GameScene";
// 添加系统
this.addSystem(new MovementSystem());
// 创建玩家
const player = this.createEntity("Player");
player.addComponent(new Transform(400, 300));
player.addComponent(new Velocity(0, 0));
player.addComponent(new Health(100));
// 创建敌人
for (let i = 0; i < 5; i++) {
const enemy = this.createEntity(`Enemy_${i}`);
enemy.addComponent(new Transform(
Math.random() * 800,
Math.random() * 600
));
enemy.addComponent(new Velocity(
Math.random() * 100 - 50,
Math.random() * 100 - 50
));
enemy.addComponent(new Health(50));
}
// 设置事件监听
this.eventSystem.on('player_died', () => {
console.log('玩家死亡!');
});
}
public onStart(): void {
console.log('游戏场景启动');
}
public unload(): void {
console.log('游戏场景卸载');
this.eventSystem.clear();
}
}
// 使用场景
import { Core, SceneManager } from '@esengine/ecs-framework';
Core.create({ debug: true });
const sceneManager = Core.services.resolve(SceneManager);
sceneManager.setScene(new GameScene());
```
## 设计原则
| 原则 | 说明 |
|------|------|
| 单一职责 | 每个场景只负责一个游戏状态 |
| 资源清理 | 在 `unload()` 中清理所有资源 |
| 系统顺序 | 按输入→逻辑→渲染顺序添加系统 |
| 事件解耦 | 使用事件系统进行场景内通信 |
| 初始化分层 | 配置→系统→实体→事件的初始化顺序 |

View File

@@ -0,0 +1,124 @@
---
title: "调试与监控"
description: "场景统计、性能监控和调试信息"
---
Scene 内置了完整的调试和性能监控功能。
## 获取场景统计
```typescript
class StatsScene extends Scene {
public showStats(): void {
const stats = this.getStats();
console.log(`实体数量: ${stats.entityCount}`);
console.log(`系统数量: ${stats.processorCount}`);
console.log('组件存储统计:', stats.componentStorageStats);
}
}
```
## 调试信息
```typescript
public showDebugInfo(): void {
const debugInfo = this.getDebugInfo();
console.log('场景调试信息:', debugInfo);
// 显示所有实体信息
debugInfo.entities.forEach(entity => {
console.log(`实体 ${entity.name}(${entity.id}): ${entity.componentCount} 个组件`);
console.log('组件类型:', entity.componentTypes);
});
// 显示所有系统信息
debugInfo.processors.forEach(processor => {
console.log(`系统 ${processor.name}: 处理 ${processor.entityCount} 个实体`);
});
}
```
## 性能监控
```typescript
class PerformanceScene extends Scene {
public showPerformance(): void {
// 获取性能数据
const perfData = this.performanceMonitor?.getPerformanceData();
if (perfData) {
console.log('FPS:', perfData.fps);
console.log('帧时间:', perfData.frameTime);
console.log('实体更新时间:', perfData.entityUpdateTime);
console.log('系统更新时间:', perfData.systemUpdateTime);
}
// 获取性能报告
const report = this.performanceMonitor?.generateReport();
if (report) {
console.log('性能报告:', report);
}
}
}
```
## API 参考
### getStats()
返回场景统计信息:
```typescript
interface SceneStats {
entityCount: number;
processorCount: number;
componentStorageStats: ComponentStorageStats;
}
```
### getDebugInfo()
返回详细调试信息:
```typescript
interface DebugInfo {
entities: EntityDebugInfo[];
processors: ProcessorDebugInfo[];
}
interface EntityDebugInfo {
id: number;
name: string;
componentCount: number;
componentTypes: string[];
}
interface ProcessorDebugInfo {
name: string;
entityCount: number;
}
```
### performanceMonitor
性能监控器接口:
```typescript
interface PerformanceMonitor {
getPerformanceData(): PerformanceData;
generateReport(): string;
}
interface PerformanceData {
fps: number;
frameTime: number;
entityUpdateTime: number;
systemUpdateTime: number;
}
```
## 调试技巧
1. **开发模式** - 在 `Core.create({ debug: true })` 启用调试模式
2. **性能分析** - 定期调用 `getStats()` 监控实体和系统数量
3. **内存监控** - 检查 `componentStorageStats` 发现内存问题
4. **系统性能** - 使用 `performanceMonitor` 识别慢系统

View File

@@ -0,0 +1,125 @@
---
title: "实体管理"
description: "场景中的实体创建、查找和销毁"
---
场景提供了完整的实体管理 API包括创建、查找和销毁实体。
## 创建实体
### 单个实体
```typescript
class EntityScene extends Scene {
createGameEntities(): void {
// 创建命名实体
const player = this.createEntity("Player");
player.addComponent(new Position(400, 300));
player.addComponent(new Health(100));
}
}
```
### 批量创建
```typescript
class EntityScene extends Scene {
createBullets(): void {
// 批量创建实体(高性能)
const bullets = this.createEntities(100, "Bullet");
// 为批量创建的实体添加组件
bullets.forEach((bullet, index) => {
bullet.addComponent(new Position(index * 10, 100));
bullet.addComponent(new Velocity(Math.random() * 200 - 100, -300));
});
}
}
```
## 查找实体
### 按名称查找
```typescript
// 按名称查找(返回第一个匹配)
const player = this.findEntity("Player");
const player2 = this.getEntityByName("Player"); // 别名方法
if (player) {
console.log(`找到玩家: ${player.name}`);
}
```
### 按 ID 查找
```typescript
// 按唯一 ID 查找
const entity = this.findEntityById(123);
if (entity) {
console.log(`找到实体: ${entity.id}`);
}
```
### 按标签查找
```typescript
// 按标签查找(返回数组)
const enemies = this.findEntitiesByTag(2);
const enemies2 = this.getEntitiesByTag(2); // 别名方法
console.log(`找到 ${enemies.length} 个敌人`);
```
## 销毁实体
### 销毁单个实体
```typescript
const enemy = this.findEntity("Enemy_1");
if (enemy) {
enemy.destroy(); // 实体会自动从场景中移除
}
```
### 销毁所有实体
```typescript
// 销毁场景中所有实体
this.destroyAllEntities();
```
## 实体查询
Scene 提供了组件查询系统:
```typescript
class QueryScene extends Scene {
protected initialize(): void {
// 创建测试实体
for (let i = 0; i < 10; i++) {
const entity = this.createEntity(`Entity_${i}`);
entity.addComponent(new Transform(i * 10, 0));
entity.addComponent(new Velocity(1, 0));
}
}
public queryEntities(): void {
// 通过 QuerySystem 查询
const entities = this.querySystem.query([Transform, Velocity]);
console.log(`找到 ${entities.length} 个有 Transform 和 Velocity 的实体`);
}
}
```
## API 参考
| 方法 | 返回值 | 说明 |
|------|--------|------|
| `createEntity(name)` | `Entity` | 创建单个实体 |
| `createEntities(count, prefix)` | `Entity[]` | 批量创建实体 |
| `findEntity(name)` | `Entity \| undefined` | 按名称查找 |
| `findEntityById(id)` | `Entity \| undefined` | 按 ID 查找 |
| `findEntitiesByTag(tag)` | `Entity[]` | 按标签查找 |
| `destroyAllEntities()` | `void` | 销毁所有实体 |

View File

@@ -0,0 +1,122 @@
---
title: "事件系统"
description: "场景内置的类型安全事件系统"
---
场景内置了类型安全的事件系统,用于场景内的解耦通信。
## 基本用法
### 监听事件
```typescript
class EventScene extends Scene {
protected initialize(): void {
// 监听事件
this.eventSystem.on('player_died', this.onPlayerDied.bind(this));
this.eventSystem.on('enemy_spawned', this.onEnemySpawned.bind(this));
this.eventSystem.on('level_complete', this.onLevelComplete.bind(this));
}
private onPlayerDied(data: any): void {
console.log('玩家死亡事件');
}
private onEnemySpawned(data: any): void {
console.log('敌人生成事件');
}
private onLevelComplete(data: any): void {
console.log('关卡完成事件');
}
}
```
### 发送事件
```typescript
public triggerGameEvent(): void {
// 同步发送事件
this.eventSystem.emitSync('custom_event', {
message: "这是自定义事件",
timestamp: Date.now()
});
// 异步发送事件
this.eventSystem.emit('async_event', {
data: "异步事件数据"
});
}
```
## API 参考
| 方法 | 说明 |
|------|------|
| `on(event, callback)` | 监听事件 |
| `once(event, callback)` | 监听一次(自动取消订阅) |
| `off(event, callback)` | 取消监听 |
| `emitSync(event, data)` | 同步发送事件 |
| `emit(event, data)` | 异步发送事件 |
| `clear()` | 清除所有事件监听 |
## 事件处理规范
### 集中管理事件监听
```typescript
class EventHandlingScene extends Scene {
protected initialize(): void {
this.setupEventListeners();
}
private setupEventListeners(): void {
this.eventSystem.on('game_pause', this.onGamePause.bind(this));
this.eventSystem.on('game_resume', this.onGameResume.bind(this));
this.eventSystem.on('player_input', this.onPlayerInput.bind(this));
}
private onGamePause(): void {
// 暂停游戏逻辑
this.systems.forEach(system => {
if (system instanceof GameLogicSystem) {
system.enabled = false;
}
});
}
private onGameResume(): void {
// 恢复游戏逻辑
this.systems.forEach(system => {
if (system instanceof GameLogicSystem) {
system.enabled = true;
}
});
}
private onPlayerInput(data: any): void {
// 处理玩家输入
}
}
```
### 清理事件监听
在场景卸载时清理事件监听,避免内存泄漏:
```typescript
public unload(): void {
// 清理所有事件监听
this.eventSystem.clear();
}
```
## 使用场景
| 场景 | 示例事件 |
|------|----------|
| 游戏状态 | `game_start`, `game_pause`, `game_over` |
| 玩家行为 | `player_died`, `player_jump`, `player_attack` |
| 敌人行为 | `enemy_spawned`, `enemy_killed` |
| 关卡进度 | `level_start`, `level_complete` |
| UI 交互 | `button_click`, `menu_open` |

View File

@@ -0,0 +1,91 @@
---
title: "场景"
description: "ECS框架的核心容器管理实体、系统和组件的生命周期"
---
在 ECS 架构中场景Scene是游戏世界的容器负责管理实体、系统和组件的生命周期。场景提供了完整的 ECS 运行环境。
## 核心功能
场景是 ECS 框架的核心容器,提供:
- 实体的创建、管理和销毁
- 系统的注册和执行调度
- 组件的存储和查询
- 事件系统支持
- 性能监控和调试信息
## 场景管理方式
ECS Framework 提供了两种场景管理方式:
| 管理器 | 适用场景 | 特点 |
|--------|----------|------|
| **SceneManager** | 95% 的游戏应用 | 轻量级,支持场景切换 |
| **WorldManager** | MMO 服务器、房间系统 | 多 World 管理,完全隔离 |
## 快速开始
### 继承 Scene 类
```typescript
import { Scene, EntitySystem } from '@esengine/ecs-framework';
class GameScene extends Scene {
protected initialize(): void {
this.name = "GameScene";
// 添加系统
this.addSystem(new MovementSystem());
this.addSystem(new RenderSystem());
// 创建初始实体
const player = this.createEntity("Player");
player.addComponent(new Position(400, 300));
player.addComponent(new Health(100));
}
public onStart(): void {
console.log("游戏场景已启动");
}
public unload(): void {
console.log("游戏场景已卸载");
}
}
```
### 使用场景配置
```typescript
import { ISceneConfig } from '@esengine/ecs-framework';
const config: ISceneConfig = {
name: "MainGame",
enableEntityDirectUpdate: false
};
class ConfiguredScene extends Scene {
constructor() {
super(config);
}
}
```
### 运行场景
```typescript
import { Core, SceneManager } from '@esengine/ecs-framework';
Core.create({ debug: true });
const sceneManager = Core.services.resolve(SceneManager);
sceneManager.setScene(new GameScene());
```
## 下一步
- [生命周期](./lifecycle/) - 场景生命周期方法
- [实体管理](./entity-management/) - 创建、查找、销毁实体
- [系统管理](./system-management/) - 系统添加与控制
- [事件系统](./events/) - 场景内事件通信
- [调试与监控](./debugging/) - 性能分析和调试
- [最佳实践](./best-practices/) - 场景设计模式

View File

@@ -0,0 +1,103 @@
---
title: "场景生命周期"
description: "场景的生命周期方法和执行顺序"
---
场景提供了完整的生命周期管理,确保资源正确初始化和清理。
## 生命周期方法
```typescript
class ExampleScene extends Scene {
protected initialize(): void {
// 1. 场景初始化:设置系统和初始实体
console.log("场景初始化");
}
public onStart(): void {
// 2. 场景开始运行:游戏逻辑开始执行
console.log("场景开始运行");
}
public update(deltaTime: number): void {
// 3. 每帧更新(由场景管理器调用)
}
public unload(): void {
// 4. 场景卸载:清理资源
console.log("场景卸载");
}
}
```
## 执行顺序
| 阶段 | 方法 | 说明 |
|------|------|------|
| 初始化 | `initialize()` | 设置系统和初始实体 |
| 开始 | `begin()` / `onStart()` | 场景开始运行 |
| 更新 | `update()` | 每帧更新(框架自动调用) |
| 结束 | `end()` / `unload()` | 场景卸载,清理资源 |
## 生命周期示例
```typescript
class GameScene extends Scene {
private resourcesLoaded = false;
protected initialize(): void {
this.name = "GameScene";
// 1. 添加系统(按依赖顺序)
this.addSystem(new InputSystem());
this.addSystem(new MovementSystem());
this.addSystem(new RenderSystem());
// 2. 创建初始实体
this.createPlayer();
this.createEnemies();
// 3. 设置事件监听
this.setupEvents();
}
public onStart(): void {
this.resourcesLoaded = true;
console.log("场景资源加载完成,游戏开始");
}
public unload(): void {
// 清理事件监听
this.eventSystem.clear();
// 清理其他资源
this.resourcesLoaded = false;
console.log("场景资源已清理");
}
private createPlayer(): void {
const player = this.createEntity("Player");
player.addComponent(new Position(400, 300));
}
private createEnemies(): void {
for (let i = 0; i < 5; i++) {
const enemy = this.createEntity(`Enemy_${i}`);
enemy.addComponent(new Position(Math.random() * 800, Math.random() * 600));
}
}
private setupEvents(): void {
this.eventSystem.on('player_died', () => {
console.log('玩家死亡');
});
}
}
```
## 注意事项
1. **initialize() 只调用一次** - 用于设置初始状态
2. **onStart() 在场景激活时调用** - 可能多次调用(如场景切换)
3. **unload() 必须清理资源** - 避免内存泄漏
4. **update() 由框架管理** - 不需要手动调用

View File

@@ -0,0 +1,115 @@
---
title: "系统管理"
description: "场景中的系统添加、移除和控制"
---
场景负责管理系统的注册、执行顺序和生命周期。
## 添加系统
```typescript
class SystemScene extends Scene {
protected initialize(): void {
// 添加系统
const movementSystem = new MovementSystem();
this.addSystem(movementSystem);
// 设置系统更新顺序(数值越小越先执行)
movementSystem.updateOrder = 1;
// 添加更多系统
this.addSystem(new PhysicsSystem());
this.addSystem(new RenderSystem());
}
}
```
## 获取系统
```typescript
// 获取指定类型的系统
const physicsSystem = this.getEntityProcessor(PhysicsSystem);
if (physicsSystem) {
console.log("找到物理系统");
}
```
## 移除系统
```typescript
public removeUnnecessarySystems(): void {
const physicsSystem = this.getEntityProcessor(PhysicsSystem);
if (physicsSystem) {
this.removeSystem(physicsSystem);
}
}
```
## 控制系统
### 启用/禁用系统
```typescript
public pausePhysics(): void {
const physicsSystem = this.getEntityProcessor(PhysicsSystem);
if (physicsSystem) {
physicsSystem.enabled = false; // 禁用系统
}
}
public resumePhysics(): void {
const physicsSystem = this.getEntityProcessor(PhysicsSystem);
if (physicsSystem) {
physicsSystem.enabled = true; // 启用系统
}
}
```
### 获取所有系统
```typescript
public getAllSystems(): EntitySystem[] {
return this.systems; // 获取所有已注册系统
}
```
## 系统组织最佳实践
按功能分组添加系统:
```typescript
class OrganizedScene extends Scene {
protected initialize(): void {
// 按功能和依赖关系添加系统
this.addInputSystems();
this.addLogicSystems();
this.addRenderSystems();
}
private addInputSystems(): void {
this.addSystem(new InputSystem());
}
private addLogicSystems(): void {
this.addSystem(new MovementSystem());
this.addSystem(new PhysicsSystem());
this.addSystem(new CollisionSystem());
}
private addRenderSystems(): void {
this.addSystem(new RenderSystem());
this.addSystem(new UISystem());
}
}
```
## API 参考
| 方法 | 返回值 | 说明 |
|------|--------|------|
| `addSystem(system)` | `void` | 添加系统到场景 |
| `removeSystem(system)` | `void` | 从场景移除系统 |
| `getEntityProcessor(Type)` | `T \| undefined` | 获取指定类型的系统 |
| `systems` | `EntitySystem[]` | 获取所有系统 |

View File

@@ -1,774 +0,0 @@
---
title: "Worker系统"
---
Worker系统WorkerEntitySystem是ECS框架中基于Web Worker的多线程处理系统专为计算密集型任务设计能够充分利用多核CPU性能实现真正的并行计算。
## 核心特性
- **真正的并行计算**利用Web Worker在后台线程执行计算密集型任务
- **自动负载均衡**根据CPU核心数自动分配工作负载
- **SharedArrayBuffer优化**:零拷贝数据共享,提升大规模计算性能
- **降级支持**不支持Worker时自动回退到主线程处理
- **类型安全**完整的TypeScript支持和类型检查
## 基本用法
### 简单的物理系统示例
```typescript
interface PhysicsData {
id: number;
x: number;
y: number;
vx: number;
vy: number;
mass: number;
radius: number;
}
@ECSSystem('Physics')
class PhysicsWorkerSystem extends WorkerEntitySystem<PhysicsData> {
constructor() {
super(Matcher.all(Position, Velocity, Physics), {
enableWorker: true, // 启用Worker并行处理
workerCount: 8, // Worker数量系统会自动限制在硬件支持范围内
entitiesPerWorker: 100, // 每个Worker处理的实体数量
useSharedArrayBuffer: true, // 启用SharedArrayBuffer优化
entityDataSize: 7, // 每个实体数据大小
maxEntities: 10000, // 最大实体数量
systemConfig: { // 传递给Worker的配置
gravity: 100,
friction: 0.95
}
});
}
// 数据提取将Entity转换为可序列化的数据
protected extractEntityData(entity: Entity): PhysicsData {
const position = entity.getComponent(Position);
const velocity = entity.getComponent(Velocity);
const physics = entity.getComponent(Physics);
return {
id: entity.id,
x: position.x,
y: position.y,
vx: velocity.x,
vy: velocity.y,
mass: physics.mass,
radius: physics.radius
};
}
// Worker处理函数纯函数在Worker中执行
protected workerProcess(
entities: PhysicsData[],
deltaTime: number,
config: any
): PhysicsData[] {
return entities.map(entity => {
// 应用重力
entity.vy += config.gravity * deltaTime;
// 更新位置
entity.x += entity.vx * deltaTime;
entity.y += entity.vy * deltaTime;
// 应用摩擦力
entity.vx *= config.friction;
entity.vy *= config.friction;
return entity;
});
}
// 结果应用将Worker处理结果应用回Entity
protected applyResult(entity: Entity, result: PhysicsData): void {
const position = entity.getComponent(Position);
const velocity = entity.getComponent(Velocity);
position.x = result.x;
position.y = result.y;
velocity.x = result.vx;
velocity.y = result.vy;
}
// SharedArrayBuffer优化支持
protected getDefaultEntityDataSize(): number {
return 7; // id, x, y, vx, vy, mass, radius
}
protected writeEntityToBuffer(entityData: PhysicsData, offset: number): void {
if (!this.sharedFloatArray) return;
this.sharedFloatArray[offset + 0] = entityData.id;
this.sharedFloatArray[offset + 1] = entityData.x;
this.sharedFloatArray[offset + 2] = entityData.y;
this.sharedFloatArray[offset + 3] = entityData.vx;
this.sharedFloatArray[offset + 4] = entityData.vy;
this.sharedFloatArray[offset + 5] = entityData.mass;
this.sharedFloatArray[offset + 6] = entityData.radius;
}
protected readEntityFromBuffer(offset: number): PhysicsData | null {
if (!this.sharedFloatArray) return null;
return {
id: this.sharedFloatArray[offset + 0],
x: this.sharedFloatArray[offset + 1],
y: this.sharedFloatArray[offset + 2],
vx: this.sharedFloatArray[offset + 3],
vy: this.sharedFloatArray[offset + 4],
mass: this.sharedFloatArray[offset + 5],
radius: this.sharedFloatArray[offset + 6]
};
}
}
```
## 配置选项
Worker系统支持丰富的配置选项
```typescript
interface WorkerSystemConfig {
/** 是否启用Worker并行处理 */
enableWorker?: boolean;
/** Worker数量默认为CPU核心数自动限制在系统最大值内 */
workerCount?: number;
/** 每个Worker处理的实体数量用于控制负载分布 */
entitiesPerWorker?: number;
/** 系统配置数据会传递给Worker */
systemConfig?: any;
/** 是否使用SharedArrayBuffer优化 */
useSharedArrayBuffer?: boolean;
/** 每个实体在SharedArrayBuffer中占用的Float32数量 */
entityDataSize?: number;
/** 最大实体数量用于预分配SharedArrayBuffer */
maxEntities?: number;
/** 预编译的Worker脚本路径用于微信小游戏等不支持动态脚本的平台 */
workerScriptPath?: string;
}
```
### 配置建议
```typescript
constructor() {
super(matcher, {
// 根据任务复杂度决定是否启用
enableWorker: this.shouldUseWorker(),
// Worker数量系统会自动限制在硬件支持范围内
workerCount: 8, // 请求8个Worker实际数量受CPU核心数限制
// 每个Worker处理的实体数量可选
entitiesPerWorker: 200, // 精确控制负载分布
// 大量简单计算时启用SharedArrayBuffer
useSharedArrayBuffer: this.entityCount > 1000,
// 根据实际数据结构设置
entityDataSize: 8, // 确保与数据结构匹配
// 预估最大实体数量
maxEntities: 10000,
// 传递给Worker的全局配置
systemConfig: {
gravity: 9.8,
friction: 0.95,
worldBounds: { width: 1920, height: 1080 }
}
});
}
private shouldUseWorker(): boolean {
// 根据实体数量和计算复杂度决定
return this.expectedEntityCount > 100;
}
// 获取系统信息
getSystemInfo() {
const info = this.getWorkerInfo();
console.log(`Worker数量: ${info.workerCount}/${info.maxSystemWorkerCount}`);
console.log(`每Worker实体数: ${info.entitiesPerWorker || '自动分配'}`);
console.log(`当前模式: ${info.currentMode}`);
}
```
## 处理模式
Worker系统支持两种处理模式
### 1. 传统Worker模式
数据通过序列化在主线程和Worker间传递
```typescript
// 适用于:复杂计算逻辑,实体数量适中
constructor() {
super(matcher, {
enableWorker: true,
useSharedArrayBuffer: false, // 使用传统模式
workerCount: 2
});
}
protected workerProcess(entities: EntityData[], deltaTime: number): EntityData[] {
// 复杂的算法逻辑
return entities.map(entity => {
// AI决策、路径规划等复杂计算
return this.complexAILogic(entity, deltaTime);
});
}
```
### 2. SharedArrayBuffer模式
零拷贝数据共享,适合大量简单计算:
```typescript
// 适用于:大量实体的简单计算
constructor() {
super(matcher, {
enableWorker: true,
useSharedArrayBuffer: true, // 启用共享内存
entityDataSize: 6,
maxEntities: 10000
});
}
protected getSharedArrayBufferProcessFunction(): SharedArrayBufferProcessFunction {
return function(sharedFloatArray: Float32Array, startIndex: number, endIndex: number, deltaTime: number, config: any) {
const entitySize = 6;
for (let i = startIndex; i < endIndex; i++) {
const offset = i * entitySize;
// 读取数据
let x = sharedFloatArray[offset];
let y = sharedFloatArray[offset + 1];
let vx = sharedFloatArray[offset + 2];
let vy = sharedFloatArray[offset + 3];
// 物理计算
vy += config.gravity * deltaTime;
x += vx * deltaTime;
y += vy * deltaTime;
// 写回数据
sharedFloatArray[offset] = x;
sharedFloatArray[offset + 1] = y;
sharedFloatArray[offset + 2] = vx;
sharedFloatArray[offset + 3] = vy;
}
};
}
```
## 完整示例:粒子物理系统
一个包含碰撞检测的完整粒子物理系统:
```typescript
interface ParticleData {
id: number;
x: number;
y: number;
dx: number;
dy: number;
mass: number;
radius: number;
bounce: number;
friction: number;
}
@ECSSystem('ParticlePhysics')
class ParticlePhysicsWorkerSystem extends WorkerEntitySystem<ParticleData> {
constructor() {
super(Matcher.all(Position, Velocity, Physics, Renderable), {
enableWorker: true,
workerCount: 6, // 请求6个Worker自动限制在CPU核心数内
entitiesPerWorker: 150, // 每个Worker处理150个粒子
useSharedArrayBuffer: true,
entityDataSize: 9,
maxEntities: 5000,
systemConfig: {
gravity: 100,
canvasWidth: 800,
canvasHeight: 600,
groundFriction: 0.98
}
});
}
protected extractEntityData(entity: Entity): ParticleData {
const position = entity.getComponent(Position);
const velocity = entity.getComponent(Velocity);
const physics = entity.getComponent(Physics);
const renderable = entity.getComponent(Renderable);
return {
id: entity.id,
x: position.x,
y: position.y,
dx: velocity.dx,
dy: velocity.dy,
mass: physics.mass,
radius: renderable.size,
bounce: physics.bounce,
friction: physics.friction
};
}
protected workerProcess(
entities: ParticleData[],
deltaTime: number,
config: any
): ParticleData[] {
const result = entities.map(e => ({ ...e }));
// 基础物理更新
for (const particle of result) {
// 应用重力
particle.dy += config.gravity * deltaTime;
// 更新位置
particle.x += particle.dx * deltaTime;
particle.y += particle.dy * deltaTime;
// 边界碰撞
if (particle.x <= particle.radius) {
particle.x = particle.radius;
particle.dx = -particle.dx * particle.bounce;
} else if (particle.x >= config.canvasWidth - particle.radius) {
particle.x = config.canvasWidth - particle.radius;
particle.dx = -particle.dx * particle.bounce;
}
if (particle.y <= particle.radius) {
particle.y = particle.radius;
particle.dy = -particle.dy * particle.bounce;
} else if (particle.y >= config.canvasHeight - particle.radius) {
particle.y = config.canvasHeight - particle.radius;
particle.dy = -particle.dy * particle.bounce;
particle.dx *= config.groundFriction;
}
// 空气阻力
particle.dx *= particle.friction;
particle.dy *= particle.friction;
}
// 粒子间碰撞检测O(n²)算法)
for (let i = 0; i < result.length; i++) {
for (let j = i + 1; j < result.length; j++) {
const p1 = result[i];
const p2 = result[j];
const dx = p2.x - p1.x;
const dy = p2.y - p1.y;
const distance = Math.sqrt(dx * dx + dy * dy);
const minDistance = p1.radius + p2.radius;
if (distance < minDistance && distance > 0) {
// 分离粒子
const nx = dx / distance;
const ny = dy / distance;
const overlap = minDistance - distance;
p1.x -= nx * overlap * 0.5;
p1.y -= ny * overlap * 0.5;
p2.x += nx * overlap * 0.5;
p2.y += ny * overlap * 0.5;
// 弹性碰撞
const relativeVelocityX = p2.dx - p1.dx;
const relativeVelocityY = p2.dy - p1.dy;
const velocityAlongNormal = relativeVelocityX * nx + relativeVelocityY * ny;
if (velocityAlongNormal > 0) continue;
const restitution = (p1.bounce + p2.bounce) * 0.5;
const impulseScalar = -(1 + restitution) * velocityAlongNormal / (1/p1.mass + 1/p2.mass);
p1.dx -= impulseScalar * nx / p1.mass;
p1.dy -= impulseScalar * ny / p1.mass;
p2.dx += impulseScalar * nx / p2.mass;
p2.dy += impulseScalar * ny / p2.mass;
}
}
}
return result;
}
protected applyResult(entity: Entity, result: ParticleData): void {
if (!entity?.enabled) return;
const position = entity.getComponent(Position);
const velocity = entity.getComponent(Velocity);
if (position && velocity) {
position.set(result.x, result.y);
velocity.set(result.dx, result.dy);
}
}
protected getDefaultEntityDataSize(): number {
return 9;
}
protected writeEntityToBuffer(data: ParticleData, offset: number): void {
if (!this.sharedFloatArray) return;
this.sharedFloatArray[offset + 0] = data.id;
this.sharedFloatArray[offset + 1] = data.x;
this.sharedFloatArray[offset + 2] = data.y;
this.sharedFloatArray[offset + 3] = data.dx;
this.sharedFloatArray[offset + 4] = data.dy;
this.sharedFloatArray[offset + 5] = data.mass;
this.sharedFloatArray[offset + 6] = data.radius;
this.sharedFloatArray[offset + 7] = data.bounce;
this.sharedFloatArray[offset + 8] = data.friction;
}
protected readEntityFromBuffer(offset: number): ParticleData | null {
if (!this.sharedFloatArray) return null;
return {
id: this.sharedFloatArray[offset + 0],
x: this.sharedFloatArray[offset + 1],
y: this.sharedFloatArray[offset + 2],
dx: this.sharedFloatArray[offset + 3],
dy: this.sharedFloatArray[offset + 4],
mass: this.sharedFloatArray[offset + 5],
radius: this.sharedFloatArray[offset + 6],
bounce: this.sharedFloatArray[offset + 7],
friction: this.sharedFloatArray[offset + 8]
};
}
// 性能监控
public getPerformanceInfo(): {
enabled: boolean;
workerCount: number;
entitiesPerWorker?: number;
maxSystemWorkerCount: number;
entityCount: number;
isProcessing: boolean;
currentMode: string;
} {
const workerInfo = this.getWorkerInfo();
return {
...workerInfo,
entityCount: this.entities.length
};
}
}
```
## 适用场景
Worker系统特别适合以下场景
### 1. 物理模拟
- **重力系统**:大量实体的重力计算
- **碰撞检测**:复杂的碰撞算法
- **流体模拟**:粒子流体系统
- **布料模拟**:顶点物理计算
### 2. AI计算
- **路径寻找**A*、Dijkstra等算法
- **行为树**复杂的AI决策逻辑
- **群体智能**:鸟群、鱼群算法
- **神经网络**简单的AI推理
### 3. 数据处理
- **大量实体更新**:状态机、生命周期管理
- **统计计算**:游戏数据分析
- **图像处理**:纹理生成、效果计算
- **音频处理**:音效合成、频谱分析
## 最佳实践
### 1. Worker函数要求
```typescript
// ✅ 推荐Worker处理函数是纯函数
protected workerProcess(entities: PhysicsData[], deltaTime: number, config: any): PhysicsData[] {
// 只使用参数和标准JavaScript API
return entities.map(entity => {
// 纯计算逻辑,不依赖外部状态
entity.y += entity.velocity * deltaTime;
return entity;
});
}
// ❌ 避免在Worker函数中使用外部引用
protected workerProcess(entities: PhysicsData[], deltaTime: number): PhysicsData[] {
// this 和外部变量在Worker中不可用
return entities.map(entity => {
entity.y += this.someProperty; // ❌ 错误
return entity;
});
}
```
### 2. 数据设计
```typescript
// ✅ 推荐:合理的数据设计
interface SimplePhysicsData {
x: number;
y: number;
vx: number;
vy: number;
// 保持数据结构简单,便于序列化
}
// ❌ 避免:复杂的嵌套对象
interface ComplexData {
transform: {
position: { x: number; y: number };
rotation: { angle: number };
};
// 复杂嵌套结构增加序列化开销
}
```
### 3. Worker数量控制
```typescript
// ✅ 推荐灵活的Worker配置
constructor() {
super(matcher, {
// 直接指定需要的Worker数量系统会自动限制在硬件支持范围内
workerCount: 8, // 请求8个Worker
entitiesPerWorker: 100, // 每个Worker处理100个实体
enableWorker: this.shouldUseWorker(), // 条件启用
});
}
private shouldUseWorker(): boolean {
// 根据实体数量和复杂度决定是否使用Worker
return this.expectedEntityCount > 100;
}
// 获取实际使用的Worker信息
checkWorkerConfiguration() {
const info = this.getWorkerInfo();
console.log(`请求Worker数量: 8`);
console.log(`实际Worker数量: ${info.workerCount}`);
console.log(`系统最大支持: ${info.maxSystemWorkerCount}`);
console.log(`每Worker实体数: ${info.entitiesPerWorker || '自动分配'}`);
}
```
### 4. 性能监控
```typescript
// ✅ 推荐:性能监控
public getPerformanceMetrics(): WorkerPerformanceMetrics {
return {
...this.getWorkerInfo(),
entityCount: this.entities.length,
averageProcessTime: this.getAverageProcessTime(),
workerUtilization: this.getWorkerUtilization()
};
}
```
## 性能优化建议
### 1. 计算密集度评估
只对计算密集型任务使用Worker避免在简单计算上增加线程开销。
### 2. 数据传输优化
- 使用SharedArrayBuffer减少序列化开销
- 保持数据结构简单和扁平
- 避免频繁的大数据传输
### 3. 降级策略
始终提供主线程回退方案确保在不支持Worker的环境中正常运行。
### 4. 内存管理
及时清理Worker池和共享缓冲区避免内存泄漏。
### 5. 负载均衡
使用 `entitiesPerWorker` 参数精确控制负载分布避免某些Worker空闲而其他Worker过载。
## 在线演示
查看完整的Worker系统演示[Worker系统演示](https://esengine.github.io/ecs-framework/demos/worker-system/)
该演示展示了:
- 多线程物理计算
- 实时性能对比
- SharedArrayBuffer优化
- 大量实体的并行处理
## 微信小游戏支持
微信小游戏对 Worker 有特殊限制,不支持动态创建 Worker 脚本。ESEngine 提供了 `@esengine/worker-generator` CLI 工具来解决这个问题。
### 微信小游戏 Worker 限制
| 特性 | 浏览器 | 微信小游戏 |
|------|--------|-----------|
| 动态脚本 (Blob URL) | ✅ 支持 | ❌ 不支持 |
| Worker 数量 | 多个 | 最多 1 个 |
| 脚本来源 | 任意 | 必须是代码包内文件 |
| SharedArrayBuffer | 需要 COOP/COEP | 有限支持 |
### 使用 Worker Generator CLI
#### 1. 安装工具
```bash
pnpm add -D @esengine/worker-generator
```
#### 2. 配置 workerScriptPath
在你的 WorkerEntitySystem 子类中配置 `workerScriptPath`
```typescript
@ECSSystem('Physics')
class PhysicsWorkerSystem extends WorkerEntitySystem<PhysicsData> {
constructor() {
super(Matcher.all(Position, Velocity, Physics), {
enableWorker: true,
workerScriptPath: 'workers/physics-worker.js', // 指定 Worker 文件路径
systemConfig: {
gravity: 100,
friction: 0.95
}
});
}
protected workerProcess(
entities: PhysicsData[],
deltaTime: number,
config: any
): PhysicsData[] {
// 物理计算逻辑
return entities.map(entity => {
entity.vy += config.gravity * deltaTime;
entity.x += entity.vx * deltaTime;
entity.y += entity.vy * deltaTime;
return entity;
});
}
// ... 其他方法
}
```
#### 3. 生成 Worker 文件
运行 CLI 工具自动提取 `workerProcess` 函数并生成兼容微信小游戏的 Worker 文件:
```bash
# 基本用法
npx esengine-worker-gen --src ./src --wechat
# 完整选项
npx esengine-worker-gen \
--src ./src \ # 源码目录
--wechat \ # 生成微信小游戏兼容代码
--mapping \ # 生成 worker-mapping.json
--verbose # 详细输出
```
CLI 工具会:
1. 扫描源码目录,找到所有 `WorkerEntitySystem` 子类
2. 读取每个类的 `workerScriptPath` 配置
3. 提取 `workerProcess` 方法体
4. 转换为 ES5 语法(微信小游戏兼容)
5. 生成到配置的路径
#### 4. 配置 game.json
在微信小游戏的 `game.json` 中配置 workers 目录:
```json
{
"deviceOrientation": "portrait",
"workers": "workers"
}
```
#### 5. 项目结构
```
your-game/
├── game.js
├── game.json # 配置 "workers": "workers"
├── src/
│ └── systems/
│ └── PhysicsSystem.ts # workerScriptPath: 'workers/physics-worker.js'
└── workers/
├── physics-worker.js # 自动生成
└── worker-mapping.json # 自动生成
```
### 临时禁用 Worker
如果需要临时禁用 Worker例如调试时有两种方式
#### 方式 1配置禁用
```typescript
constructor() {
super(matcher, {
enableWorker: false, // 禁用 Worker使用主线程处理
// ...
});
}
```
#### 方式 2平台适配器禁用
在自定义平台适配器中返回不支持 Worker
```typescript
class MyPlatformAdapter implements IPlatformAdapter {
isWorkerSupported(): boolean {
return false; // 返回 false 禁用 Worker
}
// ...
}
```
### 注意事项
1. **每次修改 `workerProcess` 后都需要重新运行 CLI 工具**生成新的 Worker 文件
2. **Worker 函数必须是纯函数**,不能依赖 `this` 或外部变量:
```typescript
// ✅ 正确:只使用参数
protected workerProcess(entities, deltaTime, config) {
return entities.map(e => {
e.y += config.gravity * deltaTime;
return e;
});
}
// ❌ 错误:使用 this
protected workerProcess(entities, deltaTime, config) {
return entities.map(e => {
e.y += this.gravity * deltaTime; // Worker 中无法访问 this
return e;
});
}
```
3. **配置数据通过 `systemConfig` 传递**,而不是类属性
4. **开发者工具中的警告可以忽略**
- `getNetworkType:fail not support` - 微信开发者工具内部行为
- `SharedArrayBuffer will require cross-origin isolation` - 开发环境警告,真机不会出现
Worker系统为ECS框架提供了强大的并行计算能力让你能够充分利用现代多核处理器的性能为复杂的游戏逻辑和计算密集型任务提供了高效的解决方案。

View File

@@ -0,0 +1,120 @@
---
title: "最佳实践"
description: "Worker 系统性能优化建议"
---
## Worker 函数要求
```typescript
// ✅ 推荐:纯函数,只使用参数和标准 API
protected workerProcess(entities: PhysicsData[], dt: number, config: any): PhysicsData[] {
return entities.map(entity => {
entity.y += entity.velocity * dt;
return entity;
});
}
// ❌ 避免:使用 this 或外部变量
protected workerProcess(entities: PhysicsData[], dt: number): PhysicsData[] {
return entities.map(entity => {
entity.y += this.someProperty; // ❌ Worker 中无法访问 this
return entity;
});
}
```
## 数据设计
```typescript
// ✅ 推荐:简单扁平的数据结构
interface SimplePhysicsData {
x: number;
y: number;
vx: number;
vy: number;
}
// ❌ 避免:复杂嵌套对象
interface ComplexData {
transform: {
position: { x: number; y: number };
rotation: { angle: number };
};
// 嵌套结构增加序列化开销
}
```
## Worker 数量控制
```typescript
constructor() {
super(matcher, {
workerCount: 8, // 系统自动限制在 CPU 核心数内
entitiesPerWorker: 100, // 精确控制负载分布
enableWorker: this.shouldUseWorker(),
});
}
private shouldUseWorker(): boolean {
return this.expectedEntityCount > 100;
}
// 获取实际配置
checkConfig() {
const info = this.getWorkerInfo();
console.log(`实际 Worker 数量: ${info.workerCount}/${info.maxSystemWorkerCount}`);
}
```
## 性能优化建议
### 1. 计算密集度评估
只对计算密集型任务使用 Worker避免简单计算的线程开销。
### 2. 数据传输优化
- 使用 SharedArrayBuffer 减少序列化开销
- 保持数据结构简单扁平
- 避免频繁大数据传输
### 3. 降级策略
始终提供主线程回退方案:
```typescript
protected processSynchronously(entities: readonly Entity[]): void {
// 当 Worker 不可用时执行
}
```
### 4. 内存管理
及时清理 Worker 池和共享缓冲区,避免内存泄漏。
### 5. 负载均衡
使用 `entitiesPerWorker` 精确控制,避免部分 Worker 空闲。
## 何时使用 Worker
| 场景 | 建议 |
|------|------|
| 实体数量 < 100 | 不推荐使用 Worker |
| 100 < 实体 < 1000 | 传统 Worker 模式 |
| 实体 > 1000 | SharedArrayBuffer 模式 |
| 复杂 AI 逻辑 | 传统 Worker 模式 |
| 简单物理计算 | SharedArrayBuffer 模式 |
## 调试技巧
```typescript
// 获取完整系统信息
const info = this.getWorkerInfo();
console.log({
enabled: info.enabled,
workerCount: info.workerCount,
currentMode: info.currentMode,
isProcessing: info.isProcessing
});
```

View File

@@ -0,0 +1,130 @@
---
title: "配置选项"
description: "Worker 系统配置和处理模式"
---
## 配置接口
```typescript
interface IWorkerSystemConfig {
/** 是否启用 Worker 并行处理 */
enableWorker?: boolean;
/** Worker 数量,默认为 CPU 核心数 */
workerCount?: number;
/** 每个 Worker 处理的实体数量 */
entitiesPerWorker?: number;
/** 系统配置数据,传递给 Worker */
systemConfig?: unknown;
/** 是否使用 SharedArrayBuffer 优化 */
useSharedArrayBuffer?: boolean;
/** 每个实体占用的 Float32 数量 */
entityDataSize?: number;
/** 最大实体数量(预分配 SharedArrayBuffer */
maxEntities?: number;
/** 预编译 Worker 脚本路径(微信小游戏必需) */
workerScriptPath?: string;
}
```
## 配置示例
```typescript
constructor() {
super(matcher, {
enableWorker: true,
workerCount: 8, // 请求 8 个 Worker
entitiesPerWorker: 200, // 每个 Worker 处理 200 个实体
useSharedArrayBuffer: true,
entityDataSize: 8,
maxEntities: 10000,
systemConfig: {
gravity: 9.8,
friction: 0.95
}
});
}
```
## 处理模式
### 传统 Worker 模式
数据通过序列化在主线程和 Worker 间传递:
```typescript
constructor() {
super(matcher, {
enableWorker: true,
useSharedArrayBuffer: false,
workerCount: 2
});
}
protected workerProcess(entities: EntityData[], dt: number): EntityData[] {
return entities.map(entity => {
// 复杂算法逻辑
return this.complexAILogic(entity, dt);
});
}
```
**适用场景**:复杂计算逻辑,实体数量适中
### SharedArrayBuffer 模式
零拷贝数据共享,适合大量简单计算:
```typescript
constructor() {
super(matcher, {
enableWorker: true,
useSharedArrayBuffer: true,
entityDataSize: 6,
maxEntities: 10000
});
}
protected getSharedArrayBufferProcessFunction(): SharedArrayBufferProcessFunction {
return function(sharedFloatArray, startIndex, endIndex, dt, config) {
const entitySize = 6;
for (let i = startIndex; i < endIndex; i++) {
const offset = i * entitySize;
let vy = sharedFloatArray[offset + 3];
vy += config.gravity * dt;
sharedFloatArray[offset + 3] = vy;
}
};
}
```
**适用场景**:大量实体的简单计算
## 获取系统信息
```typescript
const info = this.getWorkerInfo();
console.log({
enabled: info.enabled,
workerCount: info.workerCount,
maxSystemWorkerCount: info.maxSystemWorkerCount,
currentMode: info.currentMode,
sharedArrayBufferEnabled: info.sharedArrayBufferEnabled
});
```
## 动态更新配置
```typescript
// 运行时更新配置
this.updateConfig({
workerCount: 4,
entitiesPerWorker: 100
});
```

View File

@@ -0,0 +1,190 @@
---
title: "完整示例"
description: "粒子物理系统等复杂 Worker 示例"
---
## 粒子物理系统
包含碰撞检测的完整粒子物理系统:
```typescript
interface ParticleData {
id: number;
x: number;
y: number;
dx: number;
dy: number;
mass: number;
radius: number;
bounce: number;
friction: number;
}
@ECSSystem('ParticlePhysics')
class ParticlePhysicsWorkerSystem extends WorkerEntitySystem<ParticleData> {
constructor() {
super(Matcher.all(Position, Velocity, Physics, Renderable), {
enableWorker: true,
workerCount: 6,
entitiesPerWorker: 150,
useSharedArrayBuffer: true,
entityDataSize: 9,
maxEntities: 5000,
systemConfig: {
gravity: 100,
canvasWidth: 800,
canvasHeight: 600,
groundFriction: 0.98
}
});
}
protected extractEntityData(entity: Entity): ParticleData {
const pos = entity.getComponent(Position);
const vel = entity.getComponent(Velocity);
const physics = entity.getComponent(Physics);
const render = entity.getComponent(Renderable);
return {
id: entity.id,
x: pos.x,
y: pos.y,
dx: vel.dx,
dy: vel.dy,
mass: physics.mass,
radius: render.size,
bounce: physics.bounce,
friction: physics.friction
};
}
protected workerProcess(
entities: ParticleData[],
deltaTime: number,
config: any
): ParticleData[] {
const result = entities.map(e => ({ ...e }));
// 基础物理更新
for (const p of result) {
p.dy += config.gravity * deltaTime;
p.x += p.dx * deltaTime;
p.y += p.dy * deltaTime;
// 边界碰撞
if (p.x <= p.radius || p.x >= config.canvasWidth - p.radius) {
p.dx = -p.dx * p.bounce;
p.x = Math.max(p.radius, Math.min(config.canvasWidth - p.radius, p.x));
}
if (p.y <= p.radius || p.y >= config.canvasHeight - p.radius) {
p.dy = -p.dy * p.bounce;
p.y = Math.max(p.radius, Math.min(config.canvasHeight - p.radius, p.y));
p.dx *= config.groundFriction;
}
p.dx *= p.friction;
p.dy *= p.friction;
}
// 粒子间碰撞检测
for (let i = 0; i < result.length; i++) {
for (let j = i + 1; j < result.length; j++) {
const p1 = result[i];
const p2 = result[j];
const dx = p2.x - p1.x;
const dy = p2.y - p1.y;
const dist = Math.sqrt(dx * dx + dy * dy);
const minDist = p1.radius + p2.radius;
if (dist < minDist && dist > 0) {
// 分离粒子
const nx = dx / dist;
const ny = dy / dist;
const overlap = minDist - dist;
p1.x -= nx * overlap * 0.5;
p1.y -= ny * overlap * 0.5;
p2.x += nx * overlap * 0.5;
p2.y += ny * overlap * 0.5;
// 弹性碰撞
const relVx = p2.dx - p1.dx;
const relVy = p2.dy - p1.dy;
const velNormal = relVx * nx + relVy * ny;
if (velNormal > 0) continue;
const restitution = (p1.bounce + p2.bounce) * 0.5;
const impulse = -(1 + restitution) * velNormal / (1/p1.mass + 1/p2.mass);
p1.dx -= impulse * nx / p1.mass;
p1.dy -= impulse * ny / p1.mass;
p2.dx += impulse * nx / p2.mass;
p2.dy += impulse * ny / p2.mass;
}
}
}
return result;
}
protected applyResult(entity: Entity, result: ParticleData): void {
if (!entity?.enabled) return;
const pos = entity.getComponent(Position);
const vel = entity.getComponent(Velocity);
if (pos && vel) {
pos.set(result.x, result.y);
vel.set(result.dx, result.dy);
}
}
protected getDefaultEntityDataSize(): number {
return 9;
}
protected writeEntityToBuffer(data: ParticleData, offset: number): void {
if (!this.sharedFloatArray) return;
this.sharedFloatArray[offset + 0] = data.id;
this.sharedFloatArray[offset + 1] = data.x;
this.sharedFloatArray[offset + 2] = data.y;
this.sharedFloatArray[offset + 3] = data.dx;
this.sharedFloatArray[offset + 4] = data.dy;
this.sharedFloatArray[offset + 5] = data.mass;
this.sharedFloatArray[offset + 6] = data.radius;
this.sharedFloatArray[offset + 7] = data.bounce;
this.sharedFloatArray[offset + 8] = data.friction;
}
protected readEntityFromBuffer(offset: number): ParticleData | null {
if (!this.sharedFloatArray) return null;
return {
id: this.sharedFloatArray[offset + 0],
x: this.sharedFloatArray[offset + 1],
y: this.sharedFloatArray[offset + 2],
dx: this.sharedFloatArray[offset + 3],
dy: this.sharedFloatArray[offset + 4],
mass: this.sharedFloatArray[offset + 5],
radius: this.sharedFloatArray[offset + 6],
bounce: this.sharedFloatArray[offset + 7],
friction: this.sharedFloatArray[offset + 8]
};
}
}
```
## 性能监控
```typescript
public getPerformanceInfo() {
const info = this.getWorkerInfo();
return {
...info,
entityCount: this.entities.length
};
}
```

View File

@@ -0,0 +1,105 @@
---
title: "Worker 系统"
description: "基于 Web Worker 的多线程并行处理系统"
---
Worker 系统WorkerEntitySystem是 ECS 框架中基于 Web Worker 的多线程处理系统,专为计算密集型任务设计。
## 核心特性
- **真正的并行计算**:利用 Web Worker 在后台线程执行任务
- **自动负载均衡**:根据 CPU 核心数自动分配工作负载
- **SharedArrayBuffer 优化**:零拷贝数据共享,提升大规模计算性能
- **降级支持**:不支持 Worker 时自动回退到主线程
- **类型安全**:完整的 TypeScript 支持
## 快速开始
```typescript
interface PhysicsData {
id: number;
x: number;
y: number;
vx: number;
vy: number;
}
@ECSSystem('Physics')
class PhysicsWorkerSystem extends WorkerEntitySystem<PhysicsData> {
constructor() {
super(Matcher.all(Position, Velocity), {
enableWorker: true,
workerCount: 4,
systemConfig: { gravity: 100 }
});
}
// 必须实现的方法
protected getDefaultEntityDataSize(): number {
return 5; // id, x, y, vx, vy
}
protected extractEntityData(entity: Entity): PhysicsData {
const pos = entity.getComponent(Position);
const vel = entity.getComponent(Velocity);
return { id: entity.id, x: pos.x, y: pos.y, vx: vel.x, vy: vel.y };
}
protected workerProcess(entities: PhysicsData[], dt: number, config: any): PhysicsData[] {
return entities.map(e => {
e.vy += config.gravity * dt;
e.x += e.vx * dt;
e.y += e.vy * dt;
return e;
});
}
protected applyResult(entity: Entity, result: PhysicsData): void {
const pos = entity.getComponent(Position);
const vel = entity.getComponent(Velocity);
pos.x = result.x;
pos.y = result.y;
vel.x = result.vx;
vel.y = result.vy;
}
protected writeEntityToBuffer(data: PhysicsData, offset: number): void {
if (!this.sharedFloatArray) return;
this.sharedFloatArray[offset] = data.id;
this.sharedFloatArray[offset + 1] = data.x;
this.sharedFloatArray[offset + 2] = data.y;
this.sharedFloatArray[offset + 3] = data.vx;
this.sharedFloatArray[offset + 4] = data.vy;
}
protected readEntityFromBuffer(offset: number): PhysicsData | null {
if (!this.sharedFloatArray) return null;
return {
id: this.sharedFloatArray[offset],
x: this.sharedFloatArray[offset + 1],
y: this.sharedFloatArray[offset + 2],
vx: this.sharedFloatArray[offset + 3],
vy: this.sharedFloatArray[offset + 4]
};
}
}
```
## 适用场景
| 场景 | 示例 |
|------|------|
| **物理模拟** | 重力、碰撞检测、流体模拟 |
| **AI 计算** | 路径寻找、行为树、群体智能 |
| **数据处理** | 状态机、统计计算、图像处理 |
## 文档导航
- [配置选项](/guide/worker-system/configuration/) - 详细配置和处理模式
- [完整示例](/guide/worker-system/examples/) - 粒子物理等复杂示例
- [微信小游戏](/guide/worker-system/wechat/) - 微信小游戏 Worker 支持
- [最佳实践](/guide/worker-system/best-practices/) - 性能优化建议
## 在线演示
[Worker 系统演示](https://esengine.github.io/ecs-framework/demos/worker-system/) - 多线程物理计算、实时性能对比

View File

@@ -0,0 +1,138 @@
---
title: "微信小游戏支持"
description: "微信小游戏 Worker 限制和解决方案"
---
微信小游戏对 Worker 有特殊限制,不支持动态创建 Worker 脚本。ESEngine 提供了 CLI 工具来解决这个问题。
## 平台差异
| 特性 | 浏览器 | 微信小游戏 |
|------|--------|-----------|
| 动态脚本 (Blob URL) | ✅ 支持 | ❌ 不支持 |
| Worker 数量 | 多个 | 最多 1 个 |
| 脚本来源 | 任意 | 必须是代码包内文件 |
| SharedArrayBuffer | 需要 COOP/COEP | 有限支持 |
## 使用 Worker Generator CLI
### 1. 安装工具
```bash
pnpm add -D @esengine/worker-generator
```
### 2. 配置 workerScriptPath
```typescript
@ECSSystem('Physics')
class PhysicsWorkerSystem extends WorkerEntitySystem<PhysicsData> {
constructor() {
super(Matcher.all(Position, Velocity), {
enableWorker: true,
workerScriptPath: 'workers/physics-worker.js',
systemConfig: { gravity: 100 }
});
}
protected workerProcess(entities: PhysicsData[], dt: number, config: any): PhysicsData[] {
return entities.map(e => {
e.vy += config.gravity * dt;
e.x += e.vx * dt;
e.y += e.vy * dt;
return e;
});
}
}
```
### 3. 生成 Worker 文件
```bash
# 基本用法
npx esengine-worker-gen --src ./src --wechat
# 完整选项
npx esengine-worker-gen \
--src ./src \
--wechat \
--mapping \
--verbose
```
CLI 工具会自动:
1. 扫描所有 `WorkerEntitySystem` 子类
2. 提取 `workerProcess` 方法
3. 转换为 ES5 语法
4. 生成到配置的路径
### 4. 配置 game.json
```json
{
"deviceOrientation": "portrait",
"workers": "workers"
}
```
### 5. 项目结构
```
your-game/
├── game.js
├── game.json
├── src/
│ └── systems/
│ └── PhysicsSystem.ts
└── workers/
├── physics-worker.js # 自动生成
└── worker-mapping.json # 自动生成
```
## 临时禁用 Worker
### 配置禁用
```typescript
constructor() {
super(matcher, {
enableWorker: false,
});
}
```
### 平台适配器禁用
```typescript
class MyPlatformAdapter implements IPlatformAdapter {
isWorkerSupported(): boolean {
return false;
}
}
```
## 注意事项
1. **每次修改 workerProcess 后需重新运行 CLI**
2. **Worker 函数必须是纯函数**
```typescript
// ✅ 正确
protected workerProcess(entities, dt, config) {
return entities.map(e => {
e.y += config.gravity * dt;
return e;
});
}
// ❌ 错误:使用 this
protected workerProcess(entities, dt, config) {
e.y += this.gravity * dt; // Worker 中无法访问 this
}
```
3. **配置数据通过 systemConfig 传递**
4. **开发者工具警告可忽略**
- `getNetworkType:fail not support`
- `SharedArrayBuffer will require cross-origin isolation`

View File

@@ -0,0 +1,133 @@
---
title: "蓝图组合"
description: "片段、组合器和触发器"
---
## 蓝图片段
将可复用的逻辑封装为片段:
```typescript
import { createFragment } from '@esengine/blueprint';
const healthFragment = createFragment('HealthSystem', {
inputs: [
{ name: 'damage', type: 'number', internalNodeId: 'input1', internalPinName: 'value' }
],
outputs: [
{ name: 'isDead', type: 'boolean', internalNodeId: 'output1', internalPinName: 'value' }
],
graph: {
nodes: [...],
connections: [...],
variables: [...]
}
});
```
## 组合蓝图
```typescript
import { createComposer, FragmentRegistry } from '@esengine/blueprint';
// 注册片段
FragmentRegistry.instance.register('health', healthFragment);
FragmentRegistry.instance.register('movement', movementFragment);
// 创建组合器
const composer = createComposer('PlayerBlueprint');
// 添加片段到槽位
composer.addFragment(healthFragment, 'slot1', { position: { x: 0, y: 0 } });
composer.addFragment(movementFragment, 'slot2', { position: { x: 400, y: 0 } });
// 连接槽位
composer.connect('slot1', 'onDeath', 'slot2', 'disable');
// 验证
const validation = composer.validate();
if (!validation.isValid) {
console.error(validation.errors);
}
// 编译成蓝图
const blueprint = composer.compile();
```
## 片段注册表
```typescript
import { FragmentRegistry } from '@esengine/blueprint';
const registry = FragmentRegistry.instance;
// 注册片段
registry.register('health', healthFragment);
// 获取片段
const fragment = registry.get('health');
// 获取所有片段
const allFragments = registry.getAll();
// 按类别获取
const combatFragments = registry.getByCategory('combat');
```
## 触发器系统
### 定义触发条件
```typescript
import { TriggerCondition, TriggerDispatcher } from '@esengine/blueprint';
const lowHealthCondition: TriggerCondition = {
type: 'comparison',
left: { type: 'variable', name: 'health' },
operator: '<',
right: { type: 'constant', value: 20 }
};
```
### 使用触发器分发器
```typescript
const dispatcher = new TriggerDispatcher();
// 注册触发器
dispatcher.register('lowHealth', lowHealthCondition, (context) => {
context.triggerEvent('OnLowHealth');
});
// 每帧评估
dispatcher.evaluate(context);
```
### 复合条件
```typescript
const complexCondition: TriggerCondition = {
type: 'and',
conditions: [
{
type: 'comparison',
left: { type: 'variable', name: 'health' },
operator: '<',
right: { type: 'constant', value: 50 }
},
{
type: 'comparison',
left: { type: 'variable', name: 'inCombat' },
operator: '==',
right: { type: 'constant', value: true }
}
]
};
```
## 片段最佳实践
1. **单一职责** - 每个片段只做一件事
2. **清晰接口** - 输入输出引脚命名明确
3. **文档注释** - 为片段添加描述和使用示例
4. **版本控制** - 更新片段时注意向后兼容

View File

@@ -0,0 +1,128 @@
---
title: "自定义节点"
description: "创建自定义蓝图节点"
---
## 定义节点模板
```typescript
import { BlueprintNodeTemplate } from '@esengine/blueprint';
const MyNodeTemplate: BlueprintNodeTemplate = {
type: 'MyCustomNode',
title: 'My Custom Node',
category: 'custom',
description: 'A custom node example',
keywords: ['custom', 'example'],
inputs: [
{ name: 'exec', type: 'exec', direction: 'input', isExec: true },
{ name: 'value', type: 'number', direction: 'input', defaultValue: 0 }
],
outputs: [
{ name: 'exec', type: 'exec', direction: 'output', isExec: true },
{ name: 'result', type: 'number', direction: 'output' }
]
};
```
## 实现节点执行器
```typescript
import { INodeExecutor, RegisterNode } from '@esengine/blueprint';
@RegisterNode(MyNodeTemplate)
class MyNodeExecutor implements INodeExecutor {
execute(node: BlueprintNode, context: ExecutionContext): ExecutionResult {
// 获取输入
const value = context.getInput<number>(node.id, 'value');
// 执行逻辑
const result = value * 2;
// 返回结果
return {
outputs: { result },
nextExec: 'exec' // 继续执行
};
}
}
```
## 注册方式
```typescript
// 方式 1: 使用装饰器
@RegisterNode(MyNodeTemplate)
class MyNodeExecutor implements INodeExecutor { ... }
// 方式 2: 手动注册
NodeRegistry.instance.register(MyNodeTemplate, new MyNodeExecutor());
```
## 节点注册表
```typescript
import { NodeRegistry } from '@esengine/blueprint';
// 获取单例
const registry = NodeRegistry.instance;
// 获取所有模板
const allTemplates = registry.getAllTemplates();
// 按类别获取
const mathNodes = registry.getTemplatesByCategory('math');
// 搜索节点
const results = registry.searchTemplates('add');
// 检查是否存在
if (registry.has('MyCustomNode')) { ... }
```
## 纯节点
纯节点没有副作用,其输出会被缓存:
```typescript
const PureNodeTemplate: BlueprintNodeTemplate = {
type: 'GetDistance',
title: 'Get Distance',
category: 'math',
isPure: true, // 标记为纯节点
inputs: [
{ name: 'a', type: 'vector2', direction: 'input' },
{ name: 'b', type: 'vector2', direction: 'input' }
],
outputs: [
{ name: 'distance', type: 'number', direction: 'output' }
]
};
```
## 实际示例:输入处理节点
```typescript
const InputMoveTemplate: BlueprintNodeTemplate = {
type: 'InputMove',
title: 'Get Movement Input',
category: 'input',
inputs: [],
outputs: [
{ name: 'direction', type: 'vector2', direction: 'output' }
],
isPure: true
};
@RegisterNode(InputMoveTemplate)
class InputMoveExecutor implements INodeExecutor {
execute(node: BlueprintNode, context: ExecutionContext): ExecutionResult {
const input = context.scene.services.get(InputServiceToken);
const direction = {
x: input.getAxis('horizontal'),
y: input.getAxis('vertical')
};
return { outputs: { direction } };
}
}
```

View File

@@ -0,0 +1,168 @@
---
title: "实际示例"
description: "ECS 集成和最佳实践"
---
## 玩家控制蓝图
```typescript
// 定义输入处理节点
const InputMoveTemplate: BlueprintNodeTemplate = {
type: 'InputMove',
title: 'Get Movement Input',
category: 'input',
inputs: [],
outputs: [
{ name: 'direction', type: 'vector2', direction: 'output' }
],
isPure: true
};
@RegisterNode(InputMoveTemplate)
class InputMoveExecutor implements INodeExecutor {
execute(node: BlueprintNode, context: ExecutionContext): ExecutionResult {
const input = context.scene.services.get(InputServiceToken);
const direction = {
x: input.getAxis('horizontal'),
y: input.getAxis('vertical')
};
return { outputs: { direction } };
}
}
```
## 状态切换逻辑
```typescript
// 在蓝图中实现状态机逻辑
const stateBlueprint = createEmptyBlueprint('PlayerState');
// 添加状态变量
stateBlueprint.variables.push({
name: 'currentState',
type: 'string',
defaultValue: 'idle',
scope: 'instance'
});
// 在 Tick 事件中检查状态转换
// ... 通过节点连接实现
```
## 伤害处理系统
```typescript
// 自定义伤害节点
const ApplyDamageTemplate: BlueprintNodeTemplate = {
type: 'ApplyDamage',
title: 'Apply Damage',
category: 'combat',
inputs: [
{ name: 'exec', type: 'exec', direction: 'input', isExec: true },
{ name: 'target', type: 'entity', direction: 'input' },
{ name: 'amount', type: 'number', direction: 'input', defaultValue: 10 }
],
outputs: [
{ name: 'exec', type: 'exec', direction: 'output', isExec: true },
{ name: 'killed', type: 'boolean', direction: 'output' }
]
};
@RegisterNode(ApplyDamageTemplate)
class ApplyDamageExecutor implements INodeExecutor {
execute(node: BlueprintNode, context: ExecutionContext): ExecutionResult {
const target = context.getInput<Entity>(node.id, 'target');
const amount = context.getInput<number>(node.id, 'amount');
const health = target.getComponent(HealthComponent);
if (health) {
health.current -= amount;
const killed = health.current <= 0;
return {
outputs: { killed },
nextExec: 'exec'
};
}
return { outputs: { killed: false }, nextExec: 'exec' };
}
}
```
## 技能冷却系统
```typescript
// 冷却检查节点
const CheckCooldownTemplate: BlueprintNodeTemplate = {
type: 'CheckCooldown',
title: 'Check Cooldown',
category: 'ability',
inputs: [
{ name: 'skillId', type: 'string', direction: 'input' }
],
outputs: [
{ name: 'ready', type: 'boolean', direction: 'output' },
{ name: 'remaining', type: 'number', direction: 'output' }
],
isPure: true
};
```
## 最佳实践
### 1. 使用片段复用逻辑
```typescript
// 将通用逻辑封装为片段
const movementFragment = createFragment('Movement', {
inputs: [{ name: 'speed', type: 'number', ... }],
outputs: [{ name: 'position', type: 'vector2', ... }],
graph: { ... }
});
// 通过组合器构建复杂蓝图
const composer = createComposer('Player');
composer.addFragment(movementFragment, 'movement');
composer.addFragment(combatFragment, 'combat');
```
### 2. 合理使用变量作用域
```typescript
// local: 临时计算结果
{ name: 'tempValue', scope: 'local' }
// instance: 实体状态(如生命值)
{ name: 'health', scope: 'instance' }
// global: 游戏全局状态
{ name: 'score', scope: 'global' }
```
### 3. 避免无限循环
```typescript
// VM 有每帧最大执行步数限制(默认 1000
// 使用 Delay 节点打断长执行链
vm.maxStepsPerFrame = 1000;
```
### 4. 调试技巧
```typescript
// 启用调试模式查看执行日志
vm.debug = true;
// 使用 Print 节点输出中间值
// 在编辑器中设置断点
```
### 5. 性能优化
```typescript
// 纯节点的输出会被缓存
{ isPure: true }
// 避免在 Tick 中执行重计算
// 使用事件驱动而非轮询
```

View File

@@ -1,5 +1,6 @@
---
title: "蓝图可视化脚本 (Blueprint)"
description: "完整的可视化脚本系统"
---
`@esengine/blueprint` 提供了一个功能完整的可视化脚本系统,支持节点式编程、事件驱动和蓝图组合。
@@ -104,406 +105,10 @@ type VariableScope =
| 'global'; // 全局共享
```
## 虚拟机 API
## 文档导航
### BlueprintVM
蓝图虚拟机负责执行蓝图图:
```typescript
import { BlueprintVM } from '@esengine/blueprint';
// 创建 VM
const vm = new BlueprintVM(blueprintAsset, entity, scene);
// 启动(触发 BeginPlay
vm.start();
// 每帧更新(触发 Tick
vm.tick(deltaTime);
// 停止(触发 EndPlay
vm.stop();
// 暂停/恢复
vm.pause();
vm.resume();
// 触发事件
vm.triggerEvent('EventCollision', { other: otherEntity });
vm.triggerCustomEvent('OnDamage', { amount: 50 });
// 调试模式
vm.debug = true;
```
### 执行上下文
```typescript
interface ExecutionContext {
blueprint: BlueprintAsset; // 蓝图资产
entity: Entity; // 当前实体
scene: IScene; // 当前场景
deltaTime: number; // 帧间隔时间
time: number; // 总运行时间
// 获取输入值
getInput<T>(nodeId: string, pinName: string): T;
// 设置输出值
setOutput(nodeId: string, pinName: string, value: unknown): void;
// 变量访问
getVariable<T>(name: string): T;
setVariable(name: string, value: unknown): void;
}
```
### 执行结果
```typescript
interface ExecutionResult {
outputs?: Record<string, unknown>; // 输出值
nextExec?: string | null; // 下一个执行引脚
delay?: number; // 延迟执行(毫秒)
yield?: boolean; // 暂停到下一帧
error?: string; // 错误信息
}
```
## 自定义节点
### 定义节点模板
```typescript
import { BlueprintNodeTemplate } from '@esengine/blueprint';
const MyNodeTemplate: BlueprintNodeTemplate = {
type: 'MyCustomNode',
title: 'My Custom Node',
category: 'custom',
description: 'A custom node example',
keywords: ['custom', 'example'],
inputs: [
{ name: 'exec', type: 'exec', direction: 'input', isExec: true },
{ name: 'value', type: 'number', direction: 'input', defaultValue: 0 }
],
outputs: [
{ name: 'exec', type: 'exec', direction: 'output', isExec: true },
{ name: 'result', type: 'number', direction: 'output' }
]
};
```
### 实现节点执行器
```typescript
import { INodeExecutor, RegisterNode } from '@esengine/blueprint';
@RegisterNode(MyNodeTemplate)
class MyNodeExecutor implements INodeExecutor {
execute(node: BlueprintNode, context: ExecutionContext): ExecutionResult {
// 获取输入
const value = context.getInput<number>(node.id, 'value');
// 执行逻辑
const result = value * 2;
// 返回结果
return {
outputs: { result },
nextExec: 'exec' // 继续执行
};
}
}
```
### 使用装饰器注册
```typescript
// 方式 1: 使用装饰器
@RegisterNode(MyNodeTemplate)
class MyNodeExecutor implements INodeExecutor { ... }
// 方式 2: 手动注册
NodeRegistry.instance.register(MyNodeTemplate, new MyNodeExecutor());
```
## 节点注册表
```typescript
import { NodeRegistry } from '@esengine/blueprint';
// 获取单例
const registry = NodeRegistry.instance;
// 获取所有模板
const allTemplates = registry.getAllTemplates();
// 按类别获取
const mathNodes = registry.getTemplatesByCategory('math');
// 搜索节点
const results = registry.searchTemplates('add');
// 检查是否存在
if (registry.has('MyCustomNode')) { ... }
```
## 内置节点
### 事件节点
| 节点 | 说明 |
|------|------|
| `EventBeginPlay` | 蓝图启动时触发 |
| `EventTick` | 每帧触发 |
| `EventEndPlay` | 蓝图停止时触发 |
| `EventCollision` | 碰撞时触发 |
| `EventInput` | 输入事件触发 |
| `EventTimer` | 定时器触发 |
| `EventMessage` | 自定义消息触发 |
### 时间节点
| 节点 | 说明 |
|------|------|
| `Delay` | 延迟执行 |
| `GetDeltaTime` | 获取帧间隔 |
| `GetTime` | 获取运行时间 |
### 数学节点
| 节点 | 说明 |
|------|------|
| `Add` | 加法 |
| `Subtract` | 减法 |
| `Multiply` | 乘法 |
| `Divide` | 除法 |
| `Abs` | 绝对值 |
| `Clamp` | 限制范围 |
| `Lerp` | 线性插值 |
| `Min` / `Max` | 最小/最大值 |
### 调试节点
| 节点 | 说明 |
|------|------|
| `Print` | 打印到控制台 |
## 蓝图组合
### 蓝图片段
将可复用的逻辑封装为片段:
```typescript
import { createFragment } from '@esengine/blueprint';
const healthFragment = createFragment('HealthSystem', {
inputs: [
{ name: 'damage', type: 'number', internalNodeId: 'input1', internalPinName: 'value' }
],
outputs: [
{ name: 'isDead', type: 'boolean', internalNodeId: 'output1', internalPinName: 'value' }
],
graph: {
nodes: [...],
connections: [...],
variables: [...]
}
});
```
### 组合蓝图
```typescript
import { createComposer, FragmentRegistry } from '@esengine/blueprint';
// 注册片段
FragmentRegistry.instance.register('health', healthFragment);
FragmentRegistry.instance.register('movement', movementFragment);
// 创建组合器
const composer = createComposer('PlayerBlueprint');
// 添加片段到槽位
composer.addFragment(healthFragment, 'slot1', { position: { x: 0, y: 0 } });
composer.addFragment(movementFragment, 'slot2', { position: { x: 400, y: 0 } });
// 连接槽位
composer.connect('slot1', 'onDeath', 'slot2', 'disable');
// 验证
const validation = composer.validate();
if (!validation.isValid) {
console.error(validation.errors);
}
// 编译成蓝图
const blueprint = composer.compile();
```
## 触发器系统
### 定义触发条件
```typescript
import { TriggerCondition, TriggerDispatcher } from '@esengine/blueprint';
const lowHealthCondition: TriggerCondition = {
type: 'comparison',
left: { type: 'variable', name: 'health' },
operator: '<',
right: { type: 'constant', value: 20 }
};
```
### 使用触发器分发器
```typescript
const dispatcher = new TriggerDispatcher();
// 注册触发器
dispatcher.register('lowHealth', lowHealthCondition, (context) => {
context.triggerEvent('OnLowHealth');
});
// 每帧评估
dispatcher.evaluate(context);
```
## 与 ECS 集成
### 使用蓝图系统
```typescript
import { createBlueprintSystem } from '@esengine/blueprint';
class GameScene {
private blueprintSystem: BlueprintSystem;
initialize() {
this.blueprintSystem = createBlueprintSystem(this.scene);
}
update(dt: number) {
// 处理所有带蓝图组件的实体
this.blueprintSystem.process(this.entities, dt);
}
}
```
### 触发蓝图事件
```typescript
import { triggerBlueprintEvent, triggerCustomBlueprintEvent } from '@esengine/blueprint';
// 触发内置事件
triggerBlueprintEvent(entity, 'Collision', { other: otherEntity });
// 触发自定义事件
triggerCustomBlueprintEvent(entity, 'OnPickup', { item: itemEntity });
```
## 实际示例
### 玩家控制蓝图
```typescript
// 定义输入处理节点
const InputMoveTemplate: BlueprintNodeTemplate = {
type: 'InputMove',
title: 'Get Movement Input',
category: 'input',
inputs: [],
outputs: [
{ name: 'direction', type: 'vector2', direction: 'output' }
],
isPure: true
};
@RegisterNode(InputMoveTemplate)
class InputMoveExecutor implements INodeExecutor {
execute(node: BlueprintNode, context: ExecutionContext): ExecutionResult {
const input = context.scene.services.get(InputServiceToken);
const direction = {
x: input.getAxis('horizontal'),
y: input.getAxis('vertical')
};
return { outputs: { direction } };
}
}
```
### 状态切换逻辑
```typescript
// 在蓝图中实现状态机逻辑
const stateBlueprint = createEmptyBlueprint('PlayerState');
// 添加状态变量
stateBlueprint.variables.push({
name: 'currentState',
type: 'string',
defaultValue: 'idle',
scope: 'instance'
});
// 在 Tick 事件中检查状态转换
// ... 通过节点连接实现
```
## 序列化
### 保存蓝图
```typescript
import { validateBlueprintAsset } from '@esengine/blueprint';
function saveBlueprint(blueprint: BlueprintAsset, path: string): void {
if (!validateBlueprintAsset(blueprint)) {
throw new Error('Invalid blueprint structure');
}
const json = JSON.stringify(blueprint, null, 2);
fs.writeFileSync(path, json);
}
```
### 加载蓝图
```typescript
async function loadBlueprint(path: string): Promise<BlueprintAsset> {
const json = await fs.readFile(path, 'utf-8');
const asset = JSON.parse(json);
if (!validateBlueprintAsset(asset)) {
throw new Error('Invalid blueprint file');
}
return asset;
}
```
## 最佳实践
1. **使用片段复用逻辑**
- 将通用逻辑封装为片段
- 通过组合器构建复杂蓝图
2. **合理使用变量作用域**
- `local`: 临时计算结果
- `instance`: 实体状态(如生命值)
- `global`: 游戏全局状态
3. **避免无限循环**
- VM 有每帧最大执行步数限制(默认 1000
- 使用 Delay 节点打断长执行链
4. **调试技巧**
- 启用 `vm.debug = true` 查看执行日志
- 使用 Print 节点输出中间值
5. **性能优化**
- 纯节点(`isPure: true`)的输出会被缓存
- 避免在 Tick 中执行重计算
- [虚拟机 API](./vm) - BlueprintVM 执行和上下文
- [自定义节点](./custom-nodes) - 创建自定义节点
- [内置节点](./nodes) - 内置节点参考
- [蓝图组合](./composition) - 片段和组合器
- [实际示例](./examples) - ECS 集成和最佳实践

View File

@@ -0,0 +1,107 @@
---
title: "内置节点"
description: "蓝图内置节点参考"
---
## 事件节点
| 节点 | 说明 |
|------|------|
| `EventBeginPlay` | 蓝图启动时触发 |
| `EventTick` | 每帧触发 |
| `EventEndPlay` | 蓝图停止时触发 |
| `EventCollision` | 碰撞时触发 |
| `EventInput` | 输入事件触发 |
| `EventTimer` | 定时器触发 |
| `EventMessage` | 自定义消息触发 |
## 流程控制节点
| 节点 | 说明 |
|------|------|
| `Branch` | 条件分支 (if/else) |
| `Sequence` | 顺序执行多个输出 |
| `ForLoop` | 循环执行 |
| `WhileLoop` | 条件循环 |
| `DoOnce` | 只执行一次 |
| `FlipFlop` | 交替执行两个分支 |
| `Gate` | 可开关的执行门 |
## 时间节点
| 节点 | 说明 |
|------|------|
| `Delay` | 延迟执行 |
| `GetDeltaTime` | 获取帧间隔 |
| `GetTime` | 获取运行时间 |
| `SetTimer` | 设置定时器 |
| `ClearTimer` | 清除定时器 |
## 数学节点
| 节点 | 说明 |
|------|------|
| `Add` | 加法 |
| `Subtract` | 减法 |
| `Multiply` | 乘法 |
| `Divide` | 除法 |
| `Abs` | 绝对值 |
| `Clamp` | 限制范围 |
| `Lerp` | 线性插值 |
| `Min` / `Max` | 最小/最大值 |
| `Sin` / `Cos` | 三角函数 |
| `Sqrt` | 平方根 |
| `Power` | 幂运算 |
## 逻辑节点
| 节点 | 说明 |
|------|------|
| `And` | 逻辑与 |
| `Or` | 逻辑或 |
| `Not` | 逻辑非 |
| `Equal` | 相等比较 |
| `NotEqual` | 不等比较 |
| `Greater` | 大于比较 |
| `Less` | 小于比较 |
## 向量节点
| 节点 | 说明 |
|------|------|
| `MakeVector2` | 创建 2D 向量 |
| `BreakVector2` | 分解 2D 向量 |
| `VectorAdd` | 向量加法 |
| `VectorSubtract` | 向量减法 |
| `VectorMultiply` | 向量乘法 |
| `VectorLength` | 向量长度 |
| `VectorNormalize` | 向量归一化 |
| `VectorDistance` | 向量距离 |
## 实体节点
| 节点 | 说明 |
|------|------|
| `GetSelf` | 获取当前实体 |
| `GetComponent` | 获取组件 |
| `HasComponent` | 检查组件 |
| `AddComponent` | 添加组件 |
| `RemoveComponent` | 移除组件 |
| `SpawnEntity` | 创建实体 |
| `DestroyEntity` | 销毁实体 |
## 变量节点
| 节点 | 说明 |
|------|------|
| `GetVariable` | 获取变量值 |
| `SetVariable` | 设置变量值 |
## 调试节点
| 节点 | 说明 |
|------|------|
| `Print` | 打印到控制台 |
| `DrawDebugLine` | 绘制调试线 |
| `DrawDebugPoint` | 绘制调试点 |
| `Breakpoint` | 调试断点 |

View File

@@ -0,0 +1,133 @@
---
title: "虚拟机 API"
description: "BlueprintVM 执行和上下文"
---
## BlueprintVM
蓝图虚拟机负责执行蓝图图:
```typescript
import { BlueprintVM } from '@esengine/blueprint';
// 创建 VM
const vm = new BlueprintVM(blueprintAsset, entity, scene);
// 启动(触发 BeginPlay
vm.start();
// 每帧更新(触发 Tick
vm.tick(deltaTime);
// 停止(触发 EndPlay
vm.stop();
// 暂停/恢复
vm.pause();
vm.resume();
// 触发事件
vm.triggerEvent('EventCollision', { other: otherEntity });
vm.triggerCustomEvent('OnDamage', { amount: 50 });
// 调试模式
vm.debug = true;
```
## 执行上下文
```typescript
interface ExecutionContext {
blueprint: BlueprintAsset; // 蓝图资产
entity: Entity; // 当前实体
scene: IScene; // 当前场景
deltaTime: number; // 帧间隔时间
time: number; // 总运行时间
// 获取输入值
getInput<T>(nodeId: string, pinName: string): T;
// 设置输出值
setOutput(nodeId: string, pinName: string, value: unknown): void;
// 变量访问
getVariable<T>(name: string): T;
setVariable(name: string, value: unknown): void;
}
```
## 执行结果
```typescript
interface ExecutionResult {
outputs?: Record<string, unknown>; // 输出值
nextExec?: string | null; // 下一个执行引脚
delay?: number; // 延迟执行(毫秒)
yield?: boolean; // 暂停到下一帧
error?: string; // 错误信息
}
```
## 与 ECS 集成
### 使用蓝图系统
```typescript
import { createBlueprintSystem } from '@esengine/blueprint';
class GameScene {
private blueprintSystem: BlueprintSystem;
initialize() {
this.blueprintSystem = createBlueprintSystem(this.scene);
}
update(dt: number) {
// 处理所有带蓝图组件的实体
this.blueprintSystem.process(this.entities, dt);
}
}
```
### 触发蓝图事件
```typescript
import { triggerBlueprintEvent, triggerCustomBlueprintEvent } from '@esengine/blueprint';
// 触发内置事件
triggerBlueprintEvent(entity, 'Collision', { other: otherEntity });
// 触发自定义事件
triggerCustomBlueprintEvent(entity, 'OnPickup', { item: itemEntity });
```
## 序列化
### 保存蓝图
```typescript
import { validateBlueprintAsset } from '@esengine/blueprint';
function saveBlueprint(blueprint: BlueprintAsset, path: string): void {
if (!validateBlueprintAsset(blueprint)) {
throw new Error('Invalid blueprint structure');
}
const json = JSON.stringify(blueprint, null, 2);
fs.writeFileSync(path, json);
}
```
### 加载蓝图
```typescript
async function loadBlueprint(path: string): Promise<BlueprintAsset> {
const json = await fs.readFile(path, 'utf-8');
const asset = JSON.parse(json);
if (!validateBlueprintAsset(asset)) {
throw new Error('Invalid blueprint file');
}
return asset;
}
```

View File

@@ -0,0 +1,135 @@
---
title: "API 参考"
description: "状态机完整 API"
---
## createStateMachine
```typescript
function createStateMachine<TState extends string, TContext = unknown>(
initialState: TState,
options?: StateMachineOptions<TContext>
): IStateMachine<TState, TContext>
```
**参数:**
- `initialState` - 初始状态
- `options.context` - 上下文对象,在回调中可访问
- `options.maxHistorySize` - 最大历史记录数(默认 100
- `options.enableHistory` - 是否启用历史记录(默认 true
## 状态机属性
| 属性 | 类型 | 描述 |
|------|------|------|
| `current` | `TState` | 当前状态 |
| `previous` | `TState \| null` | 上一个状态 |
| `context` | `TContext` | 上下文对象 |
| `isTransitioning` | `boolean` | 是否正在转换中 |
| `currentStateDuration` | `number` | 当前状态持续时间(毫秒) |
## 状态定义
```typescript
// 定义状态
fsm.defineState('idle', {
onEnter: (ctx, from) => {},
onExit: (ctx, to) => {},
onUpdate: (ctx, dt) => {}
});
// 检查状态是否存在
fsm.hasState('idle'); // true
// 获取状态配置
fsm.getStateConfig('idle');
// 获取所有状态
fsm.getStates(); // ['idle', 'walk', ...]
```
## 转换操作
```typescript
// 定义转换
fsm.defineTransition('idle', 'walk', condition, priority);
// 移除转换
fsm.removeTransition('idle', 'walk');
// 获取从某状态出发的所有转换
fsm.getTransitionsFrom('idle');
// 检查是否可以转换
fsm.canTransition('walk'); // true/false
// 手动转换
fsm.transition('walk');
// 强制转换(忽略条件)
fsm.transition('walk', true);
// 自动评估转换条件
fsm.evaluateTransitions();
```
## 生命周期
```typescript
// 更新状态机(调用当前状态的 onUpdate
fsm.update(deltaTime);
// 重置状态机
fsm.reset(); // 重置到当前状态
fsm.reset('idle'); // 重置到指定状态
```
## 事件监听
```typescript
// 监听进入特定状态
const unsubscribe = fsm.onEnter('walk', (from) => {
console.log(`${from} 进入 walk`);
});
// 监听退出特定状态
fsm.onExit('walk', (to) => {
console.log(`从 walk 退出到 ${to}`);
});
// 监听任意状态变化
fsm.onChange((event) => {
console.log(`${event.from} -> ${event.to} at ${event.timestamp}`);
});
// 取消订阅
unsubscribe();
```
## 调试
```typescript
// 获取状态历史
const history = fsm.getHistory();
// [{ from: 'idle', to: 'walk', timestamp: 1234567890 }, ...]
// 清除历史
fsm.clearHistory();
// 获取调试信息
const info = fsm.getDebugInfo();
// { current, previous, duration, stateCount, transitionCount, historySize }
```
## 蓝图节点
FSM 模块提供了可视化脚本支持的蓝图节点:
- `GetCurrentState` - 获取当前状态
- `TransitionTo` - 转换到指定状态
- `CanTransition` - 检查是否可以转换
- `IsInState` - 检查是否在指定状态
- `WasInState` - 检查是否曾在指定状态
- `GetStateDuration` - 获取状态持续时间
- `EvaluateTransitions` - 评估转换条件
- `ResetStateMachine` - 重置状态机

View File

@@ -0,0 +1,230 @@
---
title: "实际示例"
description: "角色状态机、ECS 集成"
---
## 角色状态机
```typescript
import { createStateMachine } from '@esengine/fsm';
type CharacterState = 'idle' | 'walk' | 'run' | 'jump' | 'fall' | 'attack';
interface CharacterContext {
velocity: { x: number; y: number };
isGrounded: boolean;
isAttacking: boolean;
speed: number;
}
const characterFSM = createStateMachine<CharacterState, CharacterContext>('idle', {
context: {
velocity: { x: 0, y: 0 },
isGrounded: true,
isAttacking: false,
speed: 0
}
});
// 定义状态
characterFSM.defineState('idle', {
onEnter: (ctx) => {
ctx.speed = 0;
},
onUpdate: (ctx, dt) => {
// 播放待机动画
}
});
characterFSM.defineState('walk', {
onEnter: (ctx) => {
ctx.speed = 100;
}
});
characterFSM.defineState('run', {
onEnter: (ctx) => {
ctx.speed = 200;
}
});
characterFSM.defineState('jump', {
onEnter: (ctx) => {
ctx.velocity.y = -300;
ctx.isGrounded = false;
}
});
// 定义转换
characterFSM.defineTransition('idle', 'walk', (ctx) => Math.abs(ctx.velocity.x) > 0);
characterFSM.defineTransition('walk', 'idle', (ctx) => ctx.velocity.x === 0);
characterFSM.defineTransition('walk', 'run', (ctx) => Math.abs(ctx.velocity.x) > 150);
characterFSM.defineTransition('run', 'walk', (ctx) => Math.abs(ctx.velocity.x) <= 150);
// 跳跃有最高优先级
characterFSM.defineTransition('idle', 'jump', (ctx) => !ctx.isGrounded, 10);
characterFSM.defineTransition('walk', 'jump', (ctx) => !ctx.isGrounded, 10);
characterFSM.defineTransition('run', 'jump', (ctx) => !ctx.isGrounded, 10);
characterFSM.defineTransition('jump', 'fall', (ctx) => ctx.velocity.y > 0);
characterFSM.defineTransition('fall', 'idle', (ctx) => ctx.isGrounded);
// 游戏循环中使用
function gameUpdate(dt: number) {
// 更新上下文
characterFSM.context.velocity.x = getInputVelocity();
characterFSM.context.isGrounded = checkGrounded();
// 评估状态转换
characterFSM.evaluateTransitions();
// 更新当前状态
characterFSM.update(dt);
}
```
## 与 ECS 集成
```typescript
import { Component, EntitySystem, Matcher } from '@esengine/ecs-framework';
import { createStateMachine, type IStateMachine } from '@esengine/fsm';
// 状态机组件
class FSMComponent extends Component {
fsm: IStateMachine<string>;
constructor(initialState: string) {
super();
this.fsm = createStateMachine(initialState);
}
}
// 状态机系统
class FSMSystem extends EntitySystem {
constructor() {
super(Matcher.all(FSMComponent));
}
protected processEntity(entity: Entity, dt: number): void {
const fsmComp = entity.getComponent(FSMComponent);
fsmComp.fsm.evaluateTransitions();
fsmComp.fsm.update(dt);
}
}
```
## AI 行为状态机
```typescript
type AIState = 'patrol' | 'chase' | 'attack' | 'flee' | 'dead';
interface AIContext {
health: number;
target: Entity | null;
distanceToTarget: number;
attackRange: number;
sightRange: number;
}
const aiFSM = createStateMachine<AIState, AIContext>('patrol', {
context: {
health: 100,
target: null,
distanceToTarget: Infinity,
attackRange: 50,
sightRange: 200
}
});
// 巡逻状态
aiFSM.defineState('patrol', {
onEnter: () => console.log('开始巡逻'),
onUpdate: (ctx, dt) => {
// 沿巡逻路径移动
}
});
// 追击状态
aiFSM.defineState('chase', {
onEnter: () => console.log('发现目标,开始追击'),
onUpdate: (ctx, dt) => {
// 向目标移动
}
});
// 攻击状态
aiFSM.defineState('attack', {
onEnter: () => console.log('进入攻击范围'),
onUpdate: (ctx, dt) => {
// 执行攻击
}
});
// 逃跑状态
aiFSM.defineState('flee', {
onEnter: () => console.log('血量过低,逃跑'),
onUpdate: (ctx, dt) => {
// 远离目标
}
});
// 转换规则
aiFSM.defineTransition('patrol', 'chase',
(ctx) => ctx.target !== null && ctx.distanceToTarget < ctx.sightRange);
aiFSM.defineTransition('chase', 'attack',
(ctx) => ctx.distanceToTarget < ctx.attackRange);
aiFSM.defineTransition('attack', 'chase',
(ctx) => ctx.distanceToTarget >= ctx.attackRange);
aiFSM.defineTransition('chase', 'patrol',
(ctx) => ctx.target === null || ctx.distanceToTarget > ctx.sightRange);
// 逃跑优先级最高
aiFSM.defineTransition('patrol', 'flee', (ctx) => ctx.health < 20, 100);
aiFSM.defineTransition('chase', 'flee', (ctx) => ctx.health < 20, 100);
aiFSM.defineTransition('attack', 'flee', (ctx) => ctx.health < 20, 100);
aiFSM.defineTransition('flee', 'patrol', (ctx) => ctx.health >= 20);
```
## 动画状态机
```typescript
type AnimState = 'idle' | 'walk' | 'run' | 'jump_up' | 'jump_down' | 'land';
interface AnimContext {
animator: Animator;
velocityX: number;
velocityY: number;
isGrounded: boolean;
}
const animFSM = createStateMachine<AnimState, AnimContext>('idle', {
context: { animator: null!, velocityX: 0, velocityY: 0, isGrounded: true }
});
animFSM.defineState('idle', {
onEnter: (ctx) => ctx.animator.play('idle')
});
animFSM.defineState('walk', {
onEnter: (ctx) => ctx.animator.play('walk')
});
animFSM.defineState('run', {
onEnter: (ctx) => ctx.animator.play('run')
});
animFSM.defineState('jump_up', {
onEnter: (ctx) => ctx.animator.play('jump_up')
});
animFSM.defineState('jump_down', {
onEnter: (ctx) => ctx.animator.play('jump_down')
});
animFSM.defineState('land', {
onEnter: (ctx) => ctx.animator.play('land')
});
// 设置转换(略)
```

View File

@@ -1,5 +1,6 @@
---
title: "状态机 (FSM)"
description: "类型安全的有限状态机实现"
---
`@esengine/fsm` 提供了一个类型安全的有限状态机实现用于角色、AI 或任何需要状态管理的场景。
@@ -91,249 +92,7 @@ fsm.defineTransition('idle', 'walk', (ctx) => ctx.isMoving, 1);
// 如果同时满足,会先尝试 attack优先级 10
```
## API 参考
## 文档导航
### createStateMachine
```typescript
function createStateMachine<TState extends string, TContext = unknown>(
initialState: TState,
options?: StateMachineOptions<TContext>
): IStateMachine<TState, TContext>
```
**参数:**
- `initialState` - 初始状态
- `options.context` - 上下文对象,在回调中可访问
- `options.maxHistorySize` - 最大历史记录数(默认 100
- `options.enableHistory` - 是否启用历史记录(默认 true
### 状态机属性
| 属性 | 类型 | 描述 |
|------|------|------|
| `current` | `TState` | 当前状态 |
| `previous` | `TState \| null` | 上一个状态 |
| `context` | `TContext` | 上下文对象 |
| `isTransitioning` | `boolean` | 是否正在转换中 |
| `currentStateDuration` | `number` | 当前状态持续时间(毫秒) |
### 状态机方法
#### 状态定义
```typescript
// 定义状态
fsm.defineState('idle', {
onEnter: (ctx, from) => {},
onExit: (ctx, to) => {},
onUpdate: (ctx, dt) => {}
});
// 检查状态是否存在
fsm.hasState('idle'); // true
// 获取状态配置
fsm.getStateConfig('idle');
// 获取所有状态
fsm.getStates(); // ['idle', 'walk', ...]
```
#### 转换操作
```typescript
// 定义转换
fsm.defineTransition('idle', 'walk', condition, priority);
// 移除转换
fsm.removeTransition('idle', 'walk');
// 获取从某状态出发的所有转换
fsm.getTransitionsFrom('idle');
// 检查是否可以转换
fsm.canTransition('walk'); // true/false
// 手动转换
fsm.transition('walk');
// 强制转换(忽略条件)
fsm.transition('walk', true);
// 自动评估转换条件
fsm.evaluateTransitions();
```
#### 生命周期
```typescript
// 更新状态机(调用当前状态的 onUpdate
fsm.update(deltaTime);
// 重置状态机
fsm.reset(); // 重置到当前状态
fsm.reset('idle'); // 重置到指定状态
```
#### 事件监听
```typescript
// 监听进入特定状态
const unsubscribe = fsm.onEnter('walk', (from) => {
console.log(`${from} 进入 walk`);
});
// 监听退出特定状态
fsm.onExit('walk', (to) => {
console.log(`从 walk 退出到 ${to}`);
});
// 监听任意状态变化
fsm.onChange((event) => {
console.log(`${event.from} -> ${event.to} at ${event.timestamp}`);
});
// 取消订阅
unsubscribe();
```
#### 调试
```typescript
// 获取状态历史
const history = fsm.getHistory();
// [{ from: 'idle', to: 'walk', timestamp: 1234567890 }, ...]
// 清除历史
fsm.clearHistory();
// 获取调试信息
const info = fsm.getDebugInfo();
// { current, previous, duration, stateCount, transitionCount, historySize }
```
## 实际示例
### 角色状态机
```typescript
import { createStateMachine } from '@esengine/fsm';
type CharacterState = 'idle' | 'walk' | 'run' | 'jump' | 'fall' | 'attack';
interface CharacterContext {
velocity: { x: number; y: number };
isGrounded: boolean;
isAttacking: boolean;
speed: number;
}
const characterFSM = createStateMachine<CharacterState, CharacterContext>('idle', {
context: {
velocity: { x: 0, y: 0 },
isGrounded: true,
isAttacking: false,
speed: 0
}
});
// 定义状态
characterFSM.defineState('idle', {
onEnter: (ctx) => {
ctx.speed = 0;
},
onUpdate: (ctx, dt) => {
// 播放待机动画
}
});
characterFSM.defineState('walk', {
onEnter: (ctx) => {
ctx.speed = 100;
}
});
characterFSM.defineState('run', {
onEnter: (ctx) => {
ctx.speed = 200;
}
});
characterFSM.defineState('jump', {
onEnter: (ctx) => {
ctx.velocity.y = -300;
ctx.isGrounded = false;
}
});
// 定义转换
characterFSM.defineTransition('idle', 'walk', (ctx) => Math.abs(ctx.velocity.x) > 0);
characterFSM.defineTransition('walk', 'idle', (ctx) => ctx.velocity.x === 0);
characterFSM.defineTransition('walk', 'run', (ctx) => Math.abs(ctx.velocity.x) > 150);
characterFSM.defineTransition('run', 'walk', (ctx) => Math.abs(ctx.velocity.x) <= 150);
// 跳跃有最高优先级
characterFSM.defineTransition('idle', 'jump', (ctx) => !ctx.isGrounded, 10);
characterFSM.defineTransition('walk', 'jump', (ctx) => !ctx.isGrounded, 10);
characterFSM.defineTransition('run', 'jump', (ctx) => !ctx.isGrounded, 10);
characterFSM.defineTransition('jump', 'fall', (ctx) => ctx.velocity.y > 0);
characterFSM.defineTransition('fall', 'idle', (ctx) => ctx.isGrounded);
// 游戏循环中使用
function gameUpdate(dt: number) {
// 更新上下文
characterFSM.context.velocity.x = getInputVelocity();
characterFSM.context.isGrounded = checkGrounded();
// 评估状态转换
characterFSM.evaluateTransitions();
// 更新当前状态
characterFSM.update(dt);
}
```
### 与 ECS 集成
```typescript
import { Component, EntitySystem, Matcher } from '@esengine/ecs-framework';
import { createStateMachine, type IStateMachine } from '@esengine/fsm';
// 状态机组件
class FSMComponent extends Component {
fsm: IStateMachine<string>;
constructor(initialState: string) {
super();
this.fsm = createStateMachine(initialState);
}
}
// 状态机系统
class FSMSystem extends EntitySystem {
constructor() {
super(Matcher.all(FSMComponent));
}
protected processEntity(entity: Entity, dt: number): void {
const fsmComp = entity.getComponent(FSMComponent);
fsmComp.fsm.evaluateTransitions();
fsmComp.fsm.update(dt);
}
}
```
## 蓝图节点
FSM 模块提供了可视化脚本支持的蓝图节点:
- `GetCurrentState` - 获取当前状态
- `TransitionTo` - 转换到指定状态
- `CanTransition` - 检查是否可以转换
- `IsInState` - 检查是否在指定状态
- `WasInState` - 检查是否曾在指定状态
- `GetStateDuration` - 获取状态持续时间
- `EvaluateTransitions` - 评估转换条件
- `ResetStateMachine` - 重置状态机
- [API 参考](./api) - 完整的状态机 API
- [实际示例](./examples) - 角色状态机、ECS 集成

View File

@@ -0,0 +1,164 @@
---
title: "实际示例"
description: "游戏移动、动态障碍物、分层寻路"
---
## 游戏角色移动
```typescript
class MovementSystem {
private grid: GridMap;
private pathfinder: AStarPathfinder;
private smoother: CombinedSmoother;
constructor(width: number, height: number) {
this.grid = createGridMap(width, height);
this.pathfinder = createAStarPathfinder(this.grid);
this.smoother = createCombinedSmoother();
}
findPath(from: IPoint, to: IPoint): IPoint[] | null {
const result = this.pathfinder.findPath(
from.x, from.y,
to.x, to.y
);
if (!result.found) {
return null;
}
// 平滑路径
return this.smoother.smooth(result.path, this.grid);
}
setObstacle(x: number, y: number): void {
this.grid.setWalkable(x, y, false);
}
setTerrain(x: number, y: number, cost: number): void {
this.grid.setCost(x, y, cost);
}
}
```
## 动态障碍物
```typescript
class DynamicPathfinding {
private grid: GridMap;
private pathfinder: AStarPathfinder;
private dynamicObstacles: Set<string> = new Set();
addDynamicObstacle(x: number, y: number): void {
const key = `${x},${y}`;
if (!this.dynamicObstacles.has(key)) {
this.dynamicObstacles.add(key);
this.grid.setWalkable(x, y, false);
}
}
removeDynamicObstacle(x: number, y: number): void {
const key = `${x},${y}`;
if (this.dynamicObstacles.has(key)) {
this.dynamicObstacles.delete(key);
this.grid.setWalkable(x, y, true);
}
}
findPath(from: IPoint, to: IPoint): IPathResult {
return this.pathfinder.findPath(from.x, from.y, to.x, to.y);
}
}
```
## 不同地形代价
```typescript
// 设置不同地形的移动代价
const grid = createGridMap(50, 50);
// 普通地面 - 代价 1默认
// 沙地 - 代价 2
for (let y = 10; y < 20; y++) {
for (let x = 0; x < 50; x++) {
grid.setCost(x, y, 2);
}
}
// 沼泽 - 代价 4
for (let y = 30; y < 35; y++) {
for (let x = 20; x < 30; x++) {
grid.setCost(x, y, 4);
}
}
// 寻路时会自动考虑地形代价
const result = pathfinder.findPath(0, 0, 49, 49);
```
## 分层寻路
对于大型地图,使用层级化寻路:
```typescript
class HierarchicalPathfinding {
private coarseGrid: GridMap; // 粗粒度网格
private fineGrid: GridMap; // 细粒度网格
private coarsePathfinder: AStarPathfinder;
private finePathfinder: AStarPathfinder;
private cellSize = 10;
findPath(from: IPoint, to: IPoint): IPoint[] {
// 1. 在粗粒度网格上寻路
const coarseFrom = this.toCoarse(from);
const coarseTo = this.toCoarse(to);
const coarseResult = this.coarsePathfinder.findPath(
coarseFrom.x, coarseFrom.y,
coarseTo.x, coarseTo.y
);
if (!coarseResult.found) {
return [];
}
// 2. 在每个粗粒度单元内进行细粒度寻路
const finePath: IPoint[] = [];
// ... 详细实现略
return finePath;
}
private toCoarse(p: IPoint): IPoint {
return {
x: Math.floor(p.x / this.cellSize),
y: Math.floor(p.y / this.cellSize)
};
}
}
```
## 性能优化
1. **限制搜索范围**
```typescript
pathfinder.findPath(x1, y1, x2, y2, { maxNodes: 1000 });
```
2. **使用启发式权重**
```typescript
// 权重 > 1 会更快但可能不是最优路径
pathfinder.findPath(x1, y1, x2, y2, { heuristicWeight: 1.5 });
```
3. **复用寻路器实例**
```typescript
// 创建一次,多次使用
const pathfinder = createAStarPathfinder(grid);
```
4. **使用导航网格**
- 对于复杂地形NavMesh 比网格寻路更高效
- 多边形数量远少于网格单元格数量
5. **选择合适的启发式**
- 4方向移动用 `manhattanDistance`
- 8方向移动用 `octileDistance`(默认)

View File

@@ -0,0 +1,112 @@
---
title: "网格地图 API"
description: "网格操作和 A* 寻路"
---
## createGridMap
```typescript
function createGridMap(
width: number,
height: number,
options?: IGridMapOptions
): GridMap
```
**配置选项:**
| 属性 | 类型 | 默认值 | 描述 |
|------|------|--------|------|
| `allowDiagonal` | `boolean` | `true` | 允许对角移动 |
| `diagonalCost` | `number` | `√2` | 对角移动代价 |
| `avoidCorners` | `boolean` | `true` | 避免穿角 |
| `heuristic` | `HeuristicFunction` | `octileDistance` | 启发式函数 |
## 地图操作
```typescript
// 检查/设置可通行性
grid.isWalkable(x, y);
grid.setWalkable(x, y, false);
// 设置移动代价(如沼泽、沙地)
grid.setCost(x, y, 2); // 代价为 2默认 1
// 设置矩形区域
grid.setRectWalkable(0, 0, 5, 5, false);
// 从数组加载0=可通行非0=障碍)
grid.loadFromArray([
[0, 0, 0, 1, 0],
[0, 1, 0, 1, 0],
[0, 1, 0, 0, 0]
]);
// 从字符串加载(.=可通行,#=障碍)
grid.loadFromString(`
.....
.#.#.
.#...
`);
// 导出为字符串
console.log(grid.toString());
// 重置所有节点为可通行
grid.reset();
```
## A* 寻路器
### createAStarPathfinder
```typescript
function createAStarPathfinder(map: IPathfindingMap): AStarPathfinder
```
### findPath
```typescript
const result = pathfinder.findPath(
startX, startY,
endX, endY,
{
maxNodes: 5000, // 限制搜索节点数
heuristicWeight: 1.5 // 加速但可能非最优
}
);
```
### 重用寻路器
```typescript
// 寻路器可重用,内部会自动清理状态
pathfinder.findPath(0, 0, 10, 10);
pathfinder.findPath(5, 5, 15, 15);
// 手动清理(可选)
pathfinder.clear();
```
## 方向常量
```typescript
import { DIRECTIONS_4, DIRECTIONS_8 } from '@esengine/pathfinding';
// 4方向上下左右
DIRECTIONS_4 // [{ dx: 0, dy: -1 }, { dx: 1, dy: 0 }, ...]
// 8方向含对角线
DIRECTIONS_8 // [{ dx: 0, dy: -1 }, { dx: 1, dy: -1 }, ...]
```
## 启发式函数
```typescript
import { manhattanDistance, octileDistance } from '@esengine/pathfinding';
// 自定义启发式
const grid = createGridMap(20, 20, {
heuristic: manhattanDistance // 使用曼哈顿距离
});
```

View File

@@ -1,5 +1,6 @@
---
title: "寻路系统 (Pathfinding)"
description: "完整的 2D 寻路解决方案"
---
`@esengine/pathfinding` 提供了完整的 2D 寻路解决方案,包括 A* 算法、网格地图、导航网格和路径平滑。
@@ -67,29 +68,21 @@ const result = navmesh.findPath(1, 1, 18, 8);
## 核心概念
### IPoint - 坐标点
### 核心接口
```typescript
interface IPoint {
readonly x: number;
readonly y: number;
}
```
### IPathResult - 寻路结果
```typescript
interface IPathResult {
readonly found: boolean; // 是否找到路径
readonly path: readonly IPoint[]; // 路径点列表
readonly cost: number; // 路径总代价
readonly nodesSearched: number; // 搜索的节点数
}
```
### IPathfindingOptions - 寻路配置
```typescript
interface IPathfindingOptions {
maxNodes?: number; // 最大搜索节点数(默认 10000
heuristicWeight?: number; // 启发式权重(>1 更快但可能非最优)
@@ -98,402 +91,16 @@ interface IPathfindingOptions {
}
```
## 启发式函数
模块提供了四种启发式函数:
### 启发式函数
| 函数 | 适用场景 | 说明 |
|------|----------|------|
| `manhattanDistance` | 4方向移动 | 曼哈顿距离,只考虑水平/垂直 |
| `euclideanDistance` | 任意方向 | 欧几里得距离,直线距离 |
| `chebyshevDistance` | 8方向移动 | 切比雪夫距离,对角线代价为 1 |
| `octileDistance` | 8方向移动 | 八角距离,对角线代价为 √2(默认) |
| `manhattanDistance` | 4方向移动 | 曼哈顿距离 |
| `euclideanDistance` | 任意方向 | 欧几里得距离 |
| `chebyshevDistance` | 8方向移动 | 切比雪夫距离 |
| `octileDistance` | 8方向移动 | 八角距离(默认) |
```typescript
import { manhattanDistance, octileDistance } from '@esengine/pathfinding';
// 自定义启发式
const grid = createGridMap(20, 20, {
heuristic: manhattanDistance // 使用曼哈顿距离
});
```
## 网格地图 API
### createGridMap
```typescript
function createGridMap(
width: number,
height: number,
options?: IGridMapOptions
): GridMap
```
**配置选项:**
| 属性 | 类型 | 默认值 | 描述 |
|------|------|--------|------|
| `allowDiagonal` | `boolean` | `true` | 允许对角移动 |
| `diagonalCost` | `number` | `√2` | 对角移动代价 |
| `avoidCorners` | `boolean` | `true` | 避免穿角 |
| `heuristic` | `HeuristicFunction` | `octileDistance` | 启发式函数 |
### 地图操作
```typescript
// 检查/设置可通行性
grid.isWalkable(x, y);
grid.setWalkable(x, y, false);
// 设置移动代价(如沼泽、沙地)
grid.setCost(x, y, 2); // 代价为 2默认 1
// 设置矩形区域
grid.setRectWalkable(0, 0, 5, 5, false);
// 从数组加载0=可通行非0=障碍)
grid.loadFromArray([
[0, 0, 0, 1, 0],
[0, 1, 0, 1, 0],
[0, 1, 0, 0, 0]
]);
// 从字符串加载(.=可通行,#=障碍)
grid.loadFromString(`
.....
.#.#.
.#...
`);
// 导出为字符串
console.log(grid.toString());
// 重置所有节点为可通行
grid.reset();
```
### 方向常量
```typescript
import { DIRECTIONS_4, DIRECTIONS_8 } from '@esengine/pathfinding';
// 4方向上下左右
DIRECTIONS_4 // [{ dx: 0, dy: -1 }, { dx: 1, dy: 0 }, ...]
// 8方向含对角线
DIRECTIONS_8 // [{ dx: 0, dy: -1 }, { dx: 1, dy: -1 }, ...]
```
## A* 寻路器 API
### createAStarPathfinder
```typescript
function createAStarPathfinder(map: IPathfindingMap): AStarPathfinder
```
### findPath
```typescript
const result = pathfinder.findPath(
startX, startY,
endX, endY,
{
maxNodes: 5000, // 限制搜索节点数
heuristicWeight: 1.5 // 加速但可能非最优
}
);
```
### 重用寻路器
```typescript
// 寻路器可重用,内部会自动清理状态
pathfinder.findPath(0, 0, 10, 10);
pathfinder.findPath(5, 5, 15, 15);
// 手动清理(可选)
pathfinder.clear();
```
## 导航网格 API
### createNavMesh
```typescript
function createNavMesh(): NavMesh
```
### 构建导航网格
```typescript
const navmesh = createNavMesh();
// 添加凸多边形
const id1 = navmesh.addPolygon([
{ x: 0, y: 0 }, { x: 10, y: 0 },
{ x: 10, y: 10 }, { x: 0, y: 10 }
]);
const id2 = navmesh.addPolygon([
{ x: 10, y: 0 }, { x: 20, y: 0 },
{ x: 20, y: 10 }, { x: 10, y: 10 }
]);
// 方式1自动检测共享边并建立连接
navmesh.build();
// 方式2手动设置连接
navmesh.setConnection(id1, id2, {
left: { x: 10, y: 0 },
right: { x: 10, y: 10 }
});
```
### 查询和寻路
```typescript
// 查找包含点的多边形
const polygon = navmesh.findPolygonAt(5, 5);
// 检查位置是否可通行
navmesh.isWalkable(5, 5);
// 寻路(内部使用漏斗算法优化路径)
const result = navmesh.findPath(1, 1, 18, 8);
```
## 路径平滑 API
### 视线简化
移除不必要的中间点:
```typescript
import { createLineOfSightSmoother } from '@esengine/pathfinding';
const smoother = createLineOfSightSmoother();
const smoothedPath = smoother.smooth(result.path, grid);
// 原路径: [(0,0), (1,1), (2,2), (3,3), (4,4)]
// 简化后: [(0,0), (4,4)]
```
### 曲线平滑
使用 Catmull-Rom 样条曲线:
```typescript
import { createCatmullRomSmoother } from '@esengine/pathfinding';
const smoother = createCatmullRomSmoother(
5, // segments - 每段插值点数
0.5 // tension - 张力 (0-1)
);
const curvedPath = smoother.smooth(result.path, grid);
```
### 组合平滑
先简化再曲线平滑:
```typescript
import { createCombinedSmoother } from '@esengine/pathfinding';
const smoother = createCombinedSmoother(5, 0.5);
const finalPath = smoother.smooth(result.path, grid);
```
### 视线检测函数
```typescript
import { bresenhamLineOfSight, raycastLineOfSight } from '@esengine/pathfinding';
// Bresenham 算法(快速,网格对齐)
const hasLOS = bresenhamLineOfSight(x1, y1, x2, y2, grid);
// 射线投射(精确,支持浮点坐标)
const hasLOS = raycastLineOfSight(x1, y1, x2, y2, grid, 0.5);
```
## 实际示例
### 游戏角色移动
```typescript
class MovementSystem {
private grid: GridMap;
private pathfinder: AStarPathfinder;
private smoother: CombinedSmoother;
constructor(width: number, height: number) {
this.grid = createGridMap(width, height);
this.pathfinder = createAStarPathfinder(this.grid);
this.smoother = createCombinedSmoother();
}
findPath(from: IPoint, to: IPoint): IPoint[] | null {
const result = this.pathfinder.findPath(
from.x, from.y,
to.x, to.y
);
if (!result.found) {
return null;
}
// 平滑路径
return this.smoother.smooth(result.path, this.grid);
}
setObstacle(x: number, y: number): void {
this.grid.setWalkable(x, y, false);
}
setTerrain(x: number, y: number, cost: number): void {
this.grid.setCost(x, y, cost);
}
}
```
### 动态障碍物
```typescript
class DynamicPathfinding {
private grid: GridMap;
private pathfinder: AStarPathfinder;
private dynamicObstacles: Set<string> = new Set();
addDynamicObstacle(x: number, y: number): void {
const key = `${x},${y}`;
if (!this.dynamicObstacles.has(key)) {
this.dynamicObstacles.add(key);
this.grid.setWalkable(x, y, false);
}
}
removeDynamicObstacle(x: number, y: number): void {
const key = `${x},${y}`;
if (this.dynamicObstacles.has(key)) {
this.dynamicObstacles.delete(key);
this.grid.setWalkable(x, y, true);
}
}
findPath(from: IPoint, to: IPoint): IPathResult {
return this.pathfinder.findPath(from.x, from.y, to.x, to.y);
}
}
```
### 不同地形代价
```typescript
// 设置不同地形的移动代价
const grid = createGridMap(50, 50);
// 普通地面 - 代价 1默认
// 沙地 - 代价 2
for (let y = 10; y < 20; y++) {
for (let x = 0; x < 50; x++) {
grid.setCost(x, y, 2);
}
}
// 沼泽 - 代价 4
for (let y = 30; y < 35; y++) {
for (let x = 20; x < 30; x++) {
grid.setCost(x, y, 4);
}
}
// 寻路时会自动考虑地形代价
const result = pathfinder.findPath(0, 0, 49, 49);
```
### 分层寻路
对于大型地图,使用层级化寻路:
```typescript
class HierarchicalPathfinding {
private coarseGrid: GridMap; // 粗粒度网格
private fineGrid: GridMap; // 细粒度网格
private coarsePathfinder: AStarPathfinder;
private finePathfinder: AStarPathfinder;
private cellSize = 10;
findPath(from: IPoint, to: IPoint): IPoint[] {
// 1. 在粗粒度网格上寻路
const coarseFrom = this.toCoarse(from);
const coarseTo = this.toCoarse(to);
const coarseResult = this.coarsePathfinder.findPath(
coarseFrom.x, coarseFrom.y,
coarseTo.x, coarseTo.y
);
if (!coarseResult.found) {
return [];
}
// 2. 在每个粗粒度单元内进行细粒度寻路
const finePath: IPoint[] = [];
// ... 详细实现略
return finePath;
}
private toCoarse(p: IPoint): IPoint {
return {
x: Math.floor(p.x / this.cellSize),
y: Math.floor(p.y / this.cellSize)
};
}
}
```
## 蓝图节点
Pathfinding 模块提供了可视化脚本支持的蓝图节点:
- `FindPath` - 查找路径
- `FindPathSmooth` - 查找并平滑路径
- `IsWalkable` - 检查位置是否可通行
- `GetPathLength` - 获取路径点数
- `GetPathDistance` - 获取路径总距离
- `GetPathPoint` - 获取路径上的指定点
- `MoveAlongPath` - 沿路径移动
- `HasLineOfSight` - 检查视线
## 性能优化
1. **限制搜索范围**
```typescript
pathfinder.findPath(x1, y1, x2, y2, { maxNodes: 1000 });
```
2. **使用启发式权重**
```typescript
// 权重 > 1 会更快但可能不是最优路径
pathfinder.findPath(x1, y1, x2, y2, { heuristicWeight: 1.5 });
```
3. **复用寻路器实例**
```typescript
// 创建一次,多次使用
const pathfinder = createAStarPathfinder(grid);
```
4. **使用导航网格**
- 对于复杂地形NavMesh 比网格寻路更高效
- 多边形数量远少于网格单元格数量
5. **选择合适的启发式**
- 4方向移动用 `manhattanDistance`
- 8方向移动用 `octileDistance`(默认)
## 网格 vs 导航网格
### 网格 vs 导航网格
| 特性 | GridMap | NavMesh |
|------|---------|---------|
@@ -501,4 +108,10 @@ Pathfinding 模块提供了可视化脚本支持的蓝图节点:
| 内存占用 | 较高 (width × height) | 较低 (多边形数) |
| 精度 | 网格对齐 | 连续坐标 |
| 动态修改 | 容易 | 需要重建 |
| 设置复杂度 | 简单 | 较复杂 |
## 文档导航
- [网格地图 API](./grid-map) - 网格操作和 A* 寻路
- [导航网格 API](./navmesh) - NavMesh 构建和查询
- [路径平滑](./smoothing) - 视线简化和曲线平滑
- [实际示例](./examples) - 游戏移动、动态障碍物、分层寻路

View File

@@ -0,0 +1,67 @@
---
title: "导航网格 API"
description: "NavMesh 构建和查询"
---
## createNavMesh
```typescript
function createNavMesh(): NavMesh
```
## 构建导航网格
```typescript
const navmesh = createNavMesh();
// 添加凸多边形
const id1 = navmesh.addPolygon([
{ x: 0, y: 0 }, { x: 10, y: 0 },
{ x: 10, y: 10 }, { x: 0, y: 10 }
]);
const id2 = navmesh.addPolygon([
{ x: 10, y: 0 }, { x: 20, y: 0 },
{ x: 20, y: 10 }, { x: 10, y: 10 }
]);
// 方式1自动检测共享边并建立连接
navmesh.build();
// 方式2手动设置连接
navmesh.setConnection(id1, id2, {
left: { x: 10, y: 0 },
right: { x: 10, y: 10 }
});
```
## 查询和寻路
```typescript
// 查找包含点的多边形
const polygon = navmesh.findPolygonAt(5, 5);
// 检查位置是否可通行
navmesh.isWalkable(5, 5);
// 寻路(内部使用漏斗算法优化路径)
const result = navmesh.findPath(1, 1, 18, 8);
```
## 使用场景
导航网格适合:
- 复杂不规则地形
- 需要精确路径的场景
- 多边形数量远少于网格单元格的大地图
```typescript
// 从编辑器导出的导航网格数据
const navData = await loadNavMeshData('level1.navmesh');
const navmesh = createNavMesh();
for (const poly of navData.polygons) {
navmesh.addPolygon(poly.vertices);
}
navmesh.build();
```

View File

@@ -0,0 +1,67 @@
---
title: "路径平滑"
description: "视线简化和曲线平滑"
---
## 视线简化
移除不必要的中间点:
```typescript
import { createLineOfSightSmoother } from '@esengine/pathfinding';
const smoother = createLineOfSightSmoother();
const smoothedPath = smoother.smooth(result.path, grid);
// 原路径: [(0,0), (1,1), (2,2), (3,3), (4,4)]
// 简化后: [(0,0), (4,4)]
```
## 曲线平滑
使用 Catmull-Rom 样条曲线:
```typescript
import { createCatmullRomSmoother } from '@esengine/pathfinding';
const smoother = createCatmullRomSmoother(
5, // segments - 每段插值点数
0.5 // tension - 张力 (0-1)
);
const curvedPath = smoother.smooth(result.path, grid);
```
## 组合平滑
先简化再曲线平滑:
```typescript
import { createCombinedSmoother } from '@esengine/pathfinding';
const smoother = createCombinedSmoother(5, 0.5);
const finalPath = smoother.smooth(result.path, grid);
```
## 视线检测函数
```typescript
import { bresenhamLineOfSight, raycastLineOfSight } from '@esengine/pathfinding';
// Bresenham 算法(快速,网格对齐)
const hasLOS = bresenhamLineOfSight(x1, y1, x2, y2, grid);
// 射线投射(精确,支持浮点坐标)
const hasLOS = raycastLineOfSight(x1, y1, x2, y2, grid, 0.5);
```
## 蓝图节点
- `FindPath` - 查找路径
- `FindPathSmooth` - 查找并平滑路径
- `IsWalkable` - 检查位置是否可通行
- `GetPathLength` - 获取路径点数
- `GetPathDistance` - 获取路径总距离
- `GetPathPoint` - 获取路径上的指定点
- `MoveAlongPath` - 沿路径移动
- `HasLineOfSight` - 检查视线

View File

@@ -0,0 +1,230 @@
---
title: "实际示例"
description: "地形、战利品、敌人和关卡生成"
---
## 程序化地形生成
```typescript
import { createPerlinNoise, createFBM } from '@esengine/procgen';
class TerrainGenerator {
private fbm: FBM;
private moistureFbm: FBM;
constructor(seed: number) {
const heightNoise = createPerlinNoise(seed);
const moistureNoise = createPerlinNoise(seed + 1000);
this.fbm = createFBM(heightNoise, {
octaves: 8,
persistence: 0.5,
frequency: 0.01
});
this.moistureFbm = createFBM(moistureNoise, {
octaves: 4,
persistence: 0.6,
frequency: 0.02
});
}
getHeight(x: number, y: number): number {
// 基础高度
let height = this.fbm.noise2D(x, y);
// 添加山脉
height += this.fbm.ridged2D(x * 0.5, y * 0.5) * 0.3;
return (height + 1) * 0.5; // 归一化到 [0, 1]
}
getBiome(x: number, y: number): string {
const height = this.getHeight(x, y);
const moisture = (this.moistureFbm.noise2D(x, y) + 1) * 0.5;
if (height < 0.3) return 'water';
if (height < 0.4) return 'beach';
if (height > 0.8) return 'mountain';
if (moisture < 0.3) return 'desert';
if (moisture > 0.7) return 'forest';
return 'grassland';
}
}
```
## 战利品系统
```typescript
import { createSeededRandom, createWeightedRandom, pickOne } from '@esengine/procgen';
interface LootItem {
id: string;
rarity: string;
}
class LootSystem {
private rng: SeededRandom;
private raritySelector: WeightedRandom<string>;
private lootTables: Map<string, LootItem[]> = new Map();
constructor(seed: number) {
this.rng = createSeededRandom(seed);
this.raritySelector = createWeightedRandom([
{ value: 'common', weight: 60 },
{ value: 'uncommon', weight: 25 },
{ value: 'rare', weight: 10 },
{ value: 'legendary', weight: 5 }
]);
// 初始化战利品表
this.lootTables.set('common', [/* ... */]);
this.lootTables.set('rare', [/* ... */]);
}
generateLoot(count: number): LootItem[] {
const loot: LootItem[] = [];
for (let i = 0; i < count; i++) {
const rarity = this.raritySelector.pick(this.rng);
const table = this.lootTables.get(rarity)!;
const item = pickOne(table, this.rng);
loot.push(item);
}
return loot;
}
// 保证可重现
setSeed(seed: number): void {
this.rng = createSeededRandom(seed);
}
}
```
## 程序化敌人放置
```typescript
import { createSeededRandom } from '@esengine/procgen';
class EnemySpawner {
private rng: SeededRandom;
constructor(seed: number) {
this.rng = createSeededRandom(seed);
}
spawnEnemiesInArea(
centerX: number,
centerY: number,
radius: number,
count: number
): Array<{ x: number; y: number; type: string }> {
const enemies: Array<{ x: number; y: number; type: string }> = [];
for (let i = 0; i < count; i++) {
// 在圆内生成位置
const pos = this.rng.nextPointInCircle(radius);
// 随机选择敌人类型
const type = this.rng.nextBool(0.2) ? 'elite' : 'normal';
enemies.push({
x: centerX + pos.x,
y: centerY + pos.y,
type
});
}
return enemies;
}
}
```
## 程序化关卡布局
```typescript
import { createSeededRandom, shuffle } from '@esengine/procgen';
interface Room {
x: number;
y: number;
width: number;
height: number;
type: 'start' | 'combat' | 'treasure' | 'boss';
}
class DungeonGenerator {
private rng: SeededRandom;
constructor(seed: number) {
this.rng = createSeededRandom(seed);
}
generate(roomCount: number): Room[] {
const rooms: Room[] = [];
// 生成房间
for (let i = 0; i < roomCount; i++) {
rooms.push({
x: this.rng.nextInt(0, 100),
y: this.rng.nextInt(0, 100),
width: this.rng.nextInt(5, 15),
height: this.rng.nextInt(5, 15),
type: 'combat'
});
}
// 随机分配特殊房间
shuffle(rooms, this.rng);
rooms[0].type = 'start';
rooms[1].type = 'treasure';
rooms[rooms.length - 1].type = 'boss';
return rooms;
}
}
```
## 蓝图节点
Procgen 模块提供了可视化脚本支持的蓝图节点:
### 噪声节点
- `SampleNoise2D` - 采样 2D 噪声
- `SampleFBM` - 采样 FBM 噪声
### 随机节点
- `SeededRandom` - 生成随机浮点数
- `SeededRandomInt` - 生成随机整数
- `WeightedPick` - 加权随机选择
- `ShuffleArray` - 洗牌数组
- `PickRandom` - 随机选择元素
- `SampleArray` - 采样数组
- `RandomPointInCircle` - 圆内随机点
## 最佳实践
1. **使用种子保证可重现性**
```typescript
const seed = Date.now();
const rng = createSeededRandom(seed);
saveSeed(seed);
```
2. **预计算加权选择器** - 避免重复创建
3. **选择合适的噪声函数**
- Perlin平滑过渡的地形、云彩
- Simplex性能要求高的场景
- Worley细胞、石头纹理
- FBM需要多层细节的自然效果
4. **调整 FBM 参数**
- `octaves`:越多细节越丰富,但性能开销越大
- `persistence`0.5 是常用值,越大高频细节越明显
- `lacunarity`:通常为 2控制频率增长速度

View File

@@ -1,5 +1,6 @@
---
title: "程序化生成 (Procgen)"
description: "噪声函数、种子随机数和随机工具"
---
`@esengine/procgen` 提供了程序化内容生成的核心工具,包括噪声函数、种子随机数和各种随机工具。
@@ -67,493 +68,9 @@ const drop = loot.pick(rng);
console.log(drop); // 大概率是 'common'
```
## 噪声函数
## 文档导航
### Perlin 噪声
经典的梯度噪声,输出范围 [-1, 1]
```typescript
import { createPerlinNoise } from '@esengine/procgen';
const perlin = createPerlinNoise(seed);
// 2D 噪声
const value2D = perlin.noise2D(x, y);
// 3D 噪声
const value3D = perlin.noise3D(x, y, z);
```
### Simplex 噪声
比 Perlin 更快、更少方向性偏差:
```typescript
import { createSimplexNoise } from '@esengine/procgen';
const simplex = createSimplexNoise(seed);
const value = simplex.noise2D(x, y);
```
### Worley 噪声
基于细胞的噪声,适合生成石头、细胞等纹理:
```typescript
import { createWorleyNoise } from '@esengine/procgen';
const worley = createWorleyNoise(seed);
// 返回到最近点的距离
const distance = worley.noise2D(x, y);
```
### FBM (分形布朗运动)
叠加多层噪声创建更丰富的细节:
```typescript
import { createPerlinNoise, createFBM } from '@esengine/procgen';
const baseNoise = createPerlinNoise(seed);
const fbm = createFBM(baseNoise, {
octaves: 6, // 层数(越多细节越丰富)
lacunarity: 2.0, // 频率倍增因子
persistence: 0.5, // 振幅衰减因子
frequency: 1.0, // 初始频率
amplitude: 1.0 // 初始振幅
});
// 标准 FBM
const value = fbm.noise2D(x, y);
// Ridged FBM脊状适合山脉
const ridged = fbm.ridged2D(x, y);
// Turbulence湍流
const turb = fbm.turbulence2D(x, y);
// Billowed膨胀适合云朵
const cloud = fbm.billowed2D(x, y);
```
## 种子随机数 API
### SeededRandom
基于 xorshift128+ 算法的确定性伪随机数生成器:
```typescript
import { createSeededRandom } from '@esengine/procgen';
const rng = createSeededRandom(42);
```
### 基础方法
```typescript
// [0, 1) 浮点数
rng.next();
// [min, max] 整数
rng.nextInt(1, 10);
// [min, max) 浮点数
rng.nextFloat(0, 100);
// 布尔值(可指定概率)
rng.nextBool(); // 50%
rng.nextBool(0.3); // 30%
// 重置到初始状态
rng.reset();
```
### 分布方法
```typescript
// 正态分布(高斯分布)
rng.nextGaussian(); // 均值 0, 标准差 1
rng.nextGaussian(100, 15); // 均值 100, 标准差 15
// 指数分布
rng.nextExponential(); // λ = 1
rng.nextExponential(0.5); // λ = 0.5
```
### 几何方法
```typescript
// 圆内均匀分布的点
const point = rng.nextPointInCircle(50); // { x, y }
// 圆周上的点
const edge = rng.nextPointOnCircle(50); // { x, y }
// 球内均匀分布的点
const point3D = rng.nextPointInSphere(50); // { x, y, z }
// 随机方向向量
const dir = rng.nextDirection2D(); // { x, y },长度为 1
```
## 加权随机 API
### WeightedRandom
预计算累积权重,高效随机选择:
```typescript
import { createWeightedRandom } from '@esengine/procgen';
const selector = createWeightedRandom([
{ value: 'apple', weight: 5 },
{ value: 'banana', weight: 3 },
{ value: 'cherry', weight: 2 }
]);
// 使用种子随机数
const result = selector.pick(rng);
// 使用 Math.random
const result2 = selector.pickRandom();
// 获取概率
console.log(selector.getProbability(0)); // 0.5 (5/10)
console.log(selector.size); // 3
console.log(selector.totalWeight); // 10
```
### 便捷函数
```typescript
import { weightedPick, weightedPickFromMap } from '@esengine/procgen';
// 从数组选择
const item = weightedPick([
{ value: 'a', weight: 1 },
{ value: 'b', weight: 2 }
], rng);
// 从对象选择
const item2 = weightedPickFromMap({
'common': 60,
'rare': 30,
'epic': 10
}, rng);
```
## 洗牌和采样 API
### shuffle / shuffleCopy
Fisher-Yates 洗牌算法:
```typescript
import { shuffle, shuffleCopy } from '@esengine/procgen';
const arr = [1, 2, 3, 4, 5];
// 原地洗牌
shuffle(arr, rng);
// 创建洗牌副本(不修改原数组)
const shuffled = shuffleCopy(arr, rng);
```
### pickOne
随机选择一个元素:
```typescript
import { pickOne } from '@esengine/procgen';
const items = ['a', 'b', 'c', 'd'];
const item = pickOne(items, rng);
```
### sample / sampleWithReplacement
采样:
```typescript
import { sample, sampleWithReplacement } from '@esengine/procgen';
const arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
// 采样 3 个不重复元素
const unique = sample(arr, 3, rng);
// 采样 5 个(可重复)
const withRep = sampleWithReplacement(arr, 5, rng);
```
### randomIntegers
生成范围内的随机整数数组:
```typescript
import { randomIntegers } from '@esengine/procgen';
// 从 1-100 中随机选 5 个不重复的数
const nums = randomIntegers(1, 100, 5, rng);
```
### weightedSample
按权重采样(不重复):
```typescript
import { weightedSample } from '@esengine/procgen';
const items = ['A', 'B', 'C', 'D', 'E'];
const weights = [10, 8, 6, 4, 2];
// 按权重选 3 个
const selected = weightedSample(items, weights, 3, rng);
```
## 实际示例
### 程序化地形生成
```typescript
import { createPerlinNoise, createFBM } from '@esengine/procgen';
class TerrainGenerator {
private fbm: FBM;
private moistureFbm: FBM;
constructor(seed: number) {
const heightNoise = createPerlinNoise(seed);
const moistureNoise = createPerlinNoise(seed + 1000);
this.fbm = createFBM(heightNoise, {
octaves: 8,
persistence: 0.5,
frequency: 0.01
});
this.moistureFbm = createFBM(moistureNoise, {
octaves: 4,
persistence: 0.6,
frequency: 0.02
});
}
getHeight(x: number, y: number): number {
// 基础高度
let height = this.fbm.noise2D(x, y);
// 添加山脉
height += this.fbm.ridged2D(x * 0.5, y * 0.5) * 0.3;
return (height + 1) * 0.5; // 归一化到 [0, 1]
}
getBiome(x: number, y: number): string {
const height = this.getHeight(x, y);
const moisture = (this.moistureFbm.noise2D(x, y) + 1) * 0.5;
if (height < 0.3) return 'water';
if (height < 0.4) return 'beach';
if (height > 0.8) return 'mountain';
if (moisture < 0.3) return 'desert';
if (moisture > 0.7) return 'forest';
return 'grassland';
}
}
```
### 战利品系统
```typescript
import { createSeededRandom, createWeightedRandom, sample } from '@esengine/procgen';
interface LootItem {
id: string;
rarity: string;
}
class LootSystem {
private rng: SeededRandom;
private raritySelector: WeightedRandom<string>;
private lootTables: Map<string, LootItem[]> = new Map();
constructor(seed: number) {
this.rng = createSeededRandom(seed);
this.raritySelector = createWeightedRandom([
{ value: 'common', weight: 60 },
{ value: 'uncommon', weight: 25 },
{ value: 'rare', weight: 10 },
{ value: 'legendary', weight: 5 }
]);
// 初始化战利品表
this.lootTables.set('common', [/* ... */]);
this.lootTables.set('rare', [/* ... */]);
// ...
}
generateLoot(count: number): LootItem[] {
const loot: LootItem[] = [];
for (let i = 0; i < count; i++) {
const rarity = this.raritySelector.pick(this.rng);
const table = this.lootTables.get(rarity)!;
const item = pickOne(table, this.rng);
loot.push(item);
}
return loot;
}
// 保证可重现
setSeed(seed: number): void {
this.rng = createSeededRandom(seed);
}
}
```
### 程序化敌人放置
```typescript
import { createSeededRandom } from '@esengine/procgen';
class EnemySpawner {
private rng: SeededRandom;
constructor(seed: number) {
this.rng = createSeededRandom(seed);
}
spawnEnemiesInArea(
centerX: number,
centerY: number,
radius: number,
count: number
): Array<{ x: number; y: number; type: string }> {
const enemies: Array<{ x: number; y: number; type: string }> = [];
for (let i = 0; i < count; i++) {
// 在圆内生成位置
const pos = this.rng.nextPointInCircle(radius);
// 随机选择敌人类型
const type = this.rng.nextBool(0.2) ? 'elite' : 'normal';
enemies.push({
x: centerX + pos.x,
y: centerY + pos.y,
type
});
}
return enemies;
}
}
```
### 程序化关卡布局
```typescript
import { createSeededRandom, shuffle } from '@esengine/procgen';
interface Room {
x: number;
y: number;
width: number;
height: number;
type: 'start' | 'combat' | 'treasure' | 'boss';
}
class DungeonGenerator {
private rng: SeededRandom;
constructor(seed: number) {
this.rng = createSeededRandom(seed);
}
generate(roomCount: number): Room[] {
const rooms: Room[] = [];
// 生成房间
for (let i = 0; i < roomCount; i++) {
rooms.push({
x: this.rng.nextInt(0, 100),
y: this.rng.nextInt(0, 100),
width: this.rng.nextInt(5, 15),
height: this.rng.nextInt(5, 15),
type: 'combat'
});
}
// 随机分配特殊房间
shuffle(rooms, this.rng);
rooms[0].type = 'start';
rooms[1].type = 'treasure';
rooms[rooms.length - 1].type = 'boss';
return rooms;
}
}
```
## 蓝图节点
Procgen 模块提供了可视化脚本支持的蓝图节点:
### 噪声节点
- `SampleNoise2D` - 采样 2D 噪声
- `SampleFBM` - 采样 FBM 噪声
### 随机节点
- `SeededRandom` - 生成随机浮点数
- `SeededRandomInt` - 生成随机整数
- `WeightedPick` - 加权随机选择
- `ShuffleArray` - 洗牌数组
- `PickRandom` - 随机选择元素
- `SampleArray` - 采样数组
- `RandomPointInCircle` - 圆内随机点
## 最佳实践
1. **使用种子保证可重现性**
```typescript
// 保存种子以便重现相同结果
const seed = Date.now();
const rng = createSeededRandom(seed);
saveSeed(seed);
```
2. **预计算加权选择器**
```typescript
// 好:创建一次,多次使用
const selector = createWeightedRandom(items);
for (let i = 0; i < 1000; i++) {
selector.pick(rng);
}
// 不好:每次都创建
for (let i = 0; i < 1000; i++) {
weightedPick(items, rng);
}
```
3. **选择合适的噪声函数**
- Perlin平滑过渡的地形、云彩
- Simplex性能要求高的场景
- Worley细胞、石头纹理
- FBM需要多层细节的自然效果
4. **调整 FBM 参数**
- `octaves`:越多细节越丰富,但性能开销越大
- `persistence`0.5 是常用值,越大高频细节越明显
- `lacunarity`:通常为 2控制频率增长速度
- [噪声函数](./noise) - Perlin、Simplex、Worley、FBM
- [种子随机数](./random) - SeededRandom API 和分布方法
- [采样工具](./sampling) - 加权随机、洗牌、采样
- [实际示例](./examples) - 地形、战利品、关卡生成

Some files were not shown because too many files have changed in this diff Show More