← Back to project technical overview

TransformCharacter Line-by-Line Code Walkthrough

This article explains every line of code in TransformCharacter.h and TransformCharacter.cpp, to help you understand the implementation of the form-change and flight-dash system.


1. TransformCharacter.h

1  // TransformCharacter.h - 角色形态变换系统
2  // 支持多形态切换、Niagara 特效、Enhanced Input 绑定

Explanation: File header comment stating this file is the header of the form-change system. The original design supports multi-form switching, Niagara effects and Enhanced Input (traditional BindKey is used in practice).

3  
4  #pragma once

Explanation: Preprocessor directive ensuring the header is included only once per compilation unit, avoiding redefinition.

5  
6  #include "CoreMinimal.h"

Explanation: Includes the UE core minimal header, providing reflection macros such as UCLASS, UFUNCTION, UPROPERTY and base types.

7  #include "GameFramework/Character.h"

Explanation: Includes the UE character base-class header. TransformBase inherits from ACharacter.

8  #include "TransformCharacter.generated.h"

Explanation: Includes the reflection-code header generated by UHT (Unreal Header Tool). It must come after all other includes.

9  
10 class UNiagaraSystem;

Explanation: Forward-declares the Niagara system class to avoid including it directly in the header and reduce compile dependencies.

11 class UInputAction;

Explanation: Forward-declares the Enhanced Input action class (retained as a property but not actually used in C++).

12 class UInputMappingContext;

Explanation: Forward-declares the Enhanced Input mapping context class (retained as a property but not actually used in C++).

13 class UMaterialParameterCollection;

Explanation: Forward-declares the material parameter collection class, used for the dissolve effect.

14 
15 /** 形态变换事件委托 */
16 DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnFormChanged, int32, OldFormIndex, int32, NewFormIndex);

Explanation: Declares a dynamic multicast delegate. FOnFormChanged takes two int32 parameters: OldFormIndex and NewFormIndex, used to broadcast events to Blueprints on form change.

17 
18 UCLASS()

Explanation: UE reflection macro. Marks that the class below must be processed by UHT and have reflection code generated.

19 class TEST_API TransformBase : public ACharacter

Explanation: Defines the TransformBase class, inheriting from ACharacter, and exports the module API (TEST_API is auto-generated from the module name).

