scense_drag/drag/blocks.py
2026-08-29 21:10:23 +08:00

343 lines
16 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# -*- 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)]