"""UE5 编辑器 Python 脚本:创建 v0.1 角色控制所需的 InputAction 资产并更新 IMC。 使用方式: 方式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 按键映射 3. 保存所有修改的资产 """ import os import sys import traceback # ---- 检查 unreal 模块 ---- try: import unreal except ImportError: print("[InputSetup] FATAL: unreal 模块不可用,必须在 UE5 编辑器环境中运行") sys.exit(1) # ---- 资产路径配置 ---- IA_PACKAGE = "/Game/Input/Actions" IMC_PATH = "/Game/Input/IMC_Default" TEMPLATE_IA = f"{IA_PACKAGE}/IA_Look" # 用已存在的 IA 作为复制模板 # 新增 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: """检查资产是否已存在""" return unreal.EditorAssetLibrary.does_asset_exist(asset_path) 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}") return unreal.load_asset(asset_path) # 确保目标目录存在 if not unreal.EditorAssetLibrary.does_directory_exist(IA_PACKAGE): unreal.EditorAssetLibrary.make_directory(IA_PACKAGE) # 复制模板资产 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() except Exception: print("[InputSetup] InputActionFactory 不可用") return None 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] Factory 创建成功: {asset_path} — {desc}") return new_asset 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 try_update_imc(created_ias: dict) -> bool: """尝试更新 IMC_Default 添加按键映射。 返回 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}") return False imc = unreal.load_asset(IMC_PATH) if not imc: print(f"[InputSetup] ERROR: 无法加载 IMC") return False # 读取当前映射数量 try: current_mappings = imc.get_editor_property("mappings") print(f"[InputSetup] 当前映射数: {len(current_mappings)}") except Exception as e: print(f"[InputSetup] 无法读取 mappings 属性: {e}") return False # 解析所有需要的 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 = 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: {ia_info['name']} 创建失败") if not created_ias: print("[InputSetup] ERROR: 没有成功创建任何 IA") return False # ---- Step 2: 保存新创建的 IA 资产 ---- print(f"\n[InputSetup] Step 2/4: 保存资产 ({len(created_ias)} 个)") for name in created_ias: asset_path = f"{IA_PACKAGE}/{name}" unreal.EditorAssetLibrary.save_asset(asset_path) print(f"[InputSetup] 已保存: {asset_path}") # ---- 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 if __name__ == "__main__": success = main() if not success: sys.exit(1)