diff --git a/Content/BP/Basic/BP_ChildHoodPlayerController.uasset b/Content/BP/Basic/BP_ChildHoodPlayerController.uasset index 089eb24..e668d48 100644 --- a/Content/BP/Basic/BP_ChildHoodPlayerController.uasset +++ b/Content/BP/Basic/BP_ChildHoodPlayerController.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8aa209924313a3476a030e81479a993ad1c7170b12c3797f0ac2dcd5c273a58e -size 20473 +oid sha256:a0c7966623a6ef2ecef5d7d97f08731457ed7092c909ab0441618b5be0aabd97 +size 21323 diff --git a/Content/BP/Character/BP_ChildHoodCharacter.uasset b/Content/BP/Character/BP_ChildHoodCharacter.uasset index c803635..cc03e2d 100644 --- a/Content/BP/Character/BP_ChildHoodCharacter.uasset +++ b/Content/BP/Character/BP_ChildHoodCharacter.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ab30c1de6a469952710d2728899acbe1df72cb6c5f239d0b0352d5ade60d3423 -size 35388 +oid sha256:0b60ed9418b64d0b9137910d84c4c8e1c3ca6ef9ef7616827e87d61ef59219d3 +size 37182 diff --git a/Source/ChildHood/ChildHoodCharacter.cpp b/Source/ChildHood/ChildHoodCharacter.cpp index 60e34b8..f3a1383 100644 --- a/Source/ChildHood/ChildHoodCharacter.cpp +++ b/Source/ChildHood/ChildHoodCharacter.cpp @@ -16,55 +16,72 @@ AChildHoodCharacter::AChildHoodCharacter() { // Set size for collision capsule GetCapsuleComponent()->InitCapsuleSize(42.f, 96.0f); - + // Don't rotate when the controller rotates. Let that just affect the camera. bUseControllerRotationPitch = false; bUseControllerRotationYaw = false; bUseControllerRotationRoll = false; - // Configure character movement - GetCharacterMovement()->bOrientRotationToMovement = true; - GetCharacterMovement()->RotationRate = FRotator(0.0f, 500.0f, 0.0f); - - // Note: For faster iteration times these variables, and many more, can be tweaked in the Character Blueprint - // instead of recompiling to adjust them - GetCharacterMovement()->JumpZVelocity = 500.f; - GetCharacterMovement()->AirControl = 0.35f; - GetCharacterMovement()->MaxWalkSpeed = 500.f; - GetCharacterMovement()->MinAnalogWalkSpeed = 20.f; - GetCharacterMovement()->BrakingDecelerationWalking = 2000.f; - GetCharacterMovement()->BrakingDecelerationFalling = 1500.0f; + // Configure character movement — v0.1 player controller tech design parameters + auto* CMC = GetCharacterMovement(); + CMC->bOrientRotationToMovement = true; // 角色朝移动方向旋转 + CMC->RotationRate = FRotator(0.0f, 720.0f, 0.0f); // 转向速度 720°/s(远快于鼠标 150°/s) + CMC->MaxWalkSpeed = 450.0f; // 跑步速度 4.5 m/s + CMC->MaxAcceleration = 2250.0f; // 起步加速 0.2s (450/0.2) + CMC->BrakingDecelerationWalking = 1500.0f; // 停止减速 0.3s (450/0.3) + CMC->BrakingDecelerationFalling = 1500.0f; + CMC->MinAnalogWalkSpeed = 20.f; + // WalkableFloorAngle 默认 45°,无需修改 + // v0.1 不跳跃,不设置 JumpZVelocity / AirControl // Create a camera boom (pulls in towards the player if there is a collision) CameraBoom = CreateDefaultSubobject(TEXT("CameraBoom")); CameraBoom->SetupAttachment(RootComponent); - CameraBoom->TargetArmLength = 400.0f; + CameraBoom->TargetArmLength = 350.0f; // 摄像机距离 3.5m + CameraBoom->SocketOffset = FVector(0.f, 0.f, 80.f); // 摄像机高度 0.8m(头部上方) CameraBoom->bUsePawnControlRotation = true; + // 摄像机软跟随 + CameraBoom->bEnableCameraLag = true; // 启用位置延迟跟随 + CameraBoom->CameraLagSpeed = 6.0f; // 跟随速度(可调 2-8) + CameraBoom->bEnableCameraRotationLag = false; // v0.1 不启用旋转延迟 + + // 摄像机碰撞处理 + CameraBoom->bDoCollisionTest = true; // 启用碰撞检测 + CameraBoom->ProbeSize = 12.0f; // 碰撞探针半径 + CameraBoom->ProbeChannel = ECC_Camera; // 使用 Camera 碰撞通道 + // Create a follow camera FollowCamera = CreateDefaultSubobject(TEXT("FollowCamera")); FollowCamera->SetupAttachment(CameraBoom, USpringArmComponent::SocketName); FollowCamera->bUsePawnControlRotation = false; - - // Note: The skeletal mesh and anim blueprint references on the Mesh component (inherited from Character) - // are set in the derived blueprint asset named ThirdPersonCharacter (to avoid direct content references in C++) } void AChildHoodCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent) { // Set up action bindings if (UEnhancedInputComponent* EnhancedInputComponent = Cast(PlayerInputComponent)) { - - // Jumping - EnhancedInputComponent->BindAction(JumpAction, ETriggerEvent::Started, this, &ACharacter::Jump); - EnhancedInputComponent->BindAction(JumpAction, ETriggerEvent::Completed, this, &ACharacter::StopJumping); // Moving EnhancedInputComponent->BindAction(MoveAction, ETriggerEvent::Triggered, this, &AChildHoodCharacter::Move); + + // Looking (gamepad + mouse) + EnhancedInputComponent->BindAction(LookAction, ETriggerEvent::Triggered, this, &AChildHoodCharacter::Look); EnhancedInputComponent->BindAction(MouseLookAction, ETriggerEvent::Triggered, this, &AChildHoodCharacter::Look); - // Looking - EnhancedInputComponent->BindAction(LookAction, ETriggerEvent::Triggered, this, &AChildHoodCharacter::Look); + // Interaction IAs — bind to placeholder callbacks; interaction system will hook into these + if (InteractAction) + { + EnhancedInputComponent->BindAction(InteractAction, ETriggerEvent::Started, this, &AChildHoodCharacter::OnInteract); + } + if (CancelAction) + { + EnhancedInputComponent->BindAction(CancelAction, ETriggerEvent::Started, this, &AChildHoodCharacter::OnCancel); + } + if (SwitchInteractAction) + { + EnhancedInputComponent->BindAction(SwitchInteractAction, ETriggerEvent::Started, this, &AChildHoodCharacter::OnSwitchInteract); + } } else { @@ -90,8 +107,30 @@ void AChildHoodCharacter::Look(const FInputActionValue& Value) DoLook(LookAxisVector.X, LookAxisVector.Y); } +void AChildHoodCharacter::OnInteract(const FInputActionValue& Value) +{ + // v0.1 placeholder — interaction system will provide implementation + // Interaction system hooks in via blueprint or future C++ delegate +} + +void AChildHoodCharacter::OnCancel(const FInputActionValue& Value) +{ + // v0.1 placeholder — interaction system will provide implementation +} + +void AChildHoodCharacter::OnSwitchInteract(const FInputActionValue& Value) +{ + // v0.1 placeholder — interaction system will provide implementation +} + void AChildHoodCharacter::DoMove(float Right, float Forward) { + // P1: 交互中忽略移动输入 + if (bIsInteracting) + { + return; + } + if (GetController() != nullptr) { // find out which way is forward @@ -101,10 +140,10 @@ void AChildHoodCharacter::DoMove(float Right, float Forward) // get forward vector const FVector ForwardDirection = FRotationMatrix(YawRotation).GetUnitAxis(EAxis::X); - // get right vector + // get right vector const FVector RightDirection = FRotationMatrix(YawRotation).GetUnitAxis(EAxis::Y); - // add movement + // add movement AddMovementInput(ForwardDirection, Forward); AddMovementInput(RightDirection, Right); } @@ -120,14 +159,18 @@ void AChildHoodCharacter::DoLook(float Yaw, float Pitch) } } -void AChildHoodCharacter::DoJumpStart() +void AChildHoodCharacter::SetInteracting(bool bValue) { - // signal the character to jump - Jump(); + bIsInteracting = bValue; } -void AChildHoodCharacter::DoJumpEnd() +bool AChildHoodCharacter::IsInteracting() const { - // signal the character to stop jumping - StopJumping(); + return bIsInteracting; +} + +bool AChildHoodCharacter::IsRunning() const +{ + return GetVelocity().SizeSquared() > 100.f // 速度 > 10 cm/s 阈值 + && GetCharacterMovement()->IsMovingOnGround(); } diff --git a/Source/ChildHood/ChildHoodCharacter.h b/Source/ChildHood/ChildHoodCharacter.h index 33f7414..59f53e2 100644 --- a/Source/ChildHood/ChildHoodCharacter.h +++ b/Source/ChildHood/ChildHoodCharacter.h @@ -30,12 +30,8 @@ class AChildHoodCharacter : public ACharacter /** Follow camera */ UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="Components", meta = (AllowPrivateAccess = "true")) UCameraComponent* FollowCamera; - -protected: - /** Jump Input Action */ - UPROPERTY(EditAnywhere, Category="Input") - UInputAction* JumpAction; +protected: /** Move Input Action */ UPROPERTY(EditAnywhere, Category="Input") @@ -49,10 +45,25 @@ protected: UPROPERTY(EditAnywhere, Category="Input") UInputAction* MouseLookAction; + /** Interact Input Action (E key) — delegates to interaction system */ + UPROPERTY(EditAnywhere, Category="Input") + UInputAction* InteractAction; + + /** Cancel Input Action (ESC key) — delegates to interaction system */ + UPROPERTY(EditAnywhere, Category="Input") + UInputAction* CancelAction; + + /** Switch Interact Target Input Action (Tab / Mouse Wheel) — delegates to interaction system */ + UPROPERTY(EditAnywhere, Category="Input") + UInputAction* SwitchInteractAction; + + /** Whether the character is currently in an interaction */ + bool bIsInteracting = false; + public: /** Constructor */ - AChildHoodCharacter(); + AChildHoodCharacter(); protected: @@ -67,6 +78,15 @@ protected: /** Called for looking input */ void Look(const FInputActionValue& Value); + /** Called for interact input (E) — placeholder, delegates to interaction system */ + void OnInteract(const FInputActionValue& Value); + + /** Called for cancel input (ESC) — placeholder, delegates to interaction system */ + void OnCancel(const FInputActionValue& Value); + + /** Called for switch interact target input (Tab/Wheel) — placeholder, delegates to interaction system */ + void OnSwitchInteract(const FInputActionValue& Value); + public: /** Handles move inputs from either controls or UI interfaces */ @@ -77,13 +97,17 @@ public: UFUNCTION(BlueprintCallable, Category="Input") virtual void DoLook(float Yaw, float Pitch); - /** Handles jump pressed inputs from either controls or UI interfaces */ - UFUNCTION(BlueprintCallable, Category="Input") - virtual void DoJumpStart(); + /** Set interaction state — called by interaction system */ + UFUNCTION(BlueprintCallable, Category="Interaction") + void SetInteracting(bool bValue); - /** Handles jump pressed inputs from either controls or UI interfaces */ - UFUNCTION(BlueprintCallable, Category="Input") - virtual void DoJumpEnd(); + /** Query: is the character currently interacting? */ + UFUNCTION(BlueprintCallable, BlueprintPure, Category="Interaction") + bool IsInteracting() const; + + /** Query: is the character currently running (moving on ground)? */ + UFUNCTION(BlueprintCallable, BlueprintPure, Category="Interaction") + bool IsRunning() const; public: @@ -93,4 +117,3 @@ public: /** Returns FollowCamera subobject **/ FORCEINLINE class UCameraComponent* GetFollowCamera() const { return FollowCamera; } }; - diff --git a/Source/ChildHood/ChildHoodPlayerController.cpp b/Source/ChildHood/ChildHoodPlayerController.cpp index cadaba8..83d008d 100644 --- a/Source/ChildHood/ChildHoodPlayerController.cpp +++ b/Source/ChildHood/ChildHoodPlayerController.cpp @@ -13,6 +13,19 @@ void AChildHoodPlayerController::BeginPlay() { Super::BeginPlay(); + // C3: 设置摄像机俯仰角限制(垂直旋转 +45°/-30°) + if (PlayerCameraManager) + { + PlayerCameraManager->ViewPitchMin = -30.0f; // 向下看最多 30° + PlayerCameraManager->ViewPitchMax = 45.0f; // 向上看最多 45° + } + + // C1: 初始俯角 -15°(摄像机默认略向下看向角色) + if (GetControlRotation().Pitch == 0.f) + { + SetControlRotation(FRotator(-15.0f, GetControlRotation().Yaw, GetControlRotation().Roll)); + } + // only spawn touch controls on local player controllers if (ShouldUseTouchControls() && IsLocalPlayerController()) { diff --git a/tools/input-setup/setup_input.bat b/tools/input-setup/setup_input.bat new file mode 100644 index 0000000..ee8219b --- /dev/null +++ b/tools/input-setup/setup_input.bat @@ -0,0 +1,132 @@ +@echo off +setlocal enabledelayedexpansion + +REM === ChildHood Input Setup - Create v0.1 InputAction assets + update IMC === +REM +REM Usage: +REM setup_input.bat (default, editor quits after done) +REM setup_input.bat --no-quit (keep editor open) + +set "SCRIPT_DIR=%~dp0" +set "PROJECT_DIR=%SCRIPT_DIR%..\.." +for %%i in ("%PROJECT_DIR%\.") do set "PROJECT_ROOT=%%~fi" +set "UPROJECT=%PROJECT_ROOT%\ChildHood.uproject" + +REM ---- Defaults ---- +set "NO_QUIT=0" +set "DRY_RUN=0" + +REM ---- Parse Arguments ---- +:parse_args +if "%~1"=="" goto done_args +if /i "%~1"=="--no-quit" ( + set "NO_QUIT=1" + shift + goto parse_args +) +if /i "%~1"=="--dry-run" ( + set "DRY_RUN=1" + shift + goto parse_args +) +if /i "%~1"=="--help" goto show_help +if /i "%~1"=="-h" goto show_help +echo [WARN] Unknown argument: %~1 +shift +goto parse_args +:done_args + +REM ---- Find UE5 Editor ---- +set "UE_EDITOR=" +if defined UE5_ROOT ( + for %%b in (UnrealEditor.exe UE5Editor.exe) do ( + if exist "%UE5_ROOT%\Engine\Binaries\Win64\%%b" ( + set "UE_EDITOR=%UE5_ROOT%\Engine\Binaries\Win64\%%b" + ) + ) +) + +if "%UE_EDITOR%"=="" ( + for %%p in ( + "C:\Program Files\Epic Games\UE_5.7" + "C:\Program Files (x86)\Epic Games\UE_5.7" + "D:\Program Files\Epic Games\UE_5.7" + "E:\Program Files\Epic Games\UE_5.7" + ) do ( + if not defined UE_EDITOR ( + for %%b in (UnrealEditor.exe UE5Editor.exe) do ( + if exist "%%~p\Engine\Binaries\Win64\%%b" ( + set "UE_EDITOR=%%~p\Engine\Binaries\Win64\%%b" + ) + ) + ) + ) +) + +if "%UE_EDITOR%"=="" ( + echo. + echo [ERROR] UE5 Editor not found! + echo Set UE5_ROOT, e.g.: set UE5_ROOT=C:\Program Files\Epic Games\UE_5.7 + echo. + exit /b 1 +) + +REM ---- Path conversion ---- +set "SCRIPT_PATH=%SCRIPT_DIR%setup_input_assets.py" +set "SCRIPT_PATH=!SCRIPT_PATH:\=/!" +set "UPROJECT_FWD=!UPROJECT:\=/!" + +REM ---- Build command line ---- +set "CMD_ARGS=-unattended -NoSound -NullRHI -ExecutePythonScript=!SCRIPT_PATH!" + +REM ---- Pass config via env vars ---- +set "INPUT_PROJECT_ROOT=!PROJECT_ROOT!" +if "!NO_QUIT!"=="1" set "INPUT_NO_QUIT=1" + +REM ---- Print info ---- +echo. +echo ============================================ +echo ChildHood Input Setup - v0.1 +echo ============================================ +echo UE5: !UE_EDITOR! +echo Project: !UPROJECT_FWD! +echo Script: !SCRIPT_PATH! +echo ============================================ +echo. + +REM ---- Dry Run ---- +if "%DRY_RUN%"=="1" ( + echo [DRY RUN] Command: + echo. + echo "!UE_EDITOR!" "!UPROJECT_FWD!" !CMD_ARGS! + echo. + exit /b 0 +) + +REM ---- Execute ---- +echo [InputSetup] Launching UE5 Editor ... +echo. + +"!UE_EDITOR!" "!UPROJECT_FWD!" !CMD_ARGS! + +set "EXIT_CODE=!ERRORLEVEL!" +echo. +if "!EXIT_CODE!"=="0" ( + echo [InputSetup] Done. Editor exited. +) else ( + echo [InputSetup] Editor exit code: !EXIT_CODE! +) +exit /b !EXIT_CODE! + +:show_help +echo. +echo ChildHood Input Setup - v0.1 +echo. +echo Usage: setup_input.bat [options] +echo. +echo Options: +echo --no-quit Keep editor open after done +echo --dry-run Print command without executing +echo --help, -h Show help +echo. +exit /b 0 diff --git a/tools/input-setup/setup_input_assets.py b/tools/input-setup/setup_input_assets.py new file mode 100644 index 0000000..69982a5 --- /dev/null +++ b/tools/input-setup/setup_input_assets.py @@ -0,0 +1,225 @@ +"""UE5 编辑器 Python 脚本:创建 v0.1 角色控制所需的 InputAction 资产并更新 IMC。 + +由 setup_input.bat 通过 UE5 的 -ExecutePythonScript 参数调用。 +在 UE5 编辑器 Python 环境中运行。 + +功能: + 1. 创建 3 个 InputAction 资产:IA_Interact, IA_Cancel, IA_SwitchInteract + 2. 更新 IMC_Default:添加 E / ESC / Tab 按键映射 + 3. 保存所有修改的资产 + +环境变量(由 setup_input.bat 设置): + INPUT_PROJECT_ROOT — 项目根目录绝对路径 +""" + +import os +import sys +from pathlib import Path + +# ---- 检查 unreal 模块 ---- +try: + import unreal +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" + +# 新增 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()], + }, +} + + +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 资产""" + asset_path = f"{IA_PACKAGE}/{name}" + + if asset_exists(asset_path): + print(f"[InputSetup] 资产已存在,跳过创建: {asset_path}") + return unreal.load_asset(asset_path) + + # 确保目录存在 + unreal.EditorAssetLibrary.make_directory(IA_PACKAGE) + + # 使用 AssetTools 创建 + asset_tools = unreal.get_editor_subsystem(unreal.EditorAssetSubsystem) + if asset_tools: + # 尝试通过 EditorAssetSubsystem 创建 + 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 + + # 备用方案:使用 Python BPLib 或直接构造 + print(f"[InputSetup] 尝试备用方案创建: {name}") + factory = unreal.InputActionFactory() + new_asset = unreal.AssetToolsHelpers.get_asset_tools().create_asset( + name, IA_PACKAGE[5:], # 去掉 /Game/ 前缀 + unreal.InputAction, + factory + ) + if new_asset: + print(f"[InputSetup] 备用方案创建成功: {asset_path}") + return new_asset + + 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) + + 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}") + + 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: {IMC_PATH}") + return False + + # 读取当前映射数量 + try: + current_mappings = imc.get_editor_property("mappings") + print(f"[InputSetup] 当前 IMC 映射数: {len(current_mappings)}") + except Exception as e: + print(f"[InputSetup] WARNING: 无法读取映射: {e}") + current_mappings = [] + + # 添加新映射 + for name, config in NEW_INPUT_ACTIONS.items(): + if name not in created_ias: + continue + ia = created_ias[name] + key = config["key"] + success = add_key_mapping_to_imc(imc, ia, key) + if success: + print(f"[InputSetup] 已添加映射: {name} → {key}") + else: + print(f"[InputSetup] WARNING: 映射添加失败: {name}") + + # ---- Step 4: 保存 IMC ---- + print("\n[InputSetup] Step 4: 保存 IMC") + unreal.EditorAssetLibrary.save_asset(IMC_PATH) + print(f"[InputSetup] 已保存: {IMC_PATH}") + + # ---- 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: + asset_path = f"{IA_PACKAGE}/{name}" + exists = asset_exists(asset_path) + print(f"[InputSetup] {name}: {'✅ 存在' if exists else '❌ 不存在'}") + + print("\n" + "=" * 50) + print("[InputSetup] 完成!请在 UE5 编辑器中验证。") + print("[InputSetup] 注意:需要在 BP_ChildHoodCharacter 中将新增 IA 属性指向对应资产") + print("=" * 50) + + return True + + +if __name__ == "__main__": + success = main() + if not success: + sys.exit(1)