20 {
21     GENERATED_BODY()

Explanation: GENERATED_BODY() is the reflection code insertion point generated by UHT, replacing the old GENERATED_UCLASS_BODY().

22 
23 public:
24     TransformBase();

Explanation: Declares the default constructor for initializing components and default values.

25 
26 protected:
27     virtual void BeginPlay() override;

Explanation: Overrides AActor::BeginPlay(), executing some initialization logic when the game starts (such as speed fallback and asset-reference repair).

28     virtual void PossessedBy(AController* NewController) override;

Explanation: Overrides APawn::PossessedBy(), called when the character is taken over by a controller. The current implementation is empty to preserve extensibility.

29 
30     /** 输入绑定:C++ 覆盖后也会调用蓝图中的 SetupPlayerInputComponent 实现 */
31     virtual void SetupPlayerInputComponent(UInputComponent* PlayerInputComponent) override;

Explanation: Overrides ACharacter::SetupPlayerInputComponent(), used to bind the T key and the right mouse button.

32 
33 public:
34     virtual void Tick(float DeltaTime) override;

Explanation: Overrides AActor::Tick(), called once per frame to handle the dissolve animation, flight movement, Niagara speed pass-through and other persistent logic.

35 
36     // ========== 形态系统 ==========

Explanation: Comment divider. Marks the form-system related properties below.

37 
38     /** 可用形态列表(Character 子类) */
39     UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Transform|Forms")
40     TArray<TSubclassOf<ACharacter>> Forms;

Explanation: Declares an array that is editable in the editor and read/write in Blueprints, storing multiple form classes. TSubclassOf guarantees that each element is a subclass of ACharacter.

41 
42     /** 当前形态索引 */
43     UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Transform|Forms")
44     int32 FormIndex = 0;

Explanation: Array index of the current form, default 0. VisibleAnywhere means it is visible but not editable in the property panel, read-only.

45 
46     /** 切换到下一个形态 */
47     UFUNCTION(BlueprintCallable, Category = "Transform")
48     void TransformToNextForm();

Explanation: Declares a BlueprintCallable function to switch to the next form. The current implementation calls OnTransformInput() directly.

49 
50     /** 切换到指定形态 */
51     UFUNCTION(BlueprintCallable, Category = "Transform")
52     void TransformToForm(int32 Index);

Explanation: Declares a BlueprintCallable function to switch form by the given index. The current implementation also calls OnTransformInput() directly.

53 
54     /** 获取当前形态类 */
55     UFUNCTION(BlueprintPure, Category = "Transform")
56     TSubclassOf<ACharacter> GetCurrentFormClass() const;

Explanation: Declares a pure function (no side effects) that returns the Character subclass for the current form. Not actually used.

57 
58     /** 形态变化事件 */
59     UPROPERTY(BlueprintAssignable, Category = "Transform")
60     FOnFormChanged OnFormChanged;

Explanation: Declares the form-change event delegate variable. Blueprints can bind this event to react to form switches.

61 
62     // ========== 特效 ==========

Explanation: Comment divider. Marks the VFX related properties below.

63 
64     /** 变身爆发 Niagara 系统 */
65     UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Transform|VFX")
66     TObjectPtr<UNiagaraSystem> TransformEffect;

Explanation: Declares a Niagara system reference for the transform-burst effect. Not used in C++; kept for Blueprint configuration.

67 
68     /** 变身特效持续时间 */
69     UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Transform|VFX")
70     float TransformEffectDuration = 1.5f;

Explanation: Transform effect duration, default 1.5 s. Not used in C++.

71 
72     // ========== 输入 ==========

Explanation: Comment divider. Marks the input related properties below.

73 
74     /** 变身输入动作 */
75     UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Input")
76     TObjectPtr<UInputAction> IA_Transform;

Explanation: Enhanced Input action reference. Not used in C++; retained for future migration.

77 
78     /** 默认输入映射上下文 */
79     UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Input")
80     TObjectPtr<UInputMappingContext> DefaultMappingContext;

Explanation: Enhanced Input mapping context reference. Not used in C++.

81 
82     /** 输入映射上下文优先级 */
83     UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Input")
84     int32 MappingContextPriority = 0;

Explanation: Input mapping context priority. Not used in C++.

85 
86     // ========== 相机 ==========

Explanation: Comment divider. Marks the camera related properties below.

87 
88     /** 变身时相机过渡时间 */
89     UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Transform|Camera")
90     float CameraBlendTime = 0.3f;

Explanation: Camera blend transition time, default 0.3 s. Not used in C++.

91 
92 protected:
93     /** 变身输入处理 */
94     void OnTransformInput();

Explanation: Declares the transform input handler function, invoked when the T key is pressed.

95 
96     /** 鼠标右键加速输入处理 */
97     void OnBoostInput();

Explanation: Declares the right-mouse-button boost input handler function.

98 
99     /** 服务端执行变身(RPC) */
100    UFUNCTION(Server, Reliable)
101    void Server_ExecuteTransform(int32 NewFormIndex);

Explanation: Declares a reliable server RPC function for transform synchronization in multi-player contexts. The current implementation is empty.

102 
103    /** 执行变身逻辑(C++ 实现) */
104    void ExecuteTransform(int32 NewFormIndex);

Explanation: Declares the function that executes transform logic. The current implementation is empty.

105 
106    /** 多播播放变身特效 */
107    UFUNCTION(NetMulticast, Reliable)
108    void Multicast_PlayTransformEffect();

Explanation: Declares a reliable multicast RPC function to synchronously play the transform effect in multi-player contexts. Currently calls PlayTransformEffect().

109 
110    /** 播放特效 */
111    void PlayTransformEffect();
112 
113    /** 变身冷却计时器 */
114    FTimerHandle TransformCooldownTimer;

Explanation: Declares the transform cooldown timer handle, used to prevent consecutive transform triggers.

115 
116    /** 是否正在变身中 */
117    bool bIsTransforming = false;

Explanation: Marks whether the transform flow is in progress, preventing re-triggering during cooldown.

118 
119    /** 变身冷却时间 */
120    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Transform")
121    float TransformCooldown = 0.5f;

Explanation: Transform cooldown, default 0.5 s.

122 
123    /** 变身形态显示网格(Sphere 等,已废弃改用 FormNiagaraComponent) */
124    UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Transform|Components")
125    TObjectPtr<class UStaticMeshComponent> FormMeshComponent;

Explanation: Declares the static mesh component for the early transform-form sphere display. Currently deprecated but retained for compatibility.

126 
127    /** 加速 Niagara 特效(按鼠标右键进入加速时播放一次) */
128    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Transform|VFX")
129    TObjectPtr<UNiagaraSystem> BoostEffect;

Explanation: Declares the boost-effect Niagara system reference, played once when entering boost via the right mouse button.

130 
131    /** 变身闪光过渡特效(按 T 瞬间播放,0.4s 后才正式变身) */
132    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Transform|VFX")
133    TObjectPtr<UNiagaraSystem> TransformFlashEffect;

Explanation: Declares the transform-flash transition effect, played instantly on T; the actual transform happens only after a delay.

134 
135    /** 变身闪光过渡延迟(秒),闪光播放多久后才正式变身 */
136    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Transform|VFX")
137    float TransformFlashDelay = 0.4f;

Explanation: Flash transition delay, default 0.4 s.

138 
139    /** 溶解效果 MPC 资源(Material Parameter Collection,驱动角色材质从四周→中间溶解) */
140    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Transform|VFX")
141    TObjectPtr<UMaterialParameterCollection> DissolveMPC;

Explanation: Declares the material parameter collection reference used to drive the character dissolve effect.

142 
143    /** MPC 中的溶解进度参数名 */
144    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Transform|VFX")
145    FName DissolveParamName = "DissolveProgress";

Explanation: Declares the parameter name in the MPC that controls the dissolve progress, default "DissolveProgress".

146 
147    /** 变身特效蓝图 Actor 类(包含所有 Niagara 组件) */
148    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Transform|VFX")
149    TSubclassOf<AActor> TransformVFXBlueprint;

Explanation: Declares the transform-effect Blueprint Actor class; that Actor is spawned after the transform.

150 
151    /** 已生成的变身特效 Actor 实例 */
152    UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Transform|VFX")
153    AActor* SpawnedTransformVFX = nullptr;

Explanation: Declares the pointer to the spawned transform-effect Actor instance, used for later destruction.

154 
155    /** 是否处于变身形态 */
156    UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Transform")
157    bool bIsInTransformedForm = false;

Explanation: Marks whether the character is currently in a transformed form.

158 
159    /** 是否处于加速状态 */
160    UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Transform")
161    bool bIsBoosting = false;

Explanation: Marks whether the character is currently in a boost state.

162 
163    /** 普通飞行速度 */
164    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Transform|Flight")
165    float NormalFlySpeed = 1200.0f;

Explanation: Auto-forward speed in normal flight, default 1200 cm/s.

166 
167    /** 普通操控速度 */
168    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Transform|Flight")
169    float NormalSteerSpeed = 800.0f;

Explanation: WASD steering speed in normal flight, default 800 cm/s.

171 
172    /** 加速飞行速度 */
173    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Transform|Flight")
174    float BoostFlySpeed = 3000.0f;

Explanation: Auto-forward speed in the boost state, default 3000 cm/s (overridden to 5000 at runtime by BeginPlay()).

175 
176    /** 加速操控速度 */
177    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Transform|Flight")
178    float BoostSteerSpeed = 2000.0f;

Explanation: WASD control speed in the boost state, default 2000 cm/s (overridden to 4000 at runtime by BeginPlay()).

179 
180 private:
181    /** 闪光过渡计时器 */
182    FTimerHandle TransformFlashTimer;

Explanation: Declares the flash transition timer handle, used to delay the actual transform enter/exit.

183 
184    /** 正式执行进入变身逻辑(闪光结束后调用) */
185    void ExecuteEnterTransform();

Explanation: Declares the enter-transform function executed after the flash ends.

186 
187    /** 正式执行退出变身逻辑(瞬间) */
188    void ExecuteExitTransform();

Explanation: Declares the exit-transform function executed after the flash ends.

189 
190    /** 溶解动画是否激活 */
191    bool bDissolveActive = false;

Explanation: Marks whether the dissolve animation is in progress.

192 
193    /** 溶解消失的起始值(0=可见,1=溶解消失) */
194    float DissolveStart = 0.0f;

Explanation: Dissolve start value: 0 means fully visible, 1 means fully dissolved out.

195 
196    /** 溶解消失的目标值 */
197    float DissolveTarget = 0.0f;

Explanation: Dissolve target value.

198 
199    /** 溶解已用时间 */
200    float DissolveElapsed = 0.0f;

Explanation: Elapsed time of the dissolve animation.

201 
202    /** 开始溶解(进入变身,0→1) */
203    void StartDissolveOut();
204 
205    /** 开始反溶解(退出变身,1→0) */
206    void StartDissolveIn();
207 };

