---
title: "AST + 中文声明 DSL + Bevy 的积木编程"
slug: "ast---中文声明-dsl---bevy-的积木编程"
canonical_url: "https://blog.youngoing.cn/posts/ast---%E4%B8%AD%E6%96%87%E5%A3%B0%E6%98%8E-dsl---bevy-%E7%9A%84%E7%A7%AF%E6%9C%A8%E7%BC%96%E7%A8%8B/"
collection: todo
published_at: 2026-08-27T00:00:00.000Z
updated_at: 2026-08-27T00:00:00.000Z
tags: []
author: youngo
---

## Navigation Context

- Canonical URL: https://blog.youngoing.cn/posts/ast---%E4%B8%AD%E6%96%87%E5%A3%B0%E6%98%8E-dsl---bevy-%E7%9A%84%E7%A7%AF%E6%9C%A8%E7%BC%96%E7%A8%8B/
- You are here: Home > Posts > todo > AST + 中文声明 DSL + Bevy 的积木编程

### Useful Next Links
- [Home](https://blog.youngoing.cn/)
- [todo](https://blog.youngoing.cn/collections/todo/)
- [技术](https://blog.youngoing.cn/collections/%E6%8A%80%E6%9C%AF/)
- [测试](https://blog.youngoing.cn/collections/%E6%B5%8B%E8%AF%95/)

### Related Content

#### Pages That Mention This Page
- [TODO](https://blog.youngoing.cn/posts/todo/)

### 1\. 系统概述

#### 1.1 项目目标

实现一个类似 Scratch 的可视化积木编程环境，使用 Rust 语言和 Bevy 游戏引擎构建，支持中文积木，采用 AST 作为核心数据结构，通过声明式 DSL 管理积木定义。

#### 1.2 核心特性

-   中文积木界面
-   拖拽式编程
-   实时执行与预览
-   ECS 架构集成
-   可扩展的积木系统
-   项目保存与加载

#### 1.3 技术栈

-   **语言**: Rust 2021 edition
-   **引擎**: Bevy 0.12+
-   **UI**: Bevy UI + egui
-   **序列化**: serde + serde\_json
-   **ECS**: Bevy ECS

### 2\. 系统架构

#### 2.1 分层架构

```
┌─────────────────────────────────────────────┐
│           UI Layer (Bevy UI / egui)         │
│  积木面板 | 工作区 | 属性面板 | 预览窗口      │
├─────────────────────────────────────────────┤
│         Visual Editor Layer                 │
│  拖拽系统 | 连接验证 | 积木渲染 | 选择管理   │
├─────────────────────────────────────────────┤
│         DSL Definition Layer                │
│  积木注册表 | 参数定义 | 中文映射 | 模板渲染  │
├─────────────────────────────────────────────┤
│           AST Core Layer                    │
│  积木节点 | 树结构 | 序列化 | 遍历          │
├─────────────────────────────────────────────┤
│         Execution Engine Layer              │
│  解释器 | 调度器 | 事件系统 | 并发执行      │
├─────────────────────────────────────────────┤
│           Bevy ECS Runtime                  │
│  实体 | 组件 | 系统 | 资源 | 查询          │
└─────────────────────────────────────────────┘
```

var e=\[\];function t(t){if(t.dataset.htmlFrameAuto===\`done\`)return;t.dataset.htmlFrameAuto=\`done\`;let n=t.getAttribute(\`style\`)||\`\`;if(t.classList.contains(\`code-rendered-aspect\`)||n.includes(\`--html-frame-height\`)||n.includes(\`--html-frame-aspect-ratio\`))return;let r=t.querySelector(\`iframe.code-iframe\`);if(!r)return;let i=()=>{try{let e=r.contentDocument;if(!e||!e.body)return;let n=Math.max(e.body.scrollHeight,e.body.offsetHeight);if(!n)return;let i=Math.round(window.innerHeight\*.95),a=Math.min(Math.max(n+12,96),i);t.style.setProperty(\`--html-frame-height\`,\`${a}px\`)}catch{}};e.push(i);let a=()=>{i();try{let e=r.contentDocument;e&&e.body&&\`ResizeObserver\`in window&&new ResizeObserver(()=>i()).observe(e.body)}catch{}};r.contentDocument&&r.contentDocument.readyState===\`complete\`?a():r.addEventListener(\`load\`,a)}function n(){let e=document.querySelectorAll(\`\[data-html-frame\]\`);if(!e.length)return;if(!(\`IntersectionObserver\`in window)){e.forEach(t);return}let n=new IntersectionObserver((e,n)=>{for(let r of e)r.isIntersecting&&(n.unobserve(r.target),t(r.target))},{rootMargin:\`250px 0px\`});e.forEach(e=>n.observe(e))}var r=()=>\`requestIdleCallback\`in window?window.requestIdleCallback(n,{timeout:2e3}):setTimeout(n,200);document.readyState===\`complete\`?r():window.addEventListener(\`load\`,r,{once:!0});var i=0;window.addEventListener(\`resize\`,()=>{i&&cancelAnimationFrame(i),i=requestAnimationFrame(()=>e.forEach(e=>e()))});

#### 2.2 数据流

```
用户拖拽 → UI事件 → 创建AST节点 → 更新积木树
    ↓
积木树变更 → 触发验证 → 更新执行计划
    ↓
执行计划 → 解释器执行 → 更新ECS状态
    ↓
ECS状态变更 → 渲染更新 → 用户看到结果
```

### 3\. 核心数据结构

#### 3.1 AST 定义

```
// src/ast/mod.rs
use serde::{Serialize, Deserialize};
use bevy::prelude::*;

/// 积木 ID
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct BlockId(pub u64);

/// 积木节点（AST 节点）
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BlockNode {
    pub id: BlockId,
    pub block_type: BlockType,
    pub params: HashMap<String, Value>,
    pub children: Vec<BlockId>,
}

/// 积木类型
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum BlockType {
    // 事件类
    WhenFlagClicked,
    WhenKeyPressed { key: KeyCode },
    WhenSpriteClicked,

    // 运动类
    Move { steps: f32 },
    Turn { degrees: f32 },
    GoTo { x: f32, y: f32 },
    Glide { seconds: f32, x: f32, y: f32 },

    // 外观类
    Say { text: String },
    Think { text: String },
    Show,
    Hide,
    ChangeSize { percent: f32 },

    // 控制类
    Wait { seconds: f32 },
    Repeat { times: u32 },
    Forever,
    If { condition: Condition },
    IfElse { condition: Condition },
    WaitUntil { condition: Condition },
    Stop { stop_type: StopType },

    // 变量类
    SetVariable { name: String },
    ChangeVariable { name: String, delta: f32 },

    // 自定义积木
    Custom { name: String },
}

/// 值类型
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum Value {
    Number(f64),
    String(String),
    Boolean(bool),
    List(Vec<Value>),
    Sprite(Entity),
    Color(Color),
    Null,
}

/// 条件表达式
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum Condition {
    True,
    False,
    Equals(Box<Value>, Box<Value>),
    GreaterThan(Box<Value>, Box<Value>),
    LessThan(Box<Value>, Box<Value>),
    And(Box<Condition>, Box<Condition>),
    Or(Box<Condition>, Box<Condition>),
    Not(Box<Condition>),
    KeyPressed(KeyCode),
    TouchingSprite(Entity),
}

/// 积木树（整个程序）
#[derive(Debug, Clone, Serialize, Deserialize, Resource)]
pub struct BlockTree {
    pub nodes: HashMap<BlockId, BlockNode>,
    pub root_nodes: Vec<BlockId>,
    pub next_id: u64,
}
```

#### 3.2 声明式 DSL 定义

```
// src/dsl/mod.rs
use std::collections::HashMap;
use bevy::prelude::*;

/// 参数类型
#[derive(Debug, Clone, PartialEq)]
pub enum ParamType {
    Number,
    String,
    Boolean,
    Color,
    Sprite,
    List,
    Any,
}

/// 参数定义
#[derive(Debug, Clone)]
pub struct ParamDef {
    pub name: &'static str,
    pub chinese_name: &'static str,
    pub param_type: ParamType,
    pub default_value: Value,
    pub constraints: Vec<ParamConstraint>,
}

/// 参数约束
#[derive(Debug, Clone)]
pub enum ParamConstraint {
    Range(f64, f64),
    MinLength(usize),
    MaxLength(usize),
    NonEmpty,
    Positive,
}

/// 积木分类
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum BlockCategory {
    Event,
    Motion,
    Look,
    Control,
    Variable,
    Custom,
}

impl BlockCategory {
    pub fn chinese_name(&self) -> &'static str {
        match self {
            BlockCategory::Event => "事件",
            BlockCategory::Motion => "运动",
            BlockCategory::Look => "外观",
            BlockCategory::Control => "控制",
            BlockCategory::Variable => "变量",
            BlockCategory::Custom => "自定义",
        }
    }
}

/// 积木执行器类型
pub type BlockExecutor = fn(&mut ExecutionContext, &HashMap<String, Value>, &[BlockId]) -> Result<(), ExecutionError>;

/// 积木定义
pub struct BlockDef {
    pub id: &'static str,
    pub category: BlockCategory,
    pub chinese_name: &'static str,
    pub template: &'static str,
    pub description: &'static str,
    pub params: Vec<ParamDef>,
    pub children_slots: Vec<ChildSlot>,
    pub executor: BlockExecutor,
    pub shape: BlockShape,
}

/// 子积木槽位
#[derive(Debug, Clone)]
pub struct ChildSlot {
    pub name: &'static str,
    pub chinese_name: &'static str,
    pub slot_type: SlotType,
    pub accepts: Vec<BlockCategory>,
}

/// 槽位类型
#[derive(Debug, Clone)]
pub enum SlotType {
    Sequence,    // 可容纳多个积木
    Single,      // 只能容纳一个积木
    Boolean,     // 布尔值积木
    Value,       // 值积木
}

/// 积木形状
#[derive(Debug, Clone)]
pub enum BlockShape {
    Hat,         // 帽子形状（事件积木）
    Stack,       // 堆叠形状（命令积木）
    Boolean,     // 菱形（布尔积木）
    Reporter,    // 圆角（值积木）
    CShape,      // C 形（包含子积木）
}

/// 积木注册表
#[derive(Resource)]
pub struct BlockRegistry {
    blocks: HashMap<&'static str, BlockDef>,
    by_category: HashMap<BlockCategory, Vec<&'static str>>,
}

impl BlockRegistry {
    pub fn new() -> Self {
        let mut registry = Self {
            blocks: HashMap::new(),
            by_category: HashMap::new(),
        };

        registry.register_default_blocks();
        registry
    }

    pub fn register(&mut self, def: BlockDef) {
        self.by_category
            .entry(def.category.clone())
            .or_default()
            .push(def.id);
        self.blocks.insert(def.id, def);
    }

    pub fn get(&self, id: &str) -> Option<&BlockDef> {
        self.blocks.get(id)
    }

    pub fn get_by_category(&self, category: &BlockCategory) -> &[&'static str] {
        self.by_category
            .get(category)
            .map(|v| v.as_slice())
            .unwrap_or(&[])
    }

    pub fn all_blocks(&self) -> impl Iterator<Item = &BlockDef> {
        self.blocks.values()
    }

    fn register_default_blocks(&mut self) {
        self.register_motion_blocks();
        self.register_control_blocks();
        self.register_event_blocks();
        self.register_look_blocks();
        self.register_variable_blocks();
    }

    fn register_motion_blocks(&mut self) {
        self.register(BlockDef {
            id: "move",
            category: BlockCategory::Motion,
            chinese_name: "移动",
            template: "移动 {步数} 步",
            description: "让角色移动指定步数",
            params: vec![
                ParamDef {
                    name: "steps",
                    chinese_name: "步数",
                    param_type: ParamType::Number,
                    default_value: Value::Number(10.0),
                    constraints: vec![ParamConstraint::Range(0.0, 1000.0)],
                }
            ],
            children_slots: vec![],
            executor: execute_move,
            shape: BlockShape::Stack,
        });

        self.register(BlockDef {
            id: "turn",
            category: BlockCategory::Motion,
            chinese_name: "转向",
            template: "向右转 {角度} 度",
            description: "让角色向右旋转指定角度",
            params: vec![
                ParamDef {
                    name: "degrees",
                    chinese_name: "角度",
                    param_type: ParamType::Number,
                    default_value: Value::Number(90.0),
                    constraints: vec![ParamConstraint::Range(-360.0, 360.0)],
                }
            ],
            children_slots: vec![],
            executor: execute_turn,
            shape: BlockShape::Stack,
        });
    }

    fn register_control_blocks(&mut self) {
        self.register(BlockDef {
            id: "repeat",
            category: BlockCategory::Control,
            chinese_name: "重复",
            template: "重复 {次数} 次",
            description: "重复执行包含的积木指定次数",
            params: vec![
                ParamDef {
                    name: "times",
                    chinese_name: "次数",
                    param_type: ParamType::Number,
                    default_value: Value::Number(4.0),
                    constraints: vec![ParamConstraint::Range(0.0, 100.0)],
                }
            ],
            children_slots: vec![
                ChildSlot {
                    name: "body",
                    chinese_name: "循环体",
                    slot_type: SlotType::Sequence,
                    accepts: vec![BlockCategory::Motion, BlockCategory::Look, BlockCategory::Control],
                }
            ],
            executor: execute_repeat,
            shape: BlockShape::CShape,
        });
    }

    // ... 其他积木注册
}

/// 执行上下文
pub struct ExecutionContext {
    pub world: World,
    pub sprite_entity: Entity,
    pub variables: HashMap<String, Value>,
    pub stop_requested: bool,
}

impl ExecutionContext {
    pub fn new(world: World, sprite_entity: Entity) -> Self {
        Self {
            world,
            sprite_entity,
            variables: HashMap::new(),
            stop_requested: false,
        }
    }

    pub fn get_sprite_transform(&self) -> Option<&Transform> {
        self.world.get::<Transform>(self.sprite_entity)
    }

    pub fn get_sprite_transform_mut(&mut self) -> Option<Mut<Transform>> {
        self.world.get_mut::<Transform>(self.sprite_entity)
    }

    pub fn execute_children(&mut self, children: &[BlockId], tree: &BlockTree) -> Result<(), ExecutionError> {
        for child_id in children {
            if self.stop_requested {
                break;
            }
            if let Some(node) = tree.nodes.get(child_id) {
                self.execute_node(node, tree)?;
            }
        }
        Ok(())
    }

    pub fn execute_node(&mut self, node: &BlockNode, tree: &BlockTree) -> Result<(), ExecutionError> {
        // 获取积木定义
        let registry = self.world.get_resource::<BlockRegistry>().unwrap();
        let block_type_str = match &node.block_type {
            BlockType::Move { .. } => "move",
            BlockType::Turn { .. } => "turn",
            BlockType::Repeat { .. } => "repeat",
            // ... 映射
            _ => return Err(ExecutionError::UnknownBlock),
        };

        let def = registry.get(block_type_str)
            .ok_or(ExecutionError::BlockNotRegistered)?;

        // 执行积木
        (def.executor)(self, &node.params, &node.children)
    }
}

/// 执行错误
#[derive(Debug)]
pub enum ExecutionError {
    UnknownBlock,
    BlockNotRegistered,
    InvalidParams,
    SpriteNotFound,
    StopRequested,
}

/// 积木执行函数
fn execute_move(ctx: &mut ExecutionContext, params: &HashMap<String, Value>, _children: &[BlockId]) -> Result<(), ExecutionError> {
    let steps = params.get("steps")
        .and_then(|v| if let Value::Number(n) = v { Some(*n as f32) } else { None })
        .ok_or(ExecutionError::InvalidParams)?;

    if let Some(mut transform) = ctx.get_sprite_transform_mut() {
        let rotation = transform.rotation.to_euler(EulerRot::ZYX).0;
        let dx = steps * rotation.cos();
        let dy = steps * rotation.sin();
        transform.translation.x += dx;
        transform.translation.y += dy;
    }

    Ok(())
}

fn execute_turn(ctx: &mut ExecutionContext, params: &HashMap<String, Value>, _children: &[BlockId]) -> Result<(), ExecutionError> {
    let degrees = params.get("degrees")
        .and_then(|v| if let Value::Number(n) = v { Some(*n as f32) } else { None })
        .ok_or(ExecutionError::InvalidParams)?;

    if let Some(mut transform) = ctx.get_sprite_transform_mut() {
        transform.rotate(Quat::from_rotation_z(degrees.to_radians()));
    }

    Ok(())
}

fn execute_repeat(ctx: &mut ExecutionContext, params: &HashMap<String, Value>, children: &[BlockId]) -> Result<(), ExecutionError> {
    let times = params.get("times")
        .and_then(|v| if let Value::Number(n) = v { Some(*n as u32) } else { None })
        .ok_or(ExecutionError::InvalidParams)?;

    let tree = ctx.world.get_resource::<BlockTree>().unwrap().clone();

    for _ in 0..times {
        if ctx.stop_requested {
            break;
        }
        ctx.execute_children(children, &tree)?;
    }

    Ok(())
}
```

### 4\. 积木 DSL 宏系统

```
// src/dsl/macros.rs

/// 声明式积木定义宏
#[macro_export]
macro_rules! define_block {
    (
        $block_id:ident {
            category: $category:expr,
            name: $chinese_name:expr,
            template: $template:expr,
            description: $description:expr,
            $(params: {
                $($param_name:ident: $param_type:expr = $default:expr),*
            },)?
            $(children: {
                $($child_name:ident: $child_type:expr),*
            },)?
            executor: $executor:expr,
            shape: $shape:expr
        }
    ) => {
        BlockDef {
            id: stringify!($block_id),
            category: $category,
            chinese_name: $chinese_name,
            template: $template,
            description: $description,
            params: vec![
                $($(
                    ParamDef {
                        name: stringify!($param_name),
                        chinese_name: stringify!($param_name),
                        param_type: $param_type,
                        default_value: $default,
                        constraints: vec![],
                    }
                ),*)?
            ],
            children_slots: vec![
                $($(
                    ChildSlot {
                        name: stringify!($child_name),
                        chinese_name: stringify!($child_name),
                        slot_type: $child_type,
                        accepts: vec![],
                    }
                ),*)?
            ],
            executor: $executor,
            shape: $shape,
        }
    };
}

/// 使用示例
pub fn create_move_block() -> BlockDef {
    define_block! {
        move_block {
            category: BlockCategory::Motion,
            name: "移动",
            template: "移动 {步数} 步",
            description: "让角色移动指定步数",
            params: {
                steps: ParamType::Number = Value::Number(10.0)
            },
            executor: execute_move,
            shape: BlockShape::Stack
        }
    }
}
```

### 5\. ECS 集成

```
// src/ecs/mod.rs
use bevy::prelude::*;

/// 积木实体组件
#[derive(Component)]
pub struct BlockEntity {
    pub block_id: BlockId,
    pub position: Vec2,
    pub size: Vec2,
    pub selected: bool,
}

/// 积木连接关系组件
#[derive(Component)]
pub struct BlockConnection {
    pub parent: Option<Entity>,
    pub children: Vec<Entity>,
    pub slot: Option<String>,
}

/// 精灵组件（对应 Scratch 的角色）
#[derive(Component)]
pub struct SpriteComponent {
    pub name: String,
    pub variables: HashMap<String, Value>,
    pub visible: bool,
    pub layer: i32,
}

/// 积木执行状态
#[derive(Component)]
pub struct ExecutionState {
    pub current_block: Option<BlockId>,
    pub running: bool,
    pub highlight: bool,
}

/// 积木系统
pub struct BlockSystemPlugin;

impl Plugin for BlockSystemPlugin {
    fn build(&self, app: &mut App) {
        app.init_resource::<BlockRegistry>()
            .init_resource::<BlockTree>()
            .add_systems(Startup, setup_block_system)
            .add_systems(Update, (
                handle_block_drag,
                handle_block_drop,
                validate_connections,
                update_block_positions,
                render_blocks,
                execute_program,
            ).chain());
    }
}

/// 处理积木拖拽
fn handle_block_drag(
    mut commands: Commands,
    mouse_button: Res<Input<MouseButton>>,
    windows: Query<&Window>,
    mut block_query: Query<(Entity, &mut BlockEntity, &mut Transform)>,
) {
    if mouse_button.just_pressed(MouseButton::Left) {
        // 检测是否点击了积木
        if let Some(window) = windows.get_single().ok() {
            if let Some(cursor_pos) = window.cursor_position() {
                for (entity, mut block, mut transform) in block_query.iter_mut() {
                    let block_rect = Rect::from_center_size(
                        transform.translation.truncate(),
                        block.size,
                    );

                    if block_rect.contains(cursor_pos) {
                        block.selected = true;
                        commands.entity(entity).insert(Dragging);
                    }
                }
            }
        }
    }
}

/// 标记拖拽中的组件
#[derive(Component)]
struct Dragging;

/// 处理积木放置
fn handle_block_drop(
    mut commands: Commands,
    mouse_button: Res<Input<MouseButton>>,
    mut block_query: Query<(Entity, &mut BlockEntity, &mut BlockConnection, &Transform), With<Dragging>>,
    all_blocks: Query<(Entity, &BlockEntity, &Transform), Without<Dragging>>,
) {
    if mouse_button.just_released(MouseButton::Left) {
        for (entity, mut block, mut connection, transform) in block_query.iter_mut() {
            block.selected = false;

            // 找到最近的积木槽位
            let mut closest_slot: Option<(Entity, f32)> = None;

            for (other_entity, other_block, other_transform) in all_blocks.iter() {
                let distance = transform.translation.distance(other_transform.translation);

                if distance < 50.0 {
                    if closest_slot.map_or(true, |(_, d)| distance < d) {
                        closest_slot = Some((other_entity, distance));
                    }
                }
            }

            // 更新连接关系
            if let Some((parent_entity, _)) = closest_slot {
                connection.parent = Some(parent_entity);
            }

            commands.entity(entity).remove::<Dragging>();
        }
    }
}

/// 执行程序
fn execute_program(
    mut commands: Commands,
    mut block_tree: ResMut<BlockTree>,
    registry: Res<BlockRegistry>,
    sprite_query: Query<(Entity, &SpriteComponent)>,
    keyboard: Res<Input<KeyCode>>,
) {
    // 检查触发事件
    let should_execute = keyboard.just_pressed(KeyCode::Space) ||
                         keyboard.just_pressed(KeyCode::Return);

    if !should_execute {
        return;
    }

    // 为每个精灵执行程序
    for (sprite_entity, sprite) in sprite_query.iter() {
        let mut context = ExecutionContext::new(
            commands.world_mut().clone(),
            sprite_entity,
        );

        // 复制积木树
        let tree = block_tree.clone();

        // 执行根积木
        for root_id in &tree.root_nodes {
            if let Some(node) = tree.nodes.get(root_id) {
                if let Err(e) = context.execute_node(node, &tree) {
                    eprintln!("执行错误: {:?}", e);
                }
            }
        }
    }
}
```

### 6\. UI 渲染系统

```
// src/ui/mod.rs
use bevy::prelude::*;
use bevy_egui::{egui, EguiContexts, EguiPlugin};

/// 积木编辑器 UI
pub struct BlockEditorUI;

impl Plugin for BlockEditorUI {
    fn build(&self, app: &mut App) {
        app.add_plugins(EguiPlugin)
            .add_systems(Update, render_block_palette)
            .add_systems(Update, render_workspace)
            .add_systems(Update, render_block_inspector);
    }
}

/// 渲染积木面板
fn render_block_palette(
    mut contexts: EguiContexts,
    registry: Res<BlockRegistry>,
) {
    egui::SidePanel::left("block_palette")
        .default_width(200.0)
        .show(contexts.ctx_mut(), |ui| {
            ui.heading("积木面板");
            ui.separator();

            // 按分类显示积木
            for category in [
                BlockCategory::Event,
                BlockCategory::Motion,
                BlockCategory::Look,
                BlockCategory::Control,
                BlockCategory::Variable,
            ] {
                ui.collapsing(category.chinese_name(), |ui| {
                    for block_id in registry.get_by_category(&category) {
                        if let Some(def) = registry.get(block_id) {
                            render_block_button(ui, def);
                        }
                    }
                });
            }
        });
}

/// 渲染积木按钮
fn render_block_button(ui: &mut egui::Ui, def: &BlockDef) {
    let button = egui::Button::new(
        egui::RichText::new(def.chinese_name)
            .size(14.0)
            .color(egui::Color32::WHITE)
    )
    .fill(match def.category {
        BlockCategory::Motion => egui::Color32::from_rgb(76, 151, 255),
        BlockCategory::Look => egui::Color32::from_rgb(153, 102, 255),
        BlockCategory::Control => egui::Color32::from_rgb(255, 171, 25),
        BlockCategory::Event => egui::Color32::from_rgb(255, 191, 0),
        BlockCategory::Variable => egui::Color32::from_rgb(255, 102, 26),
        BlockCategory::Custom => egui::Color32::from_rgb(255, 102, 102),
    })
    .corner_radius(5.0);

    if ui.add(button).clicked() {
        // 创建新积木
        create_block_from_def(def);
    }

    // 显示工具提示
    button.on_hover_text(format!("{}\n{}", def.chinese_name, def.description));
}

/// 渲染工作区
fn render_workspace(
    mut contexts: EguiContexts,
    block_tree: Res<BlockTree>,
) {
    egui::CentralPanel::default()
        .frame(egui::Frame::none().fill(egui::Color32::from_gray(240)))
        .show(contexts.ctx_mut(), |ui| {
            // 渲染积木树
            for root_id in &block_tree.root_nodes {
                if let Some(node) = block_tree.nodes.get(root_id) {
                    render_block_node(ui, node, &block_tree, 0.0);
                }
            }
        });
}

/// 递归渲染积木节点
fn render_block_node(
    ui: &mut egui::Ui,
    node: &BlockNode,
    tree: &BlockTree,
    indent: f32,
) {
    let registry = ui.data(|data| {
        data.get_temp::<BlockRegistry>(egui::Id::new("block_registry"))
    });

    if let Some(registry) = registry {
        if let Some(def) = registry.get(block_type_to_str(&node.block_type)) {
            // 渲染积木
            let text = render_template(def.template, &node.params);

            ui.horizontal(|ui| {
                ui.add_space(indent);

                // 积木背景
                let frame = egui::Frame::none()
                    .fill(egui::Color32::from_rgb(76, 151, 255))
                    .corner_radius(5.0)
                    .inner_margin(egui::Margin::symmetric(8.0, 4.0));

                frame.show(ui, |ui| {
                    ui.label(
                        egui::RichText::new(text)
                            .color(egui::Color32::WHITE)
                            .size(14.0)
                    );
                });
            });

            // 渲染子积木
            for child_id in &node.children {
                if let Some(child) = tree.nodes.get(child_id) {
                    render_block_node(ui, child, tree, indent + 20.0);
                }
            }
        }
    }
}

/// 渲染积木属性面板
fn render_block_inspector(
    mut contexts: EguiContexts,
    block_query: Query<(&BlockEntity, &BlockNode), With<Selected>>,
) {
    egui::SidePanel::right("block_inspector")
        .default_width(250.0)
        .show(contexts.ctx_mut(), |ui| {
            ui.heading("积木属性");
            ui.separator();

            for (entity, node) in block_query.iter() {
                ui.label(format!("积木 ID: {:?}", node.id));

                // 显示和编辑参数
                for (param_name, param_value) in &node.params {
                    ui.horizontal(|ui| {
                        ui.label(param_name);

                        match param_value {
                            Value::Number(n) => {
                                let mut value = *n;
                                if ui.add(egui::DragValue::new(&mut value)).changed() {
                                    // 更新参数
                                }
                            }
                            Value::String(s) => {
                                let mut value = s.clone();
                                if ui.text_edit_singleline(&mut value).changed() {
                                    // 更新参数
                                }
                            }
                            _ => {}
                        }
                    });
                }
            }
        });
}

/// 模板渲染函数
fn render_template(template: &str, params: &HashMap<String, Value>) -> String {
    let mut result = template.to_string();

    for (key, value) in params {
        let placeholder = format!("{{{}}}", key);
        let value_str = match value {
            Value::Number(n) => n.to_string(),
            Value::String(s) => s.clone(),
            Value::Boolean(b) => b.to_string(),
            _ => String::new(),
        };
        result = result.replace(&placeholder, &value_str);
    }

    result
}
```

### 7\. 序列化与持久化

```
// src/serialization/mod.rs
use serde::{Serialize, Deserialize};
use std::fs;

/// 项目文件格式
#[derive(Debug, Serialize, Deserialize)]
pub struct ProjectFile {
    pub version: String,
    pub sprites: Vec<SpriteData>,
    pub block_tree: BlockTree,
    pub variables: HashMap<String, Value>,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct SpriteData {
    pub name: String,
    pub entity_id: u64,
    pub position: (f32, f32),
    pub rotation: f32,
    pub size: f32,
    pub visible: bool,
}

impl ProjectFile {
    pub fn save(&self, path: &str) -> Result<(), std::io::Error> {
        let json = serde_json::to_string_pretty(self)?;
        fs::write(path, json)?;
        Ok(())
    }

    pub fn load(path: &str) -> Result<Self, std::io::Error> {
        let json = fs::read_to_string(path)?;
        let project = serde_json::from_str(&json)?;
        Ok(project)
    }
}

/// 自动保存系统
pub fn auto_save_system(
    time: Res<Time>,
    project: Res<ProjectFile>,
    mut last_save: Local<f32>,
) {
    // 每 30 秒自动保存
    if time.elapsed_seconds() - *last_save > 30.0 {
        if let Err(e) = project.save("autosave.json") {
            eprintln!("自动保存失败: {}", e);
        }
        *last_save = time.elapsed_seconds();
    }
}
```

### 8\. 性能优化

```
// src/optimization/mod.rs

/// 积木树优化
pub fn optimize_block_tree(tree: &mut BlockTree) {
    // 1. 常量折叠
    fold_constants(tree);

    // 2. 死代码消除
    eliminate_dead_code(tree);

    // 3. 循环优化
    optimize_loops(tree);
}

/// 常量折叠
fn fold_constants(tree: &mut BlockTree) {
    for node in tree.nodes.values_mut() {
        if let BlockType::Move { steps } = &mut node.block_type {
            // 如果步数是常量，直接计算
            *steps = steps.round();
        }
    }
}

/// 并行执行独立积木
pub fn execute_parallel(
    tree: &BlockTree,
    context: &mut ExecutionContext,
) -> Result<(), ExecutionError> {
    // 分析依赖关系
    let dependencies = analyze_dependencies(tree);

    // 使用线程池并行执行独立分支
    let pool = rayon::ThreadPoolBuilder::new()
        .num_threads(4)
        .build()
        .unwrap();

    pool.install(|| {
        for root in &tree.root_nodes {
            if !dependencies.has_dependencies(root) {
                // 独立积木可以并行执行
                rayon::spawn(|| {
                    execute_subtree(tree, *root, context);
                });
            }
        }
    });

    Ok(())
}
```

### 9\. 测试策略

```
// tests/block_execution.rs
use bevy::prelude::*;
use block_programming::*;

#[test]
fn test_move_block() {
    let mut app = App::new();
    app.add_plugins(MinimalPlugins)
        .init_resource::<BlockRegistry>()
        .init_resource::<BlockTree>();

    // 创建精灵
    let sprite_entity = app.world.spawn((
        SpriteComponent {
            name: "测试精灵".to_string(),
            variables: HashMap::new(),
            visible: true,
            layer: 0,
        },
        Transform::default(),
    )).id();

    // 创建移动积木
    let move_block = BlockNode {
        id: BlockId(1),
        block_type: BlockType::Move { steps: 100.0 },
        params: HashMap::from([
            ("steps".to_string(), Value::Number(100.0))
        ]),
        children: vec![],
    };

    // 执行
    let mut context = ExecutionContext::new(app.world.clone(), sprite_entity);
    context.execute_node(&move_block, &BlockTree::default()).unwrap();

    // 验证位置变化
    let transform = app.world.get::<Transform>(sprite_entity).unwrap();
    assert!(transform.translation.x > 0.0);
}

#[test]
fn test_repeat_block() {
    // 测试循环积木
}

#[test]
fn test_serialization() {
    // 测试序列化
}
```

### 10\. 扩展指南

#### 10.1 添加新积木

```
// 1. 在 AST 中添加新类型
pub enum BlockType {
    // ...
    PlaySound { sound_name: String },
}

// 2. 注册积木定义
fn register_sound_blocks(registry: &mut BlockRegistry) {
    registry.register(BlockDef {
        id: "play_sound",
        category: BlockCategory::Look, // 或新建 Sound 分类
        chinese_name: "播放声音",
        template: "播放声音 {声音名称}",
        description: "播放指定的声音",
        params: vec![
            ParamDef {
                name: "sound_name",
                chinese_name: "声音名称",
                param_type: ParamType::String,
                default_value: Value::String("喵".to_string()),
                constraints: vec![],
            }
        ],
        children_slots: vec![],
        executor: execute_play_sound,
        shape: BlockShape::Stack,
    });
}

// 3. 实现执行函数
fn execute_play_sound(
    ctx: &mut ExecutionContext,
    params: &HashMap<String, Value>,
    _children: &[BlockId],
) -> Result<(), ExecutionError> {
    let sound_name = params.get("sound_name")
        .and_then(|v| if let Value::String(s) = v { Some(s.clone()) } else { None })
        .ok_or(ExecutionError::InvalidParams)?;

    // 播放声音
    println!("播放声音: {}", sound_name);

    Ok(())
}
```

#### 10.2 自定义积木

```
// 用户自定义积木
#[derive(Debug, Clone)]
pub struct CustomBlock {
    pub name: String,
    pub params: Vec<CustomParam>,
    pub body: Vec<BlockId>,
}

// 自定义积木注册
pub fn register_custom_block(
    registry: &mut BlockRegistry,
    custom_block: CustomBlock,
) {
    let def = BlockDef {
        id: Box::leak(custom_block.name.clone().into_boxed_str()),
        category: BlockCategory::Custom,
        chinese_name: Box::leak(custom_block.name.clone().into_boxed_str()),
        template: Box::leak(format!("定义 {} 积木", custom_block.name).into_boxed_str()),
        description: "用户自定义积木",
        params: custom_block.params.iter().map(|p| ParamDef {
            name: Box::leak
```