Merge pull request 'chore: �������������������ʲ������ű�' (#36) from chore/misc-updates into main

This commit was merged in pull request #36.
This commit is contained in:
ai-pm
2026-06-11 00:55:28 +08:00
3 changed files with 276 additions and 151 deletions
@@ -2,3 +2,4 @@
- [Gitea Token Permission](gitea-token-permission.md) — reviewer token 仅可评论,标签操作需用 PM token
- [P-006 Review](p006-review-note.md) — Issue #17 白模搭建审查通过记录
- [P-009 Review](p009-review-note.md) — Issue #20 收集系统程序设计审查通过记录
@@ -0,0 +1,19 @@
---
name: p009-review-note
description: Issue #20 P-009 收集系统程序设计审查通过记录
metadata:
type: project
---
## P-009 收集系统程序设计审查 (2026-06-10)
- Issue: #20
- 审查类型: 程序设计(质量门 2)
- 产出文件: `docs/technical/收集系统-技术方案.md`
- 结果: **通过**
### 审查要点
- 技术方案文档路径实际为中文文件名 `收集系统-技术方案.md`(非 Issue 中标注的 `collection-system-tech.md`)
- 执行案文件名也是中文:`收集系统-执行案.md`(非 `collection-system-execution.md`)
- 6 个检查维度全部通过:执行案覆盖度、美术资源需求、架构清晰度、数据流、技术风险、工作量与拆分建议
- 标签操作:移除了 gate:art-fail(30) 和 status:in-review(14),添加了 review:pass(33)
+256 -151
View File
@@ -1,20 +1,19 @@
"""UE5 编辑器 Python 脚本:创建 v0.1 角色控制所需的 InputAction 资产并更新 IMC。
由 setup_input.bat 通过 UE5 的 -ExecutePythonScript 参数调用。
在 UE5 编辑器 Python 环境中运行。
使用方式:
方式1 — 在编辑器 Python 控制台运行:
py "E:/MiceCola/work/ChildHood/tools/input-setup/setup_input_assets.py"
方式2 — 通过 setup_input.bat 启动编辑器执行
功能:
1. 创建 3 个 InputAction 资产:IA_Interact, IA_Cancel, IA_SwitchInteract
2. 更新 IMC_Default:添加 E / ESC / Tab 按键映射
2. 尝试更新 IMC_Default:添加 E / ESC / Tab 按键映射
3. 保存所有修改的资产
环境变量(由 setup_input.bat 设置):
INPUT_PROJECT_ROOT — 项目根目录绝对路径
"""
import os
import sys
from pathlib import Path
import traceback
# ---- 检查 unreal 模块 ----
try:
@@ -23,36 +22,54 @@ except ImportError:
print("[InputSetup] FATAL: unreal 模块不可用,必须在 UE5 编辑器环境中运行")
sys.exit(1)
# ---- 项目根目录 ----
_project_root_str = os.environ.get("INPUT_PROJECT_ROOT", "")
if _project_root_str:
PROJECT_ROOT = Path(_project_root_str)
else:
print("[InputSetup] FATAL: INPUT_PROJECT_ROOT 环境变量未设置")
sys.exit(1)
# ---- 资产路径配置 ----
IA_PACKAGE = "/Game/Input/Actions"
IMC_PATH = "/Game/Input/IMC_Default"
TEMPLATE_IA = f"{IA_PACKAGE}/IA_Look" # 用已存在的 IA 作为复制模板
# 新增 InputAction 定义
NEW_INPUT_ACTIONS = {
"IA_Interact": {
"description": "E 键交互输入动作",
"key": unreal.Key.E,
"triggers": [unreal.InputTriggerDown()],
},
"IA_Cancel": {
"description": "ESC 取消输入动作",
"key": unreal.Key.ESCAPE,
"triggers": [unreal.InputTriggerDown()],
},
"IA_SwitchInteract": {
"description": "Tab 切换交互目标输入动作",
"key": unreal.Key.TAB,
"triggers": [unreal.InputTriggerDown()],
},
}
# 新增 InputAction 定义(key_name 为字符串,运行时解析)
NEW_INPUT_ACTIONS = [
{"name": "IA_Interact", "desc": "E 键交互输入动作", "key_name": "E"},
{"name": "IA_Cancel", "desc": "ESC 取消输入动作", "key_name": "Escape"},
{"name": "IA_SwitchInteract", "desc": "Tab 切换交互目标", "key_name": "Tab"},
]
# 按键名 → UE5 Key 对象的多种尝试路径
KEY_RESOLVE_ORDER = ["E", "Escape", "Tab"]
def resolve_key(key_name: str):
"""尝试多种方式从 UE5 Python API 获取 Key 对象。
UE5 各版本的 Python 绑定对 Key 的暴露方式不同,
这里依次尝试多种路径,找到可用的那一个。
"""
attempts = [
# 方式1: unreal.Key 枚举属性 (如 unreal.Key.E, unreal.Key.Escape)
lambda: getattr(unreal.Key, key_name),
# 方式2: unreal.Keys 类 (部分版本用这个)
lambda: getattr(unreal.Keys, key_name),
# 方式3: 全大写 (如 ESCAPE, TAB)
lambda: getattr(unreal.Key, key_name.upper()),
lambda: getattr(unreal.Keys, key_name.upper()),
# 方式4: KEY_ 前缀 (如 KEY_E, KEY_ESCAPE)
lambda: getattr(unreal.Key, f"KEY_{key_name.upper()}"),
lambda: getattr(unreal.Keys, f"KEY_{key_name.upper()}"),
# 方式5: 构造函数 (部分版本支持从字符串构造)
lambda: unreal.Key(key_name),
]
for i, attempt in enumerate(attempts, 1):
try:
key = attempt()
if key is not None:
print(f"[InputSetup] Key('{key_name}') 解析成功 (方式{i})")
return key
except Exception:
continue
print(f"[InputSetup] Key('{key_name}') 所有解析方式均失败")
return None
def asset_exists(asset_path: str) -> bool:
@@ -60,161 +77,249 @@ def asset_exists(asset_path: str) -> bool:
return unreal.EditorAssetLibrary.does_asset_exist(asset_path)
def create_input_action(name: str, description: str) -> unreal.InputAction:
"""创建 InputAction 资产"""
def create_input_action_by_duplicate(name: str, desc: str) -> object:
"""通过复制已有 IA 模板创建新 InputAction 资产。
这是最可靠的方式——不依赖 Factory 类的 Python 绑定。
"""
asset_path = f"{IA_PACKAGE}/{name}"
if asset_exists(asset_path):
print(f"[InputSetup] 资产已存在,跳过创建: {asset_path}")
print(f"[InputSetup] 已存在,跳过: {asset_path}")
return unreal.load_asset(asset_path)
# 确保目录存在
unreal.EditorAssetLibrary.make_directory(IA_PACKAGE)
# 确保目标目录存在
if not unreal.EditorAssetLibrary.does_directory_exist(IA_PACKAGE):
unreal.EditorAssetLibrary.make_directory(IA_PACKAGE)
# 使用 AssetTools 创建
asset_tools = unreal.get_editor_subsystem(unreal.EditorAssetSubsystem)
if asset_tools:
# 尝试通过 EditorAssetSubsystem 创建
# 复制模板资产
new_asset = unreal.EditorAssetLibrary.duplicate_asset(TEMPLATE_IA, asset_path)
if new_asset:
print(f"[InputSetup] 复制创建成功: {asset_path} — {desc}")
return new_asset
print(f"[InputSetup] 复制创建失败: {name}")
return None
def create_input_action_by_factory(name: str, desc: str) -> object:
"""通过 AssetTools + Factory 创建 InputAction(备用方案)。"""
asset_path = f"{IA_PACKAGE}/{name}"
if asset_exists(asset_path):
return unreal.load_asset(asset_path)
try:
factory = unreal.InputActionFactory()
new_asset = asset_tools.create_asset(name, IA_PACKAGE, unreal.InputAction, factory)
if new_asset:
print(f"[InputSetup] 创建成功: {asset_path} — {description}")
return new_asset
except Exception:
print("[InputSetup] InputActionFactory 不可用")
return None
# 备用方案:使用 Python BPLib 或直接构造
print(f"[InputSetup] 尝试备用方案创建: {name}")
factory = unreal.InputActionFactory()
new_asset = unreal.AssetToolsHelpers.get_asset_tools().create_asset(
name, IA_PACKAGE[5:], # 去掉 /Game/ 前缀
asset_tools = unreal.AssetToolsHelpers.get_asset_tools()
if not asset_tools:
return None
# create_asset 的 package_path 需要去掉 /Game/ 前缀
new_asset = asset_tools.create_asset(
name,
IA_PACKAGE[5:], # "Input/Actions"
unreal.InputAction,
factory
)
if new_asset:
print(f"[InputSetup] 备用方案创建成功: {asset_path}")
return new_asset
print(f"[InputSetup] Factory 创建成功: {asset_path} — {desc}")
return new_asset
print(f"[InputSetup] ERROR: 无法创建 {name}")
def create_input_action(name: str, desc: str) -> object:
"""创建 InputAction 资产,依次尝试多种方法。"""
asset_path = f"{IA_PACKAGE}/{name}"
# 已存在则直接加载
if asset_exists(asset_path):
print(f"[InputSetup] 已存在: {asset_path}")
return unreal.load_asset(asset_path)
print(f"[InputSetup] 尝试创建: {name} — {desc}")
# 方法1: 复制已有模板(最可靠)
result = create_input_action_by_duplicate(name, desc)
if result:
return result
# 方法2: Factory 创建
result = create_input_action_by_factory(name, desc)
if result:
return result
print(f"[InputSetup] ERROR: 所有创建方法均失败: {name}")
return None
def add_key_mapping_to_imc(imc: unreal.InputMappingContext, ia: unreal.InputAction, key: unreal.Key):
"""向 IMC 添加按键映射"""
mapping = unreal.InputMapping()
mapping.set_editor_property("action", ia)
def try_update_imc(created_ias: dict) -> bool:
"""尝试更新 IMC_Default 添加按键映射。
key_mapping = unreal.KeyMapping()
key_mapping.set_editor_property("key", key)
key_mapping.set_editor_property("scale", 1.0)
# 设置触发器
# key_mapping 的 triggers 由 IMC 内部管理
# 使用 IMC 的 API 添加映射
try:
# UE5.7: 使用 mappings 属性
existing_mappings = imc.get_editor_property("mappings")
new_mapping = unreal.InputMapping()
new_mapping.set_editor_property("action", ia)
km = unreal.KeyMapping()
km.set_editor_property("key", key)
km.set_editor_property("scale", 1.0)
# 设置 triggers
triggers = [unreal.InputTriggerDown()]
km.set_editor_property("triggers", triggers)
new_mapping.set_editor_property("key_mappings", [km])
existing_mappings.append(new_mapping)
imc.set_editor_property("mappings", existing_mappings)
return True
except Exception as e:
print(f"[InputSetup] 添加映射失败: {e}")
return False
def main():
print("=" * 50)
print("[InputSetup] v0.1 角色控制 Input 资产创建脚本")
print(f"[InputSetup] 项目: {PROJECT_ROOT}")
print("=" * 50)
# ---- Step 1: 创建 InputAction 资产 ----
print("\n[InputSetup] Step 1: 创建 InputAction 资产")
created_ias = {}
for name, config in NEW_INPUT_ACTIONS.items():
ia = create_input_action(name, config["description"])
if ia:
created_ias[name] = ia
else:
print(f"[InputSetup] WARNING: {name} 创建失败,后续 IMC 映射将跳过此 IA")
if not created_ias:
print("[InputSetup] ERROR: 没有成功创建任何 IA,终止")
return False
# ---- Step 2: 保存新创建的 IA 资产 ----
print("\n[InputSetup] Step 2: 保存新创建的资产")
for name, ia in created_ias.items():
asset_path = f"{IA_PACKAGE}/{name}"
unreal.EditorAssetLibrary.save_asset(asset_path)
print(f"[InputSetup] 已保存: {asset_path}")
# ---- Step 3: 更新 IMC_Default ----
print(f"\n[InputSetup] Step 3: 更新 IMC: {IMC_PATH}")
返回 True 表示成功,False 表示需要手动操作。
由于 UE5 Python API 对 Enhanced Input 的暴露有限,
此功能可能失败——失败时提供手动操作指引。
"""
print(f"[InputSetup] 加载 IMC: {IMC_PATH}")
if not asset_exists(IMC_PATH):
print(f"[InputSetup] ERROR: IMC 不存在: {IMC_PATH}")
print(f"[InputSetup] ERROR: IMC 不存在: {IMC_PATH}")
return False
imc = unreal.load_asset(IMC_PATH)
if not imc:
print(f"[InputSetup] ERROR: 无法加载 IMC: {IMC_PATH}")
print(f"[InputSetup] ERROR: 无法加载 IMC")
return False
# 读取当前映射数量
try:
current_mappings = imc.get_editor_property("mappings")
print(f"[InputSetup] 当前 IMC 映射数: {len(current_mappings)}")
print(f"[InputSetup] 当前映射数: {len(current_mappings)}")
except Exception as e:
print(f"[InputSetup] WARNING: 无法读取映射: {e}")
current_mappings = []
print(f"[InputSetup] 无法读取 mappings 属性: {e}")
return False
# 添加新映射
for name, config in NEW_INPUT_ACTIONS.items():
if name not in created_ias:
# 解析所有需要的 Key
key_map = {}
for ia_info in NEW_INPUT_ACTIONS:
if ia_info["name"] not in created_ias:
continue
key = resolve_key(ia_info["key_name"])
if key is None:
print(f"[InputSetup] 无法解析按键: {ia_info['key_name']},跳过 IMC 自动更新")
return False
key_map[ia_info["name"]] = key
# 尝试添加映射
success_count = 0
for ia_info in NEW_INPUT_ACTIONS:
name = ia_info["name"]
if name not in created_ias or name not in key_map:
continue
ia = created_ias[name]
key = config["key"]
success = add_key_mapping_to_imc(imc, ia, key)
if success:
print(f"[InputSetup] 已添加映射: {name} → {key}")
key = key_map[name]
try:
new_mapping = unreal.InputMapping()
new_mapping.set_editor_property("action", ia)
km = unreal.KeyMapping()
km.set_editor_property("key", key)
# 尝试设置触发器(可能不支持)
try:
km.set_editor_property("triggers", [unreal.InputTriggerDown()])
except Exception:
pass # 触发器使用默认值
new_mapping.set_editor_property("key_mappings", [km])
current_mappings.append(new_mapping)
imc.set_editor_property("mappings", current_mappings)
success_count += 1
print(f"[InputSetup] 映射已添加: {name} → {ia_info['key_name']}")
except Exception as e:
print(f"[InputSetup] 映射添加失败: {name} — {e}")
return False
if success_count == len(created_ias):
# 保存 IMC
unreal.EditorAssetLibrary.save_asset(IMC_PATH)
print(f"[InputSetup] IMC 已保存")
return True
return False
def main():
print("=" * 60)
print("[InputSetup] v0.1 角色控制 Input 资产创建脚本")
print("=" * 60)
# ---- Step 1: 创建 InputAction 资产 ----
print("\n[InputSetup] Step 1/4: 创建 InputAction 资产")
print(f"[InputSetup] 模板: {TEMPLATE_IA}")
if not asset_exists(TEMPLATE_IA):
print(f"[InputSetup] ERROR: 模板 IA 不存在: {TEMPLATE_IA}")
print("[InputSetup] 可用 IA 资产:")
# 尝试列出 Actions 目录下的资产作为替代
try:
assets = unreal.EditorAssetLibrary.list_assets(IA_PACKAGE)
for a in assets:
print(f"[InputSetup] {a}")
except Exception:
pass
return False
created_ias = {}
for ia_info in NEW_INPUT_ACTIONS:
ia = create_input_action(ia_info["name"], ia_info["desc"])
if ia:
created_ias[ia_info["name"]] = ia
else:
print(f"[InputSetup] WARNING: 映射添加失败: {name}")
print(f"[InputSetup] WARNING: {ia_info['name']} 创建失败")
# ---- Step 4: 保存 IMC ----
print("\n[InputSetup] Step 4: 保存 IMC")
unreal.EditorAssetLibrary.save_asset(IMC_PATH)
print(f"[InputSetup] 已保存: {IMC_PATH}")
if not created_ias:
print("[InputSetup] ERROR: 没有成功创建任何 IA")
return False
# ---- Step 5: 验证 ----
print("\n[InputSetup] Step 5: 验证")
try:
updated_mappings = imc.get_editor_property("mappings")
print(f"[InputSetup] 更新后 IMC 映射数: {len(updated_mappings)}")
except:
pass
# 验证 IA 资产
for name in NEW_INPUT_ACTIONS:
# ---- Step 2: 保存新创建的 IA 资产 ----
print(f"\n[InputSetup] Step 2/4: 保存资产 ({len(created_ias)} 个)")
for name in created_ias:
asset_path = f"{IA_PACKAGE}/{name}"
exists = asset_exists(asset_path)
print(f"[InputSetup] {name}: {'✅ 存在' if exists else '❌ 不存在'}")
unreal.EditorAssetLibrary.save_asset(asset_path)
print(f"[InputSetup] 已保存: {asset_path}")
print("\n" + "=" * 50)
print("[InputSetup] 完成!请在 UE5 编辑器中验证。")
print("[InputSetup] 注意:需要在 BP_ChildHoodCharacter 中将新增 IA 属性指向对应资产")
print("=" * 50)
# ---- Step 3: 尝试更新 IMC ----
print(f"\n[InputSetup] Step 3/4: 更新 IMC_Default")
imc_ok = False
try:
imc_ok = try_update_imc(created_ias)
except Exception as e:
print(f"[InputSetup] IMC 更新异常: {e}")
traceback.print_exc()
if not imc_ok:
print("[InputSetup] ⚠️ IMC 自动更新未成功,需要手动操作(见下方指引)")
# ---- Step 4: 验证 ----
print(f"\n[InputSetup] Step 4/4: 验证")
all_ok = True
for ia_info in NEW_INPUT_ACTIONS:
asset_path = f"{IA_PACKAGE}/{ia_info['name']}"
exists = asset_exists(asset_path)
status = "✅" if exists else "❌"
print(f"[InputSetup] {status} {ia_info['name']}")
if not exists:
all_ok = False
# ---- 总结 ----
print("\n" + "=" * 60)
if imc_ok:
print("[InputSetup] ✅ 全部完成(IA 创建 + IMC 更新)")
print("[InputSetup] ⚠️ 仍需手动:BP_ChildHoodCharacter → Details → Input → 绑定 IA 属性")
else:
print("[InputSetup] ⚠️ IA 资产已创建,IMC 需手动配置")
print()
print("[InputSetup] === 手动操作指引 ===")
print()
print("1. 打开 IMC_Default (Content/Input/IMC_Default)")
print(" 添加 3 个映射:")
for ia_info in NEW_INPUT_ACTIONS:
print(f" - {ia_info['key_name']} → {ia_info['name']}")
print()
print("2. 打开 BP_ChildHoodCharacter (Content/BP/Character/)")
print(" Details → Input → 设置以下属性:")
print(" - Interact Action → IA_Interact")
print(" - Cancel Action → IA_Cancel")
print(" - Switch Interact Action → IA_SwitchInteract")
print()
print("=" * 60)
return True