Explanation: Declares the start-dissolve and reverse-dissolve helper functions. The final }; closes the class definition.


2. TransformCharacter.cpp

1  // TransformCharacter.cpp - 角色形态变换系统实现

Explanation: File header comment, stating this file is the implementation file for TransformCharacter.h.

2  
3  #include "TransformCharacter.h"

Explanation: Includes the corresponding header file.

4  #include "Components/StaticMeshComponent.h"

Explanation: Includes the static mesh component header, used to create FormMeshComponent.

5  #include "GameFramework/CharacterMovementComponent.h"

Explanation: Includes the character movement component header, used to control movement mode and speed.

6  #include "NiagaraFunctionLibrary.h"

Explanation: Includes the Niagara function library header, used to spawn Niagara effects.

7  #include "NiagaraComponent.h"

Explanation: Includes the Niagara component header, used to retrieve and manipulate Niagara components.

8  #include "NiagaraSystem.h"

Explanation: Includes the Niagara system header, used to declare Niagara system references.

9  #include "Engine/World.h"

Explanation: Includes the world header, used to spawn Actors, perform ray tests, etc.

10 #include "TimerManager.h"

Explanation: Includes the timer manager header, used to set delayed calls.

11 #include "Components/CapsuleComponent.h"

Explanation: Includes the capsule component header, used to retrieve the character capsule for ground checks.

12 #include "Camera/CameraComponent.h"
13 #include "GameFramework/PlayerController.h"

Explanation: Includes the camera component and player controller headers (the camera component include is retained; the FOV is actually set via the PlayerCameraManager).

14 #include "Kismet/GameplayStatics.h"

Explanation: Includes the Gameplay statics header. Not currently used, retained.

15 #include "Engine/Blueprint.h"

Explanation: Includes the Blueprint header, used to load UBlueprint assets.

16 #include "GameFramework/ProjectileMovementComponent.h"

Explanation: Includes the projectile movement component header, used to disable projectile movement on the transform-effect Actor.

17 #include "Materials/MaterialParameterCollectionInstance.h"

Explanation: Includes the material parameter collection instance header, used to write MPC parameter values.

18 
19 TransformBase::TransformBase()

Explanation: Defines the default constructor.

20 {
21     PrimaryActorTick.bCanEverTick = true;

Explanation: Enables per-frame Tick calls.

22 
23     // 创建变身形态显示网格组件(保留兼容,实际使用蓝图 Actor 中的 Niagara 组件)
24     FormMeshComponent = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("FormMesh"));

Explanation: Creates the static mesh child object in the constructor, named FormMesh.

25     FormMeshComponent->SetupAttachment(RootComponent);

Explanation: Attaches the mesh component to the root component (the capsule).

26     FormMeshComponent->SetVisibility(false);

Explanation: Hides that mesh component by default.

27     FormMeshComponent->SetCollisionEnabled(ECollisionEnabled::NoCollision);

Explanation: Disables collision on that mesh component.

28 
29     // 加载引擎内置 Sphere 网格(已废弃,保留兼容)
30     static ConstructorHelpers::FObjectFinder<UStaticMesh> SphereMesh(TEXT("/Engine/BasicShapes/Sphere"));

Explanation: Uses ConstructorHelpers::FObjectFinder to load the engine's built-in sphere mesh in the constructor.

31     if (SphereMesh.Succeeded())

Explanation: If the load succeeded (True branch).

32     {
33         FormMeshComponent->SetStaticMesh(SphereMesh.Object);

Explanation: Sets the sphere mesh onto the component.

34         FormMeshComponent->SetRelativeLocation(FVector(0, 0, -40));

Explanation: Sets the mesh relative position, offset 40 units downward.

35         FormMeshComponent->SetRelativeScale3D(FVector(1.2f));

Explanation: Sets the mesh relative scale to 1.2x.

36     }
37 
38     // 加载默认变身特效蓝图:BP_Projectile_Ice(Niagara 结合而成的变身特效 Actor)
39     static ConstructorHelpers::FObjectFinder<UBlueprint> DefaultVFXBlueprint(TEXT("/Game/Test_02/VFX/VFXBP/BP_Projectile_Ice"));

Explanation: Loads the transform-effect Blueprint Actor class.

