diff --git a/extensions/cocos/cocos-ecs/extensions/cocos-ecs-extension/source/panels/behavior-tree/composables/useBehaviorTreeEditor.ts b/extensions/cocos/cocos-ecs/extensions/cocos-ecs-extension/source/panels/behavior-tree/composables/useBehaviorTreeEditor.ts index 36e010bb..2180b741 100644 --- a/extensions/cocos/cocos-ecs/extensions/cocos-ecs-extension/source/panels/behavior-tree/composables/useBehaviorTreeEditor.ts +++ b/extensions/cocos/cocos-ecs/extensions/cocos-ecs-extension/source/panels/behavior-tree/composables/useBehaviorTreeEditor.ts @@ -9,6 +9,7 @@ import { useConnectionManager } from './useConnectionManager'; import { useCanvasManager } from './useCanvasManager'; import { useNodeDisplay } from './useNodeDisplay'; import { useConditionAttachment } from './useConditionAttachment'; +import { useBlackboard } from './useBlackboard'; import { validateTree as validateTreeStructure } from '../utils/nodeUtils'; /** @@ -77,6 +78,15 @@ export function useBehaviorTreeEditor() { appState.isInstalling ); + // Blackboard功能 + const blackboard = useBlackboard(); + + // Blackboard常驻侧边面板状态 + const blackboardSidebarState = reactive({ + collapsed: false, + transparent: true + }); + const connectionState = reactive({ isConnecting: false, startNodeId: null as string | null, @@ -134,6 +144,65 @@ export function useBehaviorTreeEditor() { updateCounter: 0 }); + // Blackboard拖拽相关功能 + const isBlackboardDroppable = (prop: any): boolean => { + return prop && (prop.type === 'string' || prop.type === 'number' || prop.type === 'boolean'); + }; + + const isBlackboardReference = (value: string): boolean => { + return typeof value === 'string' && value.startsWith('{{') && value.endsWith('}}'); + }; + + const handleBlackboardDrop = (event: DragEvent, propertyKey: string) => { + event.preventDefault(); + event.stopPropagation(); + + try { + const blackboardData = event.dataTransfer?.getData('application/blackboard-variable'); + if (!blackboardData) return; + + const variable = JSON.parse(blackboardData); + const activeNode = computedProps.activeNode.value; + + if (!activeNode || !activeNode.properties) return; + + const property = activeNode.properties[propertyKey]; + if (!property) return; + + // 设置Blackboard引用 + const referenceValue = `{{${variable.name}}}`; + nodeOps.updateNodeProperty(`properties.${propertyKey}.value`, referenceValue); + + // 移除拖拽样式 + const element = event.currentTarget as HTMLElement; + element.classList.remove('drag-over'); + + } catch (error) { + console.error('处理Blackboard拖拽失败:', error); + } + }; + + const handleBlackboardDragOver = (event: DragEvent) => { + event.preventDefault(); + event.stopPropagation(); + + const hasBlackboardData = event.dataTransfer?.types.includes('application/blackboard-variable'); + if (hasBlackboardData) { + event.dataTransfer!.dropEffect = 'copy'; + const element = event.currentTarget as HTMLElement; + element.classList.add('drag-over'); + } + }; + + const handleBlackboardDragLeave = (event: DragEvent) => { + const element = event.currentTarget as HTMLElement; + element.classList.remove('drag-over'); + }; + + const clearBlackboardReference = (propertyKey: string) => { + nodeOps.updateNodeProperty(`properties.${propertyKey}.value`, ''); + }; + const startNodeDrag = (event: MouseEvent, node: any) => { event.stopPropagation(); event.preventDefault(); @@ -448,8 +517,6 @@ export function useBehaviorTreeEditor() { }, 3000); }; - - onMounted(() => { // 自动检查安装状态 installation.checkInstallStatus(); @@ -551,6 +618,7 @@ export function useBehaviorTreeEditor() { ...fileOps, ...codeGen, ...installation, + ...blackboard, handleInstall, connectionState, ...connectionManager, @@ -656,6 +724,23 @@ export function useBehaviorTreeEditor() { if (node.type === 'conditional-decorator') { conditionAttachment.handleDecoratorDragLeave(node); } - } + }, + // Blackboard拖拽相关功能 + isBlackboardDroppable, + isBlackboardReference, + handleBlackboardDrop, + handleBlackboardDragOver, + handleBlackboardDragLeave, + clearBlackboardReference, + + // Blackboard常驻侧边面板功能 + blackboardCollapsed: computed({ + get: () => blackboardSidebarState.collapsed, + set: (value: boolean) => blackboardSidebarState.collapsed = value + }), + blackboardTransparent: computed({ + get: () => blackboardSidebarState.transparent, + set: (value: boolean) => blackboardSidebarState.transparent = value + }) }; } \ No newline at end of file diff --git a/extensions/cocos/cocos-ecs/extensions/cocos-ecs-extension/source/panels/behavior-tree/composables/useBlackboard.ts b/extensions/cocos/cocos-ecs/extensions/cocos-ecs-extension/source/panels/behavior-tree/composables/useBlackboard.ts new file mode 100644 index 00000000..562f83c4 --- /dev/null +++ b/extensions/cocos/cocos-ecs/extensions/cocos-ecs-extension/source/panels/behavior-tree/composables/useBlackboard.ts @@ -0,0 +1,575 @@ +import { ref, computed, reactive } from 'vue'; + +export interface BlackboardVariable { + name: string; + type: 'string' | 'number' | 'boolean' | 'vector2' | 'vector3' | 'object' | 'array'; + value: any; + defaultValue: any; + description?: string; + group?: string; + readOnly?: boolean; + constraints?: { + min?: number; + max?: number; + step?: number; + allowedValues?: string[]; + }; +} + +export interface BlackboardModalData { + name: string; + type: 'string' | 'number' | 'boolean' | 'vector2' | 'vector3' | 'object' | 'array'; + defaultValue: any; + description: string; + group: string; + readOnly: boolean; + constraints: { + min?: number; + max?: number; + step?: number; + }; + useAllowedValues: boolean; + allowedValuesText: string; +} + +export function useBlackboard() { + const blackboardVariables = ref>(new Map()); + const expandedGroups = ref>(new Set(['未分组'])); + const selectedVariable = ref(null); + const showBlackboardModal = ref(false); + const editingBlackboardVariable = ref(null); + + const blackboardModalData = reactive({ + name: '', + type: 'string', + defaultValue: '', + description: '', + group: '', + readOnly: false, + constraints: {}, + useAllowedValues: false, + allowedValuesText: '' + }); + + const showAddVariableDialog = ref(false); + const editingVariable = ref(null); + const newVariable = reactive({ + name: '', + type: 'string' as any, + defaultValue: '' as any, + defaultValueText: '', + description: '', + group: '', + readonly: false, + min: undefined as number | undefined, + max: undefined as number | undefined, + optionsText: '' + }); + + const showImportExportDialog = ref(false); + const activeTab = ref('export'); + const exportData = computed(() => { + const data = Array.from(blackboardVariables.value.values()); + return JSON.stringify(data, null, 2); + }); + const importData = ref(''); + const clearBeforeImport = ref(false); + + const blackboardCollapsed = ref(false); + const blackboardTransparent = ref(true); + + const blackboardVariablesArray = computed(() => { + return Array.from(blackboardVariables.value.values()); + }); + + const blackboardVariableGroups = computed(() => { + const groups: Record = {}; + + blackboardVariables.value.forEach(variable => { + const groupName = variable.group || '未分组'; + if (!groups[groupName]) { + groups[groupName] = []; + } + groups[groupName].push(variable); + }); + + const sortedGroups: Record = {}; + const groupNames = Object.keys(groups).sort((a, b) => { + if (a === '未分组') return -1; + if (b === '未分组') return 1; + return a.localeCompare(b); + }); + + groupNames.forEach(groupName => { + groups[groupName].sort((a, b) => a.name.localeCompare(b.name)); + sortedGroups[groupName] = groups[groupName]; + }); + + return sortedGroups; + }); + + const groups = computed(() => { + const groupSet = new Set(); + blackboardVariables.value.forEach(variable => { + groupSet.add(variable.group || '未分组'); + }); + return Array.from(groupSet); + }); + + const isValidVariable = computed(() => { + return newVariable.name.trim().length > 0; + }); + + const groupedBlackboardVariables = () => { + return Object.entries(blackboardVariableGroups.value); + }; + + const isGroupExpanded = (groupName: string): boolean => { + return expandedGroups.value.has(groupName); + }; + + const toggleGroup = (groupName: string) => { + if (expandedGroups.value.has(groupName)) { + expandedGroups.value.delete(groupName); + } else { + expandedGroups.value.add(groupName); + } + }; + + const getVariableTypeIcon = (type: string): string => { + const iconMap: Record = { + string: '📝', + number: '🔢', + boolean: '☑️', + vector2: '📐', + vector3: '🧊', + object: '📦', + array: '📋' + }; + return iconMap[type] || '❓'; + }; + + const formatBlackboardValue = (variable: BlackboardVariable): string => { + if (variable.value === null || variable.value === undefined) { + return 'null'; + } + + switch (variable.type) { + case 'boolean': + return variable.value ? 'true' : 'false'; + case 'string': + return `"${variable.value}"`; + case 'number': + return variable.value.toString(); + default: + return String(variable.value); + } + }; + + const hasVisibleConstraints = (variable: BlackboardVariable): boolean => { + if (!variable.constraints) return false; + + return !!( + variable.constraints.min !== undefined || + variable.constraints.max !== undefined || + variable.constraints.allowedValues?.length + ); + }; + + const formatConstraints = (constraints: BlackboardVariable['constraints']): string => { + const parts: string[] = []; + + if (constraints?.min !== undefined) { + parts.push(`最小: ${constraints.min}`); + } + if (constraints?.max !== undefined) { + parts.push(`最大: ${constraints.max}`); + } + if (constraints?.allowedValues?.length) { + parts.push(`可选: ${constraints.allowedValues.join(', ')}`); + } + + return parts.join(', '); + }; + + const getTypeDisplayName = (type: string): string => { + const typeMap: Record = { + string: 'STR', + number: 'NUM', + boolean: 'BOOL', + vector2: 'VEC2', + vector3: 'VEC3', + object: 'OBJ', + array: 'ARR' + }; + return typeMap[type] || type.toUpperCase(); + }; + + const getDisplayValue = (variable: BlackboardVariable): string => { + if (variable.value === null || variable.value === undefined) { + return 'null'; + } + + switch (variable.type) { + case 'string': + return String(variable.value); + case 'number': + return variable.value.toString(); + case 'boolean': + return variable.value ? 'true' : 'false'; + case 'vector2': + if (typeof variable.value === 'object' && variable.value.x !== undefined && variable.value.y !== undefined) { + return `(${variable.value.x}, ${variable.value.y})`; + } + return String(variable.value); + case 'vector3': + if (typeof variable.value === 'object' && variable.value.x !== undefined && variable.value.y !== undefined && variable.value.z !== undefined) { + return `(${variable.value.x}, ${variable.value.y}, ${variable.value.z})`; + } + return String(variable.value); + case 'object': + case 'array': + try { + const jsonStr = JSON.stringify(variable.value); + return jsonStr.length > 20 ? jsonStr.substring(0, 17) + '...' : jsonStr; + } catch { + return String(variable.value); + } + default: + return String(variable.value); + } + }; + + const onTypeChange = () => { + switch (newVariable.type) { + case 'string': + newVariable.defaultValue = ''; + break; + case 'number': + newVariable.defaultValue = 0; + break; + case 'boolean': + newVariable.defaultValue = false; + break; + case 'vector2': + newVariable.defaultValue = { x: 0, y: 0 }; + break; + case 'vector3': + newVariable.defaultValue = { x: 0, y: 0, z: 0 }; + break; + case 'object': + case 'array': + newVariable.defaultValue = ''; + newVariable.defaultValueText = ''; + break; + } + }; + + const saveBlackboardVariable = () => { + if (!blackboardModalData.name.trim()) { + alert('请输入变量名称'); + return; + } + + let finalValue = blackboardModalData.defaultValue; + + // 处理对象和数组类型的JSON格式 + if (blackboardModalData.type === 'object' || blackboardModalData.type === 'array') { + try { + if (typeof blackboardModalData.defaultValue === 'string') { + finalValue = blackboardModalData.defaultValue ? JSON.parse(blackboardModalData.defaultValue) : (blackboardModalData.type === 'array' ? [] : {}); + } + } catch (error) { + alert('JSON格式错误,请检查输入'); + return; + } + } + + const constraints: BlackboardVariable['constraints'] = {}; + if (blackboardModalData.constraints.min !== undefined) constraints.min = blackboardModalData.constraints.min; + if (blackboardModalData.constraints.max !== undefined) constraints.max = blackboardModalData.constraints.max; + if (blackboardModalData.constraints.step !== undefined) constraints.step = blackboardModalData.constraints.step; + + // 处理字符串的可选值列表 + if (blackboardModalData.useAllowedValues && blackboardModalData.allowedValuesText.trim()) { + constraints.allowedValues = blackboardModalData.allowedValuesText + .split('\n') + .map(val => val.trim()) + .filter(val => val.length > 0); + } + + const variable: BlackboardVariable = { + name: blackboardModalData.name, + type: blackboardModalData.type, + value: finalValue, + defaultValue: finalValue, + description: blackboardModalData.description, + group: blackboardModalData.group || undefined, + readOnly: blackboardModalData.readOnly, + constraints: Object.keys(constraints).length > 0 ? constraints : undefined + }; + + blackboardVariables.value.set(variable.name, variable); + + const groupName = variable.group || '未分组'; + expandedGroups.value.add(groupName); + + showBlackboardModal.value = false; + editingBlackboardVariable.value = null; + + // 重置模态框数据 + Object.assign(blackboardModalData, { + name: '', + type: 'string', + defaultValue: '', + description: '', + group: '', + readOnly: false, + constraints: {}, + useAllowedValues: false, + allowedValuesText: '' + }); + }; + + const deleteBlackboardVariable = (variableName: string) => { + if (confirm(`确定要删除变量 "${variableName}" 吗?`)) { + blackboardVariables.value.delete(variableName); + } + }; + + const updateBlackboardVariable = (variableName: string, newValue: any) => { + const variable = blackboardVariables.value.get(variableName); + if (!variable) return; + + if (variable.readOnly) { + alert('该变量为只读,无法修改'); + return; + } + + const updatedVariable = { ...variable, value: newValue }; + blackboardVariables.value.set(variableName, updatedVariable); + }; + + const selectVariable = (variable: BlackboardVariable) => { + selectedVariable.value = variable; + }; + + const clearBlackboard = () => { + if (confirm('确定要清空所有变量吗?此操作不可恢复。')) { + blackboardVariables.value.clear(); + selectedVariable.value = null; + } + }; + + const exportBlackboard = () => { + const data = Array.from(blackboardVariables.value.values()); + const json = JSON.stringify(data, null, 2); + + try { + navigator.clipboard.writeText(json); + alert('Blackboard配置已复制到剪贴板'); + } catch (error) { + console.error('复制失败:', error); + } + }; + + const importBlackboard = () => { + const input = document.createElement('input'); + input.type = 'file'; + input.accept = '.json'; + + input.onchange = (event) => { + const file = (event.target as HTMLInputElement).files?.[0]; + if (!file) return; + + const reader = new FileReader(); + reader.onload = (e) => { + try { + const data = JSON.parse(e.target?.result as string); + if (!Array.isArray(data)) { + throw new Error('格式错误:期望数组格式'); + } + + let importCount = 0; + data.forEach(varData => { + if (varData.name && varData.type) { + blackboardVariables.value.set(varData.name, varData); + importCount++; + } + }); + + alert(`成功导入 ${importCount} 个变量`); + } catch (error) { + alert('导入失败:' + (error as Error).message); + } + }; + reader.readAsText(file); + }; + + input.click(); + }; + + const onVariableDragStart = (event: DragEvent, variable: BlackboardVariable) => { + if (!event.dataTransfer) return; + + event.dataTransfer.setData('application/blackboard-variable', JSON.stringify({ + name: variable.name, + type: variable.type, + value: variable.value + })); + + event.dataTransfer.effectAllowed = 'copy'; + }; + + const removeBlackboardVariable = (variableName: string) => { + deleteBlackboardVariable(variableName); + }; + + const editVariable = (variable: BlackboardVariable) => { + editingBlackboardVariable.value = variable; + + Object.assign(blackboardModalData, { + name: variable.name, + type: variable.type, + defaultValue: (variable.type === 'object' || variable.type === 'array') ? JSON.stringify(variable.value, null, 2) : variable.value, + description: variable.description || '', + group: variable.group || '', + readOnly: variable.readOnly || false, + constraints: { + min: variable.constraints?.min, + max: variable.constraints?.max, + step: variable.constraints?.step + }, + useAllowedValues: !!(variable.constraints?.allowedValues?.length), + allowedValuesText: variable.constraints?.allowedValues?.join('\n') || '' + }); + + showBlackboardModal.value = true; + }; + + const onBlackboardDragStart = (event: DragEvent, variable: BlackboardVariable) => { + onVariableDragStart(event, variable); + }; + + const closeAddVariableDialog = () => { + showAddVariableDialog.value = false; + editingVariable.value = null; + }; + + const saveVariable = () => { + saveBlackboardVariable(); + }; + + const closeImportExportDialog = () => { + showImportExportDialog.value = false; + }; + + const copyExportData = () => { + navigator.clipboard.writeText(exportData.value); + alert('已复制到剪贴板'); + }; + + const importVariables = () => { + try { + const data = JSON.parse(importData.value); + if (!Array.isArray(data)) { + throw new Error('格式错误:期望数组格式'); + } + + if (clearBeforeImport.value) { + blackboardVariables.value.clear(); + } + + let importCount = 0; + data.forEach((varData: any) => { + if (varData.name && varData.type) { + blackboardVariables.value.set(varData.name, varData); + importCount++; + } + }); + + alert(`成功导入 ${importCount} 个变量`); + showImportExportDialog.value = false; + importData.value = ''; + } catch (error) { + alert('导入失败:' + (error as Error).message); + } + }; + + const addBlackboardVariable = () => { + Object.assign(newVariable, { + name: '', + type: 'string', + defaultValue: '', + defaultValueText: '', + description: '', + group: '', + readonly: false, + min: undefined, + max: undefined, + optionsText: '' + }); + + editingVariable.value = null; + showAddVariableDialog.value = true; + }; + + return { + blackboardVariables: blackboardVariablesArray, + selectedVariable, + showBlackboardModal, + editingBlackboardVariable, + blackboardModalData, + expandedGroups, + blackboardVariableGroups, + + showAddVariableDialog, + editingVariable, + newVariable, + groups, + showImportExportDialog, + activeTab, + exportData, + importData, + clearBeforeImport, + isValidVariable, + blackboardCollapsed, + blackboardTransparent, + + groupedBlackboardVariables, + isGroupExpanded, + toggleGroup, + getVariableTypeIcon, + formatBlackboardValue, + hasVisibleConstraints, + formatConstraints, + getTypeDisplayName, + getDisplayValue, + + addBlackboardVariable, + saveBlackboardVariable, + deleteBlackboardVariable, + removeBlackboardVariable, + updateBlackboardVariable, + editVariable, + selectVariable, + clearBlackboard, + + closeAddVariableDialog, + saveVariable, + onTypeChange, + closeImportExportDialog, + copyExportData, + importVariables, + + exportBlackboard, + importBlackboard, + + onBlackboardDragStart, + + editBlackboardVariable: editVariable, + onBlackboardValueChange: (variable: BlackboardVariable) => { + updateBlackboardVariable(variable.name, variable.value); + } + }; +} \ No newline at end of file diff --git a/extensions/cocos/cocos-ecs/extensions/cocos-ecs-extension/source/panels/behavior-tree/data/nodeTemplates.ts b/extensions/cocos/cocos-ecs/extensions/cocos-ecs-extension/source/panels/behavior-tree/data/nodeTemplates.ts index 9ba49825..632a7a02 100644 --- a/extensions/cocos/cocos-ecs/extensions/cocos-ecs-extension/source/panels/behavior-tree/data/nodeTemplates.ts +++ b/extensions/cocos/cocos-ecs/extensions/cocos-ecs-extension/source/panels/behavior-tree/data/nodeTemplates.ts @@ -702,5 +702,370 @@ export const nodeTemplates: NodeTemplate[] = [ maxChildren: 0, className: 'DestroyEntityAction', namespace: 'ecs-integration/behaviors' + }, + + // 黑板相关节点 - 动作节点 + { + type: 'set-blackboard-value', + name: '设置黑板变量', + icon: '📝', + category: 'action', + description: '设置黑板变量的值', + canHaveChildren: false, + canHaveParent: true, + maxChildren: 0, + className: 'SetBlackboardValue', + namespace: 'behaviourTree/actions', + properties: { + variableName: { + name: '变量名', + type: 'string', + value: '', + description: '黑板变量名', + required: true + }, + value: { + name: '设置值', + type: 'string', + value: '', + description: '要设置的值(留空则使用源变量)', + required: false + }, + sourceVariable: { + name: '源变量名', + type: 'string', + value: '', + description: '从另一个黑板变量复制值', + required: false + }, + force: { + name: '强制设置', + type: 'boolean', + value: false, + description: '是否忽略只读限制', + required: false + } + } + }, + { + type: 'add-blackboard-value', + name: '增加数值变量', + icon: '➕', + category: 'action', + description: '增加数值型黑板变量的值', + canHaveChildren: false, + canHaveParent: true, + maxChildren: 0, + className: 'AddToBlackboardValue', + namespace: 'behaviourTree/actions', + properties: { + variableName: { + name: '变量名', + type: 'string', + value: '', + description: '数值型黑板变量名', + required: true + }, + increment: { + name: '增量', + type: 'number', + value: 1, + description: '增加的数值', + required: true + }, + incrementVariable: { + name: '增量变量名', + type: 'string', + value: '', + description: '从另一个变量获取增量值', + required: false + } + } + }, + { + type: 'toggle-blackboard-bool', + name: '切换布尔变量', + icon: '🔄', + category: 'action', + description: '切换布尔型黑板变量的值', + canHaveChildren: false, + canHaveParent: true, + maxChildren: 0, + className: 'ToggleBlackboardBool', + namespace: 'behaviourTree/actions', + properties: { + variableName: { + name: '变量名', + type: 'string', + value: '', + description: '布尔型黑板变量名', + required: true + } + } + }, + { + type: 'reset-blackboard-variable', + name: '重置变量', + icon: '🔄', + category: 'action', + description: '重置黑板变量到默认值', + canHaveChildren: false, + canHaveParent: true, + maxChildren: 0, + className: 'ResetBlackboardVariable', + namespace: 'behaviourTree/actions', + properties: { + variableName: { + name: '变量名', + type: 'string', + value: '', + description: '要重置的黑板变量名', + required: true + } + } + }, + { + type: 'wait-blackboard-condition', + name: '等待黑板条件', + icon: '⏳', + category: 'action', + description: '等待黑板变量满足指定条件', + canHaveChildren: false, + canHaveParent: true, + maxChildren: 0, + className: 'WaitForBlackboardCondition', + namespace: 'behaviourTree/actions', + properties: { + variableName: { + name: '变量名', + type: 'string', + value: '', + description: '要监听的黑板变量名', + required: true + }, + expectedValue: { + name: '期望值', + type: 'string', + value: '', + description: '期望的变量值', + required: true + } + } + }, + { + type: 'log-blackboard-value', + name: '记录黑板变量', + icon: '📊', + category: 'action', + description: '将黑板变量值记录到控制台', + canHaveChildren: false, + canHaveParent: true, + maxChildren: 0, + className: 'LogBlackboardValue', + namespace: 'behaviourTree/actions', + properties: { + variableName: { + name: '变量名', + type: 'string', + value: '', + description: '要记录的黑板变量名', + required: true + }, + prefix: { + name: '日志前缀', + type: 'string', + value: '[Blackboard]', + description: '日志消息的前缀', + required: false + } + } + }, + { + type: 'math-blackboard-operation', + name: '数学运算', + icon: '🧮', + category: 'action', + description: '对黑板变量执行数学运算', + canHaveChildren: false, + canHaveParent: true, + maxChildren: 0, + className: 'MathBlackboardOperation', + namespace: 'behaviourTree/actions', + properties: { + targetVariable: { + name: '目标变量', + type: 'string', + value: '', + description: '存储结果的变量名', + required: true + }, + operand1Variable: { + name: '操作数1变量', + type: 'string', + value: '', + description: '第一个操作数的变量名', + required: true + }, + operand2: { + name: '操作数2', + type: 'string', + value: '', + description: '第二个操作数(数值或变量名)', + required: true + }, + operation: { + name: '运算类型', + type: 'select', + value: 'add', + options: ['add', 'subtract', 'multiply', 'divide', 'modulo', 'power', 'min', 'max'], + description: '要执行的数学运算', + required: true + } + } + }, + + // 黑板相关节点 - 条件节点 + { + type: 'blackboard-value-comparison', + name: '黑板值比较', + icon: '⚖️', + category: 'condition', + description: '比较黑板变量与指定值或另一个变量 (拖拽到条件装饰器上使用)', + canHaveChildren: false, + canHaveParent: false, + maxChildren: 0, + isDraggableCondition: true, + attachableToDecorator: true, + className: 'BlackboardValueComparison', + namespace: 'behaviourTree/conditionals', + properties: { + variableName: { + name: '变量名', + type: 'string', + value: '', + description: '要比较的黑板变量名', + required: true + }, + operator: { + name: '比较操作符', + type: 'select', + value: 'equal', + options: ['equal', 'notEqual', 'greater', 'greaterOrEqual', 'less', 'lessOrEqual', 'contains', 'notContains'], + description: '比较操作类型', + required: true + }, + compareValue: { + name: '比较值', + type: 'string', + value: '', + description: '用于比较的值(留空则使用比较变量)', + required: false + }, + compareVariable: { + name: '比较变量名', + type: 'string', + value: '', + description: '用于比较的另一个黑板变量名', + required: false + } + } + }, + { + type: 'blackboard-variable-exists', + name: '黑板变量存在', + icon: '✅', + category: 'condition', + description: '检查黑板变量是否存在且有效 (拖拽到条件装饰器上使用)', + canHaveChildren: false, + canHaveParent: false, + maxChildren: 0, + isDraggableCondition: true, + attachableToDecorator: true, + className: 'BlackboardVariableExists', + namespace: 'behaviourTree/conditionals', + properties: { + variableName: { + name: '变量名', + type: 'string', + value: '', + description: '要检查的黑板变量名', + required: true + }, + invert: { + name: '反转结果', + type: 'boolean', + value: false, + description: '是否反转检查结果', + required: false + } + } + }, + { + type: 'blackboard-variable-type-check', + name: '黑板变量类型检查', + icon: '🔍', + category: 'condition', + description: '检查黑板变量是否为指定类型 (拖拽到条件装饰器上使用)', + canHaveChildren: false, + canHaveParent: false, + maxChildren: 0, + isDraggableCondition: true, + attachableToDecorator: true, + className: 'BlackboardVariableTypeCheck', + namespace: 'behaviourTree/conditionals', + properties: { + variableName: { + name: '变量名', + type: 'string', + value: '', + description: '要检查的黑板变量名', + required: true + }, + expectedType: { + name: '期望类型', + type: 'select', + value: 'string', + options: ['string', 'number', 'boolean', 'vector2', 'vector3', 'object', 'array'], + description: '期望的变量类型', + required: true + } + } + }, + { + type: 'blackboard-variable-range-check', + name: '黑板变量范围检查', + icon: '📏', + category: 'condition', + description: '检查数值型黑板变量是否在指定范围内 (拖拽到条件装饰器上使用)', + canHaveChildren: false, + canHaveParent: false, + maxChildren: 0, + isDraggableCondition: true, + attachableToDecorator: true, + className: 'BlackboardVariableRangeCheck', + namespace: 'behaviourTree/conditionals', + properties: { + variableName: { + name: '变量名', + type: 'string', + value: '', + description: '要检查的数值型黑板变量名', + required: true + }, + minValue: { + name: '最小值', + type: 'number', + value: 0, + description: '范围的最小值(包含)', + required: true + }, + maxValue: { + name: '最大值', + type: 'number', + value: 100, + description: '范围的最大值(包含)', + required: true + } + } } ]; \ No newline at end of file diff --git a/extensions/cocos/cocos-ecs/extensions/cocos-ecs-extension/static/style/behavior-tree/base.css b/extensions/cocos/cocos-ecs/extensions/cocos-ecs-extension/static/style/behavior-tree/base.css index c54affc4..1cec7fb0 100644 --- a/extensions/cocos/cocos-ecs/extensions/cocos-ecs-extension/static/style/behavior-tree/base.css +++ b/extensions/cocos/cocos-ecs/extensions/cocos-ecs-extension/static/style/behavior-tree/base.css @@ -45,16 +45,50 @@ display: flex; flex: 1; overflow: hidden; + position: relative; +} + +/* 画布容器 */ +.canvas-container { + flex: 1; + display: flex; + flex-direction: column; + overflow: hidden; + background: #2d3748; } /* 响应式设计 */ +@media (max-width: 1400px) { + .properties-panel-container { + width: 280px; + } + + .blackboard-sidebar { + width: 260px; + right: 280px; + } + + .blackboard-sidebar.collapsed { + right: 232px; + } +} + @media (max-width: 1200px) { .nodes-panel { width: 240px; } - .properties-panel { - width: 280px; + .properties-panel-container { + width: 260px; + } + + .blackboard-sidebar { + width: 240px; + right: 260px; + } + + .blackboard-sidebar.collapsed { + right: 212px; } } @@ -63,14 +97,33 @@ flex-direction: column; } - .nodes-panel, .properties-panel { + .nodes-panel { + width: 100%; + height: 160px; + flex: none; + } + + .properties-panel-container { width: 100%; height: 200px; + flex: none; + } + + .blackboard-sidebar { + width: 250px; + max-height: 300px; + top: 380px; + right: 20px; + border-radius: 8px; + } + + .blackboard-sidebar.collapsed { + right: 20px; } .canvas-container { flex: 1; - min-height: 400px; + min-height: 300px; } } @@ -199,13 +252,10 @@ } .overlay-note { - padding: 12px; - background: rgba(59, 130, 246, 0.1); - border: 1px solid rgba(59, 130, 246, 0.3); - border-radius: 8px; - color: rgba(255, 255, 255, 0.7); + margin-top: 16px; } .overlay-note small { - font-size: 14px; + color: rgba(255, 255, 255, 0.6); + font-size: 13px; } \ No newline at end of file diff --git a/extensions/cocos/cocos-ecs/extensions/cocos-ecs-extension/static/style/behavior-tree/modals.css b/extensions/cocos/cocos-ecs/extensions/cocos-ecs-extension/static/style/behavior-tree/modals.css index 03e4c2ec..a4f7bb01 100644 --- a/extensions/cocos/cocos-ecs/extensions/cocos-ecs-extension/static/style/behavior-tree/modals.css +++ b/extensions/cocos/cocos-ecs/extensions/cocos-ecs-extension/static/style/behavior-tree/modals.css @@ -32,52 +32,112 @@ left: 0; width: 100%; height: 100%; - background: rgba(0, 0, 0, 0.7); + background: rgba(0, 0, 0, 0.75); + backdrop-filter: blur(4px); display: flex; justify-content: center; align-items: center; z-index: 10000; + animation: modalFadeIn 0.2s ease-out; +} + +@keyframes modalFadeIn { + from { + opacity: 0; + backdrop-filter: blur(0px); + } + to { + opacity: 1; + backdrop-filter: blur(4px); + } } .modal-content { background: #2d3748; - border-radius: 8px; - box-shadow: 0 20px 60px rgba(0, 0, 0, 0.5); + border-radius: 12px; + box-shadow: 0 25px 80px rgba(0, 0, 0, 0.6); max-width: 90vw; max-height: 90vh; display: flex; flex-direction: column; border: 1px solid #4a5568; + animation: modalSlideIn 0.3s ease-out; + overflow: hidden; +} + +@keyframes modalSlideIn { + from { + opacity: 0; + transform: translateY(-20px) scale(0.95); + } + to { + opacity: 1; + transform: translateY(0) scale(1); + } } .modal-header { display: flex; justify-content: space-between; align-items: center; - padding: 16px 20px; + padding: 20px 24px; border-bottom: 1px solid #4a5568; - background: #4a5568; - border-radius: 8px 8px 0 0; + background: linear-gradient(135deg, #4a5568 0%, #2d3748 100%); + position: relative; +} + +.modal-header::before { + content: ''; + position: absolute; + top: 0; + left: 0; + right: 0; + height: 2px; + background: linear-gradient(90deg, #667eea 0%, #764ba2 100%); } .modal-header h3 { margin: 0; - color: #e2e8f0; + color: #ffffff; font-size: 16px; + font-weight: 600; + letter-spacing: 0.5px; + display: flex; + align-items: center; + gap: 8px; +} + +.modal-header h3::before { + content: '✨'; + font-size: 14px; } .modal-header button { - background: none; + background: rgba(255, 255, 255, 0.1); border: none; - color: #a0aec0; + color: #cbd5e0; cursor: pointer; - font-size: 18px; + font-size: 16px; + width: 32px; + height: 32px; + border-radius: 6px; + display: flex; + align-items: center; + justify-content: center; + transition: all 0.2s ease; +} + +.modal-header button:hover { + background: rgba(255, 255, 255, 0.2); + color: #ffffff; + transform: scale(1.05); } .modal-body { flex: 1; overflow-y: auto; - padding: 20px; + padding: 24px; + background: #2d3748; } .export-options { @@ -112,22 +172,260 @@ .modal-footer { display: flex; gap: 12px; - padding: 16px 20px; + padding: 20px 24px; border-top: 1px solid #4a5568; justify-content: flex-end; + background: #374151; } .modal-footer button { - padding: 8px 16px; - background: #667eea; + padding: 12px 20px; border: none; - border-radius: 4px; + border-radius: 8px; color: white; cursor: pointer; - font-size: 12px; - transition: background 0.3s ease; + font-size: 13px; + font-weight: 600; + transition: all 0.2s ease; + text-transform: uppercase; + letter-spacing: 0.5px; + position: relative; + overflow: hidden; +} + +.modal-footer button::before { + content: ''; + position: absolute; + top: 0; + left: -100%; + width: 100%; + height: 100%; + background: linear-gradient(90deg, transparent, rgba(255,255,255,0.2), transparent); + transition: left 0.5s ease; +} + +.modal-footer button:hover::before { + left: 100%; } .modal-footer button:hover { - background: #5a67d8; + transform: translateY(-2px); + box-shadow: 0 8px 16px rgba(0, 0, 0, 0.3); +} + +.modal-footer button:active { + transform: translateY(0); +} + +.modal-footer .save-btn { + background: linear-gradient(135deg, #48bb78 0%, #38a169 100%); +} + +.modal-footer .save-btn:hover { + background: linear-gradient(135deg, #38a169 0%, #2f855a 100%); +} + +.modal-footer .cancel-btn { + background: linear-gradient(135deg, #6b7280 0%, #4b5563 100%); +} + +.modal-footer .cancel-btn:hover { + background: linear-gradient(135deg, #4b5563 0%, #374151 100%); +} + +/* Blackboard模态框特定样式 */ +.blackboard-modal { + width: 520px; + max-width: 90vw; +} + +.form-group { + margin-bottom: 20px; + position: relative; +} + +.form-group label { + display: block; + margin-bottom: 8px; + color: #e2e8f0; + font-size: 13px; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.5px; + display: flex; + align-items: center; + gap: 6px; +} + +.form-group label::before { + content: ''; + width: 3px; + height: 14px; + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + border-radius: 2px; +} + +.form-input, +.form-select, +.form-textarea { + width: 100%; + padding: 12px 16px; + background: #1a202c; + border: 2px solid #4a5568; + border-radius: 8px; + color: white; + font-size: 13px; + font-family: inherit; + transition: all 0.2s ease; + box-sizing: border-box; +} + +.form-input:focus, +.form-select:focus, +.form-textarea:focus { + border-color: #667eea; + outline: none; + box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.15); + transform: translateY(-1px); +} + +.form-input:hover, +.form-select:hover, +.form-textarea:hover { + border-color: #718096; +} + +.form-input::placeholder, +.form-textarea::placeholder { + color: #718096; + font-style: italic; +} + +.constraint-inputs { + display: flex; + flex-direction: column; + gap: 12px; + background: rgba(255, 255, 255, 0.02); + border: 1px solid #4a5568; + border-radius: 8px; + padding: 16px; + margin-top: 8px; +} + +.constraint-row { + display: flex; + align-items: center; + gap: 12px; +} + +.constraint-row label { + min-width: 60px; + font-size: 12px; + color: #a0aec0; + margin: 0; + text-transform: none; + font-weight: 500; +} + +.constraint-row label::before { + display: none; +} + +/* 自定义复选框样式 */ +.checkbox-group { + display: flex; + align-items: center; + gap: 10px; + margin: 16px 0; + padding: 12px 16px; + background: rgba(255, 255, 255, 0.02); + border-radius: 8px; + border: 1px solid #4a5568; + cursor: pointer; + transition: all 0.2s ease; +} + +.checkbox-group:hover { + background: rgba(255, 255, 255, 0.05); + border-color: #667eea; +} + +.checkbox-group input[type="checkbox"] { + appearance: none; + width: 18px; + height: 18px; + border: 2px solid #4a5568; + border-radius: 4px; + background: #1a202c; + cursor: pointer; + position: relative; + transition: all 0.2s ease; +} + +.checkbox-group input[type="checkbox"]:checked { + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + border-color: #667eea; +} + +.checkbox-group input[type="checkbox"]:checked::after { + content: '✓'; + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + color: white; + font-size: 12px; + font-weight: bold; +} + +.checkbox-group label { + margin: 0; + color: #e2e8f0; + font-size: 13px; + font-weight: 500; + cursor: pointer; + text-transform: none; + display: block; +} + +.checkbox-group label::before { + display: none; +} + +.constraint-row label { + min-width: 60px; + margin-bottom: 0; + font-size: 11px; +} + +.constraint-row input { + flex: 1; +} + +.allowed-values textarea { + margin-top: 6px; + resize: vertical; +} + +.checkbox-label { + display: flex; + align-items: center; + gap: 8px; + cursor: pointer; +} + +.checkbox-label input[type="checkbox"] { + width: auto; +} + +.export-code { + background: #1a202c; + border: 1px solid #4a5568; + border-radius: 4px; + padding: 12px; + font-family: 'Consolas', 'Monaco', monospace; + font-size: 11px; + color: #e2e8f0; + resize: none; + min-height: 300px; } \ No newline at end of file diff --git a/extensions/cocos/cocos-ecs/extensions/cocos-ecs-extension/static/style/behavior-tree/panels.css b/extensions/cocos/cocos-ecs/extensions/cocos-ecs-extension/static/style/behavior-tree/panels.css index ffbb3bdd..31fecad0 100644 --- a/extensions/cocos/cocos-ecs/extensions/cocos-ecs-extension/static/style/behavior-tree/panels.css +++ b/extensions/cocos/cocos-ecs/extensions/cocos-ecs-extension/static/style/behavior-tree/panels.css @@ -11,15 +11,53 @@ } .panel-header { - padding: 16px; + padding: 12px 16px; background: #4a5568; border-bottom: 1px solid #718096; + display: flex; + justify-content: space-between; + align-items: center; + min-height: 48px; } .panel-header h3 { - margin: 0 0 12px 0; - font-size: 16px; + margin: 0; + font-size: 13px; + font-weight: 600; color: #e2e8f0; + flex: 1; + letter-spacing: 0.5px; + white-space: nowrap; + overflow: visible; + text-overflow: ellipsis; + min-width: 0; +} + +.header-actions { + display: flex; + gap: 8px; + flex-wrap: wrap; +} + +.tool-btn.small { + padding: 6px 10px; + background: #1a202c; + border: 1px solid #4a5568; + border-radius: 4px; + color: #e2e8f0; + font-size: 11px; + cursor: pointer; + transition: all 0.2s ease; + display: flex; + align-items: center; + gap: 4px; + white-space: nowrap; +} + +.tool-btn.small:hover { + background: #2d3748; + border-color: #667eea; + transform: translateY(-1px); } .search-input { @@ -49,10 +87,36 @@ .category-title { margin: 0 0 8px 0; padding: 8px 12px; - background: #4a5568; - border-radius: 4px; - font-size: 14px; - color: #e2e8f0; + background: linear-gradient(135deg, #4a5568 0%, #2d3748 100%); + border-radius: 6px; + font-size: 12px; + font-weight: 500; + color: #cbd5e0; + letter-spacing: 0.3px; + border-left: 3px solid #667eea; + position: relative; + overflow: hidden; +} + +.category-title::before { + content: ''; + position: absolute; + top: 0; + left: -100%; + width: 100%; + height: 100%; + background: linear-gradient(90deg, transparent, rgba(255,255,255,0.1), transparent); + transition: left 0.5s ease; +} + +.category-title:hover::before { + left: 100%; +} + +.category-title:hover { + background: linear-gradient(135deg, #667eea 0%, #4a5568 100%); + color: #ffffff; + transform: translateX(2px); } .node-list { @@ -105,8 +169,8 @@ text-overflow: ellipsis; } -/* 右侧属性面板 */ -.properties-panel { +/* 右侧属性面板容器 */ +.properties-panel-container { width: 320px; background: #2d3748; border-left: 1px solid #4a5568; @@ -115,6 +179,19 @@ overflow: hidden; } + + + +/* 属性面板 */ +.properties-panel { + flex: 1; + background: #2d3748; + display: flex; + flex-direction: column; + overflow: hidden; + width: 100%; +} + .node-properties { flex: 1; overflow-y: auto; @@ -126,13 +203,19 @@ } .property-section h4 { - margin: 0 0 12px 0; + margin: 0 0 10px 0; color: #e2e8f0; - font-size: 14px; + font-size: 12px; + font-weight: 600; + border-bottom: 1px solid #4a5568; + padding-bottom: 4px; + letter-spacing: 0.5px; + text-transform: uppercase; } .property-item { margin-bottom: 12px; + position: relative; } .property-item label { @@ -143,6 +226,10 @@ font-weight: 500; } +.property-input-container { + position: relative; +} + .property-item input, .property-item textarea, .property-item select { @@ -154,6 +241,7 @@ color: white; font-size: 12px; font-family: inherit; + box-sizing: border-box; } .property-item input:focus, @@ -178,7 +266,52 @@ line-height: 1.4; } -.code-preview { +.blackboard-droppable { + border: 2px dashed transparent; + transition: border-color 0.2s ease; +} + +.blackboard-droppable.drag-over { + border-color: #667eea; + background: rgba(102, 126, 234, 0.1); +} + +.blackboard-reference { + display: flex; + align-items: center; + gap: 6px; + margin-top: 4px; + padding: 4px 8px; + background: #4a5568; + border-radius: 3px; + font-size: 11px; +} + +.ref-icon { + color: #667eea; +} + +.ref-text { + color: #e2e8f0; + flex: 1; +} + +.clear-ref { + background: none; + border: none; + color: #ef4444; + cursor: pointer; + padding: 0 4px; + border-radius: 2px; + font-size: 12px; +} + +.clear-ref:hover { + background: #ef4444; + color: white; +} + +.config-preview { background: #1a202c; border: 1px solid #4a5568; border-radius: 4px; @@ -248,4 +381,543 @@ .tree-node-type { font-size: 9px; color: #718096; -} \ No newline at end of file +} + +/* Blackboard悬浮窗口样式 */ +.blackboard-sidebar { + position: fixed; + top: 160px; + right: 320px; + width: 280px; + max-height: calc(100vh - 180px); + background: #2d3748; + border: 1px solid #4a5568; + border-radius: 8px 0 0 8px; + z-index: 999; + display: flex; + flex-direction: column; + transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); + backdrop-filter: blur(8px); + box-shadow: -4px 0 20px rgba(0, 0, 0, 0.15); +} + +.blackboard-sidebar.collapsed { + width: 48px; + right: 272px; +} + +.blackboard-sidebar.transparent { + opacity: 0.7; + background: rgba(45, 55, 72, 0.85); +} + +.blackboard-sidebar.transparent:not(.collapsed) { + transform: translateX(-20px); +} + +.blackboard-toggle { + position: absolute; + left: -24px; + top: 50%; + transform: translateY(-50%); + width: 24px; + height: 48px; + background: #667eea; + border: none; + border-radius: 8px 0 0 8px; + color: white; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + font-size: 14px; + transition: all 0.3s ease; + z-index: 1001; + box-shadow: -2px 0 8px rgba(0, 0, 0, 0.2); +} + +.blackboard-toggle:hover { + background: #5a6acf; + transform: translateY(-50%) translateX(-2px); + box-shadow: -4px 0 12px rgba(0, 0, 0, 0.3); +} + +.blackboard-toggle span { + transition: transform 0.3s ease; +} + +.blackboard-sidebar.collapsed .blackboard-toggle span { + transform: rotate(180deg); +} + +.blackboard-content { + flex: 1; + display: flex; + flex-direction: column; + overflow: hidden; +} + +.blackboard-header { + background: linear-gradient(135deg, #667eea, #764ba2); + color: white; + padding: 12px; + position: relative; + min-height: 48px; + display: flex; + align-items: center; +} + +.blackboard-header::before { + content: ''; + position: absolute; + top: 0; + left: 0; + right: 0; + height: 2px; + background: linear-gradient(90deg, #ff6b6b, #4ecdc4, #45b7d1, #96ceb4); +} + +.blackboard-header h3 { + margin: 0; + font-size: 13px; + font-weight: 600; + letter-spacing: 0.5px; +} + +.blackboard-scroll-area { + flex: 1; + overflow-y: auto; + padding: 0 12px 12px 12px; +} + +.blackboard-sidebar .blackboard-actions { + display: flex; + gap: 8px; + margin-bottom: 16px; + flex-wrap: wrap; + padding: 12px; + border-bottom: 1px solid #4a5568; +} + +.blackboard-sidebar .blackboard-actions button { + padding: 6px 12px; + border: none; + border-radius: 4px; + font-size: 11px; + font-weight: 500; + cursor: pointer; + transition: all 0.2s ease; + text-transform: uppercase; + letter-spacing: 0.3px; +} + +.blackboard-sidebar .add-var-btn { + background: #48bb78; + color: white; +} + +.blackboard-sidebar .add-var-btn:hover { + background: #38a169; + transform: translateY(-1px); +} + +.blackboard-sidebar .clear-btn { + background: #f56565; + color: white; +} + +.blackboard-sidebar .clear-btn:hover { + background: #e53e3e; + transform: translateY(-1px); +} + +.blackboard-sidebar .blackboard-groups { + display: flex; + flex-direction: column; + gap: 16px; +} + +.blackboard-sidebar .variable-group { + background: rgba(255, 255, 255, 0.02); + border: 1px solid #4a5568; + border-radius: 6px; + overflow: hidden; +} + +.blackboard-sidebar .group-title { + margin: 0; + padding: 8px 12px; + background: #1a202c; + color: #e2e8f0; + font-size: 11px; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.5px; + border-bottom: 1px solid #4a5568; +} + +.blackboard-sidebar .variable-list { + padding: 8px; + display: flex; + flex-direction: column; + gap: 6px; +} + +.blackboard-sidebar .variable-item { + background: linear-gradient(135deg, #2d3748 0%, #1a202c 100%); + border: 1px solid #4a5568; + border-radius: 8px; + padding: 12px 16px; + cursor: grab; + transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); + position: relative; + display: flex; + align-items: center; + gap: 12px; + border-left-width: 0; + min-height: 44px; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2); + backdrop-filter: blur(10px); + overflow: hidden; +} + +.blackboard-sidebar .variable-item::before { + content: ''; + position: absolute; + left: 12px; + top: 50%; + transform: translateY(-50%); + width: 8px; + height: 8px; + border-radius: 50%; + transition: all 0.3s ease; + box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.1), 0 2px 4px rgba(0, 0, 0, 0.3); +} + +.blackboard-sidebar .variable-item:hover { + background: linear-gradient(135deg, #4a5568 0%, #2d3748 100%); + border-color: #667eea; + transform: translateY(-2px) scale(1.02); + box-shadow: 0 8px 25px rgba(0, 0, 0, 0.4), 0 0 0 1px rgba(102, 126, 234, 0.3); +} + +.blackboard-sidebar .variable-item:hover::before { + transform: translateY(-50%) scale(1.2); + box-shadow: 0 0 0 3px rgba(255, 255, 255, 0.2), 0 0 12px currentColor, 0 4px 8px rgba(0, 0, 0, 0.4); +} + +.blackboard-sidebar .variable-item:active { + cursor: grabbing; + transform: scale(0.98); +} + +.blackboard-sidebar .variable-header { + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; + flex: 1; + min-width: 0; + margin-left: 20px; +} + +.blackboard-sidebar .variable-info { + display: flex; + align-items: center; + gap: 6px; + flex: 1; + min-width: 0; +} + +.blackboard-sidebar .variable-name { + font-weight: 600; + color: #f7fafc; + font-size: 13px; + flex-shrink: 0; + letter-spacing: 0.3px; + text-shadow: 0 1px 2px rgba(0, 0, 0, 0.3); +} + +.blackboard-sidebar .value-separator { + color: #64748b; + font-weight: 500; + flex-shrink: 0; + opacity: 0.7; +} + +.blackboard-sidebar .variable-type { + font-size: 8px; + padding: 4px 8px; + border-radius: 12px; + text-transform: uppercase; + letter-spacing: 0.5px; + font-weight: 700; + flex-shrink: 0; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.3), inset 0 1px 0 rgba(255, 255, 255, 0.2); + line-height: 1; + border: 1px solid rgba(255, 255, 255, 0.1); +} + +.blackboard-sidebar .variable-item.string::before { + background: #48bb78; +} + +.blackboard-sidebar .variable-item.string .variable-type { + background: #48bb78; + color: white; +} + +.blackboard-sidebar .variable-item.number::before { + background: #3182ce; +} + +.blackboard-sidebar .variable-item.number .variable-type { + background: #3182ce; + color: white; +} + +.blackboard-sidebar .variable-item.boolean::before { + background: #d69e2e; +} + +.blackboard-sidebar .variable-item.boolean .variable-type { + background: #d69e2e; + color: white; +} + +.blackboard-sidebar .variable-item.vector2::before, +.blackboard-sidebar .variable-item.vector3::before { + background: #9f7aea; +} + +.blackboard-sidebar .variable-item.vector2 .variable-type, +.blackboard-sidebar .variable-item.vector3 .variable-type { + background: #9f7aea; + color: white; +} + +.blackboard-sidebar .variable-item.object::before, +.blackboard-sidebar .variable-item.array::before { + background: #ed8936; +} + +.blackboard-sidebar .variable-item.object .variable-type, +.blackboard-sidebar .variable-item.array .variable-type { + background: #ed8936; + color: white; +} + +.blackboard-sidebar .variable-actions { + display: flex; + gap: 4px; + opacity: 0; + transition: all 0.3s ease; + flex-shrink: 0; + transform: translateX(8px); +} + +.blackboard-sidebar .variable-item:hover .variable-actions { + opacity: 1; + transform: translateX(0); +} + +.blackboard-sidebar .variable-actions button { + background: rgba(255, 255, 255, 0.1); + border: 1px solid rgba(255, 255, 255, 0.15); + font-size: 10px; + cursor: pointer; + padding: 6px; + border-radius: 6px; + transition: all 0.2s ease; + color: #cbd5e0; + width: 24px; + height: 24px; + display: flex; + align-items: center; + justify-content: center; + backdrop-filter: blur(4px); + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2); +} + +.blackboard-sidebar .edit-btn:hover { + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + border-color: #667eea; + transform: translateY(-1px) scale(1.05); + box-shadow: 0 4px 12px rgba(102, 126, 234, 0.4); + color: white; +} + +.blackboard-sidebar .remove-btn:hover { + background: linear-gradient(135deg, #f56565 0%, #e53e3e 100%); + border-color: #f56565; + transform: translateY(-1px) scale(1.05); + box-shadow: 0 4px 12px rgba(245, 101, 101, 0.4); + color: white; +} + +.blackboard-sidebar .value-display { + font-family: 'JetBrains Mono', 'Consolas', 'Monaco', monospace; + font-size: 11px; + color: #10b981; + background: none; + border: none; + padding: 0; + flex: 1; + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + font-weight: 500; +} + +/* 不同类型的值颜色 */ +.blackboard-sidebar .variable-item.string .value-display { + color: #f59e0b; /* 琥珀色 - 字符串 */ +} + +.blackboard-sidebar .variable-item.number .value-display { + color: #3b82f6; /* 蓝色 - 数字 */ +} + +.blackboard-sidebar .variable-item.boolean .value-display { + color: #8b5cf6; /* 紫色 - 布尔值 */ +} + +.blackboard-sidebar .variable-item.vector2 .value-display, +.blackboard-sidebar .variable-item.vector3 .value-display { + color: #ec4899; /* 粉色 - 向量 */ +} + +.blackboard-sidebar .variable-item.object .value-display, +.blackboard-sidebar .variable-item.array .value-display { + color: #10b981; /* 绿色 - 对象/数组 */ +} + +.blackboard-sidebar .variable-constraints { + margin-top: 4px; + font-size: 10px; + color: #718096; + font-style: italic; +} + +.blackboard-sidebar .empty-blackboard { + text-align: center; + padding: 24px 16px; + color: #718096; +} + +.blackboard-sidebar .empty-icon { + font-size: 32px; + margin-bottom: 12px; + opacity: 0.5; +} + +.blackboard-sidebar .empty-blackboard p { + margin: 0 0 16px 0; + font-size: 13px; +} + +.blackboard-sidebar .add-first-var { + background: #667eea; + color: white; + border: none; + padding: 8px 16px; + border-radius: 4px; + font-size: 12px; + cursor: pointer; + transition: all 0.2s ease; +} + +.blackboard-sidebar .add-first-var:hover { + background: #5a67d8; + transform: translateY(-1px); +} + +/* 只读变量样式 */ +.blackboard-sidebar .variable-item.readonly { + opacity: 0.8; + background: linear-gradient(135deg, #374151 0%, #1f2937 100%) !important; + border-color: #6b7280; +} + +.blackboard-sidebar .variable-item.readonly::before { + background: #6b7280 !important; +} + +.blackboard-sidebar .variable-item.readonly .variable-name { + color: #9ca3af; +} + +.blackboard-sidebar .variable-item.readonly .variable-type { + background: #6b7280 !important; + color: #e5e7eb !important; +} + +/* 拖拽状态样式 */ +.blackboard-sidebar .variable-item:active { + cursor: grabbing !important; + transform: scale(0.95) rotate(2deg); + opacity: 0.8; + z-index: 1000; +} + +/* 响应式断点调整 */ +@media (max-width: 1400px) { + .blackboard-sidebar .variable-item { + padding: 10px 14px; + gap: 10px; + } + + .blackboard-sidebar .variable-name { + font-size: 12px; + } + + .blackboard-sidebar .value-display { + font-size: 10px; + } + + .blackboard-sidebar .variable-type { + font-size: 7px; + padding: 3px 6px; + } + + .blackboard-sidebar .variable-actions button { + width: 20px; + height: 20px; + font-size: 9px; + } +} + +@media (max-width: 1200px) { + .blackboard-sidebar .variable-item { + padding: 8px 12px; + gap: 8px; + min-height: 38px; + } + + .blackboard-sidebar .variable-header { + margin-left: 16px; + gap: 8px; + } + + .blackboard-sidebar .variable-name { + font-size: 11px; + } + + .blackboard-sidebar .value-display { + font-size: 9px; + } + + .blackboard-sidebar .variable-item::before { + left: 10px; + width: 6px; + height: 6px; + } + + .blackboard-sidebar .variable-actions button { + width: 18px; + height: 18px; + font-size: 8px; + padding: 4px; + } +} \ No newline at end of file diff --git a/extensions/cocos/cocos-ecs/extensions/cocos-ecs-extension/static/style/behavior-tree/toolbar.css b/extensions/cocos/cocos-ecs/extensions/cocos-ecs-extension/static/style/behavior-tree/toolbar.css index 94ced083..ca284af8 100644 --- a/extensions/cocos/cocos-ecs/extensions/cocos-ecs-extension/static/style/behavior-tree/toolbar.css +++ b/extensions/cocos/cocos-ecs/extensions/cocos-ecs-extension/static/style/behavior-tree/toolbar.css @@ -17,15 +17,16 @@ .toolbar-left h2 { margin: 0; - font-size: 20px; + font-size: 16px; font-weight: 600; text-shadow: 0 2px 4px rgba(0,0,0,0.3); + letter-spacing: 0.5px; } .current-file { color: #a0aec0; font-weight: 400; - font-size: 16px; + font-size: 13px; margin-left: 8px; } @@ -64,6 +65,12 @@ transform: translateY(-1px); } +.tool-btn.active { + background: rgba(102, 126, 234, 0.3); + border-color: #667eea; + color: #e2e8f0; +} + .tool-btn.has-changes { diff --git a/extensions/cocos/cocos-ecs/extensions/cocos-ecs-extension/static/template/behavior-tree/BehaviorTreeEditor.html b/extensions/cocos/cocos-ecs/extensions/cocos-ecs-extension/static/template/behavior-tree/BehaviorTreeEditor.html index 3cadfc90..ba5b92c7 100644 --- a/extensions/cocos/cocos-ecs/extensions/cocos-ecs-extension/static/template/behavior-tree/BehaviorTreeEditor.html +++ b/extensions/cocos/cocos-ecs/extensions/cocos-ecs-extension/static/template/behavior-tree/BehaviorTreeEditor.html @@ -21,6 +21,7 @@ +
@@ -395,104 +396,123 @@
-
-
-

⚙️ 属性面板

-
- -
-
-

基本信息

-
- - -
-
- - -
+
+ +
+
+

⚙️ 属性面板

- -
-

节点属性

-
- - - - - - - {{ option }} - - -

{{ prop.description }}

+
+
+ + +
+
+ +
+

节点属性

+
+ +
+ + + + + + + +
+ 🔗 + {{ prop.value }} + +
+
+

{{ prop.description }}

+
+
+ +
+

节点配置

+
{{ activeNode ? JSON.stringify(activeNode, null, 2) : '{}' }}
- -
-

节点配置

-
{{ activeNode ? JSON.stringify(activeNode, null, 2) : '{}' }}
+ +
+

请选择一个节点查看属性

- -
-

请选择一个节点查看属性

-
+
@@ -514,6 +534,233 @@ {{ validationResult().message }}
+ + + + +
+ + + + + +
+
+

📋 Blackboard

+
+ + +
+ + +
+ + +
+
+
+

{{ groupName }}

+
+
+
+ {{ variable.name }} + {{ getTypeDisplayName(variable.type) }} +
+ + +
+
+ +
+ + {{ getDisplayValue(variable) }} +
+ + +
+ {{ formatConstraints(variable.constraints) }} +
+
+
+
+
+ + +
+
📋
+

还没有定义任何变量

+ +
+
+
+
+ + +