82fb409a9e
- reviewer记忆: 新增P-009审查通过记录 - setup_input_assets.py: 重写为多方案容错架构(模板复制+Factory备选) Task: P-009, P-011
331 lines
11 KiB
Python
331 lines
11 KiB
Python
"""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)
|