40     if (DefaultVFXBlueprint.Succeeded() && DefaultVFXBlueprint.Object->GeneratedClass)

Explanation: If the load succeeded and the class was generated (True branch).

41     {
42         TransformVFXBlueprint = DefaultVFXBlueprint.Object->GeneratedClass;
43     }

Explanation: Assigns the generated UClass to TransformVFXBlueprint.

44 
45     // 加载默认变身闪光过渡特效:NS_SpriteSpawn(闪光后 0.4s 才正式变身)
46     static ConstructorHelpers::FObjectFinder<UNiagaraSystem> DefaultFlashEffect(TEXT("/Game/Test_02/VFX/FlashVFX/NS_SpriteSpawn"));

Explanation: Loads the transform-flash transition Niagara system.

47     if (DefaultFlashEffect.Succeeded())
48     {
49         TransformFlashEffect = DefaultFlashEffect.Object;
50     }

Explanation: If the load succeeded, assign it to TransformFlashEffect (True branch).

51 
52     // 加载默认加速特效:NS_JiaSu(变身后按右键加速时播放一次)
53     static ConstructorHelpers::FObjectFinder<UNiagaraSystem> DefaultBoostEffect(TEXT("/Game/Test_02/VFX/FeiDunLight/NS/NS_JiaSu"));

Explanation: Loads the boost-effect Niagara system.

54     if (DefaultBoostEffect.Succeeded())
55     {
56         BoostEffect = DefaultBoostEffect.Object;
57     }

Explanation: If the load succeeded, assign it to BoostEffect (True branch).

58 
59     // 配置移动组件
60     UCharacterMovementComponent* Movement = GetCharacterMovement();

Explanation: Retrieves the character's movement component.

61     if (Movement)
62     {
63         Movement->bOrientRotationToMovement = true;

Explanation: Orients the character toward the movement direction.

64         Movement->RotationRate = FRotator(0.0f, 540.0f, 0.0f);

Explanation: Sets the rotation rate to 540 degrees per second (Yaw axis only).

65     }
66 
67     bUseControllerRotationPitch = false;
68     bUseControllerRotationYaw = false;
69     bUseControllerRotationRoll = false;

Explanation: Disables the character's use of controller rotation to avoid conflicts with custom flight control.

70 }
71 
72 void TransformBase::BeginPlay()
73 {
74     Super::BeginPlay();

Explanation: Calls the parent class's BeginPlay().

75 
76     // 兜底:若蓝图 CDO 把 TransformVFXBlueprint 序列化为空,则用 BP_Projectile_Ice 覆盖
77     if (!TransformVFXBlueprint)
78     {
79         UBlueprint* IceBP = LoadObject<UBlueprint>(nullptr, TEXT("/Game/Test_02/VFX/VFXBP/BP_Projectile_Ice"));

Explanation: If the Blueprint CDO left TransformVFXBlueprint empty, reload it at runtime.

80         if (IceBP && IceBP->GeneratedClass)
81         {
82             TransformVFXBlueprint = IceBP->GeneratedClass;
83         }
84     }
85 
86     // 兜底:强制设置飞行速度(防止蓝图 CDO 序列化覆盖为 0)
87     NormalFlySpeed  = 1200.0f;
88     NormalSteerSpeed = 800.0f;
89     BoostFlySpeed   = 5000.0f;
90     BoostSteerSpeed = 4000.0f;
91 }

Explanation: Force-sets the speed values in BeginPlay(), overriding values that may have been serialized to 0 due to the Blueprint CDO.

92 
93 void TransformBase::PossessedBy(AController* NewController)
94 {
95     Super::PossessedBy(NewController);
96 }

Explanation: Overrides PossessedBy; the current implementation is empty.

97 
98 void TransformBase::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
99 {
100    Super::SetupPlayerInputComponent(PlayerInputComponent);

Explanation: Calls the parent class's input bindings.

101 
102    if (PlayerInputComponent)
103    {
104        PlayerInputComponent->BindKey(EKeys::T, IE_Pressed, this, &TransformBase::OnTransformInput);

Explanation: Binds the press event of the T key to OnTransformInput.

105        PlayerInputComponent->BindKey(EKeys::RightMouseButton, IE_Pressed, this, &TransformBase::OnBoostInput);

Explanation: Binds the right mouse button press event to OnBoostInput.

106        GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Green, TEXT("[Transform] T / RMB bound"));
107    }
108 }

Explanation: Prints a debug message on screen confirming the input bindings succeeded.

