Compare commits
15 Commits
@esengine/
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b6aef13d93 | ||
|
|
470abb8750 | ||
|
|
2f95758911 | ||
|
|
2e9f36b656 | ||
|
|
c188a36f2b | ||
|
|
50681553b5 | ||
|
|
4e66bd8e2b | ||
|
|
7caa69a22e | ||
|
|
5a5daf7565 | ||
|
|
3415737fcc | ||
|
|
876312deb2 | ||
|
|
a790fc9e92 | ||
|
|
fa593a3c69 | ||
|
|
e7d95dfdaf | ||
|
|
bffe90b6a1 |
2
.github/workflows/ci.yml
vendored
2
.github/workflows/ci.yml
vendored
@@ -63,9 +63,9 @@ jobs:
|
||||
- name: Build framework packages
|
||||
run: |
|
||||
pnpm --filter @esengine/ecs-framework build
|
||||
pnpm --filter @esengine/blueprint build
|
||||
pnpm --filter @esengine/ecs-framework-math build
|
||||
pnpm --filter @esengine/behavior-tree build
|
||||
pnpm --filter @esengine/blueprint build
|
||||
pnpm --filter @esengine/fsm build
|
||||
pnpm --filter @esengine/timer build
|
||||
pnpm --filter @esengine/spatial build
|
||||
|
||||
2
.github/workflows/release-changesets.yml
vendored
2
.github/workflows/release-changesets.yml
vendored
@@ -45,9 +45,9 @@ jobs:
|
||||
run: |
|
||||
# Only build packages managed by Changesets (not in ignore list)
|
||||
pnpm --filter "@esengine/ecs-framework" build
|
||||
pnpm --filter "@esengine/blueprint" build
|
||||
pnpm --filter "@esengine/ecs-framework-math" build
|
||||
pnpm --filter "@esengine/behavior-tree" build
|
||||
pnpm --filter "@esengine/blueprint" build
|
||||
pnpm --filter "@esengine/fsm" build
|
||||
pnpm --filter "@esengine/timer" build
|
||||
pnpm --filter "@esengine/spatial" build
|
||||
|
||||
@@ -247,6 +247,14 @@ export default defineConfig({
|
||||
{ label: '实际示例', slug: 'modules/blueprint/examples', translations: { en: 'Examples' } },
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '数学库',
|
||||
translations: { en: 'Math' },
|
||||
items: [
|
||||
{ label: '概述', slug: 'modules/math', translations: { en: 'Overview' } },
|
||||
{ label: '蓝图节点', slug: 'modules/math/blueprint-nodes', translations: { en: 'Blueprint Nodes' } },
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '程序生成',
|
||||
translations: { en: 'Procgen' },
|
||||
|
||||
@@ -7,37 +7,26 @@ This document explains how to install and use the blueprint visual scripting edi
|
||||
|
||||
## Installation
|
||||
|
||||
### 1. Copy Extension to Project
|
||||
### 1. Download Extension
|
||||
|
||||
Copy the `cocos-node-editor` extension to your Cocos Creator project's `extensions` directory:
|
||||
Download the `cocos-node-editor.zip` extension package from the release page.
|
||||
|
||||
```
|
||||
your-project/
|
||||
├── assets/
|
||||
├── extensions/
|
||||
│ └── cocos-node-editor/ # Blueprint editor extension
|
||||
└── ...
|
||||
```
|
||||
|
||||
### 2. Install Dependencies
|
||||
|
||||
Install dependencies in the extension directory:
|
||||
|
||||
```bash
|
||||
cd extensions/cocos-node-editor
|
||||
npm install
|
||||
```
|
||||
|
||||
### 3. Enable Extension
|
||||
### 2. Import Extension
|
||||
|
||||
1. Open Cocos Creator
|
||||
2. Go to **Extensions → Extension Manager**
|
||||
3. Find `cocos-node-editor` and enable it
|
||||
3. Click the **Import Extension** button
|
||||
4. Select the downloaded `cocos-node-editor.zip` file
|
||||
5. Enable the extension after importing
|
||||
|
||||
## Opening the Blueprint Editor
|
||||
|
||||
Open the blueprint editor panel via menu **Panel → Node Editor**.
|
||||
|
||||
### First Launch - Install Dependencies
|
||||
|
||||
When opening the panel for the first time, the plugin will check if `@esengine/blueprint` is installed in your project. If not installed, it will display **"Missing required dependencies"** prompt. Click the **"Install Dependencies"** button to install automatically.
|
||||
|
||||
## Editor Interface
|
||||
|
||||
### Toolbar
|
||||
@@ -110,89 +99,22 @@ Blueprints are saved as `.blueprint.json` files, fully compatible with runtime:
|
||||
|
||||
## Running Blueprints in Game
|
||||
|
||||
Use ECS system to manage and execute blueprints.
|
||||
The `@esengine/blueprint` package provides complete ECS integration, including `BlueprintComponent` and `BlueprintSystem` ready to use.
|
||||
|
||||
### 1. Define Blueprint Component
|
||||
### 1. Add Blueprint System to Scene
|
||||
|
||||
```typescript
|
||||
import { Component, ECSComponent, Property, Serialize } from '@esengine/ecs-framework';
|
||||
import type { BlueprintAsset } from '@esengine/blueprint';
|
||||
import { BlueprintSystem } from '@esengine/blueprint';
|
||||
|
||||
@ECSComponent('Blueprint')
|
||||
export class BlueprintComponent extends Component {
|
||||
@Serialize()
|
||||
@Property({ type: 'asset', label: 'Blueprint Asset' })
|
||||
blueprintPath: string = '';
|
||||
|
||||
@Serialize()
|
||||
@Property({ type: 'boolean', label: 'Auto Start' })
|
||||
autoStart: boolean = true;
|
||||
|
||||
// Runtime data (not serialized)
|
||||
blueprintAsset: BlueprintAsset | null = null;
|
||||
vm: BlueprintVM | null = null;
|
||||
isStarted: boolean = false;
|
||||
}
|
||||
// Add blueprint system during scene initialization
|
||||
scene.addSystem(new BlueprintSystem());
|
||||
```
|
||||
|
||||
### 2. Create Blueprint Execution System
|
||||
|
||||
```typescript
|
||||
import { EntitySystem, Matcher, Entity } from '@esengine/ecs-framework';
|
||||
import {
|
||||
BlueprintVM,
|
||||
validateBlueprintAsset
|
||||
} from '@esengine/blueprint';
|
||||
import { BlueprintComponent } from './BlueprintComponent';
|
||||
|
||||
export class BlueprintExecutionSystem extends EntitySystem {
|
||||
constructor() {
|
||||
super(Matcher.empty().all(BlueprintComponent));
|
||||
}
|
||||
|
||||
protected override process(entities: readonly Entity[]): void {
|
||||
const dt = Time.deltaTime;
|
||||
|
||||
for (const entity of entities) {
|
||||
const bp = entity.getComponent(BlueprintComponent)!;
|
||||
|
||||
// Skip entities without blueprint asset
|
||||
if (!bp.blueprintAsset) continue;
|
||||
|
||||
// Initialize VM
|
||||
if (!bp.vm) {
|
||||
bp.vm = new BlueprintVM(bp.blueprintAsset, entity, this.scene!);
|
||||
}
|
||||
|
||||
// Auto start
|
||||
if (bp.autoStart && !bp.isStarted) {
|
||||
bp.vm.start();
|
||||
bp.isStarted = true;
|
||||
}
|
||||
|
||||
// Update blueprint
|
||||
if (bp.isStarted) {
|
||||
bp.vm.tick(dt);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override onRemoved(entity: Entity): void {
|
||||
const bp = entity.getComponent(BlueprintComponent);
|
||||
if (bp?.vm && bp.isStarted) {
|
||||
bp.vm.stop();
|
||||
bp.vm = null;
|
||||
bp.isStarted = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Load Blueprint and Add to Entity
|
||||
### 2. Load Blueprint and Add to Entity
|
||||
|
||||
```typescript
|
||||
import { resources, JsonAsset } from 'cc';
|
||||
import { validateBlueprintAsset } from '@esengine/blueprint';
|
||||
import { BlueprintComponent, validateBlueprintAsset, BlueprintAsset } from '@esengine/blueprint';
|
||||
|
||||
// Load blueprint asset
|
||||
async function loadBlueprint(path: string): Promise<BlueprintAsset | null> {
|
||||
@@ -227,12 +149,22 @@ async function createBlueprintEntity(scene: IScene, blueprintPath: string): Prom
|
||||
}
|
||||
```
|
||||
|
||||
### 4. Register System to Scene
|
||||
### BlueprintComponent Properties
|
||||
|
||||
```typescript
|
||||
// During scene initialization
|
||||
scene.addSystem(new BlueprintExecutionSystem());
|
||||
```
|
||||
| Property | Type | Description |
|
||||
|----------|------|-------------|
|
||||
| `blueprintAsset` | `BlueprintAsset \| null` | Blueprint asset data |
|
||||
| `blueprintPath` | `string` | Blueprint asset path (for serialization) |
|
||||
| `autoStart` | `boolean` | Auto-start execution (default `true`) |
|
||||
| `debug` | `boolean` | Enable debug mode |
|
||||
|
||||
### BlueprintComponent Methods
|
||||
|
||||
| Method | Description |
|
||||
|--------|-------------|
|
||||
| `start()` | Manually start blueprint execution |
|
||||
| `stop()` | Stop blueprint execution |
|
||||
| `cleanup()` | Cleanup blueprint resources |
|
||||
|
||||
## Creating Custom Nodes
|
||||
|
||||
|
||||
@@ -3,6 +3,164 @@ title: "Custom Nodes"
|
||||
description: "Creating custom blueprint nodes"
|
||||
---
|
||||
|
||||
## Blueprint Decorators
|
||||
|
||||
Use decorators to quickly expose ECS components as blueprint nodes.
|
||||
|
||||
### @BlueprintComponent
|
||||
|
||||
Mark a component class as blueprint-enabled:
|
||||
|
||||
```typescript
|
||||
import { BlueprintComponent, BlueprintProperty } from '@esengine/blueprint';
|
||||
|
||||
@BlueprintComponent({
|
||||
title: 'Player Controller',
|
||||
category: 'gameplay',
|
||||
color: '#4a90d9',
|
||||
description: 'Controls player movement and interaction'
|
||||
})
|
||||
class PlayerController extends Component {
|
||||
@BlueprintProperty({ displayName: 'Move Speed' })
|
||||
speed: number = 100;
|
||||
|
||||
@BlueprintProperty({ displayName: 'Jump Height' })
|
||||
jumpHeight: number = 200;
|
||||
}
|
||||
```
|
||||
|
||||
### @BlueprintProperty
|
||||
|
||||
Expose component properties as node inputs:
|
||||
|
||||
```typescript
|
||||
@BlueprintProperty({
|
||||
displayName: 'Health',
|
||||
description: 'Current health value',
|
||||
isInput: true,
|
||||
isOutput: true
|
||||
})
|
||||
health: number = 100;
|
||||
```
|
||||
|
||||
### @BlueprintArray
|
||||
|
||||
For array type properties, supports editing complex object arrays:
|
||||
|
||||
```typescript
|
||||
import { BlueprintArray, Schema } from '@esengine/blueprint';
|
||||
|
||||
interface Waypoint {
|
||||
position: { x: number; y: number };
|
||||
waitTime: number;
|
||||
speed: number;
|
||||
}
|
||||
|
||||
@BlueprintComponent({
|
||||
title: 'Patrol Path',
|
||||
category: 'ai'
|
||||
})
|
||||
class PatrolPath extends Component {
|
||||
@BlueprintArray({
|
||||
displayName: 'Waypoints',
|
||||
description: 'Points along the patrol path',
|
||||
itemSchema: Schema.object({
|
||||
position: Schema.vector2({ defaultValue: { x: 0, y: 0 } }),
|
||||
waitTime: Schema.float({ min: 0, max: 10, defaultValue: 1.0 }),
|
||||
speed: Schema.float({ min: 0, max: 500, defaultValue: 100 })
|
||||
}),
|
||||
reorderable: true,
|
||||
exposeElementPorts: true,
|
||||
portNameTemplate: 'Waypoint {index1}'
|
||||
})
|
||||
waypoints: Waypoint[] = [];
|
||||
}
|
||||
```
|
||||
|
||||
## Schema Type System
|
||||
|
||||
Schema defines type information for complex data structures, enabling the editor to automatically generate corresponding UI.
|
||||
|
||||
### Primitive Types
|
||||
|
||||
```typescript
|
||||
import { Schema } from '@esengine/blueprint';
|
||||
|
||||
// Number types
|
||||
Schema.float({ min: 0, max: 100, defaultValue: 50, step: 0.1 })
|
||||
Schema.int({ min: 0, max: 10, defaultValue: 5 })
|
||||
|
||||
// String
|
||||
Schema.string({ defaultValue: 'Hello', multiline: false, placeholder: 'Enter text...' })
|
||||
|
||||
// Boolean
|
||||
Schema.boolean({ defaultValue: true })
|
||||
|
||||
// Vectors
|
||||
Schema.vector2({ defaultValue: { x: 0, y: 0 } })
|
||||
Schema.vector3({ defaultValue: { x: 0, y: 0, z: 0 } })
|
||||
```
|
||||
|
||||
### Composite Types
|
||||
|
||||
```typescript
|
||||
// Object
|
||||
Schema.object({
|
||||
name: Schema.string({ defaultValue: '' }),
|
||||
health: Schema.float({ min: 0, max: 100 }),
|
||||
position: Schema.vector2()
|
||||
})
|
||||
|
||||
// Array
|
||||
Schema.array({
|
||||
items: Schema.float(),
|
||||
minItems: 0,
|
||||
maxItems: 10
|
||||
})
|
||||
|
||||
// Enum
|
||||
Schema.enum({
|
||||
options: ['idle', 'walk', 'run', 'jump'],
|
||||
defaultValue: 'idle'
|
||||
})
|
||||
|
||||
// Reference
|
||||
Schema.ref({ refType: 'entity' })
|
||||
Schema.ref({ refType: 'asset', assetType: 'texture' })
|
||||
```
|
||||
|
||||
### Complete Example
|
||||
|
||||
```typescript
|
||||
@BlueprintComponent({ title: 'Enemy Config', category: 'ai' })
|
||||
class EnemyConfig extends Component {
|
||||
@BlueprintArray({
|
||||
displayName: 'Attack Patterns',
|
||||
itemSchema: Schema.object({
|
||||
name: Schema.string({ defaultValue: 'Basic Attack' }),
|
||||
damage: Schema.float({ min: 0, max: 100, defaultValue: 10 }),
|
||||
cooldown: Schema.float({ min: 0, max: 10, defaultValue: 1 }),
|
||||
range: Schema.float({ min: 0, max: 500, defaultValue: 50 }),
|
||||
animation: Schema.string({ defaultValue: 'attack_01' })
|
||||
}),
|
||||
reorderable: true
|
||||
})
|
||||
attackPatterns: AttackPattern[] = [];
|
||||
|
||||
@BlueprintProperty({
|
||||
displayName: 'Patrol Area',
|
||||
schema: Schema.object({
|
||||
center: Schema.vector2(),
|
||||
radius: Schema.float({ min: 0, defaultValue: 100 })
|
||||
})
|
||||
})
|
||||
patrolArea: { center: { x: number; y: number }; radius: number } = {
|
||||
center: { x: 0, y: 0 },
|
||||
radius: 100
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
## Defining Node Template
|
||||
|
||||
```typescript
|
||||
@@ -33,16 +191,11 @@ import { INodeExecutor, RegisterNode, BlueprintNode, ExecutionContext, Execution
|
||||
@RegisterNode(MyNodeTemplate)
|
||||
class MyNodeExecutor implements INodeExecutor {
|
||||
execute(node: BlueprintNode, context: ExecutionContext): ExecutionResult {
|
||||
// Get input (using evaluateInput)
|
||||
const value = context.evaluateInput(node.id, 'value', 0) as number;
|
||||
|
||||
// Execute logic
|
||||
const result = value * 2;
|
||||
|
||||
// Return result
|
||||
return {
|
||||
outputs: { result },
|
||||
nextExec: 'exec' // Continue execution
|
||||
nextExec: 'exec'
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -64,19 +217,10 @@ NodeRegistry.instance.register(MyNodeTemplate, new MyNodeExecutor());
|
||||
```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')) { ... }
|
||||
```
|
||||
|
||||
@@ -89,7 +233,7 @@ const PureNodeTemplate: BlueprintNodeTemplate = {
|
||||
type: 'GetDistance',
|
||||
title: 'Get Distance',
|
||||
category: 'math',
|
||||
isPure: true, // Mark as pure node
|
||||
isPure: true,
|
||||
inputs: [
|
||||
{ name: 'a', type: 'vector2', direction: 'input' },
|
||||
{ name: 'b', type: 'vector2', direction: 'input' }
|
||||
@@ -99,59 +243,3 @@ const PureNodeTemplate: BlueprintNodeTemplate = {
|
||||
]
|
||||
};
|
||||
```
|
||||
|
||||
## Example: ECS Component Operation Node
|
||||
|
||||
```typescript
|
||||
import type { Entity } from '@esengine/ecs-framework';
|
||||
import { BlueprintNodeTemplate, BlueprintNode } from '@esengine/blueprint';
|
||||
import { ExecutionContext, ExecutionResult } from '@esengine/blueprint';
|
||||
import { INodeExecutor, RegisterNode } from '@esengine/blueprint';
|
||||
|
||||
// Custom heal node
|
||||
const HealEntityTemplate: BlueprintNodeTemplate = {
|
||||
type: 'HealEntity',
|
||||
title: 'Heal Entity',
|
||||
category: 'gameplay',
|
||||
color: '#22aa22',
|
||||
description: 'Heal an entity with HealthComponent',
|
||||
keywords: ['heal', 'health', 'restore'],
|
||||
menuPath: ['Gameplay', 'Combat', 'Heal Entity'],
|
||||
inputs: [
|
||||
{ name: 'exec', type: 'exec', displayName: '' },
|
||||
{ name: 'entity', type: 'entity', displayName: 'Target' },
|
||||
{ name: 'amount', type: 'float', displayName: 'Amount', defaultValue: 10 }
|
||||
],
|
||||
outputs: [
|
||||
{ name: 'exec', type: 'exec', displayName: '' },
|
||||
{ name: 'newHealth', type: 'float', displayName: 'New Health' }
|
||||
]
|
||||
};
|
||||
|
||||
@RegisterNode(HealEntityTemplate)
|
||||
class HealEntityExecutor implements INodeExecutor {
|
||||
execute(node: BlueprintNode, context: ExecutionContext): ExecutionResult {
|
||||
const entity = context.evaluateInput(node.id, 'entity', context.entity) as Entity;
|
||||
const amount = context.evaluateInput(node.id, 'amount', 10) as number;
|
||||
|
||||
if (!entity || entity.isDestroyed) {
|
||||
return { outputs: { newHealth: 0 }, nextExec: 'exec' };
|
||||
}
|
||||
|
||||
// Get HealthComponent
|
||||
const health = entity.components.find(c =>
|
||||
(c.constructor as any).__componentName__ === 'Health'
|
||||
) as any;
|
||||
|
||||
if (health) {
|
||||
health.current = Math.min(health.current + amount, health.max);
|
||||
return {
|
||||
outputs: { newHealth: health.current },
|
||||
nextExec: 'exec'
|
||||
};
|
||||
}
|
||||
|
||||
return { outputs: { newHealth: 0 }, nextExec: 'exec' };
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
@@ -13,7 +13,7 @@ This guide covers how to use the Blueprint Visual Scripting Editor in Cocos Crea
|
||||
|
||||
Download the latest version from GitHub Release (Free):
|
||||
|
||||
**[Download Cocos Node Editor v1.1.0](https://github.com/esengine/esengine/releases/tag/cocos-node-editor-v1.1.0)**
|
||||
**[Download Cocos Node Editor v1.2.0](https://github.com/esengine/esengine/releases/tag/cocos-node-editor-v1.2.0)**
|
||||
|
||||
> QQ Group: **481923584** | Website: [esengine.cn](https://esengine.cn/)
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ description: "Visual scripting system deeply integrated with ECS framework"
|
||||
|
||||
Blueprint Editor Plugin for Cocos Creator (Free):
|
||||
|
||||
**[Download Cocos Node Editor v1.1.0](https://github.com/esengine/esengine/releases/tag/cocos-node-editor-v1.1.0)**
|
||||
**[Download Cocos Node Editor v1.2.0](https://github.com/esengine/esengine/releases/tag/cocos-node-editor-v1.2.0)**
|
||||
|
||||
> QQ Group: **481923584** | Website: [esengine.cn](https://esengine.cn/)
|
||||
|
||||
|
||||
@@ -472,6 +472,12 @@ Control blueprint execution flow:
|
||||
| `Less` | A < B | Boolean |
|
||||
| `Less Or Equal` | A <= B | Boolean |
|
||||
|
||||
### Extended Math Nodes
|
||||
|
||||
> **Vector2, Fixed32, FixedVector2, Color** and other advanced math nodes are provided by the `@esengine/ecs-framework-math` module.
|
||||
>
|
||||
> See: [Math Blueprint Nodes](/en/modules/math/blueprint-nodes)
|
||||
|
||||
### Example: Clamp Value
|
||||
|
||||
<div class="bp-graph" style="" data-connections='[{"from":"en-rand-result","to":"en-clamp-value","type":"float"}]'>
|
||||
@@ -604,6 +610,7 @@ Blueprint-defined variables automatically generate Get and Set nodes:
|
||||
|
||||
## Related Documentation
|
||||
|
||||
- [Math Blueprint Nodes](/en/modules/math/blueprint-nodes) - Vector2, Fixed32, Color and other math nodes
|
||||
- [Blueprint Editor Guide](/en/modules/blueprint/editor-guide) - Learn how to use the editor
|
||||
- [Custom Nodes](/en/modules/blueprint/custom-nodes) - Create custom nodes
|
||||
- [Blueprint VM](/en/modules/blueprint/vm) - Runtime API
|
||||
|
||||
489
docs/src/content/docs/en/modules/math/blueprint-nodes.md
Normal file
489
docs/src/content/docs/en/modules/math/blueprint-nodes.md
Normal file
@@ -0,0 +1,489 @@
|
||||
---
|
||||
title: "Math Blueprint Nodes"
|
||||
description: "Blueprint nodes provided by the Math module - Vector2, Fixed32, FixedVector2, Color"
|
||||
---
|
||||
|
||||
This document describes the blueprint nodes provided by the `@esengine/ecs-framework-math` module.
|
||||
|
||||
> **Note**: These nodes require the math module to be installed.
|
||||
|
||||
<script src="/js/blueprint-graph.js"></script>
|
||||
|
||||
## Pin Type Legend
|
||||
|
||||
<div class="bp-legend">
|
||||
<div class="bp-legend-item"><svg width="12" height="12"><circle cx="6" cy="6" r="4" fill="transparent" stroke="#7ecd32" stroke-width="2"/></svg> Float</div>
|
||||
<div class="bp-legend-item"><svg width="12" height="12"><circle cx="6" cy="6" r="4" fill="transparent" stroke="#2196F3" stroke-width="2"/></svg> Vector2</div>
|
||||
<div class="bp-legend-item"><svg width="12" height="12"><circle cx="6" cy="6" r="4" fill="transparent" stroke="#9C27B0" stroke-width="2"/></svg> Fixed32</div>
|
||||
<div class="bp-legend-item"><svg width="12" height="12"><circle cx="6" cy="6" r="4" fill="transparent" stroke="#673AB7" stroke-width="2"/></svg> FixedVector2</div>
|
||||
<div class="bp-legend-item"><svg width="12" height="12"><circle cx="6" cy="6" r="4" fill="transparent" stroke="#FF9800" stroke-width="2"/></svg> Color</div>
|
||||
</div>
|
||||
|
||||
---
|
||||
|
||||
## Vector2 Nodes
|
||||
|
||||
2D vector operations for position, velocity, and direction calculations.
|
||||
|
||||
### Node List
|
||||
|
||||
| Node | Description | Inputs | Output |
|
||||
|------|-------------|--------|--------|
|
||||
| `Make Vector2` | Create Vector2 from X, Y | X, Y | Vector2 |
|
||||
| `Break Vector2` | Decompose Vector2 to X, Y | Vector | X, Y |
|
||||
| `Vector2 +` | Vector addition | A, B | Vector2 |
|
||||
| `Vector2 -` | Vector subtraction | A, B | Vector2 |
|
||||
| `Vector2 *` | Vector scaling | Vector, Scalar | Vector2 |
|
||||
| `Vector2 Length` | Get vector length | Vector | Float |
|
||||
| `Vector2 Normalize` | Normalize to unit vector | Vector | Vector2 |
|
||||
| `Vector2 Dot` | Dot product | A, B | Float |
|
||||
| `Vector2 Cross` | 2D cross product | A, B | Float |
|
||||
| `Vector2 Distance` | Distance between two points | A, B | Float |
|
||||
| `Vector2 Lerp` | Linear interpolation | A, B, T | Vector2 |
|
||||
| `Vector2 Rotate` | Rotate by angle (radians) | Vector, Angle | Vector2 |
|
||||
| `Vector2 From Angle` | Create unit vector from angle | Angle | Vector2 |
|
||||
|
||||
### Example: Calculate Movement Direction
|
||||
|
||||
Direction vector from start to end point:
|
||||
|
||||
<div class="bp-graph" data-connections='[{"from":"v2-start","to":"v2-sub-a","type":"vector2"},{"from":"v2-end","to":"v2-sub-b","type":"vector2"},{"from":"v2-sub-result","to":"v2-norm-in","type":"vector2"}]'>
|
||||
<svg class="bp-connections"></svg>
|
||||
<div class="bp-node" style="position: absolute; left: 20px; top: 10px; width: 130px;">
|
||||
<div class="bp-node-header math" style="background: #2196F3;">Make Vector2</div>
|
||||
<div class="bp-node-body">
|
||||
<div class="bp-pin-row input">
|
||||
<span class="bp-pin"><svg width="12" height="12"><circle cx="6" cy="6" r="4" fill="none" stroke="#7ecd32" stroke-width="2"/></svg></span>
|
||||
<span class="bp-pin-label">X</span>
|
||||
<span class="bp-pin-value">0</span>
|
||||
</div>
|
||||
<div class="bp-pin-row input">
|
||||
<span class="bp-pin"><svg width="12" height="12"><circle cx="6" cy="6" r="4" fill="none" stroke="#7ecd32" stroke-width="2"/></svg></span>
|
||||
<span class="bp-pin-label">Y</span>
|
||||
<span class="bp-pin-value">0</span>
|
||||
</div>
|
||||
<div class="bp-pin-row output">
|
||||
<span class="bp-pin" data-pin="v2-start"><svg width="12" height="12"><circle cx="6" cy="6" r="4" fill="#2196F3"/></svg></span>
|
||||
<span class="bp-pin-label">Vector</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bp-node" style="position: absolute; left: 20px; top: 180px; width: 130px;">
|
||||
<div class="bp-node-header math" style="background: #2196F3;">Make Vector2</div>
|
||||
<div class="bp-node-body">
|
||||
<div class="bp-pin-row input">
|
||||
<span class="bp-pin"><svg width="12" height="12"><circle cx="6" cy="6" r="4" fill="none" stroke="#7ecd32" stroke-width="2"/></svg></span>
|
||||
<span class="bp-pin-label">X</span>
|
||||
<span class="bp-pin-value">100</span>
|
||||
</div>
|
||||
<div class="bp-pin-row input">
|
||||
<span class="bp-pin"><svg width="12" height="12"><circle cx="6" cy="6" r="4" fill="none" stroke="#7ecd32" stroke-width="2"/></svg></span>
|
||||
<span class="bp-pin-label">Y</span>
|
||||
<span class="bp-pin-value">50</span>
|
||||
</div>
|
||||
<div class="bp-pin-row output">
|
||||
<span class="bp-pin" data-pin="v2-end"><svg width="12" height="12"><circle cx="6" cy="6" r="4" fill="#2196F3"/></svg></span>
|
||||
<span class="bp-pin-label">Vector</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bp-node" style="position: absolute; left: 220px; top: 90px; width: 120px;">
|
||||
<div class="bp-node-header math" style="background: #2196F3;">Vector2 -</div>
|
||||
<div class="bp-node-body">
|
||||
<div class="bp-pin-row input">
|
||||
<span class="bp-pin" data-pin="v2-sub-b"><svg width="12" height="12"><circle cx="6" cy="6" r="4" fill="#2196F3"/></svg></span>
|
||||
<span class="bp-pin-label">A</span>
|
||||
</div>
|
||||
<div class="bp-pin-row input">
|
||||
<span class="bp-pin" data-pin="v2-sub-a"><svg width="12" height="12"><circle cx="6" cy="6" r="4" fill="#2196F3"/></svg></span>
|
||||
<span class="bp-pin-label">B</span>
|
||||
</div>
|
||||
<div class="bp-pin-row output">
|
||||
<span class="bp-pin" data-pin="v2-sub-result"><svg width="12" height="12"><circle cx="6" cy="6" r="4" fill="#2196F3"/></svg></span>
|
||||
<span class="bp-pin-label">Result</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bp-node" style="position: absolute; left: 400px; top: 55px; width: 140px;">
|
||||
<div class="bp-node-header math" style="background: #2196F3;">Vector2 Normalize</div>
|
||||
<div class="bp-node-body">
|
||||
<div class="bp-pin-row input">
|
||||
<span class="bp-pin" data-pin="v2-norm-in"><svg width="12" height="12"><circle cx="6" cy="6" r="4" fill="#2196F3"/></svg></span>
|
||||
<span class="bp-pin-label">Vector</span>
|
||||
</div>
|
||||
<div class="bp-pin-row output">
|
||||
<span class="bp-pin"><svg width="12" height="12"><circle cx="6" cy="6" r="4" fill="#2196F3"/></svg></span>
|
||||
<span class="bp-pin-label">Result</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
### Example: Circular Motion
|
||||
|
||||
Calculate circular position using angle and radius:
|
||||
|
||||
<div class="bp-graph" data-connections='[{"from":"v2-angle-out","to":"v2-scale-vec","type":"vector2"},{"from":"v2-scale-result","to":"v2-add-b","type":"vector2"}]'>
|
||||
<svg class="bp-connections"></svg>
|
||||
<div class="bp-node" style="position: absolute; left: 20px; top: 40px; width: 150px;">
|
||||
<div class="bp-node-header math" style="background: #2196F3;">Vector2 From Angle</div>
|
||||
<div class="bp-node-body">
|
||||
<div class="bp-pin-row input">
|
||||
<span class="bp-pin"><svg width="12" height="12"><circle cx="6" cy="6" r="4" fill="none" stroke="#7ecd32" stroke-width="2"/></svg></span>
|
||||
<span class="bp-pin-label">Angle</span>
|
||||
<span class="bp-pin-value">1.57</span>
|
||||
</div>
|
||||
<div class="bp-pin-row output">
|
||||
<span class="bp-pin" data-pin="v2-angle-out"><svg width="12" height="12"><circle cx="6" cy="6" r="4" fill="#2196F3"/></svg></span>
|
||||
<span class="bp-pin-label">Vector</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bp-node" style="position: absolute; left: 230px; top: 40px; width: 120px;">
|
||||
<div class="bp-node-header math" style="background: #2196F3;">Vector2 *</div>
|
||||
<div class="bp-node-body">
|
||||
<div class="bp-pin-row input">
|
||||
<span class="bp-pin" data-pin="v2-scale-vec"><svg width="12" height="12"><circle cx="6" cy="6" r="4" fill="#2196F3"/></svg></span>
|
||||
<span class="bp-pin-label">Vector</span>
|
||||
</div>
|
||||
<div class="bp-pin-row input">
|
||||
<span class="bp-pin"><svg width="12" height="12"><circle cx="6" cy="6" r="4" fill="none" stroke="#7ecd32" stroke-width="2"/></svg></span>
|
||||
<span class="bp-pin-label">Scalar</span>
|
||||
<span class="bp-pin-value">50</span>
|
||||
</div>
|
||||
<div class="bp-pin-row output">
|
||||
<span class="bp-pin" data-pin="v2-scale-result"><svg width="12" height="12"><circle cx="6" cy="6" r="4" fill="#2196F3"/></svg></span>
|
||||
<span class="bp-pin-label">Result</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bp-node" style="position: absolute; left: 420px; top: 40px; width: 120px;">
|
||||
<div class="bp-node-header math" style="background: #2196F3;">Vector2 +</div>
|
||||
<div class="bp-node-body">
|
||||
<div class="bp-pin-row input">
|
||||
<span class="bp-pin"><svg width="12" height="12"><circle cx="6" cy="6" r="4" fill="none" stroke="#2196F3" stroke-width="2"/></svg></span>
|
||||
<span class="bp-pin-label">A (Center)</span>
|
||||
</div>
|
||||
<div class="bp-pin-row input">
|
||||
<span class="bp-pin" data-pin="v2-add-b"><svg width="12" height="12"><circle cx="6" cy="6" r="4" fill="#2196F3"/></svg></span>
|
||||
<span class="bp-pin-label">B</span>
|
||||
</div>
|
||||
<div class="bp-pin-row output">
|
||||
<span class="bp-pin"><svg width="12" height="12"><circle cx="6" cy="6" r="4" fill="#2196F3"/></svg></span>
|
||||
<span class="bp-pin-label">Position</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
---
|
||||
|
||||
## Fixed32 Nodes (Fixed-Point Numbers)
|
||||
|
||||
Q16.16 fixed-point number operations for lockstep networking games, ensuring cross-platform calculation consistency.
|
||||
|
||||
### Node List
|
||||
|
||||
| Node | Description | Inputs | Output |
|
||||
|------|-------------|--------|--------|
|
||||
| `Fixed32 From Float` | Create from float | Float | Fixed32 |
|
||||
| `Fixed32 From Int` | Create from integer | Int | Fixed32 |
|
||||
| `Fixed32 To Float` | Convert to float | Fixed32 | Float |
|
||||
| `Fixed32 To Int` | Convert to integer | Fixed32 | Int |
|
||||
| `Fixed32 +` | Addition | A, B | Fixed32 |
|
||||
| `Fixed32 -` | Subtraction | A, B | Fixed32 |
|
||||
| `Fixed32 *` | Multiplication | A, B | Fixed32 |
|
||||
| `Fixed32 /` | Division | A, B | Fixed32 |
|
||||
| `Fixed32 Abs` | Absolute value | Value | Fixed32 |
|
||||
| `Fixed32 Sqrt` | Square root | Value | Fixed32 |
|
||||
| `Fixed32 Floor` | Floor | Value | Fixed32 |
|
||||
| `Fixed32 Ceil` | Ceiling | Value | Fixed32 |
|
||||
| `Fixed32 Round` | Round | Value | Fixed32 |
|
||||
| `Fixed32 Sign` | Sign (-1, 0, 1) | Value | Fixed32 |
|
||||
| `Fixed32 Min` | Minimum | A, B | Fixed32 |
|
||||
| `Fixed32 Max` | Maximum | A, B | Fixed32 |
|
||||
| `Fixed32 Clamp` | Clamp to range | Value, Min, Max | Fixed32 |
|
||||
| `Fixed32 Lerp` | Linear interpolation | A, B, T | Fixed32 |
|
||||
|
||||
### Example: Lockstep Movement Speed Calculation
|
||||
|
||||
<div class="bp-graph" data-connections='[{"from":"f32-speed","to":"f32-mul-a","type":"fixed32"},{"from":"f32-dt","to":"f32-mul-b","type":"fixed32"},{"from":"f32-mul-result","to":"f32-tofloat","type":"fixed32"}]'>
|
||||
<svg class="bp-connections"></svg>
|
||||
<div class="bp-node" style="position: absolute; left: 20px; top: 10px; width: 150px;">
|
||||
<div class="bp-node-header math" style="background: #9C27B0;">Fixed32 From Float</div>
|
||||
<div class="bp-node-body">
|
||||
<div class="bp-pin-row input">
|
||||
<span class="bp-pin"><svg width="12" height="12"><circle cx="6" cy="6" r="4" fill="none" stroke="#7ecd32" stroke-width="2"/></svg></span>
|
||||
<span class="bp-pin-label">Value</span>
|
||||
<span class="bp-pin-value">5.0</span>
|
||||
</div>
|
||||
<div class="bp-pin-row output">
|
||||
<span class="bp-pin" data-pin="f32-speed"><svg width="12" height="12"><circle cx="6" cy="6" r="4" fill="#9C27B0"/></svg></span>
|
||||
<span class="bp-pin-label">Speed</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bp-node" style="position: absolute; left: 20px; top: 160px; width: 150px;">
|
||||
<div class="bp-node-header math" style="background: #9C27B0;">Fixed32 From Float</div>
|
||||
<div class="bp-node-body">
|
||||
<div class="bp-pin-row input">
|
||||
<span class="bp-pin"><svg width="12" height="12"><circle cx="6" cy="6" r="4" fill="none" stroke="#7ecd32" stroke-width="2"/></svg></span>
|
||||
<span class="bp-pin-label">Value</span>
|
||||
<span class="bp-pin-value">0.016</span>
|
||||
</div>
|
||||
<div class="bp-pin-row output">
|
||||
<span class="bp-pin" data-pin="f32-dt"><svg width="12" height="12"><circle cx="6" cy="6" r="4" fill="#9C27B0"/></svg></span>
|
||||
<span class="bp-pin-label">DeltaTime</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bp-node" style="position: absolute; left: 240px; top: 75px; width: 120px;">
|
||||
<div class="bp-node-header math" style="background: #9C27B0;">Fixed32 *</div>
|
||||
<div class="bp-node-body">
|
||||
<div class="bp-pin-row input">
|
||||
<span class="bp-pin" data-pin="f32-mul-a"><svg width="12" height="12"><circle cx="6" cy="6" r="4" fill="#9C27B0"/></svg></span>
|
||||
<span class="bp-pin-label">A</span>
|
||||
</div>
|
||||
<div class="bp-pin-row input">
|
||||
<span class="bp-pin" data-pin="f32-mul-b"><svg width="12" height="12"><circle cx="6" cy="6" r="4" fill="#9C27B0"/></svg></span>
|
||||
<span class="bp-pin-label">B</span>
|
||||
</div>
|
||||
<div class="bp-pin-row output">
|
||||
<span class="bp-pin" data-pin="f32-mul-result"><svg width="12" height="12"><circle cx="6" cy="6" r="4" fill="#9C27B0"/></svg></span>
|
||||
<span class="bp-pin-label">Result</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bp-node" style="position: absolute; left: 430px; top: 75px; width: 150px;">
|
||||
<div class="bp-node-header math" style="background: #9C27B0;">Fixed32 To Float</div>
|
||||
<div class="bp-node-body">
|
||||
<div class="bp-pin-row input">
|
||||
<span class="bp-pin" data-pin="f32-tofloat"><svg width="12" height="12"><circle cx="6" cy="6" r="4" fill="#9C27B0"/></svg></span>
|
||||
<span class="bp-pin-label">Fixed</span>
|
||||
</div>
|
||||
<div class="bp-pin-row output">
|
||||
<span class="bp-pin"><svg width="12" height="12"><circle cx="6" cy="6" r="4" fill="#7ecd32"/></svg></span>
|
||||
<span class="bp-pin-label">Float</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
---
|
||||
|
||||
## FixedVector2 Nodes (Fixed-Point Vectors)
|
||||
|
||||
Fixed-point vector operations for deterministic physics calculations, suitable for lockstep networking.
|
||||
|
||||
### Node List
|
||||
|
||||
| Node | Description | Inputs | Output |
|
||||
|------|-------------|--------|--------|
|
||||
| `Make FixedVector2` | Create from X, Y floats | X, Y | FixedVector2 |
|
||||
| `Break FixedVector2` | Decompose to X, Y floats | Vector | X, Y |
|
||||
| `FixedVector2 +` | Vector addition | A, B | FixedVector2 |
|
||||
| `FixedVector2 -` | Vector subtraction | A, B | FixedVector2 |
|
||||
| `FixedVector2 *` | Scale by Fixed32 | Vector, Scalar | FixedVector2 |
|
||||
| `FixedVector2 Negate` | Negate vector | Vector | FixedVector2 |
|
||||
| `FixedVector2 Length` | Get length | Vector | Fixed32 |
|
||||
| `FixedVector2 Normalize` | Normalize | Vector | FixedVector2 |
|
||||
| `FixedVector2 Dot` | Dot product | A, B | Fixed32 |
|
||||
| `FixedVector2 Cross` | 2D cross product | A, B | Fixed32 |
|
||||
| `FixedVector2 Distance` | Distance between points | A, B | Fixed32 |
|
||||
| `FixedVector2 Lerp` | Linear interpolation | A, B, T | FixedVector2 |
|
||||
|
||||
### Example: Deterministic Position Update
|
||||
|
||||
<div class="bp-graph" data-connections='[{"from":"fv2-pos","to":"fv2-add-a","type":"fixedvector2"},{"from":"fv2-vel","to":"fv2-add-b","type":"fixedvector2"}]'>
|
||||
<svg class="bp-connections"></svg>
|
||||
<div class="bp-node" style="position: absolute; left: 20px; top: 10px; width: 150px;">
|
||||
<div class="bp-node-header math" style="background: #673AB7;">Make FixedVector2</div>
|
||||
<div class="bp-node-body">
|
||||
<div class="bp-pin-row input">
|
||||
<span class="bp-pin"><svg width="12" height="12"><circle cx="6" cy="6" r="4" fill="none" stroke="#7ecd32" stroke-width="2"/></svg></span>
|
||||
<span class="bp-pin-label">X</span>
|
||||
<span class="bp-pin-value">10</span>
|
||||
</div>
|
||||
<div class="bp-pin-row input">
|
||||
<span class="bp-pin"><svg width="12" height="12"><circle cx="6" cy="6" r="4" fill="none" stroke="#7ecd32" stroke-width="2"/></svg></span>
|
||||
<span class="bp-pin-label">Y</span>
|
||||
<span class="bp-pin-value">20</span>
|
||||
</div>
|
||||
<div class="bp-pin-row output">
|
||||
<span class="bp-pin" data-pin="fv2-pos"><svg width="12" height="12"><circle cx="6" cy="6" r="4" fill="#673AB7"/></svg></span>
|
||||
<span class="bp-pin-label">Position</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bp-node" style="position: absolute; left: 20px; top: 180px; width: 150px;">
|
||||
<div class="bp-node-header math" style="background: #673AB7;">Make FixedVector2</div>
|
||||
<div class="bp-node-body">
|
||||
<div class="bp-pin-row input">
|
||||
<span class="bp-pin"><svg width="12" height="12"><circle cx="6" cy="6" r="4" fill="none" stroke="#7ecd32" stroke-width="2"/></svg></span>
|
||||
<span class="bp-pin-label">X</span>
|
||||
<span class="bp-pin-value">1</span>
|
||||
</div>
|
||||
<div class="bp-pin-row input">
|
||||
<span class="bp-pin"><svg width="12" height="12"><circle cx="6" cy="6" r="4" fill="none" stroke="#7ecd32" stroke-width="2"/></svg></span>
|
||||
<span class="bp-pin-label">Y</span>
|
||||
<span class="bp-pin-value">0</span>
|
||||
</div>
|
||||
<div class="bp-pin-row output">
|
||||
<span class="bp-pin" data-pin="fv2-vel"><svg width="12" height="12"><circle cx="6" cy="6" r="4" fill="#673AB7"/></svg></span>
|
||||
<span class="bp-pin-label">Velocity</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bp-node" style="position: absolute; left: 250px; top: 90px; width: 140px;">
|
||||
<div class="bp-node-header math" style="background: #673AB7;">FixedVector2 +</div>
|
||||
<div class="bp-node-body">
|
||||
<div class="bp-pin-row input">
|
||||
<span class="bp-pin" data-pin="fv2-add-a"><svg width="12" height="12"><circle cx="6" cy="6" r="4" fill="#673AB7"/></svg></span>
|
||||
<span class="bp-pin-label">A</span>
|
||||
</div>
|
||||
<div class="bp-pin-row input">
|
||||
<span class="bp-pin" data-pin="fv2-add-b"><svg width="12" height="12"><circle cx="6" cy="6" r="4" fill="#673AB7"/></svg></span>
|
||||
<span class="bp-pin-label">B</span>
|
||||
</div>
|
||||
<div class="bp-pin-row output">
|
||||
<span class="bp-pin"><svg width="12" height="12"><circle cx="6" cy="6" r="4" fill="#673AB7"/></svg></span>
|
||||
<span class="bp-pin-label">New Position</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
---
|
||||
|
||||
## Color Nodes
|
||||
|
||||
Color creation and manipulation nodes.
|
||||
|
||||
### Node List
|
||||
|
||||
| Node | Description | Inputs | Output |
|
||||
|------|-------------|--------|--------|
|
||||
| `Make Color` | Create from RGBA | R, G, B, A | Color |
|
||||
| `Break Color` | Decompose to RGBA | Color | R, G, B, A |
|
||||
| `Color From Hex` | Create from hex string | Hex | Color |
|
||||
| `Color To Hex` | Convert to hex string | Color | String |
|
||||
| `Color From HSL` | Create from HSL | H, S, L | Color |
|
||||
| `Color To HSL` | Convert to HSL | Color | H, S, L |
|
||||
| `Color Lerp` | Color interpolation | A, B, T | Color |
|
||||
| `Color Lighten` | Lighten | Color, Amount | Color |
|
||||
| `Color Darken` | Darken | Color, Amount | Color |
|
||||
| `Color Saturate` | Increase saturation | Color, Amount | Color |
|
||||
| `Color Desaturate` | Decrease saturation | Color, Amount | Color |
|
||||
| `Color Invert` | Invert | Color | Color |
|
||||
| `Color Grayscale` | Convert to grayscale | Color | Color |
|
||||
| `Color Luminance` | Get luminance | Color | Float |
|
||||
|
||||
### Color Constants
|
||||
|
||||
| Node | Value |
|
||||
|------|-------|
|
||||
| `Color White` | (1, 1, 1, 1) |
|
||||
| `Color Black` | (0, 0, 0, 1) |
|
||||
| `Color Red` | (1, 0, 0, 1) |
|
||||
| `Color Green` | (0, 1, 0, 1) |
|
||||
| `Color Blue` | (0, 0, 1, 1) |
|
||||
| `Color Transparent` | (0, 0, 0, 0) |
|
||||
|
||||
### Example: Color Transition Animation
|
||||
|
||||
<div class="bp-graph" data-connections='[{"from":"color-a","to":"color-lerp-a","type":"color"},{"from":"color-b","to":"color-lerp-b","type":"color"}]'>
|
||||
<svg class="bp-connections"></svg>
|
||||
<div class="bp-node" style="position: absolute; left: 20px; top: 10px; width: 120px;">
|
||||
<div class="bp-node-header math" style="background: #FF9800;">Color Red</div>
|
||||
<div class="bp-node-body">
|
||||
<div class="bp-pin-row output">
|
||||
<span class="bp-pin" data-pin="color-a"><svg width="12" height="12"><circle cx="6" cy="6" r="4" fill="#FF9800"/></svg></span>
|
||||
<span class="bp-pin-label">Color</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bp-node" style="position: absolute; left: 20px; top: 130px; width: 120px;">
|
||||
<div class="bp-node-header math" style="background: #FF9800;">Color Blue</div>
|
||||
<div class="bp-node-body">
|
||||
<div class="bp-pin-row output">
|
||||
<span class="bp-pin" data-pin="color-b"><svg width="12" height="12"><circle cx="6" cy="6" r="4" fill="#FF9800"/></svg></span>
|
||||
<span class="bp-pin-label">Color</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bp-node" style="position: absolute; left: 220px; top: 50px; width: 130px;">
|
||||
<div class="bp-node-header math" style="background: #FF9800;">Color Lerp</div>
|
||||
<div class="bp-node-body">
|
||||
<div class="bp-pin-row input">
|
||||
<span class="bp-pin" data-pin="color-lerp-a"><svg width="12" height="12"><circle cx="6" cy="6" r="4" fill="#FF9800"/></svg></span>
|
||||
<span class="bp-pin-label">A</span>
|
||||
</div>
|
||||
<div class="bp-pin-row input">
|
||||
<span class="bp-pin" data-pin="color-lerp-b"><svg width="12" height="12"><circle cx="6" cy="6" r="4" fill="#FF9800"/></svg></span>
|
||||
<span class="bp-pin-label">B</span>
|
||||
</div>
|
||||
<div class="bp-pin-row input">
|
||||
<span class="bp-pin"><svg width="12" height="12"><circle cx="6" cy="6" r="4" fill="none" stroke="#7ecd32" stroke-width="2"/></svg></span>
|
||||
<span class="bp-pin-label">T</span>
|
||||
<span class="bp-pin-value">0.5</span>
|
||||
</div>
|
||||
<div class="bp-pin-row output">
|
||||
<span class="bp-pin"><svg width="12" height="12"><circle cx="6" cy="6" r="4" fill="#FF9800"/></svg></span>
|
||||
<span class="bp-pin-label">Result</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
### Example: Create Color from Hex
|
||||
|
||||
<div class="bp-graph" data-connections='[{"from":"hex-color","to":"break-color","type":"color"}]'>
|
||||
<svg class="bp-connections"></svg>
|
||||
<div class="bp-node" style="position: absolute; left: 20px; top: 30px; width: 150px;">
|
||||
<div class="bp-node-header math" style="background: #FF9800;">Color From Hex</div>
|
||||
<div class="bp-node-body">
|
||||
<div class="bp-pin-row input">
|
||||
<span class="bp-pin"><svg width="12" height="12"><circle cx="6" cy="6" r="4" fill="none" stroke="#e060e0" stroke-width="2"/></svg></span>
|
||||
<span class="bp-pin-label">Hex</span>
|
||||
<span class="bp-pin-value">"#FF5722"</span>
|
||||
</div>
|
||||
<div class="bp-pin-row output">
|
||||
<span class="bp-pin" data-pin="hex-color"><svg width="12" height="12"><circle cx="6" cy="6" r="4" fill="#FF9800"/></svg></span>
|
||||
<span class="bp-pin-label">Color</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bp-node" style="position: absolute; left: 250px; top: 20px; width: 130px;">
|
||||
<div class="bp-node-header math" style="background: #FF9800;">Break Color</div>
|
||||
<div class="bp-node-body">
|
||||
<div class="bp-pin-row input">
|
||||
<span class="bp-pin" data-pin="break-color"><svg width="12" height="12"><circle cx="6" cy="6" r="4" fill="#FF9800"/></svg></span>
|
||||
<span class="bp-pin-label">Color</span>
|
||||
</div>
|
||||
<div class="bp-pin-row output">
|
||||
<span class="bp-pin"><svg width="12" height="12"><circle cx="6" cy="6" r="4" fill="#7ecd32"/></svg></span>
|
||||
<span class="bp-pin-label">R</span>
|
||||
</div>
|
||||
<div class="bp-pin-row output">
|
||||
<span class="bp-pin"><svg width="12" height="12"><circle cx="6" cy="6" r="4" fill="#7ecd32"/></svg></span>
|
||||
<span class="bp-pin-label">G</span>
|
||||
</div>
|
||||
<div class="bp-pin-row output">
|
||||
<span class="bp-pin"><svg width="12" height="12"><circle cx="6" cy="6" r="4" fill="#7ecd32"/></svg></span>
|
||||
<span class="bp-pin-label">B</span>
|
||||
</div>
|
||||
<div class="bp-pin-row output">
|
||||
<span class="bp-pin"><svg width="12" height="12"><circle cx="6" cy="6" r="4" fill="#7ecd32"/></svg></span>
|
||||
<span class="bp-pin-label">A</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
---
|
||||
|
||||
## Related Documentation
|
||||
|
||||
- [Blueprint Node Reference](/en/modules/blueprint/nodes) - Core blueprint nodes
|
||||
- [Blueprint Editor Guide](/en/modules/blueprint/editor-guide) - Editor usage
|
||||
- [Custom Nodes](/en/modules/blueprint/custom-nodes) - Create custom nodes
|
||||
79
docs/src/content/docs/en/modules/math/index.md
Normal file
79
docs/src/content/docs/en/modules/math/index.md
Normal file
@@ -0,0 +1,79 @@
|
||||
---
|
||||
title: "Math Library"
|
||||
description: "ESEngine Math Library - Vector2, Fixed32, FixedVector2, Color and other math types"
|
||||
---
|
||||
|
||||
The `@esengine/ecs-framework-math` module provides common math types and operations for game development.
|
||||
|
||||
## Core Types
|
||||
|
||||
| Type | Description |
|
||||
|------|-------------|
|
||||
| `Vector2` | 2D floating-point vector for position, velocity, direction |
|
||||
| `Fixed32` | Q16.16 fixed-point number for deterministic lockstep calculations |
|
||||
| `FixedVector2` | 2D fixed-point vector for deterministic physics |
|
||||
| `Color` | RGBA color |
|
||||
|
||||
## Features
|
||||
|
||||
### Vector2
|
||||
|
||||
- Addition, subtraction, scaling
|
||||
- Dot product, cross product
|
||||
- Length, normalization
|
||||
- Distance, interpolation
|
||||
- Rotation, angle conversion
|
||||
|
||||
### Fixed32 Fixed-Point Numbers
|
||||
|
||||
Designed for lockstep networking games, ensuring cross-platform calculation consistency:
|
||||
|
||||
- Basic operations: add, subtract, multiply, divide
|
||||
- Math functions: absolute value, square root, rounding
|
||||
- Comparison, clamping, interpolation
|
||||
- Constants: 0, 1, 0.5, PI, 2*PI
|
||||
|
||||
### Color
|
||||
|
||||
- RGB/RGBA creation and decomposition
|
||||
- Hex string conversion
|
||||
- HSL color space conversion
|
||||
- Color operations: lighten, darken, saturation adjustment
|
||||
- Color blending and interpolation
|
||||
|
||||
## Blueprint Support
|
||||
|
||||
The math library provides rich blueprint nodes, see:
|
||||
|
||||
- [Math Blueprint Nodes](/en/modules/math/blueprint-nodes)
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
pnpm add @esengine/ecs-framework-math
|
||||
```
|
||||
|
||||
## Basic Usage
|
||||
|
||||
```typescript
|
||||
import { Vector2, Fixed32, FixedVector2, Color } from '@esengine/ecs-framework-math';
|
||||
|
||||
// Vector2
|
||||
const pos = new Vector2(10, 20);
|
||||
const dir = pos.normalized();
|
||||
|
||||
// Fixed32 (lockstep)
|
||||
const speed = Fixed32.from(5.0);
|
||||
const dt = Fixed32.from(0.016);
|
||||
const distance = speed.mul(dt);
|
||||
|
||||
// FixedVector2
|
||||
const fixedPos = FixedVector2.from(10, 20);
|
||||
const fixedVel = FixedVector2.from(1, 0);
|
||||
const newPos = fixedPos.add(fixedVel);
|
||||
|
||||
// Color
|
||||
const red = Color.RED;
|
||||
const blue = Color.BLUE;
|
||||
const purple = Color.lerp(red, blue, 0.5);
|
||||
```
|
||||
@@ -7,37 +7,26 @@ description: "在 Cocos Creator 中使用蓝图可视化脚本系统"
|
||||
|
||||
## 安装扩展
|
||||
|
||||
### 1. 复制扩展到项目
|
||||
### 1. 下载扩展
|
||||
|
||||
将 `cocos-node-editor` 扩展复制到你的 Cocos Creator 项目的 `extensions` 目录:
|
||||
从发布页面下载 `cocos-node-editor.zip` 扩展包。
|
||||
|
||||
```
|
||||
your-project/
|
||||
├── assets/
|
||||
├── extensions/
|
||||
│ └── cocos-node-editor/ # 蓝图编辑器扩展
|
||||
└── ...
|
||||
```
|
||||
|
||||
### 2. 安装依赖
|
||||
|
||||
在扩展目录中安装依赖:
|
||||
|
||||
```bash
|
||||
cd extensions/cocos-node-editor
|
||||
npm install
|
||||
```
|
||||
|
||||
### 3. 启用扩展
|
||||
### 2. 导入扩展
|
||||
|
||||
1. 打开 Cocos Creator
|
||||
2. 进入 **扩展 → 扩展管理器**
|
||||
3. 找到 `cocos-node-editor` 并启用
|
||||
3. 点击 **导入扩展包** 按钮
|
||||
4. 选择下载的 `cocos-node-editor.zip` 文件
|
||||
5. 导入后启用扩展
|
||||
|
||||
## 打开蓝图编辑器
|
||||
|
||||
通过菜单 **面板 → Node Editor** 打开蓝图编辑器面板。
|
||||
|
||||
### 首次打开 - 安装依赖
|
||||
|
||||
首次打开面板时,插件会检测项目中是否安装了 `@esengine/blueprint` 依赖包。如果未安装,会显示 **"缺少必要的依赖包"** 提示,点击 **"安装依赖"** 按钮即可自动安装。
|
||||
|
||||
## 编辑器界面
|
||||
|
||||
### 工具栏
|
||||
@@ -110,89 +99,22 @@ npm install
|
||||
|
||||
## 在游戏中运行蓝图
|
||||
|
||||
使用 ECS 系统方式管理和执行蓝图。
|
||||
`@esengine/blueprint` 包已提供完整的 ECS 集成,包括 `BlueprintComponent` 和 `BlueprintSystem`,可以直接使用。
|
||||
|
||||
### 1. 定义蓝图组件
|
||||
### 1. 添加蓝图系统到场景
|
||||
|
||||
```typescript
|
||||
import { Component, ECSComponent, Property, Serialize } from '@esengine/ecs-framework';
|
||||
import type { BlueprintAsset } from '@esengine/blueprint';
|
||||
import { BlueprintSystem } from '@esengine/blueprint';
|
||||
|
||||
@ECSComponent('Blueprint')
|
||||
export class BlueprintComponent extends Component {
|
||||
@Serialize()
|
||||
@Property({ type: 'asset', label: 'Blueprint Asset' })
|
||||
blueprintPath: string = '';
|
||||
|
||||
@Serialize()
|
||||
@Property({ type: 'boolean', label: 'Auto Start' })
|
||||
autoStart: boolean = true;
|
||||
|
||||
// 运行时数据(不序列化)
|
||||
blueprintAsset: BlueprintAsset | null = null;
|
||||
vm: BlueprintVM | null = null;
|
||||
isStarted: boolean = false;
|
||||
}
|
||||
// 在场景初始化时添加蓝图系统
|
||||
scene.addSystem(new BlueprintSystem());
|
||||
```
|
||||
|
||||
### 2. 创建蓝图执行系统
|
||||
|
||||
```typescript
|
||||
import { EntitySystem, Matcher, Entity } from '@esengine/ecs-framework';
|
||||
import {
|
||||
BlueprintVM,
|
||||
validateBlueprintAsset
|
||||
} from '@esengine/blueprint';
|
||||
import { BlueprintComponent } from './BlueprintComponent';
|
||||
|
||||
export class BlueprintExecutionSystem extends EntitySystem {
|
||||
constructor() {
|
||||
super(Matcher.empty().all(BlueprintComponent));
|
||||
}
|
||||
|
||||
protected override process(entities: readonly Entity[]): void {
|
||||
const dt = Time.deltaTime;
|
||||
|
||||
for (const entity of entities) {
|
||||
const bp = entity.getComponent(BlueprintComponent)!;
|
||||
|
||||
// 跳过没有蓝图资产的实体
|
||||
if (!bp.blueprintAsset) continue;
|
||||
|
||||
// 初始化 VM
|
||||
if (!bp.vm) {
|
||||
bp.vm = new BlueprintVM(bp.blueprintAsset, entity, this.scene!);
|
||||
}
|
||||
|
||||
// 自动启动
|
||||
if (bp.autoStart && !bp.isStarted) {
|
||||
bp.vm.start();
|
||||
bp.isStarted = true;
|
||||
}
|
||||
|
||||
// 更新蓝图
|
||||
if (bp.isStarted) {
|
||||
bp.vm.tick(dt);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override onRemoved(entity: Entity): void {
|
||||
const bp = entity.getComponent(BlueprintComponent);
|
||||
if (bp?.vm && bp.isStarted) {
|
||||
bp.vm.stop();
|
||||
bp.vm = null;
|
||||
bp.isStarted = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 加载蓝图并添加到实体
|
||||
### 2. 加载蓝图并添加到实体
|
||||
|
||||
```typescript
|
||||
import { resources, JsonAsset } from 'cc';
|
||||
import { validateBlueprintAsset } from '@esengine/blueprint';
|
||||
import { BlueprintComponent, validateBlueprintAsset, BlueprintAsset } from '@esengine/blueprint';
|
||||
|
||||
// 加载蓝图资产
|
||||
async function loadBlueprint(path: string): Promise<BlueprintAsset | null> {
|
||||
@@ -227,12 +149,22 @@ async function createBlueprintEntity(scene: IScene, blueprintPath: string): Prom
|
||||
}
|
||||
```
|
||||
|
||||
### 4. 注册系统到场景
|
||||
### BlueprintComponent 属性
|
||||
|
||||
```typescript
|
||||
// 在场景初始化时
|
||||
scene.addSystem(new BlueprintExecutionSystem());
|
||||
```
|
||||
| 属性 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `blueprintAsset` | `BlueprintAsset \| null` | 蓝图资产数据 |
|
||||
| `blueprintPath` | `string` | 蓝图资产路径(用于序列化) |
|
||||
| `autoStart` | `boolean` | 是否自动开始执行(默认 `true`) |
|
||||
| `debug` | `boolean` | 是否启用调试模式 |
|
||||
|
||||
### BlueprintComponent 方法
|
||||
|
||||
| 方法 | 说明 |
|
||||
|------|------|
|
||||
| `start()` | 手动开始执行蓝图 |
|
||||
| `stop()` | 停止蓝图执行 |
|
||||
| `cleanup()` | 清理蓝图资源 |
|
||||
|
||||
## 创建自定义节点
|
||||
|
||||
|
||||
@@ -3,6 +3,164 @@ title: "自定义节点"
|
||||
description: "创建自定义蓝图节点"
|
||||
---
|
||||
|
||||
## 蓝图装饰器
|
||||
|
||||
使用装饰器可以快速将 ECS 组件暴露为蓝图节点。
|
||||
|
||||
### @BlueprintComponent
|
||||
|
||||
将组件类标记为蓝图可用:
|
||||
|
||||
```typescript
|
||||
import { BlueprintComponent, BlueprintProperty } from '@esengine/blueprint';
|
||||
|
||||
@BlueprintComponent({
|
||||
title: '玩家控制器',
|
||||
category: 'gameplay',
|
||||
color: '#4a90d9',
|
||||
description: '控制玩家移动和交互'
|
||||
})
|
||||
class PlayerController extends Component {
|
||||
@BlueprintProperty({ displayName: '移动速度' })
|
||||
speed: number = 100;
|
||||
|
||||
@BlueprintProperty({ displayName: '跳跃高度' })
|
||||
jumpHeight: number = 200;
|
||||
}
|
||||
```
|
||||
|
||||
### @BlueprintProperty
|
||||
|
||||
将组件属性暴露为节点输入:
|
||||
|
||||
```typescript
|
||||
@BlueprintProperty({
|
||||
displayName: '生命值',
|
||||
description: '当前生命值',
|
||||
isInput: true,
|
||||
isOutput: true
|
||||
})
|
||||
health: number = 100;
|
||||
```
|
||||
|
||||
### @BlueprintArray
|
||||
|
||||
用于数组类型属性,支持复杂对象数组的编辑:
|
||||
|
||||
```typescript
|
||||
import { BlueprintArray, Schema } from '@esengine/blueprint';
|
||||
|
||||
interface Waypoint {
|
||||
position: { x: number; y: number };
|
||||
waitTime: number;
|
||||
speed: number;
|
||||
}
|
||||
|
||||
@BlueprintComponent({
|
||||
title: '巡逻路径',
|
||||
category: 'ai'
|
||||
})
|
||||
class PatrolPath extends Component {
|
||||
@BlueprintArray({
|
||||
displayName: '路径点',
|
||||
description: '巡逻路径的各个点',
|
||||
itemSchema: Schema.object({
|
||||
position: Schema.vector2({ defaultValue: { x: 0, y: 0 } }),
|
||||
waitTime: Schema.float({ min: 0, max: 10, defaultValue: 1.0 }),
|
||||
speed: Schema.float({ min: 0, max: 500, defaultValue: 100 })
|
||||
}),
|
||||
reorderable: true,
|
||||
exposeElementPorts: true,
|
||||
portNameTemplate: '路径点 {index1}'
|
||||
})
|
||||
waypoints: Waypoint[] = [];
|
||||
}
|
||||
```
|
||||
|
||||
## Schema 类型系统
|
||||
|
||||
Schema 用于定义复杂数据结构的类型信息,支持编辑器自动生成对应的 UI。
|
||||
|
||||
### 基础类型
|
||||
|
||||
```typescript
|
||||
import { Schema } from '@esengine/blueprint';
|
||||
|
||||
// 数字类型
|
||||
Schema.float({ min: 0, max: 100, defaultValue: 50, step: 0.1 })
|
||||
Schema.int({ min: 0, max: 10, defaultValue: 5 })
|
||||
|
||||
// 字符串
|
||||
Schema.string({ defaultValue: 'Hello', multiline: false, placeholder: '输入文本...' })
|
||||
|
||||
// 布尔
|
||||
Schema.boolean({ defaultValue: true })
|
||||
|
||||
// 向量
|
||||
Schema.vector2({ defaultValue: { x: 0, y: 0 } })
|
||||
Schema.vector3({ defaultValue: { x: 0, y: 0, z: 0 } })
|
||||
```
|
||||
|
||||
### 复合类型
|
||||
|
||||
```typescript
|
||||
// 对象
|
||||
Schema.object({
|
||||
name: Schema.string({ defaultValue: '' }),
|
||||
health: Schema.float({ min: 0, max: 100 }),
|
||||
position: Schema.vector2()
|
||||
})
|
||||
|
||||
// 数组
|
||||
Schema.array({
|
||||
items: Schema.float(),
|
||||
minItems: 0,
|
||||
maxItems: 10
|
||||
})
|
||||
|
||||
// 枚举
|
||||
Schema.enum({
|
||||
options: ['idle', 'walk', 'run', 'jump'],
|
||||
defaultValue: 'idle'
|
||||
})
|
||||
|
||||
// 引用
|
||||
Schema.ref({ refType: 'entity' })
|
||||
Schema.ref({ refType: 'asset', assetType: 'texture' })
|
||||
```
|
||||
|
||||
### 完整示例
|
||||
|
||||
```typescript
|
||||
@BlueprintComponent({ title: '敌人配置', category: 'ai' })
|
||||
class EnemyConfig extends Component {
|
||||
@BlueprintArray({
|
||||
displayName: '攻击模式',
|
||||
itemSchema: Schema.object({
|
||||
name: Schema.string({ defaultValue: '普通攻击' }),
|
||||
damage: Schema.float({ min: 0, max: 100, defaultValue: 10 }),
|
||||
cooldown: Schema.float({ min: 0, max: 10, defaultValue: 1 }),
|
||||
range: Schema.float({ min: 0, max: 500, defaultValue: 50 }),
|
||||
animation: Schema.string({ defaultValue: 'attack_01' })
|
||||
}),
|
||||
reorderable: true
|
||||
})
|
||||
attackPatterns: AttackPattern[] = [];
|
||||
|
||||
@BlueprintProperty({
|
||||
displayName: '巡逻区域',
|
||||
schema: Schema.object({
|
||||
center: Schema.vector2(),
|
||||
radius: Schema.float({ min: 0, defaultValue: 100 })
|
||||
})
|
||||
})
|
||||
patrolArea: { center: { x: number; y: number }; radius: number } = {
|
||||
center: { x: 0, y: 0 },
|
||||
radius: 100
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
## 定义节点模板
|
||||
|
||||
```typescript
|
||||
@@ -33,16 +191,11 @@ import { INodeExecutor, RegisterNode, BlueprintNode, ExecutionContext, Execution
|
||||
@RegisterNode(MyNodeTemplate)
|
||||
class MyNodeExecutor implements INodeExecutor {
|
||||
execute(node: BlueprintNode, context: ExecutionContext): ExecutionResult {
|
||||
// 获取输入(使用 evaluateInput)
|
||||
const value = context.evaluateInput(node.id, 'value', 0) as number;
|
||||
|
||||
// 执行逻辑
|
||||
const result = value * 2;
|
||||
|
||||
// 返回结果
|
||||
return {
|
||||
outputs: { result },
|
||||
nextExec: 'exec' // 继续执行
|
||||
nextExec: 'exec'
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -64,19 +217,10 @@ 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')) { ... }
|
||||
```
|
||||
|
||||
@@ -89,7 +233,7 @@ const PureNodeTemplate: BlueprintNodeTemplate = {
|
||||
type: 'GetDistance',
|
||||
title: 'Get Distance',
|
||||
category: 'math',
|
||||
isPure: true, // 标记为纯节点
|
||||
isPure: true,
|
||||
inputs: [
|
||||
{ name: 'a', type: 'vector2', direction: 'input' },
|
||||
{ name: 'b', type: 'vector2', direction: 'input' }
|
||||
@@ -99,59 +243,3 @@ const PureNodeTemplate: BlueprintNodeTemplate = {
|
||||
]
|
||||
};
|
||||
```
|
||||
|
||||
## 实际示例:ECS 组件操作节点
|
||||
|
||||
```typescript
|
||||
import type { Entity } from '@esengine/ecs-framework';
|
||||
import { BlueprintNodeTemplate, BlueprintNode } from '@esengine/blueprint';
|
||||
import { ExecutionContext, ExecutionResult } from '@esengine/blueprint';
|
||||
import { INodeExecutor, RegisterNode } from '@esengine/blueprint';
|
||||
|
||||
// 自定义治疗节点
|
||||
const HealEntityTemplate: BlueprintNodeTemplate = {
|
||||
type: 'HealEntity',
|
||||
title: 'Heal Entity',
|
||||
category: 'gameplay',
|
||||
color: '#22aa22',
|
||||
description: 'Heal an entity with HealthComponent',
|
||||
keywords: ['heal', 'health', 'restore'],
|
||||
menuPath: ['Gameplay', 'Combat', 'Heal Entity'],
|
||||
inputs: [
|
||||
{ name: 'exec', type: 'exec', displayName: '' },
|
||||
{ name: 'entity', type: 'entity', displayName: 'Target' },
|
||||
{ name: 'amount', type: 'float', displayName: 'Amount', defaultValue: 10 }
|
||||
],
|
||||
outputs: [
|
||||
{ name: 'exec', type: 'exec', displayName: '' },
|
||||
{ name: 'newHealth', type: 'float', displayName: 'New Health' }
|
||||
]
|
||||
};
|
||||
|
||||
@RegisterNode(HealEntityTemplate)
|
||||
class HealEntityExecutor implements INodeExecutor {
|
||||
execute(node: BlueprintNode, context: ExecutionContext): ExecutionResult {
|
||||
const entity = context.evaluateInput(node.id, 'entity', context.entity) as Entity;
|
||||
const amount = context.evaluateInput(node.id, 'amount', 10) as number;
|
||||
|
||||
if (!entity || entity.isDestroyed) {
|
||||
return { outputs: { newHealth: 0 }, nextExec: 'exec' };
|
||||
}
|
||||
|
||||
// 获取 HealthComponent
|
||||
const health = entity.components.find(c =>
|
||||
(c.constructor as any).__componentName__ === 'Health'
|
||||
) as any;
|
||||
|
||||
if (health) {
|
||||
health.current = Math.min(health.current + amount, health.max);
|
||||
return {
|
||||
outputs: { newHealth: health.current },
|
||||
nextExec: 'exec'
|
||||
};
|
||||
}
|
||||
|
||||
return { outputs: { newHealth: 0 }, nextExec: 'exec' };
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
@@ -13,7 +13,7 @@ description: "Cocos Creator 蓝图可视化脚本编辑器完整使用教程"
|
||||
|
||||
从 GitHub Release 下载最新版本(免费):
|
||||
|
||||
**[下载 Cocos Node Editor v1.1.0](https://github.com/esengine/esengine/releases/tag/cocos-node-editor-v1.1.0)**
|
||||
**[下载 Cocos Node Editor v1.2.0](https://github.com/esengine/esengine/releases/tag/cocos-node-editor-v1.2.0)**
|
||||
|
||||
> 技术交流 QQ 群:**481923584** | 官网:[esengine.cn](https://esengine.cn/)
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ description: "与 ECS 框架深度集成的可视化脚本系统"
|
||||
|
||||
Cocos Creator 蓝图编辑器插件(免费):
|
||||
|
||||
**[下载 Cocos Node Editor v1.1.0](https://github.com/esengine/esengine/releases/tag/cocos-node-editor-v1.1.0)**
|
||||
**[下载 Cocos Node Editor v1.2.0](https://github.com/esengine/esengine/releases/tag/cocos-node-editor-v1.2.0)**
|
||||
|
||||
> 技术交流 QQ 群:**481923584** | 官网:[esengine.cn](https://esengine.cn/)
|
||||
|
||||
|
||||
@@ -470,6 +470,12 @@ description: "蓝图内置 ECS 操作节点完整参考"
|
||||
| `Less` | A < B | Boolean |
|
||||
| `Less Or Equal` | A <= B | Boolean |
|
||||
|
||||
### 扩展数学节点
|
||||
|
||||
> **Vector2、Fixed32、FixedVector2、Color** 等高级数学节点由 `@esengine/ecs-framework-math` 模块提供。
|
||||
>
|
||||
> 详见:[数学库蓝图节点](/modules/math/blueprint-nodes)
|
||||
|
||||
### 示例:钳制数值
|
||||
|
||||
<div class="bp-graph" style="" data-connections='[{"from":"rand-result","to":"clamp-value","type":"float"}]'>
|
||||
@@ -535,6 +541,7 @@ description: "蓝图内置 ECS 操作节点完整参考"
|
||||
|
||||
## 相关文档
|
||||
|
||||
- [数学库蓝图节点](/modules/math/blueprint-nodes) - Vector2、Fixed32、Color 等数学节点
|
||||
- [蓝图编辑器指南](/modules/blueprint/editor-guide) - 学习如何使用编辑器
|
||||
- [自定义节点](/modules/blueprint/custom-nodes) - 创建自定义节点
|
||||
- [蓝图虚拟机](/modules/blueprint/vm) - 运行时 API
|
||||
|
||||
489
docs/src/content/docs/modules/math/blueprint-nodes.md
Normal file
489
docs/src/content/docs/modules/math/blueprint-nodes.md
Normal file
@@ -0,0 +1,489 @@
|
||||
---
|
||||
title: "数学库蓝图节点"
|
||||
description: "Math 模块提供的蓝图节点 - Vector2、Fixed32、FixedVector2、Color"
|
||||
---
|
||||
|
||||
本文档介绍 `@esengine/ecs-framework-math` 模块提供的蓝图节点。
|
||||
|
||||
> **注意**:这些节点需要安装 math 模块才能使用。
|
||||
|
||||
<script src="/js/blueprint-graph.js"></script>
|
||||
|
||||
## 引脚类型说明
|
||||
|
||||
<div class="bp-legend">
|
||||
<div class="bp-legend-item"><svg width="12" height="12"><circle cx="6" cy="6" r="4" fill="transparent" stroke="#7ecd32" stroke-width="2"/></svg> 浮点数 (Float)</div>
|
||||
<div class="bp-legend-item"><svg width="12" height="12"><circle cx="6" cy="6" r="4" fill="transparent" stroke="#2196F3" stroke-width="2"/></svg> Vector2</div>
|
||||
<div class="bp-legend-item"><svg width="12" height="12"><circle cx="6" cy="6" r="4" fill="transparent" stroke="#9C27B0" stroke-width="2"/></svg> Fixed32</div>
|
||||
<div class="bp-legend-item"><svg width="12" height="12"><circle cx="6" cy="6" r="4" fill="transparent" stroke="#673AB7" stroke-width="2"/></svg> FixedVector2</div>
|
||||
<div class="bp-legend-item"><svg width="12" height="12"><circle cx="6" cy="6" r="4" fill="transparent" stroke="#FF9800" stroke-width="2"/></svg> Color</div>
|
||||
</div>
|
||||
|
||||
---
|
||||
|
||||
## Vector2 节点
|
||||
|
||||
2D 向量操作,用于位置、速度、方向计算。
|
||||
|
||||
### 节点列表
|
||||
|
||||
| 节点 | 说明 | 输入 | 输出 |
|
||||
|------|------|------|------|
|
||||
| `Make Vector2` | 从 X, Y 创建 Vector2 | X, Y | Vector2 |
|
||||
| `Break Vector2` | 分解 Vector2 为 X, Y | Vector | X, Y |
|
||||
| `Vector2 +` | 向量加法 | A, B | Vector2 |
|
||||
| `Vector2 -` | 向量减法 | A, B | Vector2 |
|
||||
| `Vector2 *` | 向量缩放 | Vector, Scalar | Vector2 |
|
||||
| `Vector2 Length` | 获取向量长度 | Vector | Float |
|
||||
| `Vector2 Normalize` | 归一化为单位向量 | Vector | Vector2 |
|
||||
| `Vector2 Dot` | 点积 | A, B | Float |
|
||||
| `Vector2 Cross` | 2D 叉积 | A, B | Float |
|
||||
| `Vector2 Distance` | 两点距离 | A, B | Float |
|
||||
| `Vector2 Lerp` | 线性插值 | A, B, T | Vector2 |
|
||||
| `Vector2 Rotate` | 旋转(弧度) | Vector, Angle | Vector2 |
|
||||
| `Vector2 From Angle` | 从角度创建单位向量 | Angle | Vector2 |
|
||||
|
||||
### 示例:计算移动方向
|
||||
|
||||
从起点到终点的方向向量:
|
||||
|
||||
<div class="bp-graph" data-connections='[{"from":"v2-start","to":"v2-sub-a","type":"vector2"},{"from":"v2-end","to":"v2-sub-b","type":"vector2"},{"from":"v2-sub-result","to":"v2-norm-in","type":"vector2"}]'>
|
||||
<svg class="bp-connections"></svg>
|
||||
<div class="bp-node" style="position: absolute; left: 20px; top: 10px; width: 130px;">
|
||||
<div class="bp-node-header math" style="background: #2196F3;">Make Vector2</div>
|
||||
<div class="bp-node-body">
|
||||
<div class="bp-pin-row input">
|
||||
<span class="bp-pin"><svg width="12" height="12"><circle cx="6" cy="6" r="4" fill="none" stroke="#7ecd32" stroke-width="2"/></svg></span>
|
||||
<span class="bp-pin-label">X</span>
|
||||
<span class="bp-pin-value">0</span>
|
||||
</div>
|
||||
<div class="bp-pin-row input">
|
||||
<span class="bp-pin"><svg width="12" height="12"><circle cx="6" cy="6" r="4" fill="none" stroke="#7ecd32" stroke-width="2"/></svg></span>
|
||||
<span class="bp-pin-label">Y</span>
|
||||
<span class="bp-pin-value">0</span>
|
||||
</div>
|
||||
<div class="bp-pin-row output">
|
||||
<span class="bp-pin" data-pin="v2-start"><svg width="12" height="12"><circle cx="6" cy="6" r="4" fill="#2196F3"/></svg></span>
|
||||
<span class="bp-pin-label">Vector</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bp-node" style="position: absolute; left: 20px; top: 180px; width: 130px;">
|
||||
<div class="bp-node-header math" style="background: #2196F3;">Make Vector2</div>
|
||||
<div class="bp-node-body">
|
||||
<div class="bp-pin-row input">
|
||||
<span class="bp-pin"><svg width="12" height="12"><circle cx="6" cy="6" r="4" fill="none" stroke="#7ecd32" stroke-width="2"/></svg></span>
|
||||
<span class="bp-pin-label">X</span>
|
||||
<span class="bp-pin-value">100</span>
|
||||
</div>
|
||||
<div class="bp-pin-row input">
|
||||
<span class="bp-pin"><svg width="12" height="12"><circle cx="6" cy="6" r="4" fill="none" stroke="#7ecd32" stroke-width="2"/></svg></span>
|
||||
<span class="bp-pin-label">Y</span>
|
||||
<span class="bp-pin-value">50</span>
|
||||
</div>
|
||||
<div class="bp-pin-row output">
|
||||
<span class="bp-pin" data-pin="v2-end"><svg width="12" height="12"><circle cx="6" cy="6" r="4" fill="#2196F3"/></svg></span>
|
||||
<span class="bp-pin-label">Vector</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bp-node" style="position: absolute; left: 220px; top: 90px; width: 120px;">
|
||||
<div class="bp-node-header math" style="background: #2196F3;">Vector2 -</div>
|
||||
<div class="bp-node-body">
|
||||
<div class="bp-pin-row input">
|
||||
<span class="bp-pin" data-pin="v2-sub-b"><svg width="12" height="12"><circle cx="6" cy="6" r="4" fill="#2196F3"/></svg></span>
|
||||
<span class="bp-pin-label">A</span>
|
||||
</div>
|
||||
<div class="bp-pin-row input">
|
||||
<span class="bp-pin" data-pin="v2-sub-a"><svg width="12" height="12"><circle cx="6" cy="6" r="4" fill="#2196F3"/></svg></span>
|
||||
<span class="bp-pin-label">B</span>
|
||||
</div>
|
||||
<div class="bp-pin-row output">
|
||||
<span class="bp-pin" data-pin="v2-sub-result"><svg width="12" height="12"><circle cx="6" cy="6" r="4" fill="#2196F3"/></svg></span>
|
||||
<span class="bp-pin-label">Result</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bp-node" style="position: absolute; left: 400px; top: 55px; width: 140px;">
|
||||
<div class="bp-node-header math" style="background: #2196F3;">Vector2 Normalize</div>
|
||||
<div class="bp-node-body">
|
||||
<div class="bp-pin-row input">
|
||||
<span class="bp-pin" data-pin="v2-norm-in"><svg width="12" height="12"><circle cx="6" cy="6" r="4" fill="#2196F3"/></svg></span>
|
||||
<span class="bp-pin-label">Vector</span>
|
||||
</div>
|
||||
<div class="bp-pin-row output">
|
||||
<span class="bp-pin"><svg width="12" height="12"><circle cx="6" cy="6" r="4" fill="#2196F3"/></svg></span>
|
||||
<span class="bp-pin-label">Result</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
### 示例:圆周运动
|
||||
|
||||
使用角度和半径计算圆周位置:
|
||||
|
||||
<div class="bp-graph" data-connections='[{"from":"v2-angle-out","to":"v2-scale-vec","type":"vector2"},{"from":"v2-scale-result","to":"v2-add-b","type":"vector2"}]'>
|
||||
<svg class="bp-connections"></svg>
|
||||
<div class="bp-node" style="position: absolute; left: 20px; top: 40px; width: 150px;">
|
||||
<div class="bp-node-header math" style="background: #2196F3;">Vector2 From Angle</div>
|
||||
<div class="bp-node-body">
|
||||
<div class="bp-pin-row input">
|
||||
<span class="bp-pin"><svg width="12" height="12"><circle cx="6" cy="6" r="4" fill="none" stroke="#7ecd32" stroke-width="2"/></svg></span>
|
||||
<span class="bp-pin-label">Angle</span>
|
||||
<span class="bp-pin-value">1.57</span>
|
||||
</div>
|
||||
<div class="bp-pin-row output">
|
||||
<span class="bp-pin" data-pin="v2-angle-out"><svg width="12" height="12"><circle cx="6" cy="6" r="4" fill="#2196F3"/></svg></span>
|
||||
<span class="bp-pin-label">Vector</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bp-node" style="position: absolute; left: 230px; top: 40px; width: 120px;">
|
||||
<div class="bp-node-header math" style="background: #2196F3;">Vector2 *</div>
|
||||
<div class="bp-node-body">
|
||||
<div class="bp-pin-row input">
|
||||
<span class="bp-pin" data-pin="v2-scale-vec"><svg width="12" height="12"><circle cx="6" cy="6" r="4" fill="#2196F3"/></svg></span>
|
||||
<span class="bp-pin-label">Vector</span>
|
||||
</div>
|
||||
<div class="bp-pin-row input">
|
||||
<span class="bp-pin"><svg width="12" height="12"><circle cx="6" cy="6" r="4" fill="none" stroke="#7ecd32" stroke-width="2"/></svg></span>
|
||||
<span class="bp-pin-label">Scalar</span>
|
||||
<span class="bp-pin-value">50</span>
|
||||
</div>
|
||||
<div class="bp-pin-row output">
|
||||
<span class="bp-pin" data-pin="v2-scale-result"><svg width="12" height="12"><circle cx="6" cy="6" r="4" fill="#2196F3"/></svg></span>
|
||||
<span class="bp-pin-label">Result</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bp-node" style="position: absolute; left: 420px; top: 40px; width: 120px;">
|
||||
<div class="bp-node-header math" style="background: #2196F3;">Vector2 +</div>
|
||||
<div class="bp-node-body">
|
||||
<div class="bp-pin-row input">
|
||||
<span class="bp-pin"><svg width="12" height="12"><circle cx="6" cy="6" r="4" fill="none" stroke="#2196F3" stroke-width="2"/></svg></span>
|
||||
<span class="bp-pin-label">A (Center)</span>
|
||||
</div>
|
||||
<div class="bp-pin-row input">
|
||||
<span class="bp-pin" data-pin="v2-add-b"><svg width="12" height="12"><circle cx="6" cy="6" r="4" fill="#2196F3"/></svg></span>
|
||||
<span class="bp-pin-label">B</span>
|
||||
</div>
|
||||
<div class="bp-pin-row output">
|
||||
<span class="bp-pin"><svg width="12" height="12"><circle cx="6" cy="6" r="4" fill="#2196F3"/></svg></span>
|
||||
<span class="bp-pin-label">Position</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
---
|
||||
|
||||
## Fixed32 定点数节点
|
||||
|
||||
Q16.16 定点数运算,适用于帧同步网络游戏,保证跨平台计算一致性。
|
||||
|
||||
### 节点列表
|
||||
|
||||
| 节点 | 说明 | 输入 | 输出 |
|
||||
|------|------|------|------|
|
||||
| `Fixed32 From Float` | 从浮点数创建 | Float | Fixed32 |
|
||||
| `Fixed32 From Int` | 从整数创建 | Int | Fixed32 |
|
||||
| `Fixed32 To Float` | 转换为浮点数 | Fixed32 | Float |
|
||||
| `Fixed32 To Int` | 转换为整数 | Fixed32 | Int |
|
||||
| `Fixed32 +` | 加法 | A, B | Fixed32 |
|
||||
| `Fixed32 -` | 减法 | A, B | Fixed32 |
|
||||
| `Fixed32 *` | 乘法 | A, B | Fixed32 |
|
||||
| `Fixed32 /` | 除法 | A, B | Fixed32 |
|
||||
| `Fixed32 Abs` | 绝对值 | Value | Fixed32 |
|
||||
| `Fixed32 Sqrt` | 平方根 | Value | Fixed32 |
|
||||
| `Fixed32 Floor` | 向下取整 | Value | Fixed32 |
|
||||
| `Fixed32 Ceil` | 向上取整 | Value | Fixed32 |
|
||||
| `Fixed32 Round` | 四舍五入 | Value | Fixed32 |
|
||||
| `Fixed32 Sign` | 符号 (-1, 0, 1) | Value | Fixed32 |
|
||||
| `Fixed32 Min` | 最小值 | A, B | Fixed32 |
|
||||
| `Fixed32 Max` | 最大值 | A, B | Fixed32 |
|
||||
| `Fixed32 Clamp` | 钳制范围 | Value, Min, Max | Fixed32 |
|
||||
| `Fixed32 Lerp` | 线性插值 | A, B, T | Fixed32 |
|
||||
|
||||
### 示例:帧同步移动速度计算
|
||||
|
||||
<div class="bp-graph" data-connections='[{"from":"f32-speed","to":"f32-mul-a","type":"fixed32"},{"from":"f32-dt","to":"f32-mul-b","type":"fixed32"},{"from":"f32-mul-result","to":"f32-tofloat","type":"fixed32"}]'>
|
||||
<svg class="bp-connections"></svg>
|
||||
<div class="bp-node" style="position: absolute; left: 20px; top: 10px; width: 150px;">
|
||||
<div class="bp-node-header math" style="background: #9C27B0;">Fixed32 From Float</div>
|
||||
<div class="bp-node-body">
|
||||
<div class="bp-pin-row input">
|
||||
<span class="bp-pin"><svg width="12" height="12"><circle cx="6" cy="6" r="4" fill="none" stroke="#7ecd32" stroke-width="2"/></svg></span>
|
||||
<span class="bp-pin-label">Value</span>
|
||||
<span class="bp-pin-value">5.0</span>
|
||||
</div>
|
||||
<div class="bp-pin-row output">
|
||||
<span class="bp-pin" data-pin="f32-speed"><svg width="12" height="12"><circle cx="6" cy="6" r="4" fill="#9C27B0"/></svg></span>
|
||||
<span class="bp-pin-label">Speed</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bp-node" style="position: absolute; left: 20px; top: 160px; width: 150px;">
|
||||
<div class="bp-node-header math" style="background: #9C27B0;">Fixed32 From Float</div>
|
||||
<div class="bp-node-body">
|
||||
<div class="bp-pin-row input">
|
||||
<span class="bp-pin"><svg width="12" height="12"><circle cx="6" cy="6" r="4" fill="none" stroke="#7ecd32" stroke-width="2"/></svg></span>
|
||||
<span class="bp-pin-label">Value</span>
|
||||
<span class="bp-pin-value">0.016</span>
|
||||
</div>
|
||||
<div class="bp-pin-row output">
|
||||
<span class="bp-pin" data-pin="f32-dt"><svg width="12" height="12"><circle cx="6" cy="6" r="4" fill="#9C27B0"/></svg></span>
|
||||
<span class="bp-pin-label">DeltaTime</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bp-node" style="position: absolute; left: 240px; top: 75px; width: 120px;">
|
||||
<div class="bp-node-header math" style="background: #9C27B0;">Fixed32 *</div>
|
||||
<div class="bp-node-body">
|
||||
<div class="bp-pin-row input">
|
||||
<span class="bp-pin" data-pin="f32-mul-a"><svg width="12" height="12"><circle cx="6" cy="6" r="4" fill="#9C27B0"/></svg></span>
|
||||
<span class="bp-pin-label">A</span>
|
||||
</div>
|
||||
<div class="bp-pin-row input">
|
||||
<span class="bp-pin" data-pin="f32-mul-b"><svg width="12" height="12"><circle cx="6" cy="6" r="4" fill="#9C27B0"/></svg></span>
|
||||
<span class="bp-pin-label">B</span>
|
||||
</div>
|
||||
<div class="bp-pin-row output">
|
||||
<span class="bp-pin" data-pin="f32-mul-result"><svg width="12" height="12"><circle cx="6" cy="6" r="4" fill="#9C27B0"/></svg></span>
|
||||
<span class="bp-pin-label">Result</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bp-node" style="position: absolute; left: 430px; top: 75px; width: 150px;">
|
||||
<div class="bp-node-header math" style="background: #9C27B0;">Fixed32 To Float</div>
|
||||
<div class="bp-node-body">
|
||||
<div class="bp-pin-row input">
|
||||
<span class="bp-pin" data-pin="f32-tofloat"><svg width="12" height="12"><circle cx="6" cy="6" r="4" fill="#9C27B0"/></svg></span>
|
||||
<span class="bp-pin-label">Fixed</span>
|
||||
</div>
|
||||
<div class="bp-pin-row output">
|
||||
<span class="bp-pin"><svg width="12" height="12"><circle cx="6" cy="6" r="4" fill="#7ecd32"/></svg></span>
|
||||
<span class="bp-pin-label">Float</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
---
|
||||
|
||||
## FixedVector2 定点向量节点
|
||||
|
||||
定点向量运算,用于确定性物理计算,适用于帧同步。
|
||||
|
||||
### 节点列表
|
||||
|
||||
| 节点 | 说明 | 输入 | 输出 |
|
||||
|------|------|------|------|
|
||||
| `Make FixedVector2` | 从 X, Y 浮点数创建 | X, Y | FixedVector2 |
|
||||
| `Break FixedVector2` | 分解为 X, Y 浮点数 | Vector | X, Y |
|
||||
| `FixedVector2 +` | 向量加法 | A, B | FixedVector2 |
|
||||
| `FixedVector2 -` | 向量减法 | A, B | FixedVector2 |
|
||||
| `FixedVector2 *` | 按 Fixed32 缩放 | Vector, Scalar | FixedVector2 |
|
||||
| `FixedVector2 Negate` | 取反 | Vector | FixedVector2 |
|
||||
| `FixedVector2 Length` | 获取长度 | Vector | Fixed32 |
|
||||
| `FixedVector2 Normalize` | 归一化 | Vector | FixedVector2 |
|
||||
| `FixedVector2 Dot` | 点积 | A, B | Fixed32 |
|
||||
| `FixedVector2 Cross` | 2D 叉积 | A, B | Fixed32 |
|
||||
| `FixedVector2 Distance` | 两点距离 | A, B | Fixed32 |
|
||||
| `FixedVector2 Lerp` | 线性插值 | A, B, T | FixedVector2 |
|
||||
|
||||
### 示例:确定性位置更新
|
||||
|
||||
<div class="bp-graph" data-connections='[{"from":"fv2-pos","to":"fv2-add-a","type":"fixedvector2"},{"from":"fv2-vel","to":"fv2-add-b","type":"fixedvector2"}]'>
|
||||
<svg class="bp-connections"></svg>
|
||||
<div class="bp-node" style="position: absolute; left: 20px; top: 10px; width: 150px;">
|
||||
<div class="bp-node-header math" style="background: #673AB7;">Make FixedVector2</div>
|
||||
<div class="bp-node-body">
|
||||
<div class="bp-pin-row input">
|
||||
<span class="bp-pin"><svg width="12" height="12"><circle cx="6" cy="6" r="4" fill="none" stroke="#7ecd32" stroke-width="2"/></svg></span>
|
||||
<span class="bp-pin-label">X</span>
|
||||
<span class="bp-pin-value">10</span>
|
||||
</div>
|
||||
<div class="bp-pin-row input">
|
||||
<span class="bp-pin"><svg width="12" height="12"><circle cx="6" cy="6" r="4" fill="none" stroke="#7ecd32" stroke-width="2"/></svg></span>
|
||||
<span class="bp-pin-label">Y</span>
|
||||
<span class="bp-pin-value">20</span>
|
||||
</div>
|
||||
<div class="bp-pin-row output">
|
||||
<span class="bp-pin" data-pin="fv2-pos"><svg width="12" height="12"><circle cx="6" cy="6" r="4" fill="#673AB7"/></svg></span>
|
||||
<span class="bp-pin-label">Position</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bp-node" style="position: absolute; left: 20px; top: 180px; width: 150px;">
|
||||
<div class="bp-node-header math" style="background: #673AB7;">Make FixedVector2</div>
|
||||
<div class="bp-node-body">
|
||||
<div class="bp-pin-row input">
|
||||
<span class="bp-pin"><svg width="12" height="12"><circle cx="6" cy="6" r="4" fill="none" stroke="#7ecd32" stroke-width="2"/></svg></span>
|
||||
<span class="bp-pin-label">X</span>
|
||||
<span class="bp-pin-value">1</span>
|
||||
</div>
|
||||
<div class="bp-pin-row input">
|
||||
<span class="bp-pin"><svg width="12" height="12"><circle cx="6" cy="6" r="4" fill="none" stroke="#7ecd32" stroke-width="2"/></svg></span>
|
||||
<span class="bp-pin-label">Y</span>
|
||||
<span class="bp-pin-value">0</span>
|
||||
</div>
|
||||
<div class="bp-pin-row output">
|
||||
<span class="bp-pin" data-pin="fv2-vel"><svg width="12" height="12"><circle cx="6" cy="6" r="4" fill="#673AB7"/></svg></span>
|
||||
<span class="bp-pin-label">Velocity</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bp-node" style="position: absolute; left: 250px; top: 90px; width: 140px;">
|
||||
<div class="bp-node-header math" style="background: #673AB7;">FixedVector2 +</div>
|
||||
<div class="bp-node-body">
|
||||
<div class="bp-pin-row input">
|
||||
<span class="bp-pin" data-pin="fv2-add-a"><svg width="12" height="12"><circle cx="6" cy="6" r="4" fill="#673AB7"/></svg></span>
|
||||
<span class="bp-pin-label">A</span>
|
||||
</div>
|
||||
<div class="bp-pin-row input">
|
||||
<span class="bp-pin" data-pin="fv2-add-b"><svg width="12" height="12"><circle cx="6" cy="6" r="4" fill="#673AB7"/></svg></span>
|
||||
<span class="bp-pin-label">B</span>
|
||||
</div>
|
||||
<div class="bp-pin-row output">
|
||||
<span class="bp-pin"><svg width="12" height="12"><circle cx="6" cy="6" r="4" fill="#673AB7"/></svg></span>
|
||||
<span class="bp-pin-label">New Position</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
---
|
||||
|
||||
## Color 颜色节点
|
||||
|
||||
颜色创建与操作节点。
|
||||
|
||||
### 节点列表
|
||||
|
||||
| 节点 | 说明 | 输入 | 输出 |
|
||||
|------|------|------|------|
|
||||
| `Make Color` | 从 RGBA 创建 | R, G, B, A | Color |
|
||||
| `Break Color` | 分解为 RGBA | Color | R, G, B, A |
|
||||
| `Color From Hex` | 从十六进制字符串创建 | Hex | Color |
|
||||
| `Color To Hex` | 转换为十六进制字符串 | Color | String |
|
||||
| `Color From HSL` | 从 HSL 创建 | H, S, L | Color |
|
||||
| `Color To HSL` | 转换为 HSL | Color | H, S, L |
|
||||
| `Color Lerp` | 颜色插值 | A, B, T | Color |
|
||||
| `Color Lighten` | 提亮 | Color, Amount | Color |
|
||||
| `Color Darken` | 变暗 | Color, Amount | Color |
|
||||
| `Color Saturate` | 增加饱和度 | Color, Amount | Color |
|
||||
| `Color Desaturate` | 降低饱和度 | Color, Amount | Color |
|
||||
| `Color Invert` | 反色 | Color | Color |
|
||||
| `Color Grayscale` | 灰度化 | Color | Color |
|
||||
| `Color Luminance` | 获取亮度 | Color | Float |
|
||||
|
||||
### 颜色常量
|
||||
|
||||
| 节点 | 值 |
|
||||
|------|------|
|
||||
| `Color White` | (1, 1, 1, 1) |
|
||||
| `Color Black` | (0, 0, 0, 1) |
|
||||
| `Color Red` | (1, 0, 0, 1) |
|
||||
| `Color Green` | (0, 1, 0, 1) |
|
||||
| `Color Blue` | (0, 0, 1, 1) |
|
||||
| `Color Transparent` | (0, 0, 0, 0) |
|
||||
|
||||
### 示例:颜色过渡动画
|
||||
|
||||
<div class="bp-graph" data-connections='[{"from":"color-a","to":"color-lerp-a","type":"color"},{"from":"color-b","to":"color-lerp-b","type":"color"}]'>
|
||||
<svg class="bp-connections"></svg>
|
||||
<div class="bp-node" style="position: absolute; left: 20px; top: 10px; width: 120px;">
|
||||
<div class="bp-node-header math" style="background: #FF9800;">Color Red</div>
|
||||
<div class="bp-node-body">
|
||||
<div class="bp-pin-row output">
|
||||
<span class="bp-pin" data-pin="color-a"><svg width="12" height="12"><circle cx="6" cy="6" r="4" fill="#FF9800"/></svg></span>
|
||||
<span class="bp-pin-label">Color</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bp-node" style="position: absolute; left: 20px; top: 130px; width: 120px;">
|
||||
<div class="bp-node-header math" style="background: #FF9800;">Color Blue</div>
|
||||
<div class="bp-node-body">
|
||||
<div class="bp-pin-row output">
|
||||
<span class="bp-pin" data-pin="color-b"><svg width="12" height="12"><circle cx="6" cy="6" r="4" fill="#FF9800"/></svg></span>
|
||||
<span class="bp-pin-label">Color</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bp-node" style="position: absolute; left: 220px; top: 50px; width: 130px;">
|
||||
<div class="bp-node-header math" style="background: #FF9800;">Color Lerp</div>
|
||||
<div class="bp-node-body">
|
||||
<div class="bp-pin-row input">
|
||||
<span class="bp-pin" data-pin="color-lerp-a"><svg width="12" height="12"><circle cx="6" cy="6" r="4" fill="#FF9800"/></svg></span>
|
||||
<span class="bp-pin-label">A</span>
|
||||
</div>
|
||||
<div class="bp-pin-row input">
|
||||
<span class="bp-pin" data-pin="color-lerp-b"><svg width="12" height="12"><circle cx="6" cy="6" r="4" fill="#FF9800"/></svg></span>
|
||||
<span class="bp-pin-label">B</span>
|
||||
</div>
|
||||
<div class="bp-pin-row input">
|
||||
<span class="bp-pin"><svg width="12" height="12"><circle cx="6" cy="6" r="4" fill="none" stroke="#7ecd32" stroke-width="2"/></svg></span>
|
||||
<span class="bp-pin-label">T</span>
|
||||
<span class="bp-pin-value">0.5</span>
|
||||
</div>
|
||||
<div class="bp-pin-row output">
|
||||
<span class="bp-pin"><svg width="12" height="12"><circle cx="6" cy="6" r="4" fill="#FF9800"/></svg></span>
|
||||
<span class="bp-pin-label">Result</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
### 示例:从 Hex 创建颜色
|
||||
|
||||
<div class="bp-graph" data-connections='[{"from":"hex-color","to":"break-color","type":"color"}]'>
|
||||
<svg class="bp-connections"></svg>
|
||||
<div class="bp-node" style="position: absolute; left: 20px; top: 30px; width: 150px;">
|
||||
<div class="bp-node-header math" style="background: #FF9800;">Color From Hex</div>
|
||||
<div class="bp-node-body">
|
||||
<div class="bp-pin-row input">
|
||||
<span class="bp-pin"><svg width="12" height="12"><circle cx="6" cy="6" r="4" fill="none" stroke="#e060e0" stroke-width="2"/></svg></span>
|
||||
<span class="bp-pin-label">Hex</span>
|
||||
<span class="bp-pin-value">"#FF5722"</span>
|
||||
</div>
|
||||
<div class="bp-pin-row output">
|
||||
<span class="bp-pin" data-pin="hex-color"><svg width="12" height="12"><circle cx="6" cy="6" r="4" fill="#FF9800"/></svg></span>
|
||||
<span class="bp-pin-label">Color</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bp-node" style="position: absolute; left: 250px; top: 20px; width: 130px;">
|
||||
<div class="bp-node-header math" style="background: #FF9800;">Break Color</div>
|
||||
<div class="bp-node-body">
|
||||
<div class="bp-pin-row input">
|
||||
<span class="bp-pin" data-pin="break-color"><svg width="12" height="12"><circle cx="6" cy="6" r="4" fill="#FF9800"/></svg></span>
|
||||
<span class="bp-pin-label">Color</span>
|
||||
</div>
|
||||
<div class="bp-pin-row output">
|
||||
<span class="bp-pin"><svg width="12" height="12"><circle cx="6" cy="6" r="4" fill="#7ecd32"/></svg></span>
|
||||
<span class="bp-pin-label">R</span>
|
||||
</div>
|
||||
<div class="bp-pin-row output">
|
||||
<span class="bp-pin"><svg width="12" height="12"><circle cx="6" cy="6" r="4" fill="#7ecd32"/></svg></span>
|
||||
<span class="bp-pin-label">G</span>
|
||||
</div>
|
||||
<div class="bp-pin-row output">
|
||||
<span class="bp-pin"><svg width="12" height="12"><circle cx="6" cy="6" r="4" fill="#7ecd32"/></svg></span>
|
||||
<span class="bp-pin-label">B</span>
|
||||
</div>
|
||||
<div class="bp-pin-row output">
|
||||
<span class="bp-pin"><svg width="12" height="12"><circle cx="6" cy="6" r="4" fill="#7ecd32"/></svg></span>
|
||||
<span class="bp-pin-label">A</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
---
|
||||
|
||||
## 相关文档
|
||||
|
||||
- [蓝图节点参考](/modules/blueprint/nodes) - 核心蓝图节点
|
||||
- [蓝图编辑器指南](/modules/blueprint/editor-guide) - 编辑器使用方法
|
||||
- [自定义节点](/modules/blueprint/custom-nodes) - 创建自定义节点
|
||||
79
docs/src/content/docs/modules/math/index.md
Normal file
79
docs/src/content/docs/modules/math/index.md
Normal file
@@ -0,0 +1,79 @@
|
||||
---
|
||||
title: "数学库"
|
||||
description: "ESEngine 数学库 - Vector2, Fixed32, FixedVector2, Color 等数学类型"
|
||||
---
|
||||
|
||||
`@esengine/ecs-framework-math` 模块提供游戏开发常用的数学类型和运算。
|
||||
|
||||
## 核心类型
|
||||
|
||||
| 类型 | 说明 |
|
||||
|------|------|
|
||||
| `Vector2` | 2D 浮点向量,用于位置、速度、方向 |
|
||||
| `Fixed32` | Q16.16 定点数,用于帧同步确定性计算 |
|
||||
| `FixedVector2` | 2D 定点向量,用于确定性物理 |
|
||||
| `Color` | RGBA 颜色 |
|
||||
|
||||
## 功能特性
|
||||
|
||||
### Vector2
|
||||
|
||||
- 加法、减法、缩放
|
||||
- 点积、叉积
|
||||
- 长度、归一化
|
||||
- 距离、插值
|
||||
- 旋转、角度转换
|
||||
|
||||
### Fixed32 定点数
|
||||
|
||||
专为帧同步网络游戏设计,保证跨平台计算一致性:
|
||||
|
||||
- 基本运算:加、减、乘、除
|
||||
- 数学函数:绝对值、平方根、取整
|
||||
- 比较、钳制、插值
|
||||
- 常量:0、1、0.5、π、2π
|
||||
|
||||
### Color 颜色
|
||||
|
||||
- RGB/RGBA 创建与分解
|
||||
- Hex 十六进制转换
|
||||
- HSL 色彩空间转换
|
||||
- 颜色操作:提亮、变暗、饱和度调整
|
||||
- 颜色混合与插值
|
||||
|
||||
## 蓝图支持
|
||||
|
||||
数学库提供了丰富的蓝图节点,详见:
|
||||
|
||||
- [数学库蓝图节点](/modules/math/blueprint-nodes)
|
||||
|
||||
## 安装
|
||||
|
||||
```bash
|
||||
pnpm add @esengine/ecs-framework-math
|
||||
```
|
||||
|
||||
## 基本用法
|
||||
|
||||
```typescript
|
||||
import { Vector2, Fixed32, FixedVector2, Color } from '@esengine/ecs-framework-math';
|
||||
|
||||
// Vector2
|
||||
const pos = new Vector2(10, 20);
|
||||
const dir = pos.normalized();
|
||||
|
||||
// Fixed32 (帧同步)
|
||||
const speed = Fixed32.from(5.0);
|
||||
const dt = Fixed32.from(0.016);
|
||||
const distance = speed.mul(dt);
|
||||
|
||||
// FixedVector2
|
||||
const fixedPos = FixedVector2.from(10, 20);
|
||||
const fixedVel = FixedVector2.from(1, 0);
|
||||
const newPos = fixedPos.add(fixedVel);
|
||||
|
||||
// Color
|
||||
const red = Color.RED;
|
||||
const blue = Color.BLUE;
|
||||
const purple = Color.lerp(red, blue, 0.5);
|
||||
```
|
||||
@@ -296,6 +296,10 @@ nav.sidebar-content ul li a[aria-current="page"] {
|
||||
.bp-conn.component { stroke: #7030c0; }
|
||||
.bp-conn.array { stroke: #7030c0; }
|
||||
.bp-conn.any { stroke: #707070; }
|
||||
.bp-conn.vector2 { stroke: #2196F3; }
|
||||
.bp-conn.fixed32 { stroke: #9C27B0; }
|
||||
.bp-conn.fixedvector2 { stroke: #673AB7; }
|
||||
.bp-conn.color { stroke: #FF9800; }
|
||||
|
||||
/* ==================== Node Container ==================== */
|
||||
.bp-node {
|
||||
|
||||
@@ -1,5 +1,21 @@
|
||||
# @esengine/blueprint
|
||||
|
||||
## 4.5.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- [#447](https://github.com/esengine/esengine/pull/447) [`4e66bd8`](https://github.com/esengine/esengine/commit/4e66bd8e2be80b366a7723dcc48b99df0457aed4) Thanks [@esengine](https://github.com/esengine)! - feat(blueprint): add Schema type system and @BlueprintArray decorator
|
||||
- Add `Schema` fluent API for defining complex data types:
|
||||
- Primitive types: `Schema.float()`, `Schema.int()`, `Schema.string()`, `Schema.boolean()`, `Schema.vector2()`, `Schema.vector3()`
|
||||
- Composite types: `Schema.object()`, `Schema.array()`, `Schema.enum()`, `Schema.ref()`
|
||||
- Support for constraints: `min`, `max`, `step`, `defaultValue`, `placeholder`, etc.
|
||||
- Add `@BlueprintArray` decorator for array properties:
|
||||
- `itemSchema`: Define schema for array items using Schema API
|
||||
- `reorderable`: Allow drag-and-drop reordering
|
||||
- `exposeElementPorts`: Create individual ports for each array element
|
||||
- `portNameTemplate`: Custom naming for element ports (e.g., "Waypoint {index1}")
|
||||
- Update documentation with examples and usage guide
|
||||
|
||||
## 4.4.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@esengine/blueprint",
|
||||
"version": "4.4.0",
|
||||
"version": "4.5.0",
|
||||
"description": "Visual scripting system - works with any ECS framework (ESEngine, Cocos, Laya, etc.)",
|
||||
"main": "dist/index.js",
|
||||
"module": "dist/index.js",
|
||||
|
||||
@@ -561,3 +561,325 @@ export class SignExecutor implements INodeExecutor {
|
||||
return { outputs: { result: Math.sign(value) } };
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Wrap Node (循环限制节点)
|
||||
// ============================================================================
|
||||
|
||||
export const WrapTemplate: BlueprintNodeTemplate = {
|
||||
type: 'Wrap',
|
||||
title: 'Wrap',
|
||||
category: 'math',
|
||||
color: '#4CAF50',
|
||||
description: 'Wraps value to stay within min and max range (将值循环限制在范围内)',
|
||||
keywords: ['wrap', 'loop', 'cycle', 'range', 'math'],
|
||||
isPure: true,
|
||||
inputs: [
|
||||
{ name: 'value', type: 'float', displayName: 'Value', defaultValue: 0 },
|
||||
{ name: 'min', type: 'float', displayName: 'Min', defaultValue: 0 },
|
||||
{ name: 'max', type: 'float', displayName: 'Max', defaultValue: 1 }
|
||||
],
|
||||
outputs: [
|
||||
{ name: 'result', type: 'float', displayName: 'Result' }
|
||||
]
|
||||
};
|
||||
|
||||
@RegisterNode(WrapTemplate)
|
||||
export class WrapExecutor implements INodeExecutor {
|
||||
execute(node: BlueprintNode, context: ExecutionContext): ExecutionResult {
|
||||
const value = Number(context.evaluateInput(node.id, 'value', 0));
|
||||
const min = Number(context.evaluateInput(node.id, 'min', 0));
|
||||
const max = Number(context.evaluateInput(node.id, 'max', 1));
|
||||
const range = max - min;
|
||||
if (range <= 0) return { outputs: { result: min } };
|
||||
const wrapped = ((value - min) % range + range) % range + min;
|
||||
return { outputs: { result: wrapped } };
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Sin Node (正弦节点)
|
||||
// ============================================================================
|
||||
|
||||
export const SinTemplate: BlueprintNodeTemplate = {
|
||||
type: 'Sin',
|
||||
title: 'Sin',
|
||||
category: 'math',
|
||||
color: '#4CAF50',
|
||||
description: 'Returns the sine of angle in radians (返回弧度角的正弦值)',
|
||||
keywords: ['sin', 'sine', 'trig', 'math'],
|
||||
isPure: true,
|
||||
inputs: [
|
||||
{ name: 'radians', type: 'float', displayName: 'Radians', defaultValue: 0 }
|
||||
],
|
||||
outputs: [
|
||||
{ name: 'result', type: 'float', displayName: 'Result' }
|
||||
]
|
||||
};
|
||||
|
||||
@RegisterNode(SinTemplate)
|
||||
export class SinExecutor implements INodeExecutor {
|
||||
execute(node: BlueprintNode, context: ExecutionContext): ExecutionResult {
|
||||
const radians = Number(context.evaluateInput(node.id, 'radians', 0));
|
||||
return { outputs: { result: Math.sin(radians) } };
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Cos Node (余弦节点)
|
||||
// ============================================================================
|
||||
|
||||
export const CosTemplate: BlueprintNodeTemplate = {
|
||||
type: 'Cos',
|
||||
title: 'Cos',
|
||||
category: 'math',
|
||||
color: '#4CAF50',
|
||||
description: 'Returns the cosine of angle in radians (返回弧度角的余弦值)',
|
||||
keywords: ['cos', 'cosine', 'trig', 'math'],
|
||||
isPure: true,
|
||||
inputs: [
|
||||
{ name: 'radians', type: 'float', displayName: 'Radians', defaultValue: 0 }
|
||||
],
|
||||
outputs: [
|
||||
{ name: 'result', type: 'float', displayName: 'Result' }
|
||||
]
|
||||
};
|
||||
|
||||
@RegisterNode(CosTemplate)
|
||||
export class CosExecutor implements INodeExecutor {
|
||||
execute(node: BlueprintNode, context: ExecutionContext): ExecutionResult {
|
||||
const radians = Number(context.evaluateInput(node.id, 'radians', 0));
|
||||
return { outputs: { result: Math.cos(radians) } };
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Tan Node (正切节点)
|
||||
// ============================================================================
|
||||
|
||||
export const TanTemplate: BlueprintNodeTemplate = {
|
||||
type: 'Tan',
|
||||
title: 'Tan',
|
||||
category: 'math',
|
||||
color: '#4CAF50',
|
||||
description: 'Returns the tangent of angle in radians (返回弧度角的正切值)',
|
||||
keywords: ['tan', 'tangent', 'trig', 'math'],
|
||||
isPure: true,
|
||||
inputs: [
|
||||
{ name: 'radians', type: 'float', displayName: 'Radians', defaultValue: 0 }
|
||||
],
|
||||
outputs: [
|
||||
{ name: 'result', type: 'float', displayName: 'Result' }
|
||||
]
|
||||
};
|
||||
|
||||
@RegisterNode(TanTemplate)
|
||||
export class TanExecutor implements INodeExecutor {
|
||||
execute(node: BlueprintNode, context: ExecutionContext): ExecutionResult {
|
||||
const radians = Number(context.evaluateInput(node.id, 'radians', 0));
|
||||
return { outputs: { result: Math.tan(radians) } };
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Asin Node (反正弦节点)
|
||||
// ============================================================================
|
||||
|
||||
export const AsinTemplate: BlueprintNodeTemplate = {
|
||||
type: 'Asin',
|
||||
title: 'Asin',
|
||||
category: 'math',
|
||||
color: '#4CAF50',
|
||||
description: 'Returns the arc sine in radians (返回反正弦值,单位为弧度)',
|
||||
keywords: ['asin', 'arc', 'sine', 'inverse', 'trig', 'math'],
|
||||
isPure: true,
|
||||
inputs: [
|
||||
{ name: 'value', type: 'float', displayName: 'Value', defaultValue: 0 }
|
||||
],
|
||||
outputs: [
|
||||
{ name: 'result', type: 'float', displayName: 'Radians' }
|
||||
]
|
||||
};
|
||||
|
||||
@RegisterNode(AsinTemplate)
|
||||
export class AsinExecutor implements INodeExecutor {
|
||||
execute(node: BlueprintNode, context: ExecutionContext): ExecutionResult {
|
||||
const value = Number(context.evaluateInput(node.id, 'value', 0));
|
||||
return { outputs: { result: Math.asin(Math.max(-1, Math.min(1, value))) } };
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Acos Node (反余弦节点)
|
||||
// ============================================================================
|
||||
|
||||
export const AcosTemplate: BlueprintNodeTemplate = {
|
||||
type: 'Acos',
|
||||
title: 'Acos',
|
||||
category: 'math',
|
||||
color: '#4CAF50',
|
||||
description: 'Returns the arc cosine in radians (返回反余弦值,单位为弧度)',
|
||||
keywords: ['acos', 'arc', 'cosine', 'inverse', 'trig', 'math'],
|
||||
isPure: true,
|
||||
inputs: [
|
||||
{ name: 'value', type: 'float', displayName: 'Value', defaultValue: 0 }
|
||||
],
|
||||
outputs: [
|
||||
{ name: 'result', type: 'float', displayName: 'Radians' }
|
||||
]
|
||||
};
|
||||
|
||||
@RegisterNode(AcosTemplate)
|
||||
export class AcosExecutor implements INodeExecutor {
|
||||
execute(node: BlueprintNode, context: ExecutionContext): ExecutionResult {
|
||||
const value = Number(context.evaluateInput(node.id, 'value', 0));
|
||||
return { outputs: { result: Math.acos(Math.max(-1, Math.min(1, value))) } };
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Atan Node (反正切节点)
|
||||
// ============================================================================
|
||||
|
||||
export const AtanTemplate: BlueprintNodeTemplate = {
|
||||
type: 'Atan',
|
||||
title: 'Atan',
|
||||
category: 'math',
|
||||
color: '#4CAF50',
|
||||
description: 'Returns the arc tangent in radians (返回反正切值,单位为弧度)',
|
||||
keywords: ['atan', 'arc', 'tangent', 'inverse', 'trig', 'math'],
|
||||
isPure: true,
|
||||
inputs: [
|
||||
{ name: 'value', type: 'float', displayName: 'Value', defaultValue: 0 }
|
||||
],
|
||||
outputs: [
|
||||
{ name: 'result', type: 'float', displayName: 'Radians' }
|
||||
]
|
||||
};
|
||||
|
||||
@RegisterNode(AtanTemplate)
|
||||
export class AtanExecutor implements INodeExecutor {
|
||||
execute(node: BlueprintNode, context: ExecutionContext): ExecutionResult {
|
||||
const value = Number(context.evaluateInput(node.id, 'value', 0));
|
||||
return { outputs: { result: Math.atan(value) } };
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Atan2 Node (两参数反正切节点)
|
||||
// ============================================================================
|
||||
|
||||
export const Atan2Template: BlueprintNodeTemplate = {
|
||||
type: 'Atan2',
|
||||
title: 'Atan2',
|
||||
category: 'math',
|
||||
color: '#4CAF50',
|
||||
description: 'Returns the angle in radians between the positive X axis and the point (x, y) (返回点(x,y)与正X轴之间的弧度角)',
|
||||
keywords: ['atan2', 'angle', 'direction', 'trig', 'math'],
|
||||
isPure: true,
|
||||
inputs: [
|
||||
{ name: 'y', type: 'float', displayName: 'Y', defaultValue: 0 },
|
||||
{ name: 'x', type: 'float', displayName: 'X', defaultValue: 1 }
|
||||
],
|
||||
outputs: [
|
||||
{ name: 'result', type: 'float', displayName: 'Radians' }
|
||||
]
|
||||
};
|
||||
|
||||
@RegisterNode(Atan2Template)
|
||||
export class Atan2Executor implements INodeExecutor {
|
||||
execute(node: BlueprintNode, context: ExecutionContext): ExecutionResult {
|
||||
const y = Number(context.evaluateInput(node.id, 'y', 0));
|
||||
const x = Number(context.evaluateInput(node.id, 'x', 1));
|
||||
return { outputs: { result: Math.atan2(y, x) } };
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Degrees to Radians Node (角度转弧度节点)
|
||||
// ============================================================================
|
||||
|
||||
export const DegToRadTemplate: BlueprintNodeTemplate = {
|
||||
type: 'DegToRad',
|
||||
title: 'Degrees to Radians',
|
||||
category: 'math',
|
||||
color: '#4CAF50',
|
||||
description: 'Converts degrees to radians (将角度转换为弧度)',
|
||||
keywords: ['degrees', 'radians', 'convert', 'angle', 'math'],
|
||||
isPure: true,
|
||||
inputs: [
|
||||
{ name: 'degrees', type: 'float', displayName: 'Degrees', defaultValue: 0 }
|
||||
],
|
||||
outputs: [
|
||||
{ name: 'result', type: 'float', displayName: 'Radians' }
|
||||
]
|
||||
};
|
||||
|
||||
@RegisterNode(DegToRadTemplate)
|
||||
export class DegToRadExecutor implements INodeExecutor {
|
||||
execute(node: BlueprintNode, context: ExecutionContext): ExecutionResult {
|
||||
const degrees = Number(context.evaluateInput(node.id, 'degrees', 0));
|
||||
return { outputs: { result: degrees * (Math.PI / 180) } };
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Radians to Degrees Node (弧度转角度节点)
|
||||
// ============================================================================
|
||||
|
||||
export const RadToDegTemplate: BlueprintNodeTemplate = {
|
||||
type: 'RadToDeg',
|
||||
title: 'Radians to Degrees',
|
||||
category: 'math',
|
||||
color: '#4CAF50',
|
||||
description: 'Converts radians to degrees (将弧度转换为角度)',
|
||||
keywords: ['radians', 'degrees', 'convert', 'angle', 'math'],
|
||||
isPure: true,
|
||||
inputs: [
|
||||
{ name: 'radians', type: 'float', displayName: 'Radians', defaultValue: 0 }
|
||||
],
|
||||
outputs: [
|
||||
{ name: 'result', type: 'float', displayName: 'Degrees' }
|
||||
]
|
||||
};
|
||||
|
||||
@RegisterNode(RadToDegTemplate)
|
||||
export class RadToDegExecutor implements INodeExecutor {
|
||||
execute(node: BlueprintNode, context: ExecutionContext): ExecutionResult {
|
||||
const radians = Number(context.evaluateInput(node.id, 'radians', 0));
|
||||
return { outputs: { result: radians * (180 / Math.PI) } };
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Inverse Lerp Node (反向线性插值节点)
|
||||
// ============================================================================
|
||||
|
||||
export const InverseLerpTemplate: BlueprintNodeTemplate = {
|
||||
type: 'InverseLerp',
|
||||
title: 'Inverse Lerp',
|
||||
category: 'math',
|
||||
color: '#4CAF50',
|
||||
description: 'Returns the percentage of Value between A and B (返回值在 A 和 B 之间的百分比位置)',
|
||||
keywords: ['inverse', 'lerp', 'percentage', 'ratio', 'math'],
|
||||
isPure: true,
|
||||
inputs: [
|
||||
{ name: 'a', type: 'float', displayName: 'A', defaultValue: 0 },
|
||||
{ name: 'b', type: 'float', displayName: 'B', defaultValue: 1 },
|
||||
{ name: 'value', type: 'float', displayName: 'Value', defaultValue: 0.5 }
|
||||
],
|
||||
outputs: [
|
||||
{ name: 'result', type: 'float', displayName: 'Alpha (0-1)' }
|
||||
]
|
||||
};
|
||||
|
||||
@RegisterNode(InverseLerpTemplate)
|
||||
export class InverseLerpExecutor implements INodeExecutor {
|
||||
execute(node: BlueprintNode, context: ExecutionContext): ExecutionResult {
|
||||
const a = Number(context.evaluateInput(node.id, 'a', 0));
|
||||
const b = Number(context.evaluateInput(node.id, 'b', 1));
|
||||
const value = Number(context.evaluateInput(node.id, 'value', 0.5));
|
||||
if (b === a) return { outputs: { result: 0 } };
|
||||
return { outputs: { result: (value - a) / (b - a) } };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,118 +1,75 @@
|
||||
/**
|
||||
* @zh 蓝图装饰器 - 用于标记可在蓝图中使用的组件、属性和方法
|
||||
* @en Blueprint Decorators - Mark components, properties and methods for blueprint use
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* import { BlueprintExpose, BlueprintProperty, BlueprintMethod } from '@esengine/blueprint';
|
||||
*
|
||||
* @ECSComponent('Health')
|
||||
* @BlueprintExpose({ displayName: '生命值组件', category: 'gameplay' })
|
||||
* export class HealthComponent extends Component {
|
||||
*
|
||||
* @BlueprintProperty({ displayName: '当前生命值', type: 'float' })
|
||||
* current: number = 100;
|
||||
*
|
||||
* @BlueprintProperty({ displayName: '最大生命值', type: 'float', readonly: true })
|
||||
* max: number = 100;
|
||||
*
|
||||
* @BlueprintMethod({
|
||||
* displayName: '治疗',
|
||||
* params: [{ name: 'amount', type: 'float' }]
|
||||
* })
|
||||
* heal(amount: number): void {
|
||||
* this.current = Math.min(this.current + amount, this.max);
|
||||
* }
|
||||
*
|
||||
* @BlueprintMethod({
|
||||
* displayName: '受伤',
|
||||
* params: [{ name: 'amount', type: 'float' }],
|
||||
* returnType: 'bool'
|
||||
* })
|
||||
* takeDamage(amount: number): boolean {
|
||||
* this.current -= amount;
|
||||
* return this.current <= 0;
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
|
||||
import type { BlueprintPinType } from '../types/pins';
|
||||
import type { PropertySchema, ArraySchema, ObjectSchema } from '../types/schema';
|
||||
|
||||
// ============================================================================
|
||||
// Types | 类型定义
|
||||
// Types
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* @zh 参数定义
|
||||
* @en Parameter definition
|
||||
*/
|
||||
export interface BlueprintParamDef {
|
||||
/** @zh 参数名称 @en Parameter name */
|
||||
name: string;
|
||||
/** @zh 显示名称 @en Display name */
|
||||
displayName?: string;
|
||||
/** @zh 引脚类型 @en Pin type */
|
||||
type?: BlueprintPinType;
|
||||
/** @zh 默认值 @en Default value */
|
||||
defaultValue?: unknown;
|
||||
}
|
||||
|
||||
/**
|
||||
* @zh 蓝图暴露选项
|
||||
* @en Blueprint expose options
|
||||
*/
|
||||
export interface BlueprintExposeOptions {
|
||||
/** @zh 组件显示名称 @en Component display name */
|
||||
displayName?: string;
|
||||
/** @zh 组件描述 @en Component description */
|
||||
description?: string;
|
||||
/** @zh 组件分类 @en Component category */
|
||||
category?: string;
|
||||
/** @zh 组件颜色 @en Component color */
|
||||
color?: string;
|
||||
/** @zh 组件图标 @en Component icon */
|
||||
icon?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* @zh 蓝图属性选项
|
||||
* @en Blueprint property options
|
||||
*/
|
||||
export interface BlueprintPropertyOptions {
|
||||
/** @zh 属性显示名称 @en Property display name */
|
||||
displayName?: string;
|
||||
/** @zh 属性描述 @en Property description */
|
||||
description?: string;
|
||||
/** @zh 引脚类型 @en Pin type */
|
||||
type?: BlueprintPinType;
|
||||
/** @zh 是否只读(不生成 Set 节点)@en Readonly (no Set node generated) */
|
||||
readonly?: boolean;
|
||||
/** @zh 默认值 @en Default value */
|
||||
defaultValue?: unknown;
|
||||
}
|
||||
|
||||
/**
|
||||
* @zh 蓝图方法选项
|
||||
* @en Blueprint method options
|
||||
*/
|
||||
export interface BlueprintMethodOptions {
|
||||
/** @zh 方法显示名称 @en Method display name */
|
||||
displayName?: string;
|
||||
/** @zh 方法描述 @en Method description */
|
||||
description?: string;
|
||||
/** @zh 是否是纯函数(无副作用)@en Is pure function (no side effects) */
|
||||
isPure?: boolean;
|
||||
/** @zh 参数列表 @en Parameter list */
|
||||
params?: BlueprintParamDef[];
|
||||
/** @zh 返回值类型 @en Return type */
|
||||
returnType?: BlueprintPinType;
|
||||
}
|
||||
|
||||
/**
|
||||
* @zh 属性元数据
|
||||
* @en Property metadata
|
||||
* @zh 蓝图数组属性选项
|
||||
* @en Blueprint array property options
|
||||
*/
|
||||
export interface BlueprintArrayOptions {
|
||||
displayName?: string;
|
||||
description?: string;
|
||||
itemSchema: PropertySchema;
|
||||
reorderable?: boolean;
|
||||
collapsible?: boolean;
|
||||
minItems?: number;
|
||||
maxItems?: number;
|
||||
defaultValue?: unknown[];
|
||||
itemLabel?: string;
|
||||
exposeElementPorts?: boolean;
|
||||
portNameTemplate?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* @zh 蓝图对象属性选项
|
||||
* @en Blueprint object property options
|
||||
*/
|
||||
export interface BlueprintObjectOptions {
|
||||
displayName?: string;
|
||||
description?: string;
|
||||
properties: Record<string, PropertySchema>;
|
||||
collapsible?: boolean;
|
||||
}
|
||||
|
||||
export interface PropertyMetadata {
|
||||
propertyKey: string;
|
||||
displayName: string;
|
||||
@@ -120,12 +77,12 @@ export interface PropertyMetadata {
|
||||
pinType: BlueprintPinType;
|
||||
readonly: boolean;
|
||||
defaultValue?: unknown;
|
||||
schema?: PropertySchema;
|
||||
isDynamicArray?: boolean;
|
||||
exposeElementPorts?: boolean;
|
||||
portNameTemplate?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* @zh 方法元数据
|
||||
* @en Method metadata
|
||||
*/
|
||||
export interface MethodMetadata {
|
||||
methodKey: string;
|
||||
displayName: string;
|
||||
@@ -135,10 +92,6 @@ export interface MethodMetadata {
|
||||
returnType: BlueprintPinType;
|
||||
}
|
||||
|
||||
/**
|
||||
* @zh 组件蓝图元数据
|
||||
* @en Component blueprint metadata
|
||||
*/
|
||||
export interface ComponentBlueprintMetadata extends BlueprintExposeOptions {
|
||||
componentName: string;
|
||||
properties: PropertyMetadata[];
|
||||
@@ -146,41 +99,25 @@ export interface ComponentBlueprintMetadata extends BlueprintExposeOptions {
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Registry | 注册表
|
||||
// Registry
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* @zh 已注册的蓝图组件
|
||||
* @en Registered blueprint components
|
||||
*/
|
||||
const registeredComponents = new Map<Function, ComponentBlueprintMetadata>();
|
||||
|
||||
/**
|
||||
* @zh 获取所有已注册的蓝图组件
|
||||
* @en Get all registered blueprint components
|
||||
*/
|
||||
export function getRegisteredBlueprintComponents(): Map<Function, ComponentBlueprintMetadata> {
|
||||
return registeredComponents;
|
||||
}
|
||||
|
||||
/**
|
||||
* @zh 获取组件的蓝图元数据
|
||||
* @en Get blueprint metadata for a component
|
||||
*/
|
||||
export function getBlueprintMetadata(componentClass: Function): ComponentBlueprintMetadata | undefined {
|
||||
return registeredComponents.get(componentClass);
|
||||
}
|
||||
|
||||
/**
|
||||
* @zh 清除所有注册的蓝图组件(用于测试)
|
||||
* @en Clear all registered blueprint components (for testing)
|
||||
*/
|
||||
export function clearRegisteredComponents(): void {
|
||||
registeredComponents.clear();
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Internal Helpers | 内部辅助函数
|
||||
// Internal Helpers
|
||||
// ============================================================================
|
||||
|
||||
function getOrCreateMetadata(constructor: Function): ComponentBlueprintMetadata {
|
||||
@@ -197,20 +134,9 @@ function getOrCreateMetadata(constructor: Function): ComponentBlueprintMetadata
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Decorators | 装饰器
|
||||
// Decorators
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* @zh 标记组件可在蓝图中使用
|
||||
* @en Mark component as usable in blueprint
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* @ECSComponent('Player')
|
||||
* @BlueprintExpose({ displayName: '玩家', category: 'gameplay' })
|
||||
* export class PlayerComponent extends Component { }
|
||||
* ```
|
||||
*/
|
||||
export function BlueprintExpose(options: BlueprintExposeOptions = {}): ClassDecorator {
|
||||
return function (target: Function) {
|
||||
const metadata = getOrCreateMetadata(target);
|
||||
@@ -220,19 +146,6 @@ export function BlueprintExpose(options: BlueprintExposeOptions = {}): ClassDeco
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @zh 标记属性可在蓝图中访问
|
||||
* @en Mark property as accessible in blueprint
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* @BlueprintProperty({ displayName: '生命值', type: 'float' })
|
||||
* health: number = 100;
|
||||
*
|
||||
* @BlueprintProperty({ displayName: '名称', type: 'string', readonly: true })
|
||||
* name: string = 'Player';
|
||||
* ```
|
||||
*/
|
||||
export function BlueprintProperty(options: BlueprintPropertyOptions = {}): PropertyDecorator {
|
||||
return function (target: Object, propertyKey: string | symbol) {
|
||||
const key = String(propertyKey);
|
||||
@@ -257,25 +170,108 @@ export function BlueprintProperty(options: BlueprintPropertyOptions = {}): Prope
|
||||
}
|
||||
|
||||
/**
|
||||
* @zh 标记方法可在蓝图中调用
|
||||
* @en Mark method as callable in blueprint
|
||||
* @zh 标记属性为蓝图数组(支持动态增删、排序)
|
||||
* @en Mark property as blueprint array (supports dynamic add/remove, reorder)
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* @BlueprintMethod({
|
||||
* displayName: '攻击',
|
||||
* params: [
|
||||
* { name: 'target', type: 'entity' },
|
||||
* { name: 'damage', type: 'float' }
|
||||
* ],
|
||||
* returnType: 'bool'
|
||||
* @BlueprintArray({
|
||||
* displayName: '路径点',
|
||||
* itemSchema: Schema.object({
|
||||
* position: Schema.vector2(),
|
||||
* waitTime: Schema.float({ min: 0, defaultValue: 1.0 })
|
||||
* }),
|
||||
* reorderable: true,
|
||||
* exposeElementPorts: true,
|
||||
* portNameTemplate: 'Point {index1}'
|
||||
* })
|
||||
* attack(target: Entity, damage: number): boolean { }
|
||||
*
|
||||
* @BlueprintMethod({ displayName: '获取速度', isPure: true, returnType: 'float' })
|
||||
* getSpeed(): number { return this.speed; }
|
||||
* waypoints: Waypoint[] = [];
|
||||
* ```
|
||||
*/
|
||||
export function BlueprintArray(options: BlueprintArrayOptions): PropertyDecorator {
|
||||
return function (target: Object, propertyKey: string | symbol) {
|
||||
const key = String(propertyKey);
|
||||
const metadata = getOrCreateMetadata(target.constructor);
|
||||
|
||||
const arraySchema: ArraySchema = {
|
||||
type: 'array',
|
||||
items: options.itemSchema,
|
||||
defaultValue: options.defaultValue,
|
||||
minItems: options.minItems,
|
||||
maxItems: options.maxItems,
|
||||
reorderable: options.reorderable,
|
||||
collapsible: options.collapsible,
|
||||
itemLabel: options.itemLabel
|
||||
};
|
||||
|
||||
const propMeta: PropertyMetadata = {
|
||||
propertyKey: key,
|
||||
displayName: options.displayName ?? key,
|
||||
description: options.description,
|
||||
pinType: 'array',
|
||||
readonly: false,
|
||||
defaultValue: options.defaultValue,
|
||||
schema: arraySchema,
|
||||
isDynamicArray: true,
|
||||
exposeElementPorts: options.exposeElementPorts,
|
||||
portNameTemplate: options.portNameTemplate
|
||||
};
|
||||
|
||||
const existingIndex = metadata.properties.findIndex(p => p.propertyKey === key);
|
||||
if (existingIndex >= 0) {
|
||||
metadata.properties[existingIndex] = propMeta;
|
||||
} else {
|
||||
metadata.properties.push(propMeta);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @zh 标记属性为蓝图对象(支持嵌套结构)
|
||||
* @en Mark property as blueprint object (supports nested structure)
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* @BlueprintObject({
|
||||
* displayName: '变换',
|
||||
* properties: {
|
||||
* position: Schema.vector2(),
|
||||
* rotation: Schema.float(),
|
||||
* scale: Schema.vector2({ defaultValue: { x: 1, y: 1 } })
|
||||
* }
|
||||
* })
|
||||
* transform: Transform;
|
||||
* ```
|
||||
*/
|
||||
export function BlueprintObject(options: BlueprintObjectOptions): PropertyDecorator {
|
||||
return function (target: Object, propertyKey: string | symbol) {
|
||||
const key = String(propertyKey);
|
||||
const metadata = getOrCreateMetadata(target.constructor);
|
||||
|
||||
const objectSchema: ObjectSchema = {
|
||||
type: 'object',
|
||||
properties: options.properties,
|
||||
collapsible: options.collapsible
|
||||
};
|
||||
|
||||
const propMeta: PropertyMetadata = {
|
||||
propertyKey: key,
|
||||
displayName: options.displayName ?? key,
|
||||
description: options.description,
|
||||
pinType: 'object',
|
||||
readonly: false,
|
||||
schema: objectSchema
|
||||
};
|
||||
|
||||
const existingIndex = metadata.properties.findIndex(p => p.propertyKey === key);
|
||||
if (existingIndex >= 0) {
|
||||
metadata.properties[existingIndex] = propMeta;
|
||||
} else {
|
||||
metadata.properties.push(propMeta);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function BlueprintMethod(options: BlueprintMethodOptions = {}): MethodDecorator {
|
||||
return function (target: Object, propertyKey: string | symbol, descriptor: PropertyDescriptor) {
|
||||
const key = String(propertyKey);
|
||||
@@ -302,13 +298,9 @@ export function BlueprintMethod(options: BlueprintMethodOptions = {}): MethodDec
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Utility Functions | 工具函数
|
||||
// Utility Functions
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* @zh 从 TypeScript 类型名推断蓝图引脚类型
|
||||
* @en Infer blueprint pin type from TypeScript type name
|
||||
*/
|
||||
export function inferPinType(typeName: string): BlueprintPinType {
|
||||
const typeMap: Record<string, BlueprintPinType> = {
|
||||
'number': 'float',
|
||||
|
||||
@@ -1,43 +1,6 @@
|
||||
/**
|
||||
* @zh 蓝图注册系统
|
||||
* @en Blueprint Registry System
|
||||
*
|
||||
* @zh 提供组件自动节点生成功能,用户只需使用装饰器标记组件,
|
||||
* 即可自动在蓝图编辑器中生成对应的 Get/Set/Call 节点
|
||||
*
|
||||
* @en Provides automatic node generation for components. Users only need to
|
||||
* mark components with decorators, and corresponding Get/Set/Call nodes
|
||||
* will be auto-generated in the blueprint editor
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* // 1. 定义组件时使用装饰器 | Define component with decorators
|
||||
* @ECSComponent('Health')
|
||||
* @BlueprintExpose({ displayName: '生命值', category: 'gameplay' })
|
||||
* export class HealthComponent extends Component {
|
||||
* @BlueprintProperty({ displayName: '当前生命值', type: 'float' })
|
||||
* current: number = 100;
|
||||
*
|
||||
* @BlueprintMethod({
|
||||
* displayName: '治疗',
|
||||
* params: [{ name: 'amount', type: 'float' }]
|
||||
* })
|
||||
* heal(amount: number): void {
|
||||
* this.current = Math.min(this.current + amount, 100);
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* // 2. 初始化蓝图系统时注册 | Register when initializing blueprint system
|
||||
* import { registerAllComponentNodes } from '@esengine/blueprint';
|
||||
* registerAllComponentNodes();
|
||||
*
|
||||
* // 3. 现在蓝图编辑器中会出现以下节点:
|
||||
* // Now these nodes appear in blueprint editor:
|
||||
* // - Get Health(获取组件)
|
||||
* // - Get 当前生命值(获取属性)
|
||||
* // - Set 当前生命值(设置属性)
|
||||
* // - 治疗(调用方法)
|
||||
* ```
|
||||
*/
|
||||
|
||||
// Decorators | 装饰器
|
||||
@@ -45,6 +8,8 @@ export {
|
||||
BlueprintExpose,
|
||||
BlueprintProperty,
|
||||
BlueprintMethod,
|
||||
BlueprintArray,
|
||||
BlueprintObject,
|
||||
getRegisteredBlueprintComponents,
|
||||
getBlueprintMetadata,
|
||||
clearRegisteredComponents,
|
||||
@@ -56,6 +21,8 @@ export type {
|
||||
BlueprintExposeOptions,
|
||||
BlueprintPropertyOptions,
|
||||
BlueprintMethodOptions,
|
||||
BlueprintArrayOptions,
|
||||
BlueprintObjectOptions,
|
||||
PropertyMetadata,
|
||||
MethodMetadata,
|
||||
ComponentBlueprintMetadata
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
export * from './pins';
|
||||
export * from './nodes';
|
||||
export * from './blueprint';
|
||||
export * from './schema';
|
||||
export * from './path-utils';
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
*/
|
||||
|
||||
import { BlueprintPinDefinition } from './pins';
|
||||
import { ObjectSchema } from './schema';
|
||||
|
||||
/**
|
||||
* Node category for visual styling and organization
|
||||
@@ -70,6 +71,23 @@ export interface BlueprintNodeTemplate {
|
||||
|
||||
/** Node color for visual distinction (节点颜色用于视觉区分) */
|
||||
color?: string;
|
||||
|
||||
// ========== Schema Support (Schema 支持) ==========
|
||||
|
||||
/**
|
||||
* @zh 节点数据 Schema - 定义节点存储的数据结构
|
||||
* @en Node data schema - defines the data structure stored in the node
|
||||
*
|
||||
* @zh 当定义了 schema 时,节点数据将按照 schema 结构存储和验证
|
||||
* @en When schema is defined, node data will be stored and validated according to the schema structure
|
||||
*/
|
||||
schema?: ObjectSchema;
|
||||
|
||||
/**
|
||||
* @zh 动态数组路径列表 - 指定哪些数组支持动态增删元素
|
||||
* @en Dynamic array paths - specifies which arrays support dynamic add/remove elements
|
||||
*/
|
||||
dynamicArrayPaths?: string[];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -96,6 +114,9 @@ export interface BlueprintNode {
|
||||
/**
|
||||
* Connection between two pins
|
||||
* 两个引脚之间的连接
|
||||
*
|
||||
* @zh 引脚路径支持数组索引,如 "waypoints[0].position"
|
||||
* @en Pin paths support array indices, e.g., "waypoints[0].position"
|
||||
*/
|
||||
export interface BlueprintConnection {
|
||||
/** Unique connection ID (唯一连接ID) */
|
||||
@@ -104,13 +125,19 @@ export interface BlueprintConnection {
|
||||
/** Source node ID (源节点ID) */
|
||||
fromNodeId: string;
|
||||
|
||||
/** Source pin name (源引脚名称) */
|
||||
/**
|
||||
* @zh 源引脚路径(支持数组索引如 "items[0].value")
|
||||
* @en Source pin path (supports array indices like "items[0].value")
|
||||
*/
|
||||
fromPin: string;
|
||||
|
||||
/** Target node ID (目标节点ID) */
|
||||
toNodeId: string;
|
||||
|
||||
/** Target pin name (目标引脚名称) */
|
||||
/**
|
||||
* @zh 目标引脚路径(支持数组索引如 "items[0].value")
|
||||
* @en Target pin path (supports array indices like "items[0].value")
|
||||
*/
|
||||
toPin: string;
|
||||
}
|
||||
|
||||
|
||||
549
packages/framework/blueprint/src/types/path-utils.ts
Normal file
549
packages/framework/blueprint/src/types/path-utils.ts
Normal file
@@ -0,0 +1,549 @@
|
||||
/**
|
||||
* @zh 蓝图路径工具
|
||||
* @en Blueprint Path Utilities
|
||||
*
|
||||
* @zh 用于解析和操作数据路径,支持数组索引和嵌套属性访问
|
||||
* @en Used to parse and manipulate data paths, supports array indices and nested property access
|
||||
*/
|
||||
|
||||
// ============================================================================
|
||||
// Path Types
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* @zh 路径部分类型
|
||||
* @en Path part type
|
||||
*/
|
||||
export type PathPartType = 'property' | 'index' | 'wildcard';
|
||||
|
||||
/**
|
||||
* @zh 路径部分
|
||||
* @en Path part
|
||||
*/
|
||||
export interface PathPart {
|
||||
type: PathPartType;
|
||||
/** Property name (for 'property' type) */
|
||||
name?: string;
|
||||
/** Array index (for 'index' type) */
|
||||
index?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* @zh 端口地址 - 解析后的路径结构
|
||||
* @en Port address - parsed path structure
|
||||
*/
|
||||
export interface PortAddress {
|
||||
/** Base property name (基础属性名) */
|
||||
baseName: string;
|
||||
/** Array indices [0, 2] represents arr[0][2] (数组索引路径) */
|
||||
indices: number[];
|
||||
/** Nested property path ['x', 'y'] (嵌套属性路径) */
|
||||
subPath: string[];
|
||||
/** Original path string (原始路径字符串) */
|
||||
original: string;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Path Parsing
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* @zh 解析路径字符串为部分数组
|
||||
* @en Parse path string to parts array
|
||||
*
|
||||
* @example
|
||||
* parsePath("waypoints[0].position.x")
|
||||
* // => [
|
||||
* // { type: 'property', name: 'waypoints' },
|
||||
* // { type: 'index', index: 0 },
|
||||
* // { type: 'property', name: 'position' },
|
||||
* // { type: 'property', name: 'x' }
|
||||
* // ]
|
||||
*/
|
||||
export function parsePath(path: string): PathPart[] {
|
||||
const parts: PathPart[] = [];
|
||||
const regex = /([^.\[\]]+)|\[(\*|\d+)\]/g;
|
||||
let match;
|
||||
|
||||
while ((match = regex.exec(path)) !== null) {
|
||||
if (match[1]) {
|
||||
// Property name
|
||||
parts.push({ type: 'property', name: match[1] });
|
||||
} else if (match[2] === '*') {
|
||||
// Wildcard
|
||||
parts.push({ type: 'wildcard' });
|
||||
} else {
|
||||
// Array index
|
||||
parts.push({ type: 'index', index: parseInt(match[2], 10) });
|
||||
}
|
||||
}
|
||||
|
||||
return parts;
|
||||
}
|
||||
|
||||
/**
|
||||
* @zh 解析端口路径字符串为 PortAddress
|
||||
* @en Parse port path string to PortAddress
|
||||
*
|
||||
* @example
|
||||
* parsePortPath("waypoints[0].position.x")
|
||||
* // => { baseName: "waypoints", indices: [0], subPath: ["position", "x"], original: "..." }
|
||||
*/
|
||||
export function parsePortPath(path: string): PortAddress {
|
||||
const result: PortAddress = {
|
||||
baseName: '',
|
||||
indices: [],
|
||||
subPath: [],
|
||||
original: path
|
||||
};
|
||||
|
||||
const parts = parsePath(path);
|
||||
let foundFirstIndex = false;
|
||||
let afterIndices = false;
|
||||
|
||||
for (const part of parts) {
|
||||
if (part.type === 'property') {
|
||||
if (!foundFirstIndex) {
|
||||
if (result.baseName) {
|
||||
// Multiple properties before index - treat as nested base
|
||||
result.baseName += '.' + part.name;
|
||||
} else {
|
||||
result.baseName = part.name!;
|
||||
}
|
||||
} else {
|
||||
afterIndices = true;
|
||||
result.subPath.push(part.name!);
|
||||
}
|
||||
} else if (part.type === 'index') {
|
||||
foundFirstIndex = true;
|
||||
if (!afterIndices) {
|
||||
result.indices.push(part.index!);
|
||||
} else {
|
||||
// Index after property - encode in subPath
|
||||
result.subPath.push(`[${part.index}]`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @zh 构建路径字符串
|
||||
* @en Build path string from parts
|
||||
*/
|
||||
export function buildPath(parts: PathPart[]): string {
|
||||
let path = '';
|
||||
|
||||
for (const part of parts) {
|
||||
switch (part.type) {
|
||||
case 'property':
|
||||
if (path && !path.endsWith('[')) {
|
||||
path += '.';
|
||||
}
|
||||
path += part.name;
|
||||
break;
|
||||
case 'index':
|
||||
path += `[${part.index}]`;
|
||||
break;
|
||||
case 'wildcard':
|
||||
path += '[*]';
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
/**
|
||||
* @zh 从 PortAddress 构建路径字符串
|
||||
* @en Build path string from PortAddress
|
||||
*/
|
||||
export function buildPortPath(address: PortAddress): string {
|
||||
let path = address.baseName;
|
||||
|
||||
for (const index of address.indices) {
|
||||
path += `[${index}]`;
|
||||
}
|
||||
|
||||
if (address.subPath.length > 0) {
|
||||
for (const sub of address.subPath) {
|
||||
if (sub.startsWith('[')) {
|
||||
path += sub;
|
||||
} else {
|
||||
path += '.' + sub;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Data Access
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* @zh 根据路径获取数据
|
||||
* @en Get data by path
|
||||
*
|
||||
* @example
|
||||
* const data = { waypoints: [{ position: { x: 10, y: 20 } }] };
|
||||
* getByPath(data, "waypoints[0].position.x") // => 10
|
||||
*/
|
||||
export function getByPath(data: unknown, path: string): unknown {
|
||||
if (!path) return data;
|
||||
|
||||
const parts = parsePath(path);
|
||||
let current: unknown = data;
|
||||
|
||||
for (const part of parts) {
|
||||
if (current === null || current === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
switch (part.type) {
|
||||
case 'property':
|
||||
if (typeof current === 'object' && current !== null) {
|
||||
current = (current as Record<string, unknown>)[part.name!];
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'index':
|
||||
if (Array.isArray(current)) {
|
||||
current = current[part.index!];
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'wildcard':
|
||||
// Wildcard returns array of all values
|
||||
if (Array.isArray(current)) {
|
||||
return current;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
return current;
|
||||
}
|
||||
|
||||
/**
|
||||
* @zh 根据路径设置数据
|
||||
* @en Set data by path
|
||||
*
|
||||
* @example
|
||||
* const data = { waypoints: [{ position: { x: 0, y: 0 } }] };
|
||||
* setByPath(data, "waypoints[0].position.x", 100);
|
||||
* // data.waypoints[0].position.x === 100
|
||||
*/
|
||||
export function setByPath(data: unknown, path: string, value: unknown): boolean {
|
||||
if (!path) return false;
|
||||
|
||||
const parts = parsePath(path);
|
||||
let current: unknown = data;
|
||||
|
||||
for (let i = 0; i < parts.length - 1; i++) {
|
||||
const part = parts[i];
|
||||
|
||||
if (current === null || current === undefined) {
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (part.type) {
|
||||
case 'property':
|
||||
if (typeof current === 'object' && current !== null) {
|
||||
current = (current as Record<string, unknown>)[part.name!];
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'index':
|
||||
if (Array.isArray(current)) {
|
||||
current = current[part.index!];
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'wildcard':
|
||||
// Cannot set on wildcard
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Set the final value
|
||||
const lastPart = parts[parts.length - 1];
|
||||
|
||||
if (current === null || current === undefined) {
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (lastPart.type) {
|
||||
case 'property':
|
||||
if (typeof current === 'object' && current !== null) {
|
||||
(current as Record<string, unknown>)[lastPart.name!] = value;
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'index':
|
||||
if (Array.isArray(current)) {
|
||||
current[lastPart.index!] = value;
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @zh 检查路径是否存在
|
||||
* @en Check if path exists
|
||||
*/
|
||||
export function hasPath(data: unknown, path: string): boolean {
|
||||
return getByPath(data, path) !== undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* @zh 删除路径上的数据
|
||||
* @en Delete data at path
|
||||
*/
|
||||
export function deleteByPath(data: unknown, path: string): boolean {
|
||||
if (!path) return false;
|
||||
|
||||
const parts = parsePath(path);
|
||||
let current: unknown = data;
|
||||
|
||||
for (let i = 0; i < parts.length - 1; i++) {
|
||||
const part = parts[i];
|
||||
|
||||
if (current === null || current === undefined) {
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (part.type) {
|
||||
case 'property':
|
||||
if (typeof current === 'object' && current !== null) {
|
||||
current = (current as Record<string, unknown>)[part.name!];
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'index':
|
||||
if (Array.isArray(current)) {
|
||||
current = current[part.index!];
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
const lastPart = parts[parts.length - 1];
|
||||
|
||||
if (current === null || current === undefined) {
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (lastPart.type) {
|
||||
case 'property':
|
||||
if (typeof current === 'object' && current !== null) {
|
||||
delete (current as Record<string, unknown>)[lastPart.name!];
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'index':
|
||||
if (Array.isArray(current)) {
|
||||
current.splice(lastPart.index!, 1);
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Array Operations
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* @zh 数组操作类型
|
||||
* @en Array operation type
|
||||
*/
|
||||
export type ArrayOperation = 'insert' | 'remove' | 'move';
|
||||
|
||||
/**
|
||||
* @zh 当数组元素变化时,更新路径中的索引
|
||||
* @en Update indices in path when array elements change
|
||||
*
|
||||
* @param path - Original path (原始路径)
|
||||
* @param arrayPath - Array base path (数组基础路径)
|
||||
* @param operation - Operation type (操作类型)
|
||||
* @param index - Target index (目标索引)
|
||||
* @param toIndex - Move destination (移动目标,仅 move 操作)
|
||||
* @returns Updated path or empty string if path becomes invalid (更新后的路径,如果路径失效则返回空字符串)
|
||||
*/
|
||||
export function updatePathOnArrayChange(
|
||||
path: string,
|
||||
arrayPath: string,
|
||||
operation: ArrayOperation,
|
||||
index: number,
|
||||
toIndex?: number
|
||||
): string {
|
||||
// Check if path starts with arrayPath[
|
||||
if (!path.startsWith(arrayPath + '[')) {
|
||||
return path;
|
||||
}
|
||||
|
||||
const address = parsePortPath(path);
|
||||
|
||||
if (address.indices.length === 0) {
|
||||
return path;
|
||||
}
|
||||
|
||||
const currentIndex = address.indices[0];
|
||||
|
||||
switch (operation) {
|
||||
case 'insert':
|
||||
if (currentIndex >= index) {
|
||||
address.indices[0]++;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'remove':
|
||||
if (currentIndex === index) {
|
||||
return ''; // Path becomes invalid
|
||||
}
|
||||
if (currentIndex > index) {
|
||||
address.indices[0]--;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'move':
|
||||
if (toIndex !== undefined) {
|
||||
if (currentIndex === index) {
|
||||
address.indices[0] = toIndex;
|
||||
} else if (index < toIndex) {
|
||||
// Moving down
|
||||
if (currentIndex > index && currentIndex <= toIndex) {
|
||||
address.indices[0]--;
|
||||
}
|
||||
} else {
|
||||
// Moving up
|
||||
if (currentIndex >= toIndex && currentIndex < index) {
|
||||
address.indices[0]++;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return buildPortPath(address);
|
||||
}
|
||||
|
||||
/**
|
||||
* @zh 展开带通配符的路径
|
||||
* @en Expand path with wildcards
|
||||
*
|
||||
* @example
|
||||
* const data = { items: [{ x: 1 }, { x: 2 }, { x: 3 }] };
|
||||
* expandWildcardPath("items[*].x", data)
|
||||
* // => ["items[0].x", "items[1].x", "items[2].x"]
|
||||
*/
|
||||
export function expandWildcardPath(path: string, data: unknown): string[] {
|
||||
const parts = parsePath(path);
|
||||
const results: string[] = [];
|
||||
|
||||
function expand(currentParts: PathPart[], currentData: unknown, index: number): void {
|
||||
if (index >= parts.length) {
|
||||
results.push(buildPath(currentParts));
|
||||
return;
|
||||
}
|
||||
|
||||
const part = parts[index];
|
||||
|
||||
if (part.type === 'wildcard') {
|
||||
if (Array.isArray(currentData)) {
|
||||
for (let i = 0; i < currentData.length; i++) {
|
||||
const newParts = [...currentParts, { type: 'index' as const, index: i }];
|
||||
expand(newParts, currentData[i], index + 1);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const newParts = [...currentParts, part];
|
||||
let nextData: unknown;
|
||||
|
||||
if (part.type === 'property' && typeof currentData === 'object' && currentData !== null) {
|
||||
nextData = (currentData as Record<string, unknown>)[part.name!];
|
||||
} else if (part.type === 'index' && Array.isArray(currentData)) {
|
||||
nextData = currentData[part.index!];
|
||||
}
|
||||
|
||||
expand(newParts, nextData, index + 1);
|
||||
}
|
||||
}
|
||||
|
||||
expand([], data, 0);
|
||||
return results;
|
||||
}
|
||||
|
||||
/**
|
||||
* @zh 检查路径是否包含通配符
|
||||
* @en Check if path contains wildcards
|
||||
*/
|
||||
export function hasWildcard(path: string): boolean {
|
||||
return path.includes('[*]');
|
||||
}
|
||||
|
||||
/**
|
||||
* @zh 获取路径的父路径
|
||||
* @en Get parent path
|
||||
*
|
||||
* @example
|
||||
* getParentPath("items[0].position.x") // => "items[0].position"
|
||||
* getParentPath("items[0]") // => "items"
|
||||
* getParentPath("items") // => ""
|
||||
*/
|
||||
export function getParentPath(path: string): string {
|
||||
const parts = parsePath(path);
|
||||
if (parts.length <= 1) {
|
||||
return '';
|
||||
}
|
||||
return buildPath(parts.slice(0, -1));
|
||||
}
|
||||
|
||||
/**
|
||||
* @zh 获取路径的最后一部分名称
|
||||
* @en Get the last part name of path
|
||||
*
|
||||
* @example
|
||||
* getPathLastName("items[0].position.x") // => "x"
|
||||
* getPathLastName("items[0]") // => "[0]"
|
||||
*/
|
||||
export function getPathLastName(path: string): string {
|
||||
const parts = parsePath(path);
|
||||
if (parts.length === 0) {
|
||||
return '';
|
||||
}
|
||||
|
||||
const last = parts[parts.length - 1];
|
||||
if (last.type === 'property') {
|
||||
return last.name!;
|
||||
} else if (last.type === 'index') {
|
||||
return `[${last.index}]`;
|
||||
} else {
|
||||
return '[*]';
|
||||
}
|
||||
}
|
||||
611
packages/framework/blueprint/src/types/schema.ts
Normal file
611
packages/framework/blueprint/src/types/schema.ts
Normal file
@@ -0,0 +1,611 @@
|
||||
/**
|
||||
* @zh 蓝图属性 Schema 系统
|
||||
* @en Blueprint Property Schema System
|
||||
*
|
||||
* @zh 提供递归类型定义,支持原始类型、数组、对象、枚举等复杂数据结构
|
||||
* @en Provides recursive type definitions supporting primitives, arrays, objects, enums, etc.
|
||||
*/
|
||||
|
||||
import { BlueprintPinType } from './pins';
|
||||
|
||||
// ============================================================================
|
||||
// Schema Types
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* @zh 属性 Schema - 递归定义数据结构
|
||||
* @en Property Schema - recursive data structure definition
|
||||
*/
|
||||
export type PropertySchema =
|
||||
| PrimitiveSchema
|
||||
| ArraySchema
|
||||
| ObjectSchema
|
||||
| EnumSchema
|
||||
| RefSchema;
|
||||
|
||||
/**
|
||||
* @zh 原始类型 Schema
|
||||
* @en Primitive type schema
|
||||
*/
|
||||
export interface PrimitiveSchema {
|
||||
type: 'primitive';
|
||||
primitive: BlueprintPinType;
|
||||
defaultValue?: unknown;
|
||||
|
||||
// Constraints | 约束
|
||||
min?: number;
|
||||
max?: number;
|
||||
step?: number;
|
||||
multiline?: boolean;
|
||||
placeholder?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* @zh 数组类型 Schema
|
||||
* @en Array type schema
|
||||
*/
|
||||
export interface ArraySchema {
|
||||
type: 'array';
|
||||
items: PropertySchema;
|
||||
defaultValue?: unknown[];
|
||||
|
||||
// Constraints | 约束
|
||||
minItems?: number;
|
||||
maxItems?: number;
|
||||
|
||||
// UI Behavior | UI 行为
|
||||
reorderable?: boolean;
|
||||
collapsible?: boolean;
|
||||
defaultCollapsed?: boolean;
|
||||
itemLabel?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* @zh 对象类型 Schema
|
||||
* @en Object type schema
|
||||
*/
|
||||
export interface ObjectSchema {
|
||||
type: 'object';
|
||||
properties: Record<string, PropertySchema>;
|
||||
required?: string[];
|
||||
|
||||
// UI Behavior | UI 行为
|
||||
collapsible?: boolean;
|
||||
defaultCollapsed?: boolean;
|
||||
displayName?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* @zh 枚举类型 Schema
|
||||
* @en Enum type schema
|
||||
*/
|
||||
export interface EnumSchema {
|
||||
type: 'enum';
|
||||
options: EnumOption[];
|
||||
defaultValue?: string | number;
|
||||
}
|
||||
|
||||
/**
|
||||
* @zh 枚举选项
|
||||
* @en Enum option
|
||||
*/
|
||||
export interface EnumOption {
|
||||
value: string | number;
|
||||
label: string;
|
||||
description?: string;
|
||||
icon?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* @zh 引用类型 Schema
|
||||
* @en Reference type schema
|
||||
*
|
||||
* @zh 引用 SchemaRegistry 中已注册的 Schema
|
||||
* @en References a schema registered in SchemaRegistry
|
||||
*/
|
||||
export interface RefSchema {
|
||||
type: 'ref';
|
||||
ref: string;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Schema Registry
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* @zh Schema 注册表
|
||||
* @en Schema Registry
|
||||
*
|
||||
* @zh 用于注册和复用常用的 Schema 定义
|
||||
* @en Used to register and reuse common Schema definitions
|
||||
*/
|
||||
export class SchemaRegistry {
|
||||
private static schemas = new Map<string, PropertySchema>();
|
||||
|
||||
/**
|
||||
* @zh 注册 Schema
|
||||
* @en Register a schema
|
||||
*/
|
||||
static register(id: string, schema: PropertySchema): void {
|
||||
this.schemas.set(id, schema);
|
||||
}
|
||||
|
||||
/**
|
||||
* @zh 获取 Schema
|
||||
* @en Get a schema
|
||||
*/
|
||||
static get(id: string): PropertySchema | undefined {
|
||||
return this.schemas.get(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* @zh 解析引用,返回实际 Schema
|
||||
* @en Resolve reference, return actual schema
|
||||
*/
|
||||
static resolve(schema: PropertySchema): PropertySchema {
|
||||
if (schema.type === 'ref') {
|
||||
const resolved = this.schemas.get(schema.ref);
|
||||
if (!resolved) {
|
||||
console.warn(`[SchemaRegistry] Schema not found: ${schema.ref}`);
|
||||
return { type: 'primitive', primitive: 'any' };
|
||||
}
|
||||
return this.resolve(resolved);
|
||||
}
|
||||
return schema;
|
||||
}
|
||||
|
||||
/**
|
||||
* @zh 检查 Schema 是否已注册
|
||||
* @en Check if schema is registered
|
||||
*/
|
||||
static has(id: string): boolean {
|
||||
return this.schemas.has(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* @zh 获取所有已注册的 Schema ID
|
||||
* @en Get all registered schema IDs
|
||||
*/
|
||||
static keys(): string[] {
|
||||
return Array.from(this.schemas.keys());
|
||||
}
|
||||
|
||||
/**
|
||||
* @zh 清空注册表
|
||||
* @en Clear registry
|
||||
*/
|
||||
static clear(): void {
|
||||
this.schemas.clear();
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Schema Utilities
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* @zh 获取 Schema 的默认值
|
||||
* @en Get default value for a schema
|
||||
*/
|
||||
export function getSchemaDefaultValue(schema: PropertySchema): unknown {
|
||||
const resolved = SchemaRegistry.resolve(schema);
|
||||
|
||||
switch (resolved.type) {
|
||||
case 'primitive':
|
||||
if (resolved.defaultValue !== undefined) return resolved.defaultValue;
|
||||
return getPrimitiveDefaultValue(resolved.primitive);
|
||||
|
||||
case 'array':
|
||||
if (resolved.defaultValue !== undefined) return [...resolved.defaultValue];
|
||||
return [];
|
||||
|
||||
case 'object': {
|
||||
const obj: Record<string, unknown> = {};
|
||||
for (const [key, propSchema] of Object.entries(resolved.properties)) {
|
||||
obj[key] = getSchemaDefaultValue(propSchema);
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
|
||||
case 'enum':
|
||||
if (resolved.defaultValue !== undefined) return resolved.defaultValue;
|
||||
return resolved.options[0]?.value;
|
||||
|
||||
default:
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @zh 获取原始类型的默认值
|
||||
* @en Get default value for primitive type
|
||||
*/
|
||||
export function getPrimitiveDefaultValue(primitive: BlueprintPinType): unknown {
|
||||
switch (primitive) {
|
||||
case 'bool': return false;
|
||||
case 'int': return 0;
|
||||
case 'float': return 0.0;
|
||||
case 'string': return '';
|
||||
case 'vector2': return { x: 0, y: 0 };
|
||||
case 'vector3': return { x: 0, y: 0, z: 0 };
|
||||
case 'color': return { r: 255, g: 255, b: 255, a: 255 };
|
||||
case 'entity': return null;
|
||||
case 'component': return null;
|
||||
case 'object': return null;
|
||||
case 'array': return [];
|
||||
case 'any': return null;
|
||||
case 'exec': return undefined;
|
||||
default: return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @zh 根据 Schema 获取对应的 PinType
|
||||
* @en Get corresponding PinType from Schema
|
||||
*/
|
||||
export function schemaToPinType(schema: PropertySchema): BlueprintPinType {
|
||||
const resolved = SchemaRegistry.resolve(schema);
|
||||
|
||||
switch (resolved.type) {
|
||||
case 'primitive':
|
||||
return resolved.primitive;
|
||||
case 'array':
|
||||
return 'array';
|
||||
case 'object':
|
||||
return 'object';
|
||||
case 'enum':
|
||||
return typeof resolved.options[0]?.value === 'number' ? 'int' : 'string';
|
||||
default:
|
||||
return 'any';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @zh 验证数据是否符合 Schema
|
||||
* @en Validate data against schema
|
||||
*/
|
||||
export function validateSchema(
|
||||
schema: PropertySchema,
|
||||
data: unknown,
|
||||
path: string = ''
|
||||
): ValidationResult {
|
||||
const resolved = SchemaRegistry.resolve(schema);
|
||||
const errors: ValidationError[] = [];
|
||||
|
||||
switch (resolved.type) {
|
||||
case 'primitive':
|
||||
validatePrimitive(resolved, data, path, errors);
|
||||
break;
|
||||
|
||||
case 'array':
|
||||
validateArray(resolved, data, path, errors);
|
||||
break;
|
||||
|
||||
case 'object':
|
||||
validateObject(resolved, data, path, errors);
|
||||
break;
|
||||
|
||||
case 'enum':
|
||||
validateEnum(resolved, data, path, errors);
|
||||
break;
|
||||
}
|
||||
|
||||
return {
|
||||
valid: errors.length === 0,
|
||||
errors
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @zh 验证结果
|
||||
* @en Validation result
|
||||
*/
|
||||
export interface ValidationResult {
|
||||
valid: boolean;
|
||||
errors: ValidationError[];
|
||||
}
|
||||
|
||||
/**
|
||||
* @zh 验证错误
|
||||
* @en Validation error
|
||||
*/
|
||||
export interface ValidationError {
|
||||
path: string;
|
||||
message: string;
|
||||
expected?: string;
|
||||
received?: string;
|
||||
}
|
||||
|
||||
function validatePrimitive(
|
||||
schema: PrimitiveSchema,
|
||||
data: unknown,
|
||||
path: string,
|
||||
errors: ValidationError[]
|
||||
): void {
|
||||
if (data === null || data === undefined) {
|
||||
return; // Allow null/undefined for optional fields
|
||||
}
|
||||
|
||||
const expectedType = getPrimitiveJsType(schema.primitive);
|
||||
const actualType = typeof data;
|
||||
|
||||
if (expectedType === 'object') {
|
||||
if (typeof data !== 'object') {
|
||||
errors.push({
|
||||
path,
|
||||
message: `Expected ${schema.primitive}, got ${actualType}`,
|
||||
expected: schema.primitive,
|
||||
received: actualType
|
||||
});
|
||||
}
|
||||
} else if (expectedType !== 'any' && actualType !== expectedType) {
|
||||
errors.push({
|
||||
path,
|
||||
message: `Expected ${expectedType}, got ${actualType}`,
|
||||
expected: expectedType,
|
||||
received: actualType
|
||||
});
|
||||
}
|
||||
|
||||
// Numeric constraints
|
||||
if ((schema.primitive === 'int' || schema.primitive === 'float') && typeof data === 'number') {
|
||||
if (schema.min !== undefined && data < schema.min) {
|
||||
errors.push({
|
||||
path,
|
||||
message: `Value ${data} is less than minimum ${schema.min}`
|
||||
});
|
||||
}
|
||||
if (schema.max !== undefined && data > schema.max) {
|
||||
errors.push({
|
||||
path,
|
||||
message: `Value ${data} is greater than maximum ${schema.max}`
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function validateArray(
|
||||
schema: ArraySchema,
|
||||
data: unknown,
|
||||
path: string,
|
||||
errors: ValidationError[]
|
||||
): void {
|
||||
if (!Array.isArray(data)) {
|
||||
errors.push({
|
||||
path,
|
||||
message: `Expected array, got ${typeof data}`,
|
||||
expected: 'array',
|
||||
received: typeof data
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (schema.minItems !== undefined && data.length < schema.minItems) {
|
||||
errors.push({
|
||||
path,
|
||||
message: `Array has ${data.length} items, minimum is ${schema.minItems}`
|
||||
});
|
||||
}
|
||||
|
||||
if (schema.maxItems !== undefined && data.length > schema.maxItems) {
|
||||
errors.push({
|
||||
path,
|
||||
message: `Array has ${data.length} items, maximum is ${schema.maxItems}`
|
||||
});
|
||||
}
|
||||
|
||||
// Validate each item
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
const itemResult = validateSchema(schema.items, data[i], `${path}[${i}]`);
|
||||
errors.push(...itemResult.errors);
|
||||
}
|
||||
}
|
||||
|
||||
function validateObject(
|
||||
schema: ObjectSchema,
|
||||
data: unknown,
|
||||
path: string,
|
||||
errors: ValidationError[]
|
||||
): void {
|
||||
if (typeof data !== 'object' || data === null || Array.isArray(data)) {
|
||||
errors.push({
|
||||
path,
|
||||
message: `Expected object, got ${Array.isArray(data) ? 'array' : typeof data}`,
|
||||
expected: 'object',
|
||||
received: Array.isArray(data) ? 'array' : typeof data
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const obj = data as Record<string, unknown>;
|
||||
|
||||
// Check required fields
|
||||
if (schema.required) {
|
||||
for (const key of schema.required) {
|
||||
if (!(key in obj)) {
|
||||
errors.push({
|
||||
path: path ? `${path}.${key}` : key,
|
||||
message: `Missing required field: ${key}`
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Validate each property
|
||||
for (const [key, propSchema] of Object.entries(schema.properties)) {
|
||||
if (key in obj) {
|
||||
const propPath = path ? `${path}.${key}` : key;
|
||||
const propResult = validateSchema(propSchema, obj[key], propPath);
|
||||
errors.push(...propResult.errors);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function validateEnum(
|
||||
schema: EnumSchema,
|
||||
data: unknown,
|
||||
path: string,
|
||||
errors: ValidationError[]
|
||||
): void {
|
||||
if (data === null || data === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
const validValues = schema.options.map(o => o.value);
|
||||
if (!validValues.includes(data as string | number)) {
|
||||
errors.push({
|
||||
path,
|
||||
message: `Invalid enum value: ${data}`,
|
||||
expected: validValues.join(' | '),
|
||||
received: String(data)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function getPrimitiveJsType(primitive: BlueprintPinType): string {
|
||||
switch (primitive) {
|
||||
case 'bool': return 'boolean';
|
||||
case 'int':
|
||||
case 'float': return 'number';
|
||||
case 'string': return 'string';
|
||||
case 'vector2':
|
||||
case 'vector3':
|
||||
case 'color':
|
||||
case 'entity':
|
||||
case 'component':
|
||||
case 'object':
|
||||
case 'array': return 'object';
|
||||
case 'any': return 'any';
|
||||
case 'exec': return 'undefined';
|
||||
default: return 'any';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @zh 深度克隆 Schema
|
||||
* @en Deep clone schema
|
||||
*/
|
||||
export function cloneSchema(schema: PropertySchema): PropertySchema {
|
||||
return JSON.parse(JSON.stringify(schema));
|
||||
}
|
||||
|
||||
/**
|
||||
* @zh 合并两个 ObjectSchema
|
||||
* @en Merge two ObjectSchemas
|
||||
*/
|
||||
export function mergeObjectSchemas(
|
||||
base: ObjectSchema,
|
||||
override: Partial<ObjectSchema>
|
||||
): ObjectSchema {
|
||||
return {
|
||||
...base,
|
||||
...override,
|
||||
properties: {
|
||||
...base.properties,
|
||||
...(override.properties || {})
|
||||
},
|
||||
required: [
|
||||
...(base.required || []),
|
||||
...(override.required || [])
|
||||
]
|
||||
};
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Schema Builder (Fluent API)
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* @zh Schema 构建器
|
||||
* @en Schema Builder
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* const waypointSchema = Schema.object({
|
||||
* position: Schema.vector2(),
|
||||
* waitTime: Schema.float({ min: 0, defaultValue: 1.0 }),
|
||||
* action: Schema.enum([
|
||||
* { value: 'idle', label: 'Idle' },
|
||||
* { value: 'patrol', label: 'Patrol' }
|
||||
* ])
|
||||
* });
|
||||
*
|
||||
* const pathSchema = Schema.array(waypointSchema, {
|
||||
* minItems: 2,
|
||||
* reorderable: true,
|
||||
* itemLabel: 'Point {index1}'
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
export const Schema = {
|
||||
// Primitives
|
||||
bool(options?: Partial<Omit<PrimitiveSchema, 'type' | 'primitive'>>): PrimitiveSchema {
|
||||
return { type: 'primitive', primitive: 'bool', ...options };
|
||||
},
|
||||
|
||||
int(options?: Partial<Omit<PrimitiveSchema, 'type' | 'primitive'>>): PrimitiveSchema {
|
||||
return { type: 'primitive', primitive: 'int', ...options };
|
||||
},
|
||||
|
||||
float(options?: Partial<Omit<PrimitiveSchema, 'type' | 'primitive'>>): PrimitiveSchema {
|
||||
return { type: 'primitive', primitive: 'float', ...options };
|
||||
},
|
||||
|
||||
string(options?: Partial<Omit<PrimitiveSchema, 'type' | 'primitive'>>): PrimitiveSchema {
|
||||
return { type: 'primitive', primitive: 'string', ...options };
|
||||
},
|
||||
|
||||
vector2(options?: Partial<Omit<PrimitiveSchema, 'type' | 'primitive'>>): PrimitiveSchema {
|
||||
return { type: 'primitive', primitive: 'vector2', ...options };
|
||||
},
|
||||
|
||||
vector3(options?: Partial<Omit<PrimitiveSchema, 'type' | 'primitive'>>): PrimitiveSchema {
|
||||
return { type: 'primitive', primitive: 'vector3', ...options };
|
||||
},
|
||||
|
||||
color(options?: Partial<Omit<PrimitiveSchema, 'type' | 'primitive'>>): PrimitiveSchema {
|
||||
return { type: 'primitive', primitive: 'color', ...options };
|
||||
},
|
||||
|
||||
entity(options?: Partial<Omit<PrimitiveSchema, 'type' | 'primitive'>>): PrimitiveSchema {
|
||||
return { type: 'primitive', primitive: 'entity', ...options };
|
||||
},
|
||||
|
||||
component(options?: Partial<Omit<PrimitiveSchema, 'type' | 'primitive'>>): PrimitiveSchema {
|
||||
return { type: 'primitive', primitive: 'component', ...options };
|
||||
},
|
||||
|
||||
object_ref(options?: Partial<Omit<PrimitiveSchema, 'type' | 'primitive'>>): PrimitiveSchema {
|
||||
return { type: 'primitive', primitive: 'object', ...options };
|
||||
},
|
||||
|
||||
any(options?: Partial<Omit<PrimitiveSchema, 'type' | 'primitive'>>): PrimitiveSchema {
|
||||
return { type: 'primitive', primitive: 'any', ...options };
|
||||
},
|
||||
|
||||
// Complex types
|
||||
array(
|
||||
items: PropertySchema,
|
||||
options?: Partial<Omit<ArraySchema, 'type' | 'items'>>
|
||||
): ArraySchema {
|
||||
return { type: 'array', items, ...options };
|
||||
},
|
||||
|
||||
object(
|
||||
properties: Record<string, PropertySchema>,
|
||||
options?: Partial<Omit<ObjectSchema, 'type' | 'properties'>>
|
||||
): ObjectSchema {
|
||||
return { type: 'object', properties, ...options };
|
||||
},
|
||||
|
||||
enum(
|
||||
options: EnumOption[],
|
||||
extra?: Partial<Omit<EnumSchema, 'type' | 'options'>>
|
||||
): EnumSchema {
|
||||
return { type: 'enum', options, ...extra };
|
||||
},
|
||||
|
||||
ref(id: string): RefSchema {
|
||||
return { type: 'ref', ref: id };
|
||||
}
|
||||
};
|
||||
@@ -1,5 +1,12 @@
|
||||
# @esengine/fsm
|
||||
|
||||
## 9.0.0
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`4e66bd8`](https://github.com/esengine/esengine/commit/4e66bd8e2be80b366a7723dcc48b99df0457aed4)]:
|
||||
- @esengine/blueprint@4.5.0
|
||||
|
||||
## 8.0.0
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@esengine/fsm",
|
||||
"version": "8.0.0",
|
||||
"version": "9.0.0",
|
||||
"description": "Finite State Machine for ECS Framework / ECS 框架的有限状态机",
|
||||
"type": "module",
|
||||
"main": "./dist/index.js",
|
||||
|
||||
@@ -1,5 +1,34 @@
|
||||
# @esengine/ecs-framework-math
|
||||
|
||||
## 2.10.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`4e66bd8`](https://github.com/esengine/esengine/commit/4e66bd8e2be80b366a7723dcc48b99df0457aed4)]:
|
||||
- @esengine/blueprint@4.5.0
|
||||
|
||||
## 2.10.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- [#444](https://github.com/esengine/esengine/pull/444) [`fa593a3`](https://github.com/esengine/esengine/commit/fa593a3c69292207800750f8106f418465cb7c0f) Thanks [@esengine](https://github.com/esengine)! - feat(math): add blueprint nodes for math library
|
||||
- Add Vector2 blueprint nodes (Make, Break, Add, Sub, Mul, Length, Normalize, Dot, Cross, Distance, Lerp, Rotate, FromAngle)
|
||||
- Add Fixed32 blueprint nodes (FromFloat, FromInt, ToFloat, ToInt, arithmetic operations, Abs, Sqrt, Floor, Ceil, Round, Sign, Min, Max, Clamp, Lerp)
|
||||
- Add FixedVector2 blueprint nodes (Make, Break, Add, Sub, Mul, Negate, Length, Normalize, Dot, Cross, Distance, Lerp)
|
||||
- Add Color blueprint nodes (Make, Break, FromHex, ToHex, FromHSL, ToHSL, Lerp, Lighten, Darken, Saturate, Desaturate, Invert, Grayscale, Luminance, constants)
|
||||
- Add documentation for math blueprint nodes (Chinese and English)
|
||||
|
||||
## 2.9.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- [#442](https://github.com/esengine/esengine/pull/442) [`bffe90b`](https://github.com/esengine/esengine/commit/bffe90b6a17563cc90709faf339b229dc3abd22d) Thanks [@esengine](https://github.com/esengine)! - feat(math): add blueprint nodes for math library
|
||||
- Add Vector2 blueprint nodes (Make, Break, Add, Sub, Mul, Length, Normalize, Dot, Cross, Distance, Lerp, Rotate, FromAngle)
|
||||
- Add Fixed32 blueprint nodes (FromFloat, FromInt, ToFloat, ToInt, arithmetic operations, Abs, Sqrt, Floor, Ceil, Round, Sign, Min, Max, Clamp, Lerp)
|
||||
- Add FixedVector2 blueprint nodes (Make, Break, Add, Sub, Mul, Negate, Length, Normalize, Dot, Cross, Distance, Lerp)
|
||||
- Add Color blueprint nodes (Make, Break, FromHex, ToHex, FromHSL, ToHSL, Lerp, Lighten, Darken, Saturate, Desaturate, Invert, Grayscale, Luminance, constants)
|
||||
- Add documentation for math blueprint nodes (Chinese and English)
|
||||
|
||||
## 2.8.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@esengine/ecs-framework-math",
|
||||
"version": "2.8.0",
|
||||
"version": "2.10.1",
|
||||
"description": "ECS框架2D数学库 - 提供向量、矩阵、几何形状和碰撞检测功能",
|
||||
"main": "bin/index.js",
|
||||
"types": "bin/index.d.ts",
|
||||
@@ -40,6 +40,9 @@
|
||||
},
|
||||
"author": "yhh",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@esengine/blueprint": "workspace:*"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@rollup/plugin-commonjs": "^28.0.3",
|
||||
"@rollup/plugin-node-resolve": "^16.0.1",
|
||||
|
||||
@@ -33,3 +33,6 @@ export * from './Collision';
|
||||
|
||||
// 动画和插值
|
||||
export * from './Animation';
|
||||
|
||||
// 蓝图节点
|
||||
export * from './nodes';
|
||||
|
||||
463
packages/framework/math/src/nodes/ColorNodes.ts
Normal file
463
packages/framework/math/src/nodes/ColorNodes.ts
Normal file
@@ -0,0 +1,463 @@
|
||||
/**
|
||||
* @zh 颜色蓝图节点
|
||||
* @en Color Blueprint Nodes
|
||||
*/
|
||||
|
||||
import type { BlueprintNodeTemplate, BlueprintNode, INodeExecutor, ExecutionResult } from '@esengine/blueprint';
|
||||
import { Color } from '../Color';
|
||||
|
||||
interface ColorContext {
|
||||
evaluateInput(nodeId: string, pinName: string, defaultValue?: unknown): unknown;
|
||||
}
|
||||
|
||||
// Make Color from RGBA
|
||||
export const MakeColorTemplate: BlueprintNodeTemplate = {
|
||||
type: 'MakeColor',
|
||||
title: 'Make Color',
|
||||
category: 'math',
|
||||
description: 'Creates a Color from RGBA',
|
||||
keywords: ['make', 'create', 'color', 'rgba'],
|
||||
menuPath: ['Math', 'Color', 'Make Color'],
|
||||
isPure: true,
|
||||
inputs: [
|
||||
{ name: 'r', displayName: 'R (0-255)', type: 'int', defaultValue: 255 },
|
||||
{ name: 'g', displayName: 'G (0-255)', type: 'int', defaultValue: 255 },
|
||||
{ name: 'b', displayName: 'B (0-255)', type: 'int', defaultValue: 255 },
|
||||
{ name: 'a', displayName: 'A (0-1)', type: 'float', defaultValue: 1 }
|
||||
],
|
||||
outputs: [
|
||||
{ name: 'color', displayName: 'Color', type: 'color' }
|
||||
],
|
||||
color: '#E91E63'
|
||||
};
|
||||
|
||||
export class MakeColorExecutor implements INodeExecutor {
|
||||
execute(node: BlueprintNode, context: unknown): ExecutionResult {
|
||||
const ctx = context as ColorContext;
|
||||
const r = Number(ctx.evaluateInput(node.id, 'r', 255));
|
||||
const g = Number(ctx.evaluateInput(node.id, 'g', 255));
|
||||
const b = Number(ctx.evaluateInput(node.id, 'b', 255));
|
||||
const a = Number(ctx.evaluateInput(node.id, 'a', 1));
|
||||
return { outputs: { color: new Color(r, g, b, a) } };
|
||||
}
|
||||
}
|
||||
|
||||
// Break Color
|
||||
export const BreakColorTemplate: BlueprintNodeTemplate = {
|
||||
type: 'BreakColor',
|
||||
title: 'Break Color',
|
||||
category: 'math',
|
||||
description: 'Breaks a Color into RGBA',
|
||||
keywords: ['break', 'split', 'color', 'rgba'],
|
||||
menuPath: ['Math', 'Color', 'Break Color'],
|
||||
isPure: true,
|
||||
inputs: [
|
||||
{ name: 'color', displayName: 'Color', type: 'color' }
|
||||
],
|
||||
outputs: [
|
||||
{ name: 'r', displayName: 'R', type: 'int' },
|
||||
{ name: 'g', displayName: 'G', type: 'int' },
|
||||
{ name: 'b', displayName: 'B', type: 'int' },
|
||||
{ name: 'a', displayName: 'A', type: 'float' }
|
||||
],
|
||||
color: '#E91E63'
|
||||
};
|
||||
|
||||
export class BreakColorExecutor implements INodeExecutor {
|
||||
execute(node: BlueprintNode, context: unknown): ExecutionResult {
|
||||
const ctx = context as ColorContext;
|
||||
const color = ctx.evaluateInput(node.id, 'color', Color.WHITE) as Color;
|
||||
const c = color ?? Color.WHITE;
|
||||
return { outputs: { r: c.r, g: c.g, b: c.b, a: c.a } };
|
||||
}
|
||||
}
|
||||
|
||||
// Color from Hex
|
||||
export const ColorFromHexTemplate: BlueprintNodeTemplate = {
|
||||
type: 'ColorFromHex',
|
||||
title: 'Color From Hex',
|
||||
category: 'math',
|
||||
description: 'Creates a Color from hex string',
|
||||
keywords: ['color', 'hex', 'from', 'create'],
|
||||
menuPath: ['Math', 'Color', 'From Hex'],
|
||||
isPure: true,
|
||||
inputs: [
|
||||
{ name: 'hex', displayName: 'Hex', type: 'string', defaultValue: '#FFFFFF' },
|
||||
{ name: 'alpha', displayName: 'Alpha', type: 'float', defaultValue: 1 }
|
||||
],
|
||||
outputs: [
|
||||
{ name: 'color', displayName: 'Color', type: 'color' }
|
||||
],
|
||||
color: '#E91E63'
|
||||
};
|
||||
|
||||
export class ColorFromHexExecutor implements INodeExecutor {
|
||||
execute(node: BlueprintNode, context: unknown): ExecutionResult {
|
||||
const ctx = context as ColorContext;
|
||||
const hex = String(ctx.evaluateInput(node.id, 'hex', '#FFFFFF'));
|
||||
const alpha = Number(ctx.evaluateInput(node.id, 'alpha', 1));
|
||||
return { outputs: { color: Color.fromHex(hex, alpha) } };
|
||||
}
|
||||
}
|
||||
|
||||
// Color to Hex
|
||||
export const ColorToHexTemplate: BlueprintNodeTemplate = {
|
||||
type: 'ColorToHex',
|
||||
title: 'Color To Hex',
|
||||
category: 'math',
|
||||
description: 'Converts a Color to hex string',
|
||||
keywords: ['color', 'hex', 'to', 'convert'],
|
||||
menuPath: ['Math', 'Color', 'To Hex'],
|
||||
isPure: true,
|
||||
inputs: [
|
||||
{ name: 'color', displayName: 'Color', type: 'color' }
|
||||
],
|
||||
outputs: [
|
||||
{ name: 'hex', displayName: 'Hex', type: 'string' }
|
||||
],
|
||||
color: '#E91E63'
|
||||
};
|
||||
|
||||
export class ColorToHexExecutor implements INodeExecutor {
|
||||
execute(node: BlueprintNode, context: unknown): ExecutionResult {
|
||||
const ctx = context as ColorContext;
|
||||
const color = ctx.evaluateInput(node.id, 'color', Color.WHITE) as Color;
|
||||
return { outputs: { hex: (color ?? Color.WHITE).toHex() } };
|
||||
}
|
||||
}
|
||||
|
||||
// Color from HSL
|
||||
export const ColorFromHSLTemplate: BlueprintNodeTemplate = {
|
||||
type: 'ColorFromHSL',
|
||||
title: 'Color From HSL',
|
||||
category: 'math',
|
||||
description: 'Creates a Color from HSL values',
|
||||
keywords: ['color', 'hsl', 'hue', 'saturation', 'lightness'],
|
||||
menuPath: ['Math', 'Color', 'From HSL'],
|
||||
isPure: true,
|
||||
inputs: [
|
||||
{ name: 'h', displayName: 'H (0-360)', type: 'float', defaultValue: 0 },
|
||||
{ name: 's', displayName: 'S (0-1)', type: 'float', defaultValue: 1 },
|
||||
{ name: 'l', displayName: 'L (0-1)', type: 'float', defaultValue: 0.5 },
|
||||
{ name: 'a', displayName: 'A (0-1)', type: 'float', defaultValue: 1 }
|
||||
],
|
||||
outputs: [
|
||||
{ name: 'color', displayName: 'Color', type: 'color' }
|
||||
],
|
||||
color: '#E91E63'
|
||||
};
|
||||
|
||||
export class ColorFromHSLExecutor implements INodeExecutor {
|
||||
execute(node: BlueprintNode, context: unknown): ExecutionResult {
|
||||
const ctx = context as ColorContext;
|
||||
const h = Number(ctx.evaluateInput(node.id, 'h', 0));
|
||||
const s = Number(ctx.evaluateInput(node.id, 's', 1));
|
||||
const l = Number(ctx.evaluateInput(node.id, 'l', 0.5));
|
||||
const a = Number(ctx.evaluateInput(node.id, 'a', 1));
|
||||
return { outputs: { color: Color.fromHSL(h, s, l, a) } };
|
||||
}
|
||||
}
|
||||
|
||||
// Color to HSL
|
||||
export const ColorToHSLTemplate: BlueprintNodeTemplate = {
|
||||
type: 'ColorToHSL',
|
||||
title: 'Color To HSL',
|
||||
category: 'math',
|
||||
description: 'Converts a Color to HSL values',
|
||||
keywords: ['color', 'hsl', 'convert'],
|
||||
menuPath: ['Math', 'Color', 'To HSL'],
|
||||
isPure: true,
|
||||
inputs: [
|
||||
{ name: 'color', displayName: 'Color', type: 'color' }
|
||||
],
|
||||
outputs: [
|
||||
{ name: 'h', displayName: 'H', type: 'float' },
|
||||
{ name: 's', displayName: 'S', type: 'float' },
|
||||
{ name: 'l', displayName: 'L', type: 'float' }
|
||||
],
|
||||
color: '#E91E63'
|
||||
};
|
||||
|
||||
export class ColorToHSLExecutor implements INodeExecutor {
|
||||
execute(node: BlueprintNode, context: unknown): ExecutionResult {
|
||||
const ctx = context as ColorContext;
|
||||
const color = ctx.evaluateInput(node.id, 'color', Color.WHITE) as Color;
|
||||
const hsl = (color ?? Color.WHITE).toHSL();
|
||||
return { outputs: { h: hsl.h, s: hsl.s, l: hsl.l } };
|
||||
}
|
||||
}
|
||||
|
||||
// Color Lerp
|
||||
export const ColorLerpTemplate: BlueprintNodeTemplate = {
|
||||
type: 'ColorLerp',
|
||||
title: 'Color Lerp',
|
||||
category: 'math',
|
||||
description: 'Linear interpolation between two colors',
|
||||
keywords: ['color', 'lerp', 'interpolate', 'blend'],
|
||||
menuPath: ['Math', 'Color', 'Lerp'],
|
||||
isPure: true,
|
||||
inputs: [
|
||||
{ name: 'from', displayName: 'From', type: 'color' },
|
||||
{ name: 'to', displayName: 'To', type: 'color' },
|
||||
{ name: 't', displayName: 'T', type: 'float', defaultValue: 0.5 }
|
||||
],
|
||||
outputs: [
|
||||
{ name: 'result', displayName: 'Result', type: 'color' }
|
||||
],
|
||||
color: '#E91E63'
|
||||
};
|
||||
|
||||
export class ColorLerpExecutor implements INodeExecutor {
|
||||
execute(node: BlueprintNode, context: unknown): ExecutionResult {
|
||||
const ctx = context as ColorContext;
|
||||
const from = ctx.evaluateInput(node.id, 'from', Color.BLACK) as Color;
|
||||
const to = ctx.evaluateInput(node.id, 'to', Color.WHITE) as Color;
|
||||
const t = Number(ctx.evaluateInput(node.id, 't', 0.5));
|
||||
return { outputs: { result: Color.lerp(from ?? Color.BLACK, to ?? Color.WHITE, t) } };
|
||||
}
|
||||
}
|
||||
|
||||
// Color Lighten
|
||||
export const ColorLightenTemplate: BlueprintNodeTemplate = {
|
||||
type: 'ColorLighten',
|
||||
title: 'Color Lighten',
|
||||
category: 'math',
|
||||
description: 'Lightens a color',
|
||||
keywords: ['color', 'lighten', 'bright'],
|
||||
menuPath: ['Math', 'Color', 'Lighten'],
|
||||
isPure: true,
|
||||
inputs: [
|
||||
{ name: 'color', displayName: 'Color', type: 'color' },
|
||||
{ name: 'amount', displayName: 'Amount', type: 'float', defaultValue: 0.1 }
|
||||
],
|
||||
outputs: [
|
||||
{ name: 'result', displayName: 'Result', type: 'color' }
|
||||
],
|
||||
color: '#E91E63'
|
||||
};
|
||||
|
||||
export class ColorLightenExecutor implements INodeExecutor {
|
||||
execute(node: BlueprintNode, context: unknown): ExecutionResult {
|
||||
const ctx = context as ColorContext;
|
||||
const color = ctx.evaluateInput(node.id, 'color', Color.GRAY) as Color;
|
||||
const amount = Number(ctx.evaluateInput(node.id, 'amount', 0.1));
|
||||
return { outputs: { result: Color.lighten(color ?? Color.GRAY, amount) } };
|
||||
}
|
||||
}
|
||||
|
||||
// Color Darken
|
||||
export const ColorDarkenTemplate: BlueprintNodeTemplate = {
|
||||
type: 'ColorDarken',
|
||||
title: 'Color Darken',
|
||||
category: 'math',
|
||||
description: 'Darkens a color',
|
||||
keywords: ['color', 'darken', 'dark'],
|
||||
menuPath: ['Math', 'Color', 'Darken'],
|
||||
isPure: true,
|
||||
inputs: [
|
||||
{ name: 'color', displayName: 'Color', type: 'color' },
|
||||
{ name: 'amount', displayName: 'Amount', type: 'float', defaultValue: 0.1 }
|
||||
],
|
||||
outputs: [
|
||||
{ name: 'result', displayName: 'Result', type: 'color' }
|
||||
],
|
||||
color: '#E91E63'
|
||||
};
|
||||
|
||||
export class ColorDarkenExecutor implements INodeExecutor {
|
||||
execute(node: BlueprintNode, context: unknown): ExecutionResult {
|
||||
const ctx = context as ColorContext;
|
||||
const color = ctx.evaluateInput(node.id, 'color', Color.GRAY) as Color;
|
||||
const amount = Number(ctx.evaluateInput(node.id, 'amount', 0.1));
|
||||
return { outputs: { result: Color.darken(color ?? Color.GRAY, amount) } };
|
||||
}
|
||||
}
|
||||
|
||||
// Color Saturate
|
||||
export const ColorSaturateTemplate: BlueprintNodeTemplate = {
|
||||
type: 'ColorSaturate',
|
||||
title: 'Color Saturate',
|
||||
category: 'math',
|
||||
description: 'Increases color saturation',
|
||||
keywords: ['color', 'saturate', 'saturation'],
|
||||
menuPath: ['Math', 'Color', 'Saturate'],
|
||||
isPure: true,
|
||||
inputs: [
|
||||
{ name: 'color', displayName: 'Color', type: 'color' },
|
||||
{ name: 'amount', displayName: 'Amount', type: 'float', defaultValue: 0.1 }
|
||||
],
|
||||
outputs: [
|
||||
{ name: 'result', displayName: 'Result', type: 'color' }
|
||||
],
|
||||
color: '#E91E63'
|
||||
};
|
||||
|
||||
export class ColorSaturateExecutor implements INodeExecutor {
|
||||
execute(node: BlueprintNode, context: unknown): ExecutionResult {
|
||||
const ctx = context as ColorContext;
|
||||
const color = ctx.evaluateInput(node.id, 'color', Color.GRAY) as Color;
|
||||
const amount = Number(ctx.evaluateInput(node.id, 'amount', 0.1));
|
||||
return { outputs: { result: Color.saturate(color ?? Color.GRAY, amount) } };
|
||||
}
|
||||
}
|
||||
|
||||
// Color Desaturate
|
||||
export const ColorDesaturateTemplate: BlueprintNodeTemplate = {
|
||||
type: 'ColorDesaturate',
|
||||
title: 'Color Desaturate',
|
||||
category: 'math',
|
||||
description: 'Decreases color saturation',
|
||||
keywords: ['color', 'desaturate', 'saturation'],
|
||||
menuPath: ['Math', 'Color', 'Desaturate'],
|
||||
isPure: true,
|
||||
inputs: [
|
||||
{ name: 'color', displayName: 'Color', type: 'color' },
|
||||
{ name: 'amount', displayName: 'Amount', type: 'float', defaultValue: 0.1 }
|
||||
],
|
||||
outputs: [
|
||||
{ name: 'result', displayName: 'Result', type: 'color' }
|
||||
],
|
||||
color: '#E91E63'
|
||||
};
|
||||
|
||||
export class ColorDesaturateExecutor implements INodeExecutor {
|
||||
execute(node: BlueprintNode, context: unknown): ExecutionResult {
|
||||
const ctx = context as ColorContext;
|
||||
const color = ctx.evaluateInput(node.id, 'color', Color.GRAY) as Color;
|
||||
const amount = Number(ctx.evaluateInput(node.id, 'amount', 0.1));
|
||||
return { outputs: { result: Color.desaturate(color ?? Color.GRAY, amount) } };
|
||||
}
|
||||
}
|
||||
|
||||
// Color Invert
|
||||
export const ColorInvertTemplate: BlueprintNodeTemplate = {
|
||||
type: 'ColorInvert',
|
||||
title: 'Color Invert',
|
||||
category: 'math',
|
||||
description: 'Inverts a color',
|
||||
keywords: ['color', 'invert', 'inverse'],
|
||||
menuPath: ['Math', 'Color', 'Invert'],
|
||||
isPure: true,
|
||||
inputs: [
|
||||
{ name: 'color', displayName: 'Color', type: 'color' }
|
||||
],
|
||||
outputs: [
|
||||
{ name: 'result', displayName: 'Result', type: 'color' }
|
||||
],
|
||||
color: '#E91E63'
|
||||
};
|
||||
|
||||
export class ColorInvertExecutor implements INodeExecutor {
|
||||
execute(node: BlueprintNode, context: unknown): ExecutionResult {
|
||||
const ctx = context as ColorContext;
|
||||
const color = ctx.evaluateInput(node.id, 'color', Color.WHITE) as Color;
|
||||
return { outputs: { result: Color.invert(color ?? Color.WHITE) } };
|
||||
}
|
||||
}
|
||||
|
||||
// Color Grayscale
|
||||
export const ColorGrayscaleTemplate: BlueprintNodeTemplate = {
|
||||
type: 'ColorGrayscale',
|
||||
title: 'Color Grayscale',
|
||||
category: 'math',
|
||||
description: 'Converts color to grayscale',
|
||||
keywords: ['color', 'grayscale', 'gray', 'grey'],
|
||||
menuPath: ['Math', 'Color', 'Grayscale'],
|
||||
isPure: true,
|
||||
inputs: [
|
||||
{ name: 'color', displayName: 'Color', type: 'color' }
|
||||
],
|
||||
outputs: [
|
||||
{ name: 'result', displayName: 'Result', type: 'color' }
|
||||
],
|
||||
color: '#E91E63'
|
||||
};
|
||||
|
||||
export class ColorGrayscaleExecutor implements INodeExecutor {
|
||||
execute(node: BlueprintNode, context: unknown): ExecutionResult {
|
||||
const ctx = context as ColorContext;
|
||||
const color = ctx.evaluateInput(node.id, 'color', Color.WHITE) as Color;
|
||||
return { outputs: { result: Color.grayscale(color ?? Color.WHITE) } };
|
||||
}
|
||||
}
|
||||
|
||||
// Color Luminance
|
||||
export const ColorLuminanceTemplate: BlueprintNodeTemplate = {
|
||||
type: 'ColorLuminance',
|
||||
title: 'Color Luminance',
|
||||
category: 'math',
|
||||
description: 'Gets perceived brightness (0-1)',
|
||||
keywords: ['color', 'luminance', 'brightness'],
|
||||
menuPath: ['Math', 'Color', 'Luminance'],
|
||||
isPure: true,
|
||||
inputs: [
|
||||
{ name: 'color', displayName: 'Color', type: 'color' }
|
||||
],
|
||||
outputs: [
|
||||
{ name: 'luminance', displayName: 'Luminance', type: 'float' }
|
||||
],
|
||||
color: '#E91E63'
|
||||
};
|
||||
|
||||
export class ColorLuminanceExecutor implements INodeExecutor {
|
||||
execute(node: BlueprintNode, context: unknown): ExecutionResult {
|
||||
const ctx = context as ColorContext;
|
||||
const color = ctx.evaluateInput(node.id, 'color', Color.WHITE) as Color;
|
||||
return { outputs: { luminance: Color.luminance(color ?? Color.WHITE) } };
|
||||
}
|
||||
}
|
||||
|
||||
// Color Constants
|
||||
export const ColorConstantsTemplate: BlueprintNodeTemplate = {
|
||||
type: 'ColorConstants',
|
||||
title: 'Color Constants',
|
||||
category: 'math',
|
||||
description: 'Common color constants',
|
||||
keywords: ['color', 'constant', 'red', 'green', 'blue', 'white', 'black'],
|
||||
menuPath: ['Math', 'Color', 'Constants'],
|
||||
isPure: true,
|
||||
inputs: [],
|
||||
outputs: [
|
||||
{ name: 'white', displayName: 'White', type: 'color' },
|
||||
{ name: 'black', displayName: 'Black', type: 'color' },
|
||||
{ name: 'red', displayName: 'Red', type: 'color' },
|
||||
{ name: 'green', displayName: 'Green', type: 'color' },
|
||||
{ name: 'blue', displayName: 'Blue', type: 'color' },
|
||||
{ name: 'transparent', displayName: 'Transparent', type: 'color' }
|
||||
],
|
||||
color: '#E91E63'
|
||||
};
|
||||
|
||||
export class ColorConstantsExecutor implements INodeExecutor {
|
||||
execute(): ExecutionResult {
|
||||
return {
|
||||
outputs: {
|
||||
white: Color.WHITE,
|
||||
black: Color.BLACK,
|
||||
red: Color.RED,
|
||||
green: Color.GREEN,
|
||||
blue: Color.BLUE,
|
||||
transparent: Color.TRANSPARENT
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Node definitions collection
|
||||
export const ColorNodeDefinitions = [
|
||||
{ template: MakeColorTemplate, executor: new MakeColorExecutor() },
|
||||
{ template: BreakColorTemplate, executor: new BreakColorExecutor() },
|
||||
{ template: ColorFromHexTemplate, executor: new ColorFromHexExecutor() },
|
||||
{ template: ColorToHexTemplate, executor: new ColorToHexExecutor() },
|
||||
{ template: ColorFromHSLTemplate, executor: new ColorFromHSLExecutor() },
|
||||
{ template: ColorToHSLTemplate, executor: new ColorToHSLExecutor() },
|
||||
{ template: ColorLerpTemplate, executor: new ColorLerpExecutor() },
|
||||
{ template: ColorLightenTemplate, executor: new ColorLightenExecutor() },
|
||||
{ template: ColorDarkenTemplate, executor: new ColorDarkenExecutor() },
|
||||
{ template: ColorSaturateTemplate, executor: new ColorSaturateExecutor() },
|
||||
{ template: ColorDesaturateTemplate, executor: new ColorDesaturateExecutor() },
|
||||
{ template: ColorInvertTemplate, executor: new ColorInvertExecutor() },
|
||||
{ template: ColorGrayscaleTemplate, executor: new ColorGrayscaleExecutor() },
|
||||
{ template: ColorLuminanceTemplate, executor: new ColorLuminanceExecutor() },
|
||||
{ template: ColorConstantsTemplate, executor: new ColorConstantsExecutor() }
|
||||
];
|
||||
662
packages/framework/math/src/nodes/FixedNodes.ts
Normal file
662
packages/framework/math/src/nodes/FixedNodes.ts
Normal file
@@ -0,0 +1,662 @@
|
||||
/**
|
||||
* @zh Fixed32 定点数蓝图节点
|
||||
* @en Fixed32 Blueprint Nodes
|
||||
*/
|
||||
|
||||
import type { BlueprintNodeTemplate, BlueprintNode, INodeExecutor, ExecutionResult } from '@esengine/blueprint';
|
||||
import { Fixed32 } from '../Fixed32';
|
||||
|
||||
interface FixedContext {
|
||||
evaluateInput(nodeId: string, pinName: string, defaultValue?: unknown): unknown;
|
||||
}
|
||||
|
||||
// Make Fixed32 from float
|
||||
export const Fixed32FromTemplate: BlueprintNodeTemplate = {
|
||||
type: 'Fixed32From',
|
||||
title: 'Fixed32 From Float',
|
||||
category: 'math',
|
||||
description: 'Creates Fixed32 from float',
|
||||
keywords: ['fixed', 'fixed32', 'from', 'create', 'deterministic'],
|
||||
menuPath: ['Math', 'Fixed', 'From Float'],
|
||||
isPure: true,
|
||||
inputs: [
|
||||
{ name: 'value', displayName: 'Value', type: 'float', defaultValue: 0 }
|
||||
],
|
||||
outputs: [
|
||||
{ name: 'fixed', displayName: 'Fixed32', type: 'object' }
|
||||
],
|
||||
color: '#9C27B0'
|
||||
};
|
||||
|
||||
export class Fixed32FromExecutor implements INodeExecutor {
|
||||
execute(node: BlueprintNode, context: unknown): ExecutionResult {
|
||||
const ctx = context as FixedContext;
|
||||
const value = Number(ctx.evaluateInput(node.id, 'value', 0));
|
||||
return { outputs: { fixed: Fixed32.from(value) } };
|
||||
}
|
||||
}
|
||||
|
||||
// Make Fixed32 from int
|
||||
export const Fixed32FromIntTemplate: BlueprintNodeTemplate = {
|
||||
type: 'Fixed32FromInt',
|
||||
title: 'Fixed32 From Int',
|
||||
category: 'math',
|
||||
description: 'Creates Fixed32 from integer (no precision loss)',
|
||||
keywords: ['fixed', 'fixed32', 'from', 'int', 'integer'],
|
||||
menuPath: ['Math', 'Fixed', 'From Int'],
|
||||
isPure: true,
|
||||
inputs: [
|
||||
{ name: 'value', displayName: 'Value', type: 'int', defaultValue: 0 }
|
||||
],
|
||||
outputs: [
|
||||
{ name: 'fixed', displayName: 'Fixed32', type: 'object' }
|
||||
],
|
||||
color: '#9C27B0'
|
||||
};
|
||||
|
||||
export class Fixed32FromIntExecutor implements INodeExecutor {
|
||||
execute(node: BlueprintNode, context: unknown): ExecutionResult {
|
||||
const ctx = context as FixedContext;
|
||||
const value = Math.floor(Number(ctx.evaluateInput(node.id, 'value', 0)));
|
||||
return { outputs: { fixed: Fixed32.fromInt(value) } };
|
||||
}
|
||||
}
|
||||
|
||||
// Fixed32 to float
|
||||
export const Fixed32ToFloatTemplate: BlueprintNodeTemplate = {
|
||||
type: 'Fixed32ToFloat',
|
||||
title: 'Fixed32 To Float',
|
||||
category: 'math',
|
||||
description: 'Converts Fixed32 to float',
|
||||
keywords: ['fixed', 'fixed32', 'to', 'float', 'convert'],
|
||||
menuPath: ['Math', 'Fixed', 'To Float'],
|
||||
isPure: true,
|
||||
inputs: [
|
||||
{ name: 'fixed', displayName: 'Fixed32', type: 'object' }
|
||||
],
|
||||
outputs: [
|
||||
{ name: 'value', displayName: 'Value', type: 'float' }
|
||||
],
|
||||
color: '#9C27B0'
|
||||
};
|
||||
|
||||
export class Fixed32ToFloatExecutor implements INodeExecutor {
|
||||
execute(node: BlueprintNode, context: unknown): ExecutionResult {
|
||||
const ctx = context as FixedContext;
|
||||
const fixed = ctx.evaluateInput(node.id, 'fixed', Fixed32.ZERO) as Fixed32;
|
||||
return { outputs: { value: fixed?.toNumber() ?? 0 } };
|
||||
}
|
||||
}
|
||||
|
||||
// Fixed32 to int
|
||||
export const Fixed32ToIntTemplate: BlueprintNodeTemplate = {
|
||||
type: 'Fixed32ToInt',
|
||||
title: 'Fixed32 To Int',
|
||||
category: 'math',
|
||||
description: 'Converts Fixed32 to integer (floor)',
|
||||
keywords: ['fixed', 'fixed32', 'to', 'int', 'integer'],
|
||||
menuPath: ['Math', 'Fixed', 'To Int'],
|
||||
isPure: true,
|
||||
inputs: [
|
||||
{ name: 'fixed', displayName: 'Fixed32', type: 'object' }
|
||||
],
|
||||
outputs: [
|
||||
{ name: 'value', displayName: 'Value', type: 'int' }
|
||||
],
|
||||
color: '#9C27B0'
|
||||
};
|
||||
|
||||
export class Fixed32ToIntExecutor implements INodeExecutor {
|
||||
execute(node: BlueprintNode, context: unknown): ExecutionResult {
|
||||
const ctx = context as FixedContext;
|
||||
const fixed = ctx.evaluateInput(node.id, 'fixed', Fixed32.ZERO) as Fixed32;
|
||||
return { outputs: { value: fixed?.toInt() ?? 0 } };
|
||||
}
|
||||
}
|
||||
|
||||
// Fixed32 Add
|
||||
export const Fixed32AddTemplate: BlueprintNodeTemplate = {
|
||||
type: 'Fixed32Add',
|
||||
title: 'Fixed32 +',
|
||||
category: 'math',
|
||||
description: 'Adds two Fixed32 values',
|
||||
keywords: ['fixed', 'add', 'plus', '+'],
|
||||
menuPath: ['Math', 'Fixed', 'Add'],
|
||||
isPure: true,
|
||||
inputs: [
|
||||
{ name: 'a', displayName: 'A', type: 'object' },
|
||||
{ name: 'b', displayName: 'B', type: 'object' }
|
||||
],
|
||||
outputs: [
|
||||
{ name: 'result', displayName: 'Result', type: 'object' }
|
||||
],
|
||||
color: '#9C27B0'
|
||||
};
|
||||
|
||||
export class Fixed32AddExecutor implements INodeExecutor {
|
||||
execute(node: BlueprintNode, context: unknown): ExecutionResult {
|
||||
const ctx = context as FixedContext;
|
||||
const a = ctx.evaluateInput(node.id, 'a', Fixed32.ZERO) as Fixed32;
|
||||
const b = ctx.evaluateInput(node.id, 'b', Fixed32.ZERO) as Fixed32;
|
||||
return { outputs: { result: (a ?? Fixed32.ZERO).add(b ?? Fixed32.ZERO) } };
|
||||
}
|
||||
}
|
||||
|
||||
// Fixed32 Subtract
|
||||
export const Fixed32SubtractTemplate: BlueprintNodeTemplate = {
|
||||
type: 'Fixed32Subtract',
|
||||
title: 'Fixed32 -',
|
||||
category: 'math',
|
||||
description: 'Subtracts B from A',
|
||||
keywords: ['fixed', 'subtract', 'minus', '-'],
|
||||
menuPath: ['Math', 'Fixed', 'Subtract'],
|
||||
isPure: true,
|
||||
inputs: [
|
||||
{ name: 'a', displayName: 'A', type: 'object' },
|
||||
{ name: 'b', displayName: 'B', type: 'object' }
|
||||
],
|
||||
outputs: [
|
||||
{ name: 'result', displayName: 'Result', type: 'object' }
|
||||
],
|
||||
color: '#9C27B0'
|
||||
};
|
||||
|
||||
export class Fixed32SubtractExecutor implements INodeExecutor {
|
||||
execute(node: BlueprintNode, context: unknown): ExecutionResult {
|
||||
const ctx = context as FixedContext;
|
||||
const a = ctx.evaluateInput(node.id, 'a', Fixed32.ZERO) as Fixed32;
|
||||
const b = ctx.evaluateInput(node.id, 'b', Fixed32.ZERO) as Fixed32;
|
||||
return { outputs: { result: (a ?? Fixed32.ZERO).sub(b ?? Fixed32.ZERO) } };
|
||||
}
|
||||
}
|
||||
|
||||
// Fixed32 Multiply
|
||||
export const Fixed32MultiplyTemplate: BlueprintNodeTemplate = {
|
||||
type: 'Fixed32Multiply',
|
||||
title: 'Fixed32 *',
|
||||
category: 'math',
|
||||
description: 'Multiplies two Fixed32 values',
|
||||
keywords: ['fixed', 'multiply', 'times', '*'],
|
||||
menuPath: ['Math', 'Fixed', 'Multiply'],
|
||||
isPure: true,
|
||||
inputs: [
|
||||
{ name: 'a', displayName: 'A', type: 'object' },
|
||||
{ name: 'b', displayName: 'B', type: 'object' }
|
||||
],
|
||||
outputs: [
|
||||
{ name: 'result', displayName: 'Result', type: 'object' }
|
||||
],
|
||||
color: '#9C27B0'
|
||||
};
|
||||
|
||||
export class Fixed32MultiplyExecutor implements INodeExecutor {
|
||||
execute(node: BlueprintNode, context: unknown): ExecutionResult {
|
||||
const ctx = context as FixedContext;
|
||||
const a = ctx.evaluateInput(node.id, 'a', Fixed32.ONE) as Fixed32;
|
||||
const b = ctx.evaluateInput(node.id, 'b', Fixed32.ONE) as Fixed32;
|
||||
return { outputs: { result: (a ?? Fixed32.ONE).mul(b ?? Fixed32.ONE) } };
|
||||
}
|
||||
}
|
||||
|
||||
// Fixed32 Divide
|
||||
export const Fixed32DivideTemplate: BlueprintNodeTemplate = {
|
||||
type: 'Fixed32Divide',
|
||||
title: 'Fixed32 /',
|
||||
category: 'math',
|
||||
description: 'Divides A by B',
|
||||
keywords: ['fixed', 'divide', '/'],
|
||||
menuPath: ['Math', 'Fixed', 'Divide'],
|
||||
isPure: true,
|
||||
inputs: [
|
||||
{ name: 'a', displayName: 'A', type: 'object' },
|
||||
{ name: 'b', displayName: 'B', type: 'object' }
|
||||
],
|
||||
outputs: [
|
||||
{ name: 'result', displayName: 'Result', type: 'object' }
|
||||
],
|
||||
color: '#9C27B0'
|
||||
};
|
||||
|
||||
export class Fixed32DivideExecutor implements INodeExecutor {
|
||||
execute(node: BlueprintNode, context: unknown): ExecutionResult {
|
||||
const ctx = context as FixedContext;
|
||||
const a = ctx.evaluateInput(node.id, 'a', Fixed32.ZERO) as Fixed32;
|
||||
const b = ctx.evaluateInput(node.id, 'b', Fixed32.ONE) as Fixed32;
|
||||
const divisor = b ?? Fixed32.ONE;
|
||||
if (divisor.isZero()) {
|
||||
return { outputs: { result: Fixed32.ZERO } };
|
||||
}
|
||||
return { outputs: { result: (a ?? Fixed32.ZERO).div(divisor) } };
|
||||
}
|
||||
}
|
||||
|
||||
// Fixed32 Negate
|
||||
export const Fixed32NegateTemplate: BlueprintNodeTemplate = {
|
||||
type: 'Fixed32Negate',
|
||||
title: 'Fixed32 Negate',
|
||||
category: 'math',
|
||||
description: 'Negates a Fixed32 value',
|
||||
keywords: ['fixed', 'negate', '-'],
|
||||
menuPath: ['Math', 'Fixed', 'Negate'],
|
||||
isPure: true,
|
||||
inputs: [
|
||||
{ name: 'value', displayName: 'Value', type: 'object' }
|
||||
],
|
||||
outputs: [
|
||||
{ name: 'result', displayName: 'Result', type: 'object' }
|
||||
],
|
||||
color: '#9C27B0'
|
||||
};
|
||||
|
||||
export class Fixed32NegateExecutor implements INodeExecutor {
|
||||
execute(node: BlueprintNode, context: unknown): ExecutionResult {
|
||||
const ctx = context as FixedContext;
|
||||
const value = ctx.evaluateInput(node.id, 'value', Fixed32.ZERO) as Fixed32;
|
||||
return { outputs: { result: (value ?? Fixed32.ZERO).neg() } };
|
||||
}
|
||||
}
|
||||
|
||||
// Fixed32 Abs
|
||||
export const Fixed32AbsTemplate: BlueprintNodeTemplate = {
|
||||
type: 'Fixed32Abs',
|
||||
title: 'Fixed32 Abs',
|
||||
category: 'math',
|
||||
description: 'Absolute value of Fixed32',
|
||||
keywords: ['fixed', 'abs', 'absolute'],
|
||||
menuPath: ['Math', 'Fixed', 'Abs'],
|
||||
isPure: true,
|
||||
inputs: [
|
||||
{ name: 'value', displayName: 'Value', type: 'object' }
|
||||
],
|
||||
outputs: [
|
||||
{ name: 'result', displayName: 'Result', type: 'object' }
|
||||
],
|
||||
color: '#9C27B0'
|
||||
};
|
||||
|
||||
export class Fixed32AbsExecutor implements INodeExecutor {
|
||||
execute(node: BlueprintNode, context: unknown): ExecutionResult {
|
||||
const ctx = context as FixedContext;
|
||||
const value = ctx.evaluateInput(node.id, 'value', Fixed32.ZERO) as Fixed32;
|
||||
return { outputs: { result: (value ?? Fixed32.ZERO).abs() } };
|
||||
}
|
||||
}
|
||||
|
||||
// Fixed32 Sqrt
|
||||
export const Fixed32SqrtTemplate: BlueprintNodeTemplate = {
|
||||
type: 'Fixed32Sqrt',
|
||||
title: 'Fixed32 Sqrt',
|
||||
category: 'math',
|
||||
description: 'Square root (deterministic)',
|
||||
keywords: ['fixed', 'sqrt', 'square', 'root'],
|
||||
menuPath: ['Math', 'Fixed', 'Sqrt'],
|
||||
isPure: true,
|
||||
inputs: [
|
||||
{ name: 'value', displayName: 'Value', type: 'object' }
|
||||
],
|
||||
outputs: [
|
||||
{ name: 'result', displayName: 'Result', type: 'object' }
|
||||
],
|
||||
color: '#9C27B0'
|
||||
};
|
||||
|
||||
export class Fixed32SqrtExecutor implements INodeExecutor {
|
||||
execute(node: BlueprintNode, context: unknown): ExecutionResult {
|
||||
const ctx = context as FixedContext;
|
||||
const value = ctx.evaluateInput(node.id, 'value', Fixed32.ZERO) as Fixed32;
|
||||
return { outputs: { result: Fixed32.sqrt(value ?? Fixed32.ZERO) } };
|
||||
}
|
||||
}
|
||||
|
||||
// Fixed32 Floor
|
||||
export const Fixed32FloorTemplate: BlueprintNodeTemplate = {
|
||||
type: 'Fixed32Floor',
|
||||
title: 'Fixed32 Floor',
|
||||
category: 'math',
|
||||
description: 'Floor of Fixed32',
|
||||
keywords: ['fixed', 'floor', 'round', 'down'],
|
||||
menuPath: ['Math', 'Fixed', 'Floor'],
|
||||
isPure: true,
|
||||
inputs: [
|
||||
{ name: 'value', displayName: 'Value', type: 'object' }
|
||||
],
|
||||
outputs: [
|
||||
{ name: 'result', displayName: 'Result', type: 'object' }
|
||||
],
|
||||
color: '#9C27B0'
|
||||
};
|
||||
|
||||
export class Fixed32FloorExecutor implements INodeExecutor {
|
||||
execute(node: BlueprintNode, context: unknown): ExecutionResult {
|
||||
const ctx = context as FixedContext;
|
||||
const value = ctx.evaluateInput(node.id, 'value', Fixed32.ZERO) as Fixed32;
|
||||
return { outputs: { result: Fixed32.floor(value ?? Fixed32.ZERO) } };
|
||||
}
|
||||
}
|
||||
|
||||
// Fixed32 Ceil
|
||||
export const Fixed32CeilTemplate: BlueprintNodeTemplate = {
|
||||
type: 'Fixed32Ceil',
|
||||
title: 'Fixed32 Ceil',
|
||||
category: 'math',
|
||||
description: 'Ceiling of Fixed32',
|
||||
keywords: ['fixed', 'ceil', 'ceiling', 'round', 'up'],
|
||||
menuPath: ['Math', 'Fixed', 'Ceil'],
|
||||
isPure: true,
|
||||
inputs: [
|
||||
{ name: 'value', displayName: 'Value', type: 'object' }
|
||||
],
|
||||
outputs: [
|
||||
{ name: 'result', displayName: 'Result', type: 'object' }
|
||||
],
|
||||
color: '#9C27B0'
|
||||
};
|
||||
|
||||
export class Fixed32CeilExecutor implements INodeExecutor {
|
||||
execute(node: BlueprintNode, context: unknown): ExecutionResult {
|
||||
const ctx = context as FixedContext;
|
||||
const value = ctx.evaluateInput(node.id, 'value', Fixed32.ZERO) as Fixed32;
|
||||
return { outputs: { result: Fixed32.ceil(value ?? Fixed32.ZERO) } };
|
||||
}
|
||||
}
|
||||
|
||||
// Fixed32 Round
|
||||
export const Fixed32RoundTemplate: BlueprintNodeTemplate = {
|
||||
type: 'Fixed32Round',
|
||||
title: 'Fixed32 Round',
|
||||
category: 'math',
|
||||
description: 'Rounds Fixed32 to nearest integer',
|
||||
keywords: ['fixed', 'round'],
|
||||
menuPath: ['Math', 'Fixed', 'Round'],
|
||||
isPure: true,
|
||||
inputs: [
|
||||
{ name: 'value', displayName: 'Value', type: 'object' }
|
||||
],
|
||||
outputs: [
|
||||
{ name: 'result', displayName: 'Result', type: 'object' }
|
||||
],
|
||||
color: '#9C27B0'
|
||||
};
|
||||
|
||||
export class Fixed32RoundExecutor implements INodeExecutor {
|
||||
execute(node: BlueprintNode, context: unknown): ExecutionResult {
|
||||
const ctx = context as FixedContext;
|
||||
const value = ctx.evaluateInput(node.id, 'value', Fixed32.ZERO) as Fixed32;
|
||||
return { outputs: { result: Fixed32.round(value ?? Fixed32.ZERO) } };
|
||||
}
|
||||
}
|
||||
|
||||
// Fixed32 Sign
|
||||
export const Fixed32SignTemplate: BlueprintNodeTemplate = {
|
||||
type: 'Fixed32Sign',
|
||||
title: 'Fixed32 Sign',
|
||||
category: 'math',
|
||||
description: 'Sign of Fixed32 (-1, 0, or 1)',
|
||||
keywords: ['fixed', 'sign'],
|
||||
menuPath: ['Math', 'Fixed', 'Sign'],
|
||||
isPure: true,
|
||||
inputs: [
|
||||
{ name: 'value', displayName: 'Value', type: 'object' }
|
||||
],
|
||||
outputs: [
|
||||
{ name: 'result', displayName: 'Result', type: 'object' }
|
||||
],
|
||||
color: '#9C27B0'
|
||||
};
|
||||
|
||||
export class Fixed32SignExecutor implements INodeExecutor {
|
||||
execute(node: BlueprintNode, context: unknown): ExecutionResult {
|
||||
const ctx = context as FixedContext;
|
||||
const value = ctx.evaluateInput(node.id, 'value', Fixed32.ZERO) as Fixed32;
|
||||
return { outputs: { result: Fixed32.sign(value ?? Fixed32.ZERO) } };
|
||||
}
|
||||
}
|
||||
|
||||
// Fixed32 Min
|
||||
export const Fixed32MinTemplate: BlueprintNodeTemplate = {
|
||||
type: 'Fixed32Min',
|
||||
title: 'Fixed32 Min',
|
||||
category: 'math',
|
||||
description: 'Minimum of two Fixed32 values',
|
||||
keywords: ['fixed', 'min', 'minimum'],
|
||||
menuPath: ['Math', 'Fixed', 'Min'],
|
||||
isPure: true,
|
||||
inputs: [
|
||||
{ name: 'a', displayName: 'A', type: 'object' },
|
||||
{ name: 'b', displayName: 'B', type: 'object' }
|
||||
],
|
||||
outputs: [
|
||||
{ name: 'result', displayName: 'Result', type: 'object' }
|
||||
],
|
||||
color: '#9C27B0'
|
||||
};
|
||||
|
||||
export class Fixed32MinExecutor implements INodeExecutor {
|
||||
execute(node: BlueprintNode, context: unknown): ExecutionResult {
|
||||
const ctx = context as FixedContext;
|
||||
const a = ctx.evaluateInput(node.id, 'a', Fixed32.ZERO) as Fixed32;
|
||||
const b = ctx.evaluateInput(node.id, 'b', Fixed32.ZERO) as Fixed32;
|
||||
return { outputs: { result: Fixed32.min(a ?? Fixed32.ZERO, b ?? Fixed32.ZERO) } };
|
||||
}
|
||||
}
|
||||
|
||||
// Fixed32 Max
|
||||
export const Fixed32MaxTemplate: BlueprintNodeTemplate = {
|
||||
type: 'Fixed32Max',
|
||||
title: 'Fixed32 Max',
|
||||
category: 'math',
|
||||
description: 'Maximum of two Fixed32 values',
|
||||
keywords: ['fixed', 'max', 'maximum'],
|
||||
menuPath: ['Math', 'Fixed', 'Max'],
|
||||
isPure: true,
|
||||
inputs: [
|
||||
{ name: 'a', displayName: 'A', type: 'object' },
|
||||
{ name: 'b', displayName: 'B', type: 'object' }
|
||||
],
|
||||
outputs: [
|
||||
{ name: 'result', displayName: 'Result', type: 'object' }
|
||||
],
|
||||
color: '#9C27B0'
|
||||
};
|
||||
|
||||
export class Fixed32MaxExecutor implements INodeExecutor {
|
||||
execute(node: BlueprintNode, context: unknown): ExecutionResult {
|
||||
const ctx = context as FixedContext;
|
||||
const a = ctx.evaluateInput(node.id, 'a', Fixed32.ZERO) as Fixed32;
|
||||
const b = ctx.evaluateInput(node.id, 'b', Fixed32.ZERO) as Fixed32;
|
||||
return { outputs: { result: Fixed32.max(a ?? Fixed32.ZERO, b ?? Fixed32.ZERO) } };
|
||||
}
|
||||
}
|
||||
|
||||
// Fixed32 Clamp
|
||||
export const Fixed32ClampTemplate: BlueprintNodeTemplate = {
|
||||
type: 'Fixed32Clamp',
|
||||
title: 'Fixed32 Clamp',
|
||||
category: 'math',
|
||||
description: 'Clamps Fixed32 to range',
|
||||
keywords: ['fixed', 'clamp', 'limit', 'range'],
|
||||
menuPath: ['Math', 'Fixed', 'Clamp'],
|
||||
isPure: true,
|
||||
inputs: [
|
||||
{ name: 'value', displayName: 'Value', type: 'object' },
|
||||
{ name: 'min', displayName: 'Min', type: 'object' },
|
||||
{ name: 'max', displayName: 'Max', type: 'object' }
|
||||
],
|
||||
outputs: [
|
||||
{ name: 'result', displayName: 'Result', type: 'object' }
|
||||
],
|
||||
color: '#9C27B0'
|
||||
};
|
||||
|
||||
export class Fixed32ClampExecutor implements INodeExecutor {
|
||||
execute(node: BlueprintNode, context: unknown): ExecutionResult {
|
||||
const ctx = context as FixedContext;
|
||||
const value = ctx.evaluateInput(node.id, 'value', Fixed32.ZERO) as Fixed32;
|
||||
const min = ctx.evaluateInput(node.id, 'min', Fixed32.ZERO) as Fixed32;
|
||||
const max = ctx.evaluateInput(node.id, 'max', Fixed32.ONE) as Fixed32;
|
||||
return { outputs: { result: Fixed32.clamp(value ?? Fixed32.ZERO, min ?? Fixed32.ZERO, max ?? Fixed32.ONE) } };
|
||||
}
|
||||
}
|
||||
|
||||
// Fixed32 Lerp
|
||||
export const Fixed32LerpTemplate: BlueprintNodeTemplate = {
|
||||
type: 'Fixed32Lerp',
|
||||
title: 'Fixed32 Lerp',
|
||||
category: 'math',
|
||||
description: 'Linear interpolation between A and B',
|
||||
keywords: ['fixed', 'lerp', 'interpolate', 'blend'],
|
||||
menuPath: ['Math', 'Fixed', 'Lerp'],
|
||||
isPure: true,
|
||||
inputs: [
|
||||
{ name: 'a', displayName: 'A', type: 'object' },
|
||||
{ name: 'b', displayName: 'B', type: 'object' },
|
||||
{ name: 't', displayName: 'T', type: 'object' }
|
||||
],
|
||||
outputs: [
|
||||
{ name: 'result', displayName: 'Result', type: 'object' }
|
||||
],
|
||||
color: '#9C27B0'
|
||||
};
|
||||
|
||||
export class Fixed32LerpExecutor implements INodeExecutor {
|
||||
execute(node: BlueprintNode, context: unknown): ExecutionResult {
|
||||
const ctx = context as FixedContext;
|
||||
const a = ctx.evaluateInput(node.id, 'a', Fixed32.ZERO) as Fixed32;
|
||||
const b = ctx.evaluateInput(node.id, 'b', Fixed32.ONE) as Fixed32;
|
||||
const t = ctx.evaluateInput(node.id, 't', Fixed32.HALF) as Fixed32;
|
||||
return { outputs: { result: Fixed32.lerp(a ?? Fixed32.ZERO, b ?? Fixed32.ONE, t ?? Fixed32.HALF) } };
|
||||
}
|
||||
}
|
||||
|
||||
// Fixed32 Compare
|
||||
export const Fixed32CompareTemplate: BlueprintNodeTemplate = {
|
||||
type: 'Fixed32Compare',
|
||||
title: 'Fixed32 Compare',
|
||||
category: 'math',
|
||||
description: 'Compares two Fixed32 values',
|
||||
keywords: ['fixed', 'compare', 'equal', 'less', 'greater'],
|
||||
menuPath: ['Math', 'Fixed', 'Compare'],
|
||||
isPure: true,
|
||||
inputs: [
|
||||
{ name: 'a', displayName: 'A', type: 'object' },
|
||||
{ name: 'b', displayName: 'B', type: 'object' }
|
||||
],
|
||||
outputs: [
|
||||
{ name: 'equal', displayName: 'A == B', type: 'bool' },
|
||||
{ name: 'less', displayName: 'A < B', type: 'bool' },
|
||||
{ name: 'greater', displayName: 'A > B', type: 'bool' }
|
||||
],
|
||||
color: '#9C27B0'
|
||||
};
|
||||
|
||||
export class Fixed32CompareExecutor implements INodeExecutor {
|
||||
execute(node: BlueprintNode, context: unknown): ExecutionResult {
|
||||
const ctx = context as FixedContext;
|
||||
const a = ctx.evaluateInput(node.id, 'a', Fixed32.ZERO) as Fixed32;
|
||||
const b = ctx.evaluateInput(node.id, 'b', Fixed32.ZERO) as Fixed32;
|
||||
const aVal = a ?? Fixed32.ZERO;
|
||||
const bVal = b ?? Fixed32.ZERO;
|
||||
return {
|
||||
outputs: {
|
||||
equal: aVal.eq(bVal),
|
||||
less: aVal.lt(bVal),
|
||||
greater: aVal.gt(bVal)
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Fixed32 IsZero
|
||||
export const Fixed32IsZeroTemplate: BlueprintNodeTemplate = {
|
||||
type: 'Fixed32IsZero',
|
||||
title: 'Fixed32 Is Zero',
|
||||
category: 'math',
|
||||
description: 'Checks if Fixed32 is zero, positive, or negative',
|
||||
keywords: ['fixed', 'zero', 'check'],
|
||||
menuPath: ['Math', 'Fixed', 'Is Zero'],
|
||||
isPure: true,
|
||||
inputs: [
|
||||
{ name: 'value', displayName: 'Value', type: 'object' }
|
||||
],
|
||||
outputs: [
|
||||
{ name: 'isZero', displayName: 'Is Zero', type: 'bool' },
|
||||
{ name: 'isPositive', displayName: 'Is Positive', type: 'bool' },
|
||||
{ name: 'isNegative', displayName: 'Is Negative', type: 'bool' }
|
||||
],
|
||||
color: '#9C27B0'
|
||||
};
|
||||
|
||||
export class Fixed32IsZeroExecutor implements INodeExecutor {
|
||||
execute(node: BlueprintNode, context: unknown): ExecutionResult {
|
||||
const ctx = context as FixedContext;
|
||||
const value = ctx.evaluateInput(node.id, 'value', Fixed32.ZERO) as Fixed32;
|
||||
const val = value ?? Fixed32.ZERO;
|
||||
return {
|
||||
outputs: {
|
||||
isZero: val.isZero(),
|
||||
isPositive: val.isPositive(),
|
||||
isNegative: val.isNegative()
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Fixed32 Constants
|
||||
export const Fixed32ConstantsTemplate: BlueprintNodeTemplate = {
|
||||
type: 'Fixed32Constants',
|
||||
title: 'Fixed32 Constants',
|
||||
category: 'math',
|
||||
description: 'Common Fixed32 constants',
|
||||
keywords: ['fixed', 'constant', 'pi', 'zero', 'one'],
|
||||
menuPath: ['Math', 'Fixed', 'Constants'],
|
||||
isPure: true,
|
||||
inputs: [],
|
||||
outputs: [
|
||||
{ name: 'zero', displayName: '0', type: 'object' },
|
||||
{ name: 'one', displayName: '1', type: 'object' },
|
||||
{ name: 'half', displayName: '0.5', type: 'object' },
|
||||
{ name: 'pi', displayName: 'PI', type: 'object' },
|
||||
{ name: 'twoPi', displayName: '2PI', type: 'object' }
|
||||
],
|
||||
color: '#9C27B0'
|
||||
};
|
||||
|
||||
export class Fixed32ConstantsExecutor implements INodeExecutor {
|
||||
execute(): ExecutionResult {
|
||||
return {
|
||||
outputs: {
|
||||
zero: Fixed32.ZERO,
|
||||
one: Fixed32.ONE,
|
||||
half: Fixed32.HALF,
|
||||
pi: Fixed32.PI,
|
||||
twoPi: Fixed32.TWO_PI
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Node definitions collection
|
||||
export const FixedNodeDefinitions = [
|
||||
{ template: Fixed32FromTemplate, executor: new Fixed32FromExecutor() },
|
||||
{ template: Fixed32FromIntTemplate, executor: new Fixed32FromIntExecutor() },
|
||||
{ template: Fixed32ToFloatTemplate, executor: new Fixed32ToFloatExecutor() },
|
||||
{ template: Fixed32ToIntTemplate, executor: new Fixed32ToIntExecutor() },
|
||||
{ template: Fixed32AddTemplate, executor: new Fixed32AddExecutor() },
|
||||
{ template: Fixed32SubtractTemplate, executor: new Fixed32SubtractExecutor() },
|
||||
{ template: Fixed32MultiplyTemplate, executor: new Fixed32MultiplyExecutor() },
|
||||
{ template: Fixed32DivideTemplate, executor: new Fixed32DivideExecutor() },
|
||||
{ template: Fixed32NegateTemplate, executor: new Fixed32NegateExecutor() },
|
||||
{ template: Fixed32AbsTemplate, executor: new Fixed32AbsExecutor() },
|
||||
{ template: Fixed32SqrtTemplate, executor: new Fixed32SqrtExecutor() },
|
||||
{ template: Fixed32FloorTemplate, executor: new Fixed32FloorExecutor() },
|
||||
{ template: Fixed32CeilTemplate, executor: new Fixed32CeilExecutor() },
|
||||
{ template: Fixed32RoundTemplate, executor: new Fixed32RoundExecutor() },
|
||||
{ template: Fixed32SignTemplate, executor: new Fixed32SignExecutor() },
|
||||
{ template: Fixed32MinTemplate, executor: new Fixed32MinExecutor() },
|
||||
{ template: Fixed32MaxTemplate, executor: new Fixed32MaxExecutor() },
|
||||
{ template: Fixed32ClampTemplate, executor: new Fixed32ClampExecutor() },
|
||||
{ template: Fixed32LerpTemplate, executor: new Fixed32LerpExecutor() },
|
||||
{ template: Fixed32CompareTemplate, executor: new Fixed32CompareExecutor() },
|
||||
{ template: Fixed32IsZeroTemplate, executor: new Fixed32IsZeroExecutor() },
|
||||
{ template: Fixed32ConstantsTemplate, executor: new Fixed32ConstantsExecutor() }
|
||||
];
|
||||
360
packages/framework/math/src/nodes/FixedVectorNodes.ts
Normal file
360
packages/framework/math/src/nodes/FixedVectorNodes.ts
Normal file
@@ -0,0 +1,360 @@
|
||||
/**
|
||||
* @zh FixedVector2 定点向量蓝图节点
|
||||
* @en FixedVector2 Blueprint Nodes
|
||||
*/
|
||||
|
||||
import type { BlueprintNodeTemplate, BlueprintNode, INodeExecutor, ExecutionResult } from '@esengine/blueprint';
|
||||
import { FixedVector2 } from '../FixedVector2';
|
||||
import { Fixed32 } from '../Fixed32';
|
||||
|
||||
interface FixedVectorContext {
|
||||
evaluateInput(nodeId: string, pinName: string, defaultValue?: unknown): unknown;
|
||||
}
|
||||
|
||||
// Make FixedVector2
|
||||
export const MakeFixedVector2Template: BlueprintNodeTemplate = {
|
||||
type: 'MakeFixedVector2',
|
||||
title: 'Make FixedVector2',
|
||||
category: 'math',
|
||||
description: 'Creates FixedVector2 from floats',
|
||||
keywords: ['make', 'create', 'fixed', 'vector', 'deterministic'],
|
||||
menuPath: ['Math', 'Fixed Vector', 'Make FixedVector2'],
|
||||
isPure: true,
|
||||
inputs: [
|
||||
{ name: 'x', displayName: 'X', type: 'float', defaultValue: 0 },
|
||||
{ name: 'y', displayName: 'Y', type: 'float', defaultValue: 0 }
|
||||
],
|
||||
outputs: [
|
||||
{ name: 'vector', displayName: 'Vector', type: 'object' }
|
||||
],
|
||||
color: '#673AB7'
|
||||
};
|
||||
|
||||
export class MakeFixedVector2Executor implements INodeExecutor {
|
||||
execute(node: BlueprintNode, context: unknown): ExecutionResult {
|
||||
const ctx = context as FixedVectorContext;
|
||||
const x = Number(ctx.evaluateInput(node.id, 'x', 0));
|
||||
const y = Number(ctx.evaluateInput(node.id, 'y', 0));
|
||||
return { outputs: { vector: FixedVector2.from(x, y) } };
|
||||
}
|
||||
}
|
||||
|
||||
// Break FixedVector2
|
||||
export const BreakFixedVector2Template: BlueprintNodeTemplate = {
|
||||
type: 'BreakFixedVector2',
|
||||
title: 'Break FixedVector2',
|
||||
category: 'math',
|
||||
description: 'Breaks FixedVector2 into X and Y floats',
|
||||
keywords: ['break', 'split', 'fixed', 'vector'],
|
||||
menuPath: ['Math', 'Fixed Vector', 'Break FixedVector2'],
|
||||
isPure: true,
|
||||
inputs: [
|
||||
{ name: 'vector', displayName: 'Vector', type: 'object' }
|
||||
],
|
||||
outputs: [
|
||||
{ name: 'x', displayName: 'X', type: 'float' },
|
||||
{ name: 'y', displayName: 'Y', type: 'float' }
|
||||
],
|
||||
color: '#673AB7'
|
||||
};
|
||||
|
||||
export class BreakFixedVector2Executor implements INodeExecutor {
|
||||
execute(node: BlueprintNode, context: unknown): ExecutionResult {
|
||||
const ctx = context as FixedVectorContext;
|
||||
const vector = ctx.evaluateInput(node.id, 'vector', FixedVector2.ZERO) as FixedVector2;
|
||||
const v = vector ?? FixedVector2.ZERO;
|
||||
return { outputs: { x: v.x.toNumber(), y: v.y.toNumber() } };
|
||||
}
|
||||
}
|
||||
|
||||
// FixedVector2 Add
|
||||
export const FixedVector2AddTemplate: BlueprintNodeTemplate = {
|
||||
type: 'FixedVector2Add',
|
||||
title: 'FixedVector2 +',
|
||||
category: 'math',
|
||||
description: 'Adds two fixed vectors',
|
||||
keywords: ['fixed', 'vector', 'add', '+'],
|
||||
menuPath: ['Math', 'Fixed Vector', 'Add'],
|
||||
isPure: true,
|
||||
inputs: [
|
||||
{ name: 'a', displayName: 'A', type: 'object' },
|
||||
{ name: 'b', displayName: 'B', type: 'object' }
|
||||
],
|
||||
outputs: [
|
||||
{ name: 'result', displayName: 'Result', type: 'object' }
|
||||
],
|
||||
color: '#673AB7'
|
||||
};
|
||||
|
||||
export class FixedVector2AddExecutor implements INodeExecutor {
|
||||
execute(node: BlueprintNode, context: unknown): ExecutionResult {
|
||||
const ctx = context as FixedVectorContext;
|
||||
const a = ctx.evaluateInput(node.id, 'a', FixedVector2.ZERO) as FixedVector2;
|
||||
const b = ctx.evaluateInput(node.id, 'b', FixedVector2.ZERO) as FixedVector2;
|
||||
return { outputs: { result: (a ?? FixedVector2.ZERO).add(b ?? FixedVector2.ZERO) } };
|
||||
}
|
||||
}
|
||||
|
||||
// FixedVector2 Subtract
|
||||
export const FixedVector2SubtractTemplate: BlueprintNodeTemplate = {
|
||||
type: 'FixedVector2Subtract',
|
||||
title: 'FixedVector2 -',
|
||||
category: 'math',
|
||||
description: 'Subtracts B from A',
|
||||
keywords: ['fixed', 'vector', 'subtract', '-'],
|
||||
menuPath: ['Math', 'Fixed Vector', 'Subtract'],
|
||||
isPure: true,
|
||||
inputs: [
|
||||
{ name: 'a', displayName: 'A', type: 'object' },
|
||||
{ name: 'b', displayName: 'B', type: 'object' }
|
||||
],
|
||||
outputs: [
|
||||
{ name: 'result', displayName: 'Result', type: 'object' }
|
||||
],
|
||||
color: '#673AB7'
|
||||
};
|
||||
|
||||
export class FixedVector2SubtractExecutor implements INodeExecutor {
|
||||
execute(node: BlueprintNode, context: unknown): ExecutionResult {
|
||||
const ctx = context as FixedVectorContext;
|
||||
const a = ctx.evaluateInput(node.id, 'a', FixedVector2.ZERO) as FixedVector2;
|
||||
const b = ctx.evaluateInput(node.id, 'b', FixedVector2.ZERO) as FixedVector2;
|
||||
return { outputs: { result: (a ?? FixedVector2.ZERO).sub(b ?? FixedVector2.ZERO) } };
|
||||
}
|
||||
}
|
||||
|
||||
// FixedVector2 Scale
|
||||
export const FixedVector2ScaleTemplate: BlueprintNodeTemplate = {
|
||||
type: 'FixedVector2Scale',
|
||||
title: 'FixedVector2 *',
|
||||
category: 'math',
|
||||
description: 'Scales vector by Fixed32 scalar',
|
||||
keywords: ['fixed', 'vector', 'scale', '*'],
|
||||
menuPath: ['Math', 'Fixed Vector', 'Scale'],
|
||||
isPure: true,
|
||||
inputs: [
|
||||
{ name: 'vector', displayName: 'Vector', type: 'object' },
|
||||
{ name: 'scalar', displayName: 'Scalar', type: 'object' }
|
||||
],
|
||||
outputs: [
|
||||
{ name: 'result', displayName: 'Result', type: 'object' }
|
||||
],
|
||||
color: '#673AB7'
|
||||
};
|
||||
|
||||
export class FixedVector2ScaleExecutor implements INodeExecutor {
|
||||
execute(node: BlueprintNode, context: unknown): ExecutionResult {
|
||||
const ctx = context as FixedVectorContext;
|
||||
const vector = ctx.evaluateInput(node.id, 'vector', FixedVector2.ZERO) as FixedVector2;
|
||||
const scalar = ctx.evaluateInput(node.id, 'scalar', Fixed32.ONE) as Fixed32;
|
||||
return { outputs: { result: (vector ?? FixedVector2.ZERO).mul(scalar ?? Fixed32.ONE) } };
|
||||
}
|
||||
}
|
||||
|
||||
// FixedVector2 Negate
|
||||
export const FixedVector2NegateTemplate: BlueprintNodeTemplate = {
|
||||
type: 'FixedVector2Negate',
|
||||
title: 'FixedVector2 Negate',
|
||||
category: 'math',
|
||||
description: 'Negates a fixed vector',
|
||||
keywords: ['fixed', 'vector', 'negate', '-'],
|
||||
menuPath: ['Math', 'Fixed Vector', 'Negate'],
|
||||
isPure: true,
|
||||
inputs: [
|
||||
{ name: 'vector', displayName: 'Vector', type: 'object' }
|
||||
],
|
||||
outputs: [
|
||||
{ name: 'result', displayName: 'Result', type: 'object' }
|
||||
],
|
||||
color: '#673AB7'
|
||||
};
|
||||
|
||||
export class FixedVector2NegateExecutor implements INodeExecutor {
|
||||
execute(node: BlueprintNode, context: unknown): ExecutionResult {
|
||||
const ctx = context as FixedVectorContext;
|
||||
const vector = ctx.evaluateInput(node.id, 'vector', FixedVector2.ZERO) as FixedVector2;
|
||||
return { outputs: { result: (vector ?? FixedVector2.ZERO).neg() } };
|
||||
}
|
||||
}
|
||||
|
||||
// FixedVector2 Length
|
||||
export const FixedVector2LengthTemplate: BlueprintNodeTemplate = {
|
||||
type: 'FixedVector2Length',
|
||||
title: 'FixedVector2 Length',
|
||||
category: 'math',
|
||||
description: 'Gets the length of a fixed vector',
|
||||
keywords: ['fixed', 'vector', 'length', 'magnitude'],
|
||||
menuPath: ['Math', 'Fixed Vector', 'Length'],
|
||||
isPure: true,
|
||||
inputs: [
|
||||
{ name: 'vector', displayName: 'Vector', type: 'object' }
|
||||
],
|
||||
outputs: [
|
||||
{ name: 'length', displayName: 'Length', type: 'object' }
|
||||
],
|
||||
color: '#673AB7'
|
||||
};
|
||||
|
||||
export class FixedVector2LengthExecutor implements INodeExecutor {
|
||||
execute(node: BlueprintNode, context: unknown): ExecutionResult {
|
||||
const ctx = context as FixedVectorContext;
|
||||
const vector = ctx.evaluateInput(node.id, 'vector', FixedVector2.ZERO) as FixedVector2;
|
||||
return { outputs: { length: (vector ?? FixedVector2.ZERO).length() } };
|
||||
}
|
||||
}
|
||||
|
||||
// FixedVector2 Normalize
|
||||
export const FixedVector2NormalizeTemplate: BlueprintNodeTemplate = {
|
||||
type: 'FixedVector2Normalize',
|
||||
title: 'FixedVector2 Normalize',
|
||||
category: 'math',
|
||||
description: 'Normalizes a fixed vector',
|
||||
keywords: ['fixed', 'vector', 'normalize', 'unit'],
|
||||
menuPath: ['Math', 'Fixed Vector', 'Normalize'],
|
||||
isPure: true,
|
||||
inputs: [
|
||||
{ name: 'vector', displayName: 'Vector', type: 'object' }
|
||||
],
|
||||
outputs: [
|
||||
{ name: 'result', displayName: 'Result', type: 'object' }
|
||||
],
|
||||
color: '#673AB7'
|
||||
};
|
||||
|
||||
export class FixedVector2NormalizeExecutor implements INodeExecutor {
|
||||
execute(node: BlueprintNode, context: unknown): ExecutionResult {
|
||||
const ctx = context as FixedVectorContext;
|
||||
const vector = ctx.evaluateInput(node.id, 'vector', FixedVector2.ZERO) as FixedVector2;
|
||||
return { outputs: { result: (vector ?? FixedVector2.ZERO).normalize() } };
|
||||
}
|
||||
}
|
||||
|
||||
// FixedVector2 Dot
|
||||
export const FixedVector2DotTemplate: BlueprintNodeTemplate = {
|
||||
type: 'FixedVector2Dot',
|
||||
title: 'FixedVector2 Dot',
|
||||
category: 'math',
|
||||
description: 'Calculates dot product',
|
||||
keywords: ['fixed', 'vector', 'dot', 'product'],
|
||||
menuPath: ['Math', 'Fixed Vector', 'Dot Product'],
|
||||
isPure: true,
|
||||
inputs: [
|
||||
{ name: 'a', displayName: 'A', type: 'object' },
|
||||
{ name: 'b', displayName: 'B', type: 'object' }
|
||||
],
|
||||
outputs: [
|
||||
{ name: 'result', displayName: 'Result', type: 'object' }
|
||||
],
|
||||
color: '#673AB7'
|
||||
};
|
||||
|
||||
export class FixedVector2DotExecutor implements INodeExecutor {
|
||||
execute(node: BlueprintNode, context: unknown): ExecutionResult {
|
||||
const ctx = context as FixedVectorContext;
|
||||
const a = ctx.evaluateInput(node.id, 'a', FixedVector2.ZERO) as FixedVector2;
|
||||
const b = ctx.evaluateInput(node.id, 'b', FixedVector2.ZERO) as FixedVector2;
|
||||
return { outputs: { result: FixedVector2.dot(a ?? FixedVector2.ZERO, b ?? FixedVector2.ZERO) } };
|
||||
}
|
||||
}
|
||||
|
||||
// FixedVector2 Cross
|
||||
export const FixedVector2CrossTemplate: BlueprintNodeTemplate = {
|
||||
type: 'FixedVector2Cross',
|
||||
title: 'FixedVector2 Cross',
|
||||
category: 'math',
|
||||
description: '2D cross product (returns Fixed32)',
|
||||
keywords: ['fixed', 'vector', 'cross', 'product'],
|
||||
menuPath: ['Math', 'Fixed Vector', 'Cross Product'],
|
||||
isPure: true,
|
||||
inputs: [
|
||||
{ name: 'a', displayName: 'A', type: 'object' },
|
||||
{ name: 'b', displayName: 'B', type: 'object' }
|
||||
],
|
||||
outputs: [
|
||||
{ name: 'result', displayName: 'Result', type: 'object' }
|
||||
],
|
||||
color: '#673AB7'
|
||||
};
|
||||
|
||||
export class FixedVector2CrossExecutor implements INodeExecutor {
|
||||
execute(node: BlueprintNode, context: unknown): ExecutionResult {
|
||||
const ctx = context as FixedVectorContext;
|
||||
const a = ctx.evaluateInput(node.id, 'a', FixedVector2.ZERO) as FixedVector2;
|
||||
const b = ctx.evaluateInput(node.id, 'b', FixedVector2.ZERO) as FixedVector2;
|
||||
return { outputs: { result: FixedVector2.cross(a ?? FixedVector2.ZERO, b ?? FixedVector2.ZERO) } };
|
||||
}
|
||||
}
|
||||
|
||||
// FixedVector2 Distance
|
||||
export const FixedVector2DistanceTemplate: BlueprintNodeTemplate = {
|
||||
type: 'FixedVector2Distance',
|
||||
title: 'FixedVector2 Distance',
|
||||
category: 'math',
|
||||
description: 'Distance between two points',
|
||||
keywords: ['fixed', 'vector', 'distance'],
|
||||
menuPath: ['Math', 'Fixed Vector', 'Distance'],
|
||||
isPure: true,
|
||||
inputs: [
|
||||
{ name: 'a', displayName: 'A', type: 'object' },
|
||||
{ name: 'b', displayName: 'B', type: 'object' }
|
||||
],
|
||||
outputs: [
|
||||
{ name: 'distance', displayName: 'Distance', type: 'object' }
|
||||
],
|
||||
color: '#673AB7'
|
||||
};
|
||||
|
||||
export class FixedVector2DistanceExecutor implements INodeExecutor {
|
||||
execute(node: BlueprintNode, context: unknown): ExecutionResult {
|
||||
const ctx = context as FixedVectorContext;
|
||||
const a = ctx.evaluateInput(node.id, 'a', FixedVector2.ZERO) as FixedVector2;
|
||||
const b = ctx.evaluateInput(node.id, 'b', FixedVector2.ZERO) as FixedVector2;
|
||||
return { outputs: { distance: FixedVector2.distance(a ?? FixedVector2.ZERO, b ?? FixedVector2.ZERO) } };
|
||||
}
|
||||
}
|
||||
|
||||
// FixedVector2 Lerp
|
||||
export const FixedVector2LerpTemplate: BlueprintNodeTemplate = {
|
||||
type: 'FixedVector2Lerp',
|
||||
title: 'FixedVector2 Lerp',
|
||||
category: 'math',
|
||||
description: 'Linear interpolation between two vectors',
|
||||
keywords: ['fixed', 'vector', 'lerp', 'interpolate'],
|
||||
menuPath: ['Math', 'Fixed Vector', 'Lerp'],
|
||||
isPure: true,
|
||||
inputs: [
|
||||
{ name: 'a', displayName: 'A', type: 'object' },
|
||||
{ name: 'b', displayName: 'B', type: 'object' },
|
||||
{ name: 't', displayName: 'T', type: 'object' }
|
||||
],
|
||||
outputs: [
|
||||
{ name: 'result', displayName: 'Result', type: 'object' }
|
||||
],
|
||||
color: '#673AB7'
|
||||
};
|
||||
|
||||
export class FixedVector2LerpExecutor implements INodeExecutor {
|
||||
execute(node: BlueprintNode, context: unknown): ExecutionResult {
|
||||
const ctx = context as FixedVectorContext;
|
||||
const a = ctx.evaluateInput(node.id, 'a', FixedVector2.ZERO) as FixedVector2;
|
||||
const b = ctx.evaluateInput(node.id, 'b', FixedVector2.ZERO) as FixedVector2;
|
||||
const t = ctx.evaluateInput(node.id, 't', Fixed32.HALF) as Fixed32;
|
||||
return { outputs: { result: FixedVector2.lerp(a ?? FixedVector2.ZERO, b ?? FixedVector2.ZERO, t ?? Fixed32.HALF) } };
|
||||
}
|
||||
}
|
||||
|
||||
// Node definitions collection
|
||||
export const FixedVectorNodeDefinitions = [
|
||||
{ template: MakeFixedVector2Template, executor: new MakeFixedVector2Executor() },
|
||||
{ template: BreakFixedVector2Template, executor: new BreakFixedVector2Executor() },
|
||||
{ template: FixedVector2AddTemplate, executor: new FixedVector2AddExecutor() },
|
||||
{ template: FixedVector2SubtractTemplate, executor: new FixedVector2SubtractExecutor() },
|
||||
{ template: FixedVector2ScaleTemplate, executor: new FixedVector2ScaleExecutor() },
|
||||
{ template: FixedVector2NegateTemplate, executor: new FixedVector2NegateExecutor() },
|
||||
{ template: FixedVector2LengthTemplate, executor: new FixedVector2LengthExecutor() },
|
||||
{ template: FixedVector2NormalizeTemplate, executor: new FixedVector2NormalizeExecutor() },
|
||||
{ template: FixedVector2DotTemplate, executor: new FixedVector2DotExecutor() },
|
||||
{ template: FixedVector2CrossTemplate, executor: new FixedVector2CrossExecutor() },
|
||||
{ template: FixedVector2DistanceTemplate, executor: new FixedVector2DistanceExecutor() },
|
||||
{ template: FixedVector2LerpTemplate, executor: new FixedVector2LerpExecutor() }
|
||||
];
|
||||
387
packages/framework/math/src/nodes/VectorNodes.ts
Normal file
387
packages/framework/math/src/nodes/VectorNodes.ts
Normal file
@@ -0,0 +1,387 @@
|
||||
/**
|
||||
* @zh Vector2 蓝图节点
|
||||
* @en Vector2 Blueprint Nodes
|
||||
*/
|
||||
|
||||
import type { BlueprintNodeTemplate, BlueprintNode, INodeExecutor, ExecutionResult } from '@esengine/blueprint';
|
||||
import { Vector2 } from '../Vector2';
|
||||
|
||||
interface VectorContext {
|
||||
evaluateInput(nodeId: string, pinName: string, defaultValue?: unknown): unknown;
|
||||
}
|
||||
|
||||
// Make Vector2
|
||||
export const MakeVector2Template: BlueprintNodeTemplate = {
|
||||
type: 'MakeVector2',
|
||||
title: 'Make Vector2',
|
||||
category: 'math',
|
||||
description: 'Creates a Vector2 from X and Y',
|
||||
keywords: ['make', 'create', 'vector', 'vector2'],
|
||||
menuPath: ['Math', 'Vector', 'Make Vector2'],
|
||||
isPure: true,
|
||||
inputs: [
|
||||
{ name: 'x', displayName: 'X', type: 'float', defaultValue: 0 },
|
||||
{ name: 'y', displayName: 'Y', type: 'float', defaultValue: 0 }
|
||||
],
|
||||
outputs: [
|
||||
{ name: 'vector', displayName: 'Vector', type: 'vector2' }
|
||||
],
|
||||
color: '#2196F3'
|
||||
};
|
||||
|
||||
export class MakeVector2Executor implements INodeExecutor {
|
||||
execute(node: BlueprintNode, context: unknown): ExecutionResult {
|
||||
const ctx = context as VectorContext;
|
||||
const x = Number(ctx.evaluateInput(node.id, 'x', 0));
|
||||
const y = Number(ctx.evaluateInput(node.id, 'y', 0));
|
||||
return { outputs: { vector: new Vector2(x, y) } };
|
||||
}
|
||||
}
|
||||
|
||||
// Break Vector2
|
||||
export const BreakVector2Template: BlueprintNodeTemplate = {
|
||||
type: 'BreakVector2',
|
||||
title: 'Break Vector2',
|
||||
category: 'math',
|
||||
description: 'Breaks a Vector2 into X and Y',
|
||||
keywords: ['break', 'split', 'vector', 'vector2'],
|
||||
menuPath: ['Math', 'Vector', 'Break Vector2'],
|
||||
isPure: true,
|
||||
inputs: [
|
||||
{ name: 'vector', displayName: 'Vector', type: 'vector2' }
|
||||
],
|
||||
outputs: [
|
||||
{ name: 'x', displayName: 'X', type: 'float' },
|
||||
{ name: 'y', displayName: 'Y', type: 'float' }
|
||||
],
|
||||
color: '#2196F3'
|
||||
};
|
||||
|
||||
export class BreakVector2Executor implements INodeExecutor {
|
||||
execute(node: BlueprintNode, context: unknown): ExecutionResult {
|
||||
const ctx = context as VectorContext;
|
||||
const vector = ctx.evaluateInput(node.id, 'vector', Vector2.ZERO) as Vector2;
|
||||
return { outputs: { x: vector?.x ?? 0, y: vector?.y ?? 0 } };
|
||||
}
|
||||
}
|
||||
|
||||
// Vector2 Add
|
||||
export const Vector2AddTemplate: BlueprintNodeTemplate = {
|
||||
type: 'Vector2Add',
|
||||
title: 'Vector2 +',
|
||||
category: 'math',
|
||||
description: 'Adds two vectors',
|
||||
keywords: ['add', 'plus', 'vector'],
|
||||
menuPath: ['Math', 'Vector', 'Add'],
|
||||
isPure: true,
|
||||
inputs: [
|
||||
{ name: 'a', displayName: 'A', type: 'vector2' },
|
||||
{ name: 'b', displayName: 'B', type: 'vector2' }
|
||||
],
|
||||
outputs: [
|
||||
{ name: 'result', displayName: 'Result', type: 'vector2' }
|
||||
],
|
||||
color: '#2196F3'
|
||||
};
|
||||
|
||||
export class Vector2AddExecutor implements INodeExecutor {
|
||||
execute(node: BlueprintNode, context: unknown): ExecutionResult {
|
||||
const ctx = context as VectorContext;
|
||||
const a = ctx.evaluateInput(node.id, 'a', Vector2.ZERO) as Vector2;
|
||||
const b = ctx.evaluateInput(node.id, 'b', Vector2.ZERO) as Vector2;
|
||||
return { outputs: { result: Vector2.add(a ?? Vector2.ZERO, b ?? Vector2.ZERO) } };
|
||||
}
|
||||
}
|
||||
|
||||
// Vector2 Subtract
|
||||
export const Vector2SubtractTemplate: BlueprintNodeTemplate = {
|
||||
type: 'Vector2Subtract',
|
||||
title: 'Vector2 -',
|
||||
category: 'math',
|
||||
description: 'Subtracts B from A',
|
||||
keywords: ['subtract', 'minus', 'vector'],
|
||||
menuPath: ['Math', 'Vector', 'Subtract'],
|
||||
isPure: true,
|
||||
inputs: [
|
||||
{ name: 'a', displayName: 'A', type: 'vector2' },
|
||||
{ name: 'b', displayName: 'B', type: 'vector2' }
|
||||
],
|
||||
outputs: [
|
||||
{ name: 'result', displayName: 'Result', type: 'vector2' }
|
||||
],
|
||||
color: '#2196F3'
|
||||
};
|
||||
|
||||
export class Vector2SubtractExecutor implements INodeExecutor {
|
||||
execute(node: BlueprintNode, context: unknown): ExecutionResult {
|
||||
const ctx = context as VectorContext;
|
||||
const a = ctx.evaluateInput(node.id, 'a', Vector2.ZERO) as Vector2;
|
||||
const b = ctx.evaluateInput(node.id, 'b', Vector2.ZERO) as Vector2;
|
||||
return { outputs: { result: Vector2.subtract(a ?? Vector2.ZERO, b ?? Vector2.ZERO) } };
|
||||
}
|
||||
}
|
||||
|
||||
// Vector2 Scale
|
||||
export const Vector2ScaleTemplate: BlueprintNodeTemplate = {
|
||||
type: 'Vector2Scale',
|
||||
title: 'Vector2 *',
|
||||
category: 'math',
|
||||
description: 'Scales a vector by a scalar',
|
||||
keywords: ['scale', 'multiply', 'vector'],
|
||||
menuPath: ['Math', 'Vector', 'Scale'],
|
||||
isPure: true,
|
||||
inputs: [
|
||||
{ name: 'vector', displayName: 'Vector', type: 'vector2' },
|
||||
{ name: 'scalar', displayName: 'Scalar', type: 'float', defaultValue: 1 }
|
||||
],
|
||||
outputs: [
|
||||
{ name: 'result', displayName: 'Result', type: 'vector2' }
|
||||
],
|
||||
color: '#2196F3'
|
||||
};
|
||||
|
||||
export class Vector2ScaleExecutor implements INodeExecutor {
|
||||
execute(node: BlueprintNode, context: unknown): ExecutionResult {
|
||||
const ctx = context as VectorContext;
|
||||
const vector = ctx.evaluateInput(node.id, 'vector', Vector2.ZERO) as Vector2;
|
||||
const scalar = Number(ctx.evaluateInput(node.id, 'scalar', 1));
|
||||
return { outputs: { result: Vector2.multiply(vector ?? Vector2.ZERO, scalar) } };
|
||||
}
|
||||
}
|
||||
|
||||
// Vector2 Length
|
||||
export const Vector2LengthTemplate: BlueprintNodeTemplate = {
|
||||
type: 'Vector2Length',
|
||||
title: 'Vector2 Length',
|
||||
category: 'math',
|
||||
description: 'Gets the length of a vector',
|
||||
keywords: ['length', 'magnitude', 'vector'],
|
||||
menuPath: ['Math', 'Vector', 'Length'],
|
||||
isPure: true,
|
||||
inputs: [
|
||||
{ name: 'vector', displayName: 'Vector', type: 'vector2' }
|
||||
],
|
||||
outputs: [
|
||||
{ name: 'length', displayName: 'Length', type: 'float' }
|
||||
],
|
||||
color: '#2196F3'
|
||||
};
|
||||
|
||||
export class Vector2LengthExecutor implements INodeExecutor {
|
||||
execute(node: BlueprintNode, context: unknown): ExecutionResult {
|
||||
const ctx = context as VectorContext;
|
||||
const vector = ctx.evaluateInput(node.id, 'vector', Vector2.ZERO) as Vector2;
|
||||
return { outputs: { length: (vector ?? Vector2.ZERO).length } };
|
||||
}
|
||||
}
|
||||
|
||||
// Vector2 Normalize
|
||||
export const Vector2NormalizeTemplate: BlueprintNodeTemplate = {
|
||||
type: 'Vector2Normalize',
|
||||
title: 'Vector2 Normalize',
|
||||
category: 'math',
|
||||
description: 'Normalizes a vector to unit length',
|
||||
keywords: ['normalize', 'unit', 'vector'],
|
||||
menuPath: ['Math', 'Vector', 'Normalize'],
|
||||
isPure: true,
|
||||
inputs: [
|
||||
{ name: 'vector', displayName: 'Vector', type: 'vector2' }
|
||||
],
|
||||
outputs: [
|
||||
{ name: 'result', displayName: 'Result', type: 'vector2' }
|
||||
],
|
||||
color: '#2196F3'
|
||||
};
|
||||
|
||||
export class Vector2NormalizeExecutor implements INodeExecutor {
|
||||
execute(node: BlueprintNode, context: unknown): ExecutionResult {
|
||||
const ctx = context as VectorContext;
|
||||
const vector = ctx.evaluateInput(node.id, 'vector', Vector2.ZERO) as Vector2;
|
||||
return { outputs: { result: (vector ?? Vector2.ZERO).normalized() } };
|
||||
}
|
||||
}
|
||||
|
||||
// Vector2 Dot
|
||||
export const Vector2DotTemplate: BlueprintNodeTemplate = {
|
||||
type: 'Vector2Dot',
|
||||
title: 'Vector2 Dot',
|
||||
category: 'math',
|
||||
description: 'Calculates dot product',
|
||||
keywords: ['dot', 'product', 'vector'],
|
||||
menuPath: ['Math', 'Vector', 'Dot Product'],
|
||||
isPure: true,
|
||||
inputs: [
|
||||
{ name: 'a', displayName: 'A', type: 'vector2' },
|
||||
{ name: 'b', displayName: 'B', type: 'vector2' }
|
||||
],
|
||||
outputs: [
|
||||
{ name: 'result', displayName: 'Result', type: 'float' }
|
||||
],
|
||||
color: '#2196F3'
|
||||
};
|
||||
|
||||
export class Vector2DotExecutor implements INodeExecutor {
|
||||
execute(node: BlueprintNode, context: unknown): ExecutionResult {
|
||||
const ctx = context as VectorContext;
|
||||
const a = ctx.evaluateInput(node.id, 'a', Vector2.ZERO) as Vector2;
|
||||
const b = ctx.evaluateInput(node.id, 'b', Vector2.ZERO) as Vector2;
|
||||
return { outputs: { result: Vector2.dot(a ?? Vector2.ZERO, b ?? Vector2.ZERO) } };
|
||||
}
|
||||
}
|
||||
|
||||
// Vector2 Cross
|
||||
export const Vector2CrossTemplate: BlueprintNodeTemplate = {
|
||||
type: 'Vector2Cross',
|
||||
title: 'Vector2 Cross',
|
||||
category: 'math',
|
||||
description: '2D cross product (returns scalar)',
|
||||
keywords: ['cross', 'product', 'vector'],
|
||||
menuPath: ['Math', 'Vector', 'Cross Product'],
|
||||
isPure: true,
|
||||
inputs: [
|
||||
{ name: 'a', displayName: 'A', type: 'vector2' },
|
||||
{ name: 'b', displayName: 'B', type: 'vector2' }
|
||||
],
|
||||
outputs: [
|
||||
{ name: 'result', displayName: 'Result', type: 'float' }
|
||||
],
|
||||
color: '#2196F3'
|
||||
};
|
||||
|
||||
export class Vector2CrossExecutor implements INodeExecutor {
|
||||
execute(node: BlueprintNode, context: unknown): ExecutionResult {
|
||||
const ctx = context as VectorContext;
|
||||
const a = ctx.evaluateInput(node.id, 'a', Vector2.ZERO) as Vector2;
|
||||
const b = ctx.evaluateInput(node.id, 'b', Vector2.ZERO) as Vector2;
|
||||
return { outputs: { result: Vector2.cross(a ?? Vector2.ZERO, b ?? Vector2.ZERO) } };
|
||||
}
|
||||
}
|
||||
|
||||
// Vector2 Distance
|
||||
export const Vector2DistanceTemplate: BlueprintNodeTemplate = {
|
||||
type: 'Vector2Distance',
|
||||
title: 'Vector2 Distance',
|
||||
category: 'math',
|
||||
description: 'Distance between two points',
|
||||
keywords: ['distance', 'length', 'vector'],
|
||||
menuPath: ['Math', 'Vector', 'Distance'],
|
||||
isPure: true,
|
||||
inputs: [
|
||||
{ name: 'a', displayName: 'A', type: 'vector2' },
|
||||
{ name: 'b', displayName: 'B', type: 'vector2' }
|
||||
],
|
||||
outputs: [
|
||||
{ name: 'distance', displayName: 'Distance', type: 'float' }
|
||||
],
|
||||
color: '#2196F3'
|
||||
};
|
||||
|
||||
export class Vector2DistanceExecutor implements INodeExecutor {
|
||||
execute(node: BlueprintNode, context: unknown): ExecutionResult {
|
||||
const ctx = context as VectorContext;
|
||||
const a = ctx.evaluateInput(node.id, 'a', Vector2.ZERO) as Vector2;
|
||||
const b = ctx.evaluateInput(node.id, 'b', Vector2.ZERO) as Vector2;
|
||||
return { outputs: { distance: Vector2.distance(a ?? Vector2.ZERO, b ?? Vector2.ZERO) } };
|
||||
}
|
||||
}
|
||||
|
||||
// Vector2 Lerp
|
||||
export const Vector2LerpTemplate: BlueprintNodeTemplate = {
|
||||
type: 'Vector2Lerp',
|
||||
title: 'Vector2 Lerp',
|
||||
category: 'math',
|
||||
description: 'Linear interpolation between two vectors',
|
||||
keywords: ['lerp', 'interpolate', 'vector'],
|
||||
menuPath: ['Math', 'Vector', 'Lerp'],
|
||||
isPure: true,
|
||||
inputs: [
|
||||
{ name: 'a', displayName: 'A', type: 'vector2' },
|
||||
{ name: 'b', displayName: 'B', type: 'vector2' },
|
||||
{ name: 't', displayName: 'T', type: 'float', defaultValue: 0.5 }
|
||||
],
|
||||
outputs: [
|
||||
{ name: 'result', displayName: 'Result', type: 'vector2' }
|
||||
],
|
||||
color: '#2196F3'
|
||||
};
|
||||
|
||||
export class Vector2LerpExecutor implements INodeExecutor {
|
||||
execute(node: BlueprintNode, context: unknown): ExecutionResult {
|
||||
const ctx = context as VectorContext;
|
||||
const a = ctx.evaluateInput(node.id, 'a', Vector2.ZERO) as Vector2;
|
||||
const b = ctx.evaluateInput(node.id, 'b', Vector2.ZERO) as Vector2;
|
||||
const t = Number(ctx.evaluateInput(node.id, 't', 0.5));
|
||||
return { outputs: { result: Vector2.lerp(a ?? Vector2.ZERO, b ?? Vector2.ZERO, t) } };
|
||||
}
|
||||
}
|
||||
|
||||
// Vector2 Rotate
|
||||
export const Vector2RotateTemplate: BlueprintNodeTemplate = {
|
||||
type: 'Vector2Rotate',
|
||||
title: 'Vector2 Rotate',
|
||||
category: 'math',
|
||||
description: 'Rotates a vector by angle (radians)',
|
||||
keywords: ['rotate', 'turn', 'vector'],
|
||||
menuPath: ['Math', 'Vector', 'Rotate'],
|
||||
isPure: true,
|
||||
inputs: [
|
||||
{ name: 'vector', displayName: 'Vector', type: 'vector2' },
|
||||
{ name: 'angle', displayName: 'Angle (rad)', type: 'float', defaultValue: 0 }
|
||||
],
|
||||
outputs: [
|
||||
{ name: 'result', displayName: 'Result', type: 'vector2' }
|
||||
],
|
||||
color: '#2196F3'
|
||||
};
|
||||
|
||||
export class Vector2RotateExecutor implements INodeExecutor {
|
||||
execute(node: BlueprintNode, context: unknown): ExecutionResult {
|
||||
const ctx = context as VectorContext;
|
||||
const vector = ctx.evaluateInput(node.id, 'vector', Vector2.ZERO) as Vector2;
|
||||
const angle = Number(ctx.evaluateInput(node.id, 'angle', 0));
|
||||
return { outputs: { result: (vector ?? Vector2.ZERO).rotated(angle) } };
|
||||
}
|
||||
}
|
||||
|
||||
// Vector2 From Angle
|
||||
export const Vector2FromAngleTemplate: BlueprintNodeTemplate = {
|
||||
type: 'Vector2FromAngle',
|
||||
title: 'Vector2 From Angle',
|
||||
category: 'math',
|
||||
description: 'Creates unit vector from angle (radians)',
|
||||
keywords: ['from', 'angle', 'direction', 'vector'],
|
||||
menuPath: ['Math', 'Vector', 'From Angle'],
|
||||
isPure: true,
|
||||
inputs: [
|
||||
{ name: 'angle', displayName: 'Angle (rad)', type: 'float', defaultValue: 0 }
|
||||
],
|
||||
outputs: [
|
||||
{ name: 'vector', displayName: 'Vector', type: 'vector2' }
|
||||
],
|
||||
color: '#2196F3'
|
||||
};
|
||||
|
||||
export class Vector2FromAngleExecutor implements INodeExecutor {
|
||||
execute(node: BlueprintNode, context: unknown): ExecutionResult {
|
||||
const ctx = context as VectorContext;
|
||||
const angle = Number(ctx.evaluateInput(node.id, 'angle', 0));
|
||||
return { outputs: { vector: Vector2.fromAngle(angle) } };
|
||||
}
|
||||
}
|
||||
|
||||
// Node definitions collection
|
||||
export const VectorNodeDefinitions = [
|
||||
{ template: MakeVector2Template, executor: new MakeVector2Executor() },
|
||||
{ template: BreakVector2Template, executor: new BreakVector2Executor() },
|
||||
{ template: Vector2AddTemplate, executor: new Vector2AddExecutor() },
|
||||
{ template: Vector2SubtractTemplate, executor: new Vector2SubtractExecutor() },
|
||||
{ template: Vector2ScaleTemplate, executor: new Vector2ScaleExecutor() },
|
||||
{ template: Vector2LengthTemplate, executor: new Vector2LengthExecutor() },
|
||||
{ template: Vector2NormalizeTemplate, executor: new Vector2NormalizeExecutor() },
|
||||
{ template: Vector2DotTemplate, executor: new Vector2DotExecutor() },
|
||||
{ template: Vector2CrossTemplate, executor: new Vector2CrossExecutor() },
|
||||
{ template: Vector2DistanceTemplate, executor: new Vector2DistanceExecutor() },
|
||||
{ template: Vector2LerpTemplate, executor: new Vector2LerpExecutor() },
|
||||
{ template: Vector2RotateTemplate, executor: new Vector2RotateExecutor() },
|
||||
{ template: Vector2FromAngleTemplate, executor: new Vector2FromAngleExecutor() }
|
||||
];
|
||||
29
packages/framework/math/src/nodes/index.ts
Normal file
29
packages/framework/math/src/nodes/index.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
/**
|
||||
* @zh 数学库蓝图节点
|
||||
* @en Math Library Blueprint Nodes
|
||||
*
|
||||
* @zh 导出所有数学相关的蓝图节点
|
||||
* @en Exports all math-related blueprint nodes
|
||||
*/
|
||||
|
||||
export * from './VectorNodes';
|
||||
export * from './FixedNodes';
|
||||
export * from './FixedVectorNodes';
|
||||
export * from './ColorNodes';
|
||||
|
||||
// Re-export node definition collections
|
||||
import { VectorNodeDefinitions } from './VectorNodes';
|
||||
import { FixedNodeDefinitions } from './FixedNodes';
|
||||
import { FixedVectorNodeDefinitions } from './FixedVectorNodes';
|
||||
import { ColorNodeDefinitions } from './ColorNodes';
|
||||
|
||||
/**
|
||||
* @zh 所有数学库蓝图节点定义
|
||||
* @en All math library blueprint node definitions
|
||||
*/
|
||||
export const MathNodeDefinitions = [
|
||||
...VectorNodeDefinitions,
|
||||
...FixedNodeDefinitions,
|
||||
...FixedVectorNodeDefinitions,
|
||||
...ColorNodeDefinitions
|
||||
];
|
||||
@@ -1,5 +1,27 @@
|
||||
# @esengine/network
|
||||
|
||||
## 13.0.0
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`4e66bd8`](https://github.com/esengine/esengine/commit/4e66bd8e2be80b366a7723dcc48b99df0457aed4)]:
|
||||
- @esengine/blueprint@4.5.0
|
||||
- @esengine/ecs-framework-math@2.10.1
|
||||
|
||||
## 12.0.0
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`fa593a3`](https://github.com/esengine/esengine/commit/fa593a3c69292207800750f8106f418465cb7c0f)]:
|
||||
- @esengine/ecs-framework-math@2.10.0
|
||||
|
||||
## 11.0.0
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`bffe90b`](https://github.com/esengine/esengine/commit/bffe90b6a17563cc90709faf339b229dc3abd22d)]:
|
||||
- @esengine/ecs-framework-math@2.9.0
|
||||
|
||||
## 10.0.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@esengine/network",
|
||||
"version": "10.0.0",
|
||||
"version": "13.0.0",
|
||||
"description": "Network synchronization for multiplayer games",
|
||||
"esengine": {
|
||||
"plugin": true,
|
||||
|
||||
@@ -1,5 +1,27 @@
|
||||
# @esengine/pathfinding
|
||||
|
||||
## 12.0.0
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`4e66bd8`](https://github.com/esengine/esengine/commit/4e66bd8e2be80b366a7723dcc48b99df0457aed4)]:
|
||||
- @esengine/blueprint@4.5.0
|
||||
- @esengine/ecs-framework-math@2.10.1
|
||||
|
||||
## 11.0.0
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`fa593a3`](https://github.com/esengine/esengine/commit/fa593a3c69292207800750f8106f418465cb7c0f)]:
|
||||
- @esengine/ecs-framework-math@2.10.0
|
||||
|
||||
## 10.0.0
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`bffe90b`](https://github.com/esengine/esengine/commit/bffe90b6a17563cc90709faf339b229dc3abd22d)]:
|
||||
- @esengine/ecs-framework-math@2.9.0
|
||||
|
||||
## 9.0.0
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@esengine/pathfinding",
|
||||
"version": "9.0.0",
|
||||
"version": "12.0.0",
|
||||
"description": "寻路系统 | Pathfinding System - A*, Grid, NavMesh",
|
||||
"type": "module",
|
||||
"main": "./dist/index.js",
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
# @esengine/procgen
|
||||
|
||||
## 9.0.0
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`4e66bd8`](https://github.com/esengine/esengine/commit/4e66bd8e2be80b366a7723dcc48b99df0457aed4)]:
|
||||
- @esengine/blueprint@4.5.0
|
||||
|
||||
## 8.0.0
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@esengine/procgen",
|
||||
"version": "8.0.0",
|
||||
"version": "9.0.0",
|
||||
"description": "Procedural generation tools for ECS Framework / ECS 框架的程序化生成工具",
|
||||
"type": "module",
|
||||
"main": "./dist/index.js",
|
||||
|
||||
@@ -1,5 +1,27 @@
|
||||
# @esengine/spatial
|
||||
|
||||
## 12.0.0
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`4e66bd8`](https://github.com/esengine/esengine/commit/4e66bd8e2be80b366a7723dcc48b99df0457aed4)]:
|
||||
- @esengine/blueprint@4.5.0
|
||||
- @esengine/ecs-framework-math@2.10.1
|
||||
|
||||
## 11.0.0
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`fa593a3`](https://github.com/esengine/esengine/commit/fa593a3c69292207800750f8106f418465cb7c0f)]:
|
||||
- @esengine/ecs-framework-math@2.10.0
|
||||
|
||||
## 10.0.0
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`bffe90b`](https://github.com/esengine/esengine/commit/bffe90b6a17563cc90709faf339b229dc3abd22d)]:
|
||||
- @esengine/ecs-framework-math@2.9.0
|
||||
|
||||
## 9.0.0
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@esengine/spatial",
|
||||
"version": "9.0.0",
|
||||
"version": "12.0.0",
|
||||
"description": "Spatial query and indexing system for ECS Framework / ECS 框架的空间查询和索引系统",
|
||||
"type": "module",
|
||||
"main": "./dist/index.js",
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
# @esengine/timer
|
||||
|
||||
## 9.0.0
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`4e66bd8`](https://github.com/esengine/esengine/commit/4e66bd8e2be80b366a7723dcc48b99df0457aed4)]:
|
||||
- @esengine/blueprint@4.5.0
|
||||
|
||||
## 8.0.0
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@esengine/timer",
|
||||
"version": "8.0.0",
|
||||
"version": "9.0.0",
|
||||
"description": "Timer and cooldown system for ECS Framework / ECS 框架的定时器和冷却系统",
|
||||
"type": "module",
|
||||
"main": "./dist/index.js",
|
||||
|
||||
@@ -1,5 +1,32 @@
|
||||
# @esengine/demos
|
||||
|
||||
## 1.0.18
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies []:
|
||||
- @esengine/fsm@9.0.0
|
||||
- @esengine/pathfinding@12.0.0
|
||||
- @esengine/procgen@9.0.0
|
||||
- @esengine/spatial@12.0.0
|
||||
- @esengine/timer@9.0.0
|
||||
|
||||
## 1.0.17
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies []:
|
||||
- @esengine/pathfinding@11.0.0
|
||||
- @esengine/spatial@11.0.0
|
||||
|
||||
## 1.0.16
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies []:
|
||||
- @esengine/pathfinding@10.0.0
|
||||
- @esengine/spatial@10.0.0
|
||||
|
||||
## 1.0.15
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@esengine/demos",
|
||||
"version": "1.0.15",
|
||||
"version": "1.0.18",
|
||||
"private": true,
|
||||
"description": "Demo tests for ESEngine modules documentation",
|
||||
"type": "module",
|
||||
|
||||
4
pnpm-lock.yaml
generated
4
pnpm-lock.yaml
generated
@@ -1575,6 +1575,10 @@ importers:
|
||||
version: 5.9.3
|
||||
|
||||
packages/framework/math:
|
||||
dependencies:
|
||||
'@esengine/blueprint':
|
||||
specifier: workspace:*
|
||||
version: link:../blueprint
|
||||
devDependencies:
|
||||
'@rollup/plugin-commonjs':
|
||||
specifier: ^28.0.3
|
||||
|
||||
Reference in New Issue
Block a user