Merge pull request 'feature/player-controller-v01' (#28) from feature/player-controller-v01 into main
Reviewed-on: http://micecola.top:8418/starmen1265/ChildHood/pulls/28
This commit was merged in pull request #28.
This commit is contained in:
Binary file not shown.
Binary file not shown.
@@ -16,55 +16,72 @@ AChildHoodCharacter::AChildHoodCharacter()
|
|||||||
{
|
{
|
||||||
// Set size for collision capsule
|
// Set size for collision capsule
|
||||||
GetCapsuleComponent()->InitCapsuleSize(42.f, 96.0f);
|
GetCapsuleComponent()->InitCapsuleSize(42.f, 96.0f);
|
||||||
|
|
||||||
// Don't rotate when the controller rotates. Let that just affect the camera.
|
// Don't rotate when the controller rotates. Let that just affect the camera.
|
||||||
bUseControllerRotationPitch = false;
|
bUseControllerRotationPitch = false;
|
||||||
bUseControllerRotationYaw = false;
|
bUseControllerRotationYaw = false;
|
||||||
bUseControllerRotationRoll = false;
|
bUseControllerRotationRoll = false;
|
||||||
|
|
||||||
// Configure character movement
|
// Configure character movement — v0.1 player controller tech design parameters
|
||||||
GetCharacterMovement()->bOrientRotationToMovement = true;
|
auto* CMC = GetCharacterMovement();
|
||||||
GetCharacterMovement()->RotationRate = FRotator(0.0f, 500.0f, 0.0f);
|
CMC->bOrientRotationToMovement = true; // 角色朝移动方向旋转
|
||||||
|
CMC->RotationRate = FRotator(0.0f, 720.0f, 0.0f); // 转向速度 720°/s(远快于鼠标 150°/s)
|
||||||
// Note: For faster iteration times these variables, and many more, can be tweaked in the Character Blueprint
|
CMC->MaxWalkSpeed = 450.0f; // 跑步速度 4.5 m/s
|
||||||
// instead of recompiling to adjust them
|
CMC->MaxAcceleration = 2250.0f; // 起步加速 0.2s (450/0.2)
|
||||||
GetCharacterMovement()->JumpZVelocity = 500.f;
|
CMC->BrakingDecelerationWalking = 1500.0f; // 停止减速 0.3s (450/0.3)
|
||||||
GetCharacterMovement()->AirControl = 0.35f;
|
CMC->BrakingDecelerationFalling = 1500.0f;
|
||||||
GetCharacterMovement()->MaxWalkSpeed = 500.f;
|
CMC->MinAnalogWalkSpeed = 20.f;
|
||||||
GetCharacterMovement()->MinAnalogWalkSpeed = 20.f;
|
// WalkableFloorAngle 默认 45°,无需修改
|
||||||
GetCharacterMovement()->BrakingDecelerationWalking = 2000.f;
|
// v0.1 不跳跃,不设置 JumpZVelocity / AirControl
|
||||||
GetCharacterMovement()->BrakingDecelerationFalling = 1500.0f;
|
|
||||||
|
|
||||||
// Create a camera boom (pulls in towards the player if there is a collision)
|
// Create a camera boom (pulls in towards the player if there is a collision)
|
||||||
CameraBoom = CreateDefaultSubobject<USpringArmComponent>(TEXT("CameraBoom"));
|
CameraBoom = CreateDefaultSubobject<USpringArmComponent>(TEXT("CameraBoom"));
|
||||||
CameraBoom->SetupAttachment(RootComponent);
|
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->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
|
// Create a follow camera
|
||||||
FollowCamera = CreateDefaultSubobject<UCameraComponent>(TEXT("FollowCamera"));
|
FollowCamera = CreateDefaultSubobject<UCameraComponent>(TEXT("FollowCamera"));
|
||||||
FollowCamera->SetupAttachment(CameraBoom, USpringArmComponent::SocketName);
|
FollowCamera->SetupAttachment(CameraBoom, USpringArmComponent::SocketName);
|
||||||
FollowCamera->bUsePawnControlRotation = false;
|
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)
|
void AChildHoodCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
|
||||||
{
|
{
|
||||||
// Set up action bindings
|
// Set up action bindings
|
||||||
if (UEnhancedInputComponent* EnhancedInputComponent = Cast<UEnhancedInputComponent>(PlayerInputComponent)) {
|
if (UEnhancedInputComponent* EnhancedInputComponent = Cast<UEnhancedInputComponent>(PlayerInputComponent)) {
|
||||||
|
|
||||||
// Jumping
|
|
||||||
EnhancedInputComponent->BindAction(JumpAction, ETriggerEvent::Started, this, &ACharacter::Jump);
|
|
||||||
EnhancedInputComponent->BindAction(JumpAction, ETriggerEvent::Completed, this, &ACharacter::StopJumping);
|
|
||||||
|
|
||||||
// Moving
|
// Moving
|
||||||
EnhancedInputComponent->BindAction(MoveAction, ETriggerEvent::Triggered, this, &AChildHoodCharacter::Move);
|
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);
|
EnhancedInputComponent->BindAction(MouseLookAction, ETriggerEvent::Triggered, this, &AChildHoodCharacter::Look);
|
||||||
|
|
||||||
// Looking
|
// Interaction IAs — bind to placeholder callbacks; interaction system will hook into these
|
||||||
EnhancedInputComponent->BindAction(LookAction, ETriggerEvent::Triggered, this, &AChildHoodCharacter::Look);
|
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
|
else
|
||||||
{
|
{
|
||||||
@@ -90,8 +107,30 @@ void AChildHoodCharacter::Look(const FInputActionValue& Value)
|
|||||||
DoLook(LookAxisVector.X, LookAxisVector.Y);
|
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)
|
void AChildHoodCharacter::DoMove(float Right, float Forward)
|
||||||
{
|
{
|
||||||
|
// P1: 交互中忽略移动输入
|
||||||
|
if (bIsInteracting)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (GetController() != nullptr)
|
if (GetController() != nullptr)
|
||||||
{
|
{
|
||||||
// find out which way is forward
|
// find out which way is forward
|
||||||
@@ -101,10 +140,10 @@ void AChildHoodCharacter::DoMove(float Right, float Forward)
|
|||||||
// get forward vector
|
// get forward vector
|
||||||
const FVector ForwardDirection = FRotationMatrix(YawRotation).GetUnitAxis(EAxis::X);
|
const FVector ForwardDirection = FRotationMatrix(YawRotation).GetUnitAxis(EAxis::X);
|
||||||
|
|
||||||
// get right vector
|
// get right vector
|
||||||
const FVector RightDirection = FRotationMatrix(YawRotation).GetUnitAxis(EAxis::Y);
|
const FVector RightDirection = FRotationMatrix(YawRotation).GetUnitAxis(EAxis::Y);
|
||||||
|
|
||||||
// add movement
|
// add movement
|
||||||
AddMovementInput(ForwardDirection, Forward);
|
AddMovementInput(ForwardDirection, Forward);
|
||||||
AddMovementInput(RightDirection, Right);
|
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
|
bIsInteracting = bValue;
|
||||||
Jump();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void AChildHoodCharacter::DoJumpEnd()
|
bool AChildHoodCharacter::IsInteracting() const
|
||||||
{
|
{
|
||||||
// signal the character to stop jumping
|
return bIsInteracting;
|
||||||
StopJumping();
|
}
|
||||||
|
|
||||||
|
bool AChildHoodCharacter::IsRunning() const
|
||||||
|
{
|
||||||
|
return GetVelocity().SizeSquared() > 100.f // 速度 > 10 cm/s 阈值
|
||||||
|
&& GetCharacterMovement()->IsMovingOnGround();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,12 +30,8 @@ class AChildHoodCharacter : public ACharacter
|
|||||||
/** Follow camera */
|
/** Follow camera */
|
||||||
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="Components", meta = (AllowPrivateAccess = "true"))
|
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="Components", meta = (AllowPrivateAccess = "true"))
|
||||||
UCameraComponent* FollowCamera;
|
UCameraComponent* FollowCamera;
|
||||||
|
|
||||||
protected:
|
|
||||||
|
|
||||||
/** Jump Input Action */
|
protected:
|
||||||
UPROPERTY(EditAnywhere, Category="Input")
|
|
||||||
UInputAction* JumpAction;
|
|
||||||
|
|
||||||
/** Move Input Action */
|
/** Move Input Action */
|
||||||
UPROPERTY(EditAnywhere, Category="Input")
|
UPROPERTY(EditAnywhere, Category="Input")
|
||||||
@@ -49,10 +45,25 @@ protected:
|
|||||||
UPROPERTY(EditAnywhere, Category="Input")
|
UPROPERTY(EditAnywhere, Category="Input")
|
||||||
UInputAction* MouseLookAction;
|
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:
|
public:
|
||||||
|
|
||||||
/** Constructor */
|
/** Constructor */
|
||||||
AChildHoodCharacter();
|
AChildHoodCharacter();
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
|
||||||
@@ -67,6 +78,15 @@ protected:
|
|||||||
/** Called for looking input */
|
/** Called for looking input */
|
||||||
void Look(const FInputActionValue& Value);
|
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:
|
public:
|
||||||
|
|
||||||
/** Handles move inputs from either controls or UI interfaces */
|
/** Handles move inputs from either controls or UI interfaces */
|
||||||
@@ -77,13 +97,17 @@ public:
|
|||||||
UFUNCTION(BlueprintCallable, Category="Input")
|
UFUNCTION(BlueprintCallable, Category="Input")
|
||||||
virtual void DoLook(float Yaw, float Pitch);
|
virtual void DoLook(float Yaw, float Pitch);
|
||||||
|
|
||||||
/** Handles jump pressed inputs from either controls or UI interfaces */
|
/** Set interaction state — called by interaction system */
|
||||||
UFUNCTION(BlueprintCallable, Category="Input")
|
UFUNCTION(BlueprintCallable, Category="Interaction")
|
||||||
virtual void DoJumpStart();
|
void SetInteracting(bool bValue);
|
||||||
|
|
||||||
/** Handles jump pressed inputs from either controls or UI interfaces */
|
/** Query: is the character currently interacting? */
|
||||||
UFUNCTION(BlueprintCallable, Category="Input")
|
UFUNCTION(BlueprintCallable, BlueprintPure, Category="Interaction")
|
||||||
virtual void DoJumpEnd();
|
bool IsInteracting() const;
|
||||||
|
|
||||||
|
/** Query: is the character currently running (moving on ground)? */
|
||||||
|
UFUNCTION(BlueprintCallable, BlueprintPure, Category="Interaction")
|
||||||
|
bool IsRunning() const;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
@@ -93,4 +117,3 @@ public:
|
|||||||
/** Returns FollowCamera subobject **/
|
/** Returns FollowCamera subobject **/
|
||||||
FORCEINLINE class UCameraComponent* GetFollowCamera() const { return FollowCamera; }
|
FORCEINLINE class UCameraComponent* GetFollowCamera() const { return FollowCamera; }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -13,6 +13,19 @@ void AChildHoodPlayerController::BeginPlay()
|
|||||||
{
|
{
|
||||||
Super::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
|
// only spawn touch controls on local player controllers
|
||||||
if (ShouldUseTouchControls() && IsLocalPlayerController())
|
if (ShouldUseTouchControls() && IsLocalPlayerController())
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -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)
|
||||||
Reference in New Issue
Block a user