109 
110 void TransformBase::Tick(float DeltaTime)
111 {
112    Super::Tick(DeltaTime);

Explanation: Calls the parent Tick, then runs the custom logic.

113 
114    // ========== 溶解动画(进入变身/退出变身的过渡效果)==========
115    if (bDissolveActive)
116    {
117        DissolveElapsed += DeltaTime;

Explanation: If the dissolve animation is active, accumulate the elapsed time.

118        float Progress = FMath::Clamp(DissolveElapsed / TransformFlashDelay, 0.0f, 1.0f);

Explanation: Computes the dissolve progress, clamped between 0 and 1.

119        float DissolveValue = FMath::Lerp(DissolveStart, DissolveTarget, Progress);

Explanation: Lerps between the start and target values to get the current dissolve value.

120 
121        // 写入 MPC,驱动材质溶解
122        if (DissolveMPC)
123        {
124            UMaterialParameterCollectionInstance* MPCInstance = GetWorld()->GetParameterCollectionInstance(DissolveMPC);

Explanation: If the MPC is configured, get its world instance.

125            if (MPCInstance)
126            {
127                MPCInstance->SetScalarParameterValue(DissolveParamName, DissolveValue);
128            }
129        }

Explanation: Writes the dissolve value into the MPC's scalar parameter.

130 
131        if (Progress >= 1.0f)
132        {
133            bDissolveActive = false;
134        }
135    }

Explanation: Dissolve complete; clear the flag.

136 
137    // ========== 变身飞行系统 ==========
138    if (!bIsInTransformedForm)
139    {
140        return;
141    }

Explanation: If not in a transformed form, skip the flight logic.

142 
143    // 获取移动组件
144    UCharacterMovementComponent* MoveComp = GetCharacterMovement();
145    if (!MoveComp)
146    {
147        return;
148    }
149 
150    // 清除蓝图 WASD 移动输入(否则按 S 会同时触发"后退",与飞行方向控制冲突)
151    MoveComp->ConsumeInputVector();

Explanation: Retrieves the movement component and clears the existing input vectors to avoid conflicts with Blueprint input.

152 
153    // 获取玩家视角方向
154    APlayerController* PC = Cast<APlayerController>(GetController());
155    if (!PC)
156    {
157        return;
158    }

Explanation: Retrieves the player controller and casts it to APlayerController, for reading the view direction and key state.

159    const FRotator ControlRot = PC->GetControlRotation();
160    const FVector ViewForward = FRotationMatrix(ControlRot).GetUnitAxis(EAxis::X);
161    const FVector ViewRight   = FRotationMatrix(ControlRot).GetUnitAxis(EAxis::Y);

Explanation: Retrieves the controller rotation and computes the view forward vector and right vector.

162 
163    const float AutoFlySpeed = bIsBoosting ? BoostFlySpeed : NormalFlySpeed;
164    const float SteerSpeed = bIsBoosting ? BoostSteerSpeed : NormalSteerSpeed;

Explanation: Selects the corresponding speed based on whether boosting is active.

165 
166    // 调试:显示当前速度和加速状态
167    FString DebugMsg = FString::Printf(TEXT("[Transform] Speed=%.0f | Boost=%s"),
168        AutoFlySpeed, bIsBoosting ? TEXT("ON") : TEXT("OFF"));
169    GEngine->AddOnScreenDebugMessage(-1, 0.05f, FColor::Cyan, DebugMsg);
170 }

Explanation: Prints the current speed and boost state to the screen.

171 
172    // ========== 直接计算目标速度并设置 Velocity(绕过 MaxAcceleration 限制)==========
173    FVector DesiredVelocity = ViewForward * AutoFlySpeed;

Explanation: Computes the base target velocity from the view forward vector and the auto-flight speed.

174 
175    // W → 向上飞(世界 Z+)
176    if (PC->IsInputKeyDown(EKeys::W))
177    {
178        DesiredVelocity += FVector::UpVector * SteerSpeed;
179    }
180 
181    // S → 向下飞(世界 Z-,地面限制)
182    if (PC->IsInputKeyDown(EKeys::S))
183    {
184        UCapsuleComponent* Capsule = GetCapsuleComponent();

Explanation: If W is held, add an upward component to the target velocity; if S is held, prepare a ground check.

185        if (Capsule)
186        {
187            const float HalfHeight = Capsule->GetScaledCapsuleHalfHeight();
188            const FVector FeetPos = GetActorLocation() - FVector(0, 0, HalfHeight);
189            const FVector TraceEnd = FeetPos - FVector(0, 0, HalfHeight * 2.0f);

Explanation: Retrieves the capsule half-height and computes the feet position and the ray end point.

190 
191            FHitResult Hit;
192            FCollisionQueryParams Params;
193            Params.AddIgnoredActor(this);
194 
195            bool bNearGround = GetWorld()->LineTraceSingleByChannel(Hit, FeetPos, TraceEnd, ECC_Visibility, Params);

Explanation: Ray-traces downward from the feet to determine whether the character is near the ground.

196            if (!bNearGround)
197            {
198                DesiredVelocity += FVector::DownVector * SteerSpeed;
199            }
200        }
201    }
202 
203    // A → 向左飞(视角左方)
204    if (PC->IsInputKeyDown(EKeys::A))
205    {
206        DesiredVelocity += -ViewRight * SteerSpeed;
207    }
208 
209    // D → 向右飞(视角右方)
210    if (PC->IsInputKeyDown(EKeys::D))
211    {
212        DesiredVelocity += ViewRight * SteerSpeed;
213    }

Explanation: Based on the A/D keys, adds left/right components to the target velocity.

214 
215    // 平滑过渡到目标速度(避免瞬间突变,加速/减速都有手感)
216    const float SpeedLerpRate = 8.0f;
217    FVector NewVelocity = FMath::VInterpTo(MoveComp->Velocity, DesiredVelocity, DeltaTime, SpeedLerpRate);
218    MoveComp->Velocity = NewVelocity;

Explanation: Uses VInterpTo to smoothly transition the current velocity to the target at a rate of 8.0, then assigns it to the movement component.

219 
220    // 提高上限,防止 CharacterMovement 把速度压回去
221    MoveComp->MaxFlySpeed = FMath::Max(MoveComp->MaxFlySpeed, AutoFlySpeed * 1.2f);
222    MoveComp->MaxAcceleration = FMath::Max(MoveComp->MaxAcceleration, 10000.0f);
223 }

Explanation: Ensures MaxFlySpeed and MaxAcceleration are large enough so CharacterMovement does not limit the speed.

224 
225    // ========== 将角色世界速度传给 Niagara User 参数 ==========
226    if (SpawnedTransformVFX)
227    {
228        TArray<UNiagaraComponent*> NiagaraComps;
229        SpawnedTransformVFX->GetComponents<UNiagaraComponent>(NiagaraComps);

Explanation: If the transform-effect Actor exists, retrieve all of its Niagara components.

230 
231        const FVector FlyVel = GetVelocity();
232 
233        for (UNiagaraComponent* NiagaraComp : NiagaraComps)
234        {
235            // 传飞行速度给 Niagara(拖尾惯性甩尾 + Projectile 朝向)
236            NiagaraComp->SetVariableVec3(FName("User.FlyVelocity"), FlyVel);
237        }
238    }
239 }

