343 lines
16 KiB
Python
343 lines
16 KiB
Python
# -*- 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)]
|