# -*- coding: utf-8 -*- """拖拽编程画布——块定义(JSON 结构)。 BLOCK_CATEGORIES / BLOCK_DEFS 是前端画布渲染、参数面板、后端校验与编译的唯一事实源。 新增块类型只需在此注册:category / type / label / icon / color / params / ports。 块 = 画布上一个节点。每个块有: - params: 参数定义(参数面板表单渲染 + 后端校验规则) - ports.inputs / ports.outputs: 连线端口(事件块无输入端口 = 天然入口) - dynamic_outputs: 动态端口模板(如分叉/并行按 branch_count 生成多个输出端口) """ BLOCK_CATEGORIES = [ {"key": "event", "label": "事件", "color": "#4CAF50"}, {"key": "logic", "label": "逻辑", "color": "#2196F3"}, {"key": "action", "label": "动作", "color": "#FF9800"}, {"key": "variable", "label": "变量", "color": "#9C27B0"}, {"key": "expr", "label": "表达式", "color": "#607D8B"}, ] def _p(name, label, uitype='text', required=False, default='', options=None, **kw): """参数定义。uitype: text / number / select / boolean / entity / expression / textarea""" d = {"name": name, "label": label, "uitype": uitype, "required": required, "default": default} if options: d["options"] = options d.update(kw) return d def _port(name, label, ptype='flow'): return {"name": name, "label": label, "type": ptype} def _block(btype, category, label, icon, color, params, inputs=None, outputs=None, description='', dynamic_outputs=None): return { "type": btype, "category": category, "label": label, "icon": icon, "color": color, "params": params, "ports": {"inputs": inputs or [], "outputs": outputs or []}, "description": description, "dynamic_outputs": dynamic_outputs, } _OUT = [_port('out', '下一步')] # ═══════════════════════ 事件块(入口,无输入端口) ═══════════════════════ _EVENTS = { "event_start": _block( "event_start", "event", "开始", "fa fa-play-circle", "#4CAF50", [], inputs=[], outputs=_OUT, description="程序入口:世界初始化时触发一次"), "event_click": _block( "event_click", "event", "点击", "fa fa-mouse-pointer", "#4CAF50", [ _p("target", "目标实体", "entity", required=True, placeholder="选择被点击的实体"), _p("button", "按键", "select", default="left", options=[{"value": "left", "text": "左键"}, {"value": "middle", "text": "中键"}, {"value": "right", "text": "右键"}]), ], inputs=[], outputs=_OUT, description="事件:某实体被点击时触发"), "event_hover": _block( "event_hover", "event", "悬停", "fa fa-hand-pointer", "#4CAF50", [ _p("target", "目标实体", "entity", required=True), _p("state", "状态", "select", default="enter", options=[{"value": "enter", "text": "进入"}, {"value": "leave", "text": "离开"}]), ], inputs=[], outputs=_OUT, description="事件:指针悬停进入/离开实体时触发"), "event_timer": _block( "event_timer", "event", "定时", "fa fa-clock", "#4CAF50", [ _p("interval", "间隔(秒)", "number", required=True, default="1", min=0.1), _p("repeat", "重复次数", "number", default="-1", description="-1 表示无限循环"), ], inputs=[], outputs=_OUT, description="事件:按固定间隔定时触发"), "event_collision": _block( "event_collision", "event", "碰撞", "fa fa-bolt", "#4CAF50", [ _p("entity_a", "实体 A", "entity", required=True), _p("entity_b", "实体 B", "entity", required=True), ], inputs=[], outputs=_OUT, description="事件:实体 A 与实体 B 发生碰撞时触发"), "event_keyboard": _block( "event_keyboard", "event", "键盘", "fa fa-keyboard", "#4CAF50", [ _p("key", "按键", "text", required=True, placeholder="如:Space / ArrowUp / a"), _p("action", "动作", "select", default="keydown", options=[{"value": "keydown", "text": "按下"}, {"value": "keyup", "text": "松开"}]), ], inputs=[], outputs=_OUT, description="事件:键盘按键按下/松开时触发"), } # ═══════════════════════ 逻辑框架块 ═══════════════════════ _LOGIC = { "logic_sequence": _block( "logic_sequence", "logic", "顺序", "fa fa-list-ol", "#2196F3", [], inputs=[_port('in', '上一步')], outputs=_OUT, description="顺序执行:依次执行后续块"), "logic_loop": _block( "logic_loop", "logic", "循环", "fa fa-repeat", "#2196F3", [ _p("mode", "模式", "select", default="count", options=[{"value": "count", "text": "按次数"}, {"value": "while", "text": "按条件"}]), _p("times", "次数", "number", default="3", min=0, max=10000), _p("condition", "循环条件", "expression", default="True", description="while 模式的循环条件表达式"), ], inputs=[_port('in', '上一步')], outputs=_OUT, description="循环执行后续块(按次数或按条件)"), "logic_condition": _block( "logic_condition", "logic", "条件判断", "fa fa-code-branch", "#2196F3", [ _p("condition", "条件", "expression", required=True, default="True", placeholder="如:score > 100 或 @alive == true"), ], inputs=[_port('in', '上一步')], outputs=[_port('true', '真'), _port('false', '假')], description="条件判断:条件为真走「真」出口,否则走「假」出口"), "logic_branch": _block( "logic_branch", "logic", "分叉", "fa fa-sitemap", "#2196F3", [ _p("branch_count", "分支数", "number", default="2", min=2, max=8), ], inputs=[_port('in', '上一步')], outputs=[], dynamic_outputs={"param": "branch_count", "prefix": "branch"}, description="多路分叉:按分支数生成多个出口"), "logic_wait": _block( "logic_wait", "logic", "等待", "fa fa-hourglass-half", "#2196F3", [ _p("seconds", "等待秒数", "number", required=True, default="1", min=0), ], inputs=[_port('in', '上一步')], outputs=_OUT, description="等待指定秒数后继续"), "logic_parallel": _block( "logic_parallel", "logic", "并行", "fa fa-columns", "#2196F3", [ _p("branch_count", "分支数", "number", default="2", min=2, max=8), ], inputs=[_port('in', '上一步')], outputs=[], dynamic_outputs={"param": "branch_count", "prefix": "p"}, description="并行:多个分支同时执行(编译为顺序展开)"), "logic_function_def": _block( "logic_function_def", "logic", "函数定义", "fa fa-fw fa-code", "#2196F3", [ _p("name", "函数名", "text", required=True, placeholder="如:jump"), ], inputs=[_port('in', '上一步')], outputs=_OUT, description="定义可复用函数:函数体为后续连接块"), "logic_function_call": _block( "logic_function_call", "logic", "函数调用", "fa fa-caret-right", "#2196F3", [ _p("name", "函数名", "text", required=True, placeholder="如:jump"), ], inputs=[_port('in', '上一步')], outputs=_OUT, description="调用已定义的函数"), } # ═══════════════════════ 动作块 ═══════════════════════ _ACTION = { "action_move": _block( "action_move", "action", "移动", "fa fa-arrows-alt", "#FF9800", [ _p("entity", "目标实体", "entity", required=True), _p("x", "X 位移", "number", default="0"), _p("y", "Y 位移", "number", default="0"), _p("duration", "时长(秒)", "number", default="0", min=0), ], inputs=[_port('in', '上一步')], outputs=_OUT, description="移动实体(位移量,支持负值)"), "action_rotate": _block( "action_rotate", "action", "旋转", "fa fa-undo", "#FF9800", [ _p("entity", "目标实体", "entity", required=True), _p("angle", "角度(度)", "number", default="90"), _p("duration", "时长(秒)", "number", default="0", min=0), ], inputs=[_port('in', '上一步')], outputs=_OUT, description="旋转实体指定角度"), "action_scale": _block( "action_scale", "action", "缩放", "fa fa-expand-arrows-alt", "#FF9800", [ _p("entity", "目标实体", "entity", required=True), _p("scale_x", "X 缩放", "number", default="1", min=0), _p("scale_y", "Y 缩放", "number", default="1", min=0), _p("duration", "时长(秒)", "number", default="0", min=0), ], inputs=[_port('in', '上一步')], outputs=_OUT, description="缩放实体(1 = 原始大小)"), "action_set_property": _block( "action_set_property", "action", "改属性", "fa fa-sliders-h", "#FF9800", [ _p("entity", "目标实体", "entity", required=True), _p("property", "属性名", "text", required=True, placeholder="如:alpha / color / speed"), _p("value", "属性值", "text", required=True, placeholder="如:0.5 / #ff0000"), ], inputs=[_port('in', '上一步')], outputs=_OUT, description="修改实体属性"), "action_play_animation": _block( "action_play_animation", "action", "播放动画", "fa fa-film", "#FF9800", [ _p("entity", "目标实体", "entity", required=True), _p("animation", "动画名", "text", required=True, placeholder="如:run / jump"), ], inputs=[_port('in', '上一步')], outputs=_OUT, description="播放实体动画"), "action_play_sound": _block( "action_play_sound", "action", "播放声音", "fa fa-volume-up", "#FF9800", [ _p("entity", "目标实体", "entity", required=True), _p("sound", "声音资源", "text", required=True, placeholder="如:coin.wav"), ], inputs=[_port('in', '上一步')], outputs=_OUT, description="播放声音资源"), "action_show_hide": _block( "action_show_hide", "action", "显隐", "fa fa-eye", "#FF9800", [ _p("entity", "目标实体", "entity", required=True), _p("visible", "可见", "boolean", default="true", options=[{"value": "true", "text": "显示"}, {"value": "false", "text": "隐藏"}]), ], inputs=[_port('in', '上一步')], outputs=_OUT, description="显示/隐藏实体"), "action_camera": _block( "action_camera", "action", "相机", "fa fa-camera", "#FF9800", [ _p("camera_id", "相机 ID", "text", default="main"), _p("mode", "模式", "select", default="follow", options=[{"value": "follow", "text": "跟随"}, {"value": "free", "text": "自由"}, {"value": "lookat", "text": "注视"}]), _p("x", "X", "number", default="0"), _p("y", "Y", "number", default="0"), _p("zoom", "缩放", "number", default="1", min=0.1), ], inputs=[_port('in', '上一步')], outputs=_OUT, description="控制相机(自由模式设置坐标,跟随模式注视目标)"), } # ═══════════════════════ 变量与表达式块 ═══════════════════════ _VARIABLE = { "var_create": _block( "var_create", "variable", "创建变量", "fa fa-plus-circle", "#9C27B0", [ _p("name", "变量名", "text", required=True, placeholder="如:score"), _p("initial_value", "初始值", "text", default="0"), ], inputs=[_port('in', '上一步')], outputs=_OUT, description="创建并初始化一个变量(编译时统一在脚本头部声明)"), "var_assign": _block( "var_assign", "variable", "赋值", "fa fa-pen", "#9C27B0", [ _p("name", "变量名", "variable", required=True, placeholder="如:score"), _p("value", "值", "expression", required=True, placeholder="如:100 或 @score + 10"), ], inputs=[_port('in', '上一步')], outputs=_OUT, description="给变量赋新值"), "var_read": _block( "var_read", "variable", "读取变量", "fa fa-eye", "#9C27B0", [ _p("name", "变量名", "variable", required=True, placeholder="如:score"), _p("result", "结果变量", "text", required=True, placeholder="如:cur_score"), ], inputs=[_port('in', '上一步')], outputs=_OUT, description="读取变量值存入结果变量"), } _EXPR = { "expr_arith": _block( "expr_arith", "expr", "算术", "fa fa-calculator", "#607D8B", [ _p("operand_a", "操作数 A", "expression", required=True, placeholder="如:10 或 @score"), _p("operator", "运算符", "select", default="+", options=[{"value": "+", "text": "加 +"}, {"value": "-", "text": "减 -"}, {"value": "*", "text": "乘 *"}, {"value": "/", "text": "除 /"}, {"value": "%", "text": "取余 %"}]), _p("operand_b", "操作数 B", "expression", required=True, placeholder="如:5"), _p("result", "结果变量", "text", required=True, placeholder="如:total"), ], inputs=[_port('in', '上一步')], outputs=_OUT, description="算术运算:A 运算符 B → 结果变量"), "expr_compare": _block( "expr_compare", "expr", "比较", "fa fa-balance-scale", "#607D8B", [ _p("operand_a", "操作数 A", "expression", required=True), _p("operator", "运算符", "select", default="==", options=[{"value": "==", "text": "等于 =="}, {"value": "!=", "text": "不等于 !="}, {"value": ">", "text": "大于 >"}, {"value": "<", "text": "小于 <"}, {"value": ">=", "text": "大于等于 >="}, {"value": "<=", "text": "小于等于 <="}]), _p("operand_b", "操作数 B", "expression", required=True), _p("result", "结果变量", "text", required=True, placeholder="如:is_win"), ], inputs=[_port('in', '上一步')], outputs=_OUT, description="比较运算:A 运算符 B → 结果变量(布尔)"), "expr_logic": _block( "expr_logic", "expr", "逻辑", "fa fa-toggle-on", "#607D8B", [ _p("operand_a", "操作数 A", "expression", required=True, default="True"), _p("operator", "运算符", "select", default="and", options=[{"value": "and", "text": "与 and"}, {"value": "or", "text": "或 or"}, {"value": "not", "text": "非 not"}]), _p("operand_b", "操作数 B", "expression", required=True, default="True"), _p("result", "结果变量", "text", required=True, placeholder="如:flag"), ], inputs=[_port('in', '上一步')], outputs=_OUT, description="逻辑运算:A and/or B → 结果变量(布尔)"), } BLOCK_DEFS = {} BLOCK_DEFS.update(_EVENTS) BLOCK_DEFS.update(_LOGIC) BLOCK_DEFS.update(_ACTION) BLOCK_DEFS.update(_VARIABLE) BLOCK_DEFS.update(_EXPR) def get_block_defs(): """返回 {categories, blocks} 供前端渲染画布。""" return {"categories": BLOCK_CATEGORIES, "blocks": BLOCK_DEFS} def get_block_def(btype): return BLOCK_DEFS.get(btype) def get_dynamic_outputs(btype, params): """按块参数生成动态输出端口名列表。无动态端口返回 []。""" bdef = BLOCK_DEFS.get(btype) if not bdef or not bdef.get("dynamic_outputs"): return [] d = bdef["dynamic_outputs"] try: n = int((params or {}).get(d["param"], 2)) except (TypeError, ValueError): n = 2 n = max(2, min(n, 8)) return ["%s%d" % (d["prefix"], i) for i in range(1, n + 1)]