Explanation: Iterates over the Niagara components, passing the character's world velocity into the user parameter named User.FlyVelocity.

240 
241 // ========== 形态切换 ==========
242 
243 void TransformBase::OnTransformInput()
244 {
245    GEngine->AddOnScreenDebugMessage(-1, 2.f, FColor::Cyan, TEXT("[Transform] FIRED!"));

Explanation: Defines the transform input handler function, which first prints the trigger info on screen.

246 
247    if (bIsTransforming)
248    {
249        return;
250    }

Explanation: If in the middle of a transform flow (cooldown), return immediately.

251 
252    bIsTransforming = true;
253 
254    if (bIsInTransformedForm)
255    {
256        // 退出变身:立刻标记为非变身状态,停止 Tick 中的飞行逻辑
257        bIsInTransformedForm = false;

Explanation: If currently in a transformed form, run the exit-transform logic. First mark as non-transformed immediately to stop the flight Tick.

258 
259        // 立刻停止飞行移动,避免延迟期间继续往前飞
260        UCharacterMovementComponent* PreMoveComp = GetCharacterMovement();
261        if (PreMoveComp)
262        {
263            PreMoveComp->Velocity = FVector::ZeroVector;
264            PreMoveComp->ConsumeInputVector();
265            PreMoveComp->StopMovementImmediately();
266            PreMoveComp->SetMovementMode(MOVE_Walking);
267        }

Explanation: Stops movement immediately: zeroes velocity, clears input, stops movement, switches to walking mode.

268 
269        // 退出变身:先播闪光 + 销毁特效 + 显示角色网格 + 溶解出现
270        if (SpawnedTransformVFX)
271        {
272            SpawnedTransformVFX->Destroy();
273            SpawnedTransformVFX = nullptr;
274        }

Explanation: Destroys the transform-effect Actor.

275 
276        // 显示网格(溶解会从 1→0 让角色逐渐出现)
277        USkeletalMeshComponent* BodyMesh = GetMesh();
278        if (BodyMesh)
279        {
280            BodyMesh->SetVisibility(true);
281        }

Explanation: Shows the character body mesh.

282 
283        StartDissolveIn();

Explanation: Starts the reverse dissolve animation from 1 to 0.

284 
285        // 播放闪光
286        if (TransformFlashEffect)
287        {
288            UNiagaraFunctionLibrary::SpawnSystemAtLocation(
289                GetWorld(),
290                TransformFlashEffect,
291                GetActorLocation(),
292                GetActorRotation(),
293                FVector(1.0f),
294                true,
295                true
296            );
297        }

Explanation: If the flash effect is configured, spawn a one-shot flash effect at the character location.

298 
299        // 延迟 0.4s 后正式退出变身
300        GetWorldTimerManager().SetTimer(
301            TransformFlashTimer,
302            this,
303            &TransformBase::ExecuteExitTransform,
304            TransformFlashDelay,
305            false
306        );
307    }

Explanation: Sets a 0.4 s delay timer; when it fires, call ExecuteExitTransform().

308    else
309    {
310        // 进入变身:先播闪光透特 + 溶解消失,延迟后才正式变身
311        GEngine->AddOnScreenDebugMessage(-1, 3.f, FColor::Orange, TEXT("[Transform] Playing flash VFX..."));

Explanation: If not currently in a transformed form, run the enter-transform logic.

312 
313        StartDissolveOut();

Explanation: Starts the dissolve-out animation from 0 to 1.

314 
315        // 播放一次性闪光特效(不附着、不跟随移动)
316        if (TransformFlashEffect)
317        {
318            UNiagaraFunctionLibrary::SpawnSystemAtLocation(
319                GetWorld(),
320                TransformFlashEffect,
321                GetActorLocation(),
322                GetActorRotation(),
323                FVector(1.0f),
324                true,  // bAutoDestroy
325                true   // bAutoActivate
326            );
327        }

Explanation: Spawns the flash effect at the character location. true, true means auto-destroy and auto-activate.

328 
329        // 延迟 0.4s 后正式变身
330        GetWorldTimerManager().SetTimer(
331            TransformFlashTimer,
332            this,
333            &TransformBase::ExecuteEnterTransform,
334            TransformFlashDelay,
335            false
336        );
337    }
338 }

Explanation: Sets a 0.4 s delay timer; when it fires, call ExecuteEnterTransform().

339 
340 void TransformBase::OnBoostInput()
341 {
342    GEngine->AddOnScreenDebugMessage(-1, 3.f, FColor::Yellow,
343        FString::Printf(TEXT("[Transform] RMB pressed | bIsInTransformedForm=%s"),
344            bIsInTransformedForm ? TEXT("true") : TEXT("false")));

Explanation: Defines the right-mouse-button boost input handler function, which prints whether the character is currently in a transformed form.

345 
346    // 只有变身状态下才能切换加速
347    if (!bIsInTransformedForm)
348    {
349        return;
350    }

Explanation: If not in a transformed form, ignore the right-mouse boost.

351 
352    bIsBoosting = !bIsBoosting;

Explanation: Toggles the boost state.

353 
354    // 同步修改 CharacterMovementComponent 的最大飞行速度上限
355    if (UCharacterMovementComponent* MoveComp = GetCharacterMovement())
356    {
357        MoveComp->MaxFlySpeed = bIsBoosting ? BoostFlySpeed : NormalFlySpeed;
358    }

Explanation: Adjusts MaxFlySpeed in sync with the boost state.

359 
360    // 只在进入加速时播放一次特效,退出加速不播放
361    if (bIsBoosting)
362    {
363        if (BoostEffect)
364        {
365            FVector SpawnLoc = GetActorLocation() + FVector(0, 0, 50);

Explanation: If entering boost and a boost effect is configured, compute the spawn location (50 units above the character location).

366            UNiagaraFunctionLibrary::SpawnSystemAtLocation(
367                GetWorld(),
368                BoostEffect,
369                SpawnLoc,
370                GetActorRotation(),
371                FVector(2.0f),
372                true,  // bAutoDestroy
373                true   // bAutoActivate
374            );
375            GEngine->AddOnScreenDebugMessage(-1, 3.f, FColor::Green, TEXT("[Transform] Boost VFX spawned"));
376        }
377        else
378        {
379            GEngine->AddOnScreenDebugMessage(-1, 3.f, FColor::Red, TEXT("[Transform] BoostEffect is NULL!"));
380        }
381    }

Explanation: Spawns the boost effect and prints debug info; if the effect is empty, prints an error.

382 
383    GEngine->AddOnScreenDebugMessage(-1, 2.f, FColor::Yellow,
384        bIsBoosting ? TEXT("[Transform] Boost ON") : TEXT("[Transform] Boost OFF"));
385 }

