@@ -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