Explanation: Prints the current boost state.

386 
387 void TransformBase::ExecuteEnterTransform()
388 {
389    bIsInTransformedForm = true;

Explanation: Defines the formal enter-transform function (after the flash ends), marking as a transformed form.

390 
391    // 隐藏角色网格
392    USkeletalMeshComponent* BodyMesh = GetMesh();
393    if (BodyMesh)
394    {
395        BodyMesh->SetVisibility(false);
396    }

Explanation: Hides the character body mesh.

397 
398    // 生成变身特效蓝图 Actor
399    if (TransformVFXBlueprint && !SpawnedTransformVFX)
400    {
401        FActorSpawnParameters SpawnParams;
402        SpawnParams.Owner = this;

Explanation: If the transform-effect Blueprint is configured and not yet spawned, prepare the spawn parameters and set the Owner to the current character.

403        SpawnedTransformVFX = GetWorld()->SpawnActor<AActor>(TransformVFXBlueprint, FVector::ZeroVector, FRotator::ZeroRotator, SpawnParams);
404        if (SpawnedTransformVFX)
405        {
406            SpawnedTransformVFX->AttachToActor(this, FAttachmentTransformRules::KeepRelativeTransform);

Explanation: Spawns the effect Actor at the world origin and attaches it to the character to keep relative transforms.

407            SpawnedTransformVFX->SetActorRelativeLocation(FVector(0.0f, 0.0f, 0.0f));
408 
409            // 禁用投射物移动组件
410            UProjectileMovementComponent* ProjMove = SpawnedTransformVFX->FindComponentByClass<UProjectileMovementComponent>();
411            if (ProjMove)
412            {
413                ProjMove->Deactivate();
414            }

Explanation: Sets the relative position to the origin and disables the projectile movement component on the effect Actor.

415 
416            // 强制激活所有 Niagara 组件
417            TArray<UNiagaraComponent*> NiagaraComps;
418            SpawnedTransformVFX->GetComponents<UNiagaraComponent>(NiagaraComps);
419            for (UNiagaraComponent* NiagaraComp : NiagaraComps)
420            {
421                NiagaraComp->Activate(true);
422            }
423        }
424    }

Explanation: Retrieves all Niagara components under the effect Actor and forces them active.

425 
426    // 切换飞行模式
427    UCharacterMovementComponent* MoveComp = GetCharacterMovement();
428    if (MoveComp)
429    {
430        MoveComp->SetMovementMode(MOVE_Flying);
431        MoveComp->MaxFlySpeed = NormalFlySpeed;
432    }

Explanation: Switches movement mode to flying and initializes max flight speed to the normal speed.

433 
434    // FOV 放大
435    APlayerController* PC_FOV = Cast<APlayerController>(GetController());
436    if (PC_FOV && PC_FOV->PlayerCameraManager)
437    {
438        PC_FOV->PlayerCameraManager->SetFOV(120.0f);
439    }

Explanation: Sets the FOV to 120 via the player camera manager.

440 
441    // 广播事件
442    OnFormChanged.Broadcast(FormIndex, 1);

Explanation: Broadcasts the form-change event, with parameters being the old form index and the new form index.

443 
444    // 冷却
445    GetWorldTimerManager().SetTimer(
446        TransformCooldownTimer,
447        [this]() { bIsTransforming = false; },
448        TransformCooldown,
449        false
450    );
451 }

Explanation: Sets the cooldown timer; when it fires, resets bIsTransforming.

452 
453 void TransformBase::ExecuteExitTransform()
454 {
455    bIsInTransformedForm = false;

Explanation: Defines the formal exit-transform function (after the flash ends), marking as non-transformed.

456 
457    // 切换行走模式并立即停止移动
458    UCharacterMovementComponent* MoveComp = GetCharacterMovement();
459    if (MoveComp)
460    {
461        MoveComp->SetMovementMode(MOVE_Walking);
462        MoveComp->Velocity = FVector::ZeroVector;
463        MoveComp->ConsumeInputVector();
464        MoveComp->StopMovementImmediately();
465    }

Explanation: Switches to walking mode and stops movement immediately.

466 
467    // 退出变身时重置加速状态
468    bIsBoosting = false;

Explanation: Resets the boost state when exiting transform.

469 
470    // FOV 恢复
471    APlayerController* PC_FOV = Cast<APlayerController>(GetController());
472    if (PC_FOV && PC_FOV->PlayerCameraManager)
473    {
474        PC_FOV->PlayerCameraManager->SetFOV(80.0f);
475    }

Explanation: Restores the FOV to 80.

476 
477    // 广播事件
478    OnFormChanged.Broadcast(FormIndex, 0);

Explanation: Broadcasts the form-change event.

479 
480    // 冷却
481    GetWorldTimerManager().SetTimer(
482        TransformCooldownTimer,
483        [this]() { bIsTransforming = false; },
484        TransformCooldown,
485        false
486    );
487 }

Explanation: Sets the cooldown timer.

488 
489 void TransformBase::StartDissolveOut()
490 {
491    DissolveStart = 0.0f;
492    DissolveTarget = 1.0f;
493    DissolveElapsed = 0.0f;
494    bDissolveActive = true;
495 }

Explanation: Starts dissolve-out: transitions from 0 to 1, resets elapsed time, and activates the dissolve flag.

496 
497 void TransformBase::StartDissolveIn()
498 {
499    DissolveStart = 1.0f;
500    DissolveTarget = 0.0f;
501    DissolveElapsed = 0.0f;
502    bDissolveActive = true;
503 }

Explanation: Starts reverse dissolve-in: transitions from 1 to 0.

504 
505 void TransformBase::Server_ExecuteTransform_Implementation(int32 NewFormIndex)
506 {
507 }

Explanation: Server-side RPC implementation; currently empty.

508 
509 void TransformBase::TransformToNextForm()
510 {
511    OnTransformInput();
512 }
513 
514 void TransformBase::TransformToForm(int32 Index)
515 {
516    OnTransformInput();
517 }

Explanation: Form-switch function implementations; both currently call OnTransformInput() directly.

518 
519 TSubclassOf<ACharacter> TransformBase::GetCurrentFormClass() const
520 {
521    if (Forms.IsValidIndex(FormIndex))
522    {
523        return Forms[FormIndex];
524    }
525    return nullptr;
526 }

Explanation: Returns the current form class, or empty if the index is invalid.

527 
528 void TransformBase::ExecuteTransform(int32 NewFormIndex)
529 {
530 }

Explanation: Transform-logic execution function; currently empty.

531 
532 void TransformBase::Multicast_PlayTransformEffect_Implementation()
533 {
534    PlayTransformEffect();
535 }
536 
537 void TransformBase::PlayTransformEffect()
538 {
539    // 不再动态生成,使用常驻组件 TransformNiagaraComponent
540    // 激活/停用逻辑已在 OnTransformInput() 中处理
541 }

Explanation: The multicast RPC implementation calls PlayTransformEffect(), which is deprecated; the effect-spawn logic has been moved into OnTransformInput().


3. Deprecated Code Inventory and Migration Notes

Class A: Safe to delete (no references at all, zero risk of deletion)

Code location Deprecated notes
TransformEffect / TransformEffectDuration (.h VFX section) Pure dead properties, no reads or writes at all
CameraBlendTime (.h camera section) Camera blend time, never used
PossessedBy() empty implementation (.cpp) Empty override with no logic; no impact after removal
#include "Camera/CameraComponent.h" (.cpp) FOV is actually set via PlayerCameraManager; this header is unused
#include "Kismet/GameplayStatics.h" (.cpp) Unused include
Server_ExecuteTransform empty implementation (.cpp) Multi-player RPC, not enabled in this single-player project
ExecuteTransform(int32) empty implementation (.cpp) Unused empty function
Multicast_PlayTransformEffect / PlayTransformEffect (.cpp) Deprecated; the effect logic has moved to OnTransformInput()

Class B: Delete with caution (Blueprint references must be handled too)

Code location Deprecated notes Check before deleting
FormMeshComponent (Sphere mesh, .h + constructor) Deprecated; Niagara is actually used. But the BP_ThirdPersonCharacter Blueprint component list still has a FormMesh instance After deleting the C++ member, the Blueprint must be recompiled, otherwise the Blueprint retains a stale reference to the component
Forms / FormIndex / TransformToNextForm() / TransformToForm() / GetCurrentFormClass() Multi-form system; currently only single form is used Check whether any Blueprint calls these BlueprintCallable functions
OnFormChanged event delegate Broadcast inside ExecuteEnter/ExitTransform Check whether any Blueprint binds this event; if so, handle it in sync
IA_Transform / DefaultMappingContext / MappingContextPriority Enhanced Input properties; traditional BindKey is actually used Check whether any reference is set in the Blueprint property panel; clear it before deleting

Class C: Not deletable (current features depend on them)

Migration notes to the formal project (CultivatingImmortality_RPG)

  1. Module API macro: TEST_API must be replaced with the formal project's module name macro (e.g. CULTIVATINGIMMORTALITY_RPG_API); class names are recommended to be renamed to the formal project style as well.

  2. Hard-coded asset paths: the FObjectFinder paths in the constructor (/Game/Test_02/...) are test-project paths. When migrating, either change them to the formal project paths or keep the UPROPERTY and configure it in Blueprints (the latter is recommended, more flexible).

  3. Module dependencies: the .Build.cs must include the Niagara-related modules (Niagara, NiagaraCore, NiagaraShader, etc.); add them to the formal project's .Build.cs when migrating.

  4. Transform-effect Actor: BP_Projectile_Ice and its referenced Niagara assets (NS_StarSkill002, NS_Dash, NS_JiaSu, NS_SpriteSpawn, etc.) must be copied to the formal project together, keeping relative paths consistent.

  5. Input binding conflicts: the test project uses traditional BindKey. If the formal project uses Enhanced Input and the T key / right mouse button are already taken, adjust the key bindings or switch to Enhanced Input binding.

  6. Character materials: the dissolve effect requires adding a CollectionParameter → OneMinus → OpacityMask node chain in the formal project's character material, and creating the corresponding MPC asset.

  7. Blueprint CDO override pitfall: in the formal project, if you changed flight speed etc. in the Blueprint property panel, it overrides the C++ default values (this project once hit that issue: speed serialized to 0). Keeping the fallback assignment in BeginPlay() avoids it.

  8. FOV and speeds are test values: the 120/80 FOV and the 1200/800/5000/4000 speeds are tuning results from the test project; re-tune to feel in the formal project.

  9. Compile mode: modifying .h (adding/removing UPROPERTY, UFUNCTION) requires a full recompile with the editor closed; changing only the .cpp function body can use Live Coding.