C++ 层:SkillComponent 与数据资产
数据资产(USkillAsset)
UCLASS(BlueprintType)
class USkillAsset : public UPrimaryDataAsset
{
GENERATED_BODY()
public:
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Skill", meta=(ClampMin="0"))
float Cooldown = 1.f;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Skill", meta=(ClampMin="0"))
float ManaCost = 10.f;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Skill", meta=(ClampMin="0"))
float BaseDamage = 20.f;
// 表现蓝图(软引用:不加载此蓝图也能读数据)
UPROPERTY(EditAnywhere, Category="Skill", meta=(MetaClass="BP_SkillBase"))
TSoftClassPtr<AActor> SkillVisualClass;
};
组件(USkillComponent)
UCLASS(ClassGroup=(Custom), meta=(BlueprintSpawnableComponent))
class USkillComponent : public UActorComponent
{
GENERATED_BODY()
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnSkillState, int32, Slot, FString, State);
public:
UPROPERTY(BlueprintAssignable, Category="Skill")
FOnSkillState OnSkillState; // UI 订阅刷新按钮状态
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Skill")
TArray<USkillAsset*> EquippedSkills;
UFUNCTION(BlueprintCallable, Category="Skill")
bool TryCastSkill(int32 Slot);
private:
TArray<float> CooldownEnds; // 每个槽位的冷却结束时间
FTimerHandle CooldownTimer;
};
bool USkillComponent::TryCastSkill(int32 Slot)
{
if (!EquippedSkills.IsValidIndex(Slot)) return false;
USkillAsset* S = EquippedSkills[Slot];
if (!S) return false;
const float Now = GetWorld()->GetTimeSeconds();
if (Now < CooldownEnds[Slot]) { OnSkillState.Broadcast(Slot, "CD中"); return false; }
CooldownEnds[Slot] = Now + S->Cooldown; // 进入冷却
OnSkillState.Broadcast(Slot, "施放"); // 通知 UI
// 生成表现蓝图(软引用加载)
if (UClass* C = S->SkillVisualClass.LoadSynchronous())
{
FActorSpawnParameters P; P.Owner = GetOwner();
GetWorld()->SpawnActor(C, GetOwner()->GetActorLocation(), FRotator::ZeroRotator, P);
}
return true;
}
设计要点回顾
- 组件只管"状态与规则"(冷却、槽位、广播),不管"火球长什么样";
- 数值全部从
USkillAsset读,组件内零硬编码; - 软引用
SkillVisualClass——读技能数据时不会把表现蓝图也加载进内存(第 4 章的硬引用规避); - 广播
OnSkillState给 UI:UI 只订阅不决策(第 3 章的通信选型)。
Anim Notify 怎么对接(表现蓝图侧的事件)
技能表现蓝图里订阅 AnimNotify_CastHit 事件( montage 里由美术加的 Notify),把"生效"回调发给组件:
// 在技能表现蓝图基类(C++ 侧)
UFUNCTION(BlueprintNativeEvent, Category="Skill")
void OnCastHit(); // 默认实现:告诉组件做伤害结算
伤害结算在 C++(从 SkillAsset 读 BaseDamage,走第 3 章的 IDamageable 接口发给敌人),特效在蓝图(OnCastHit 的蓝图实现里播 Niagara + 音效)。
本章小结
- C++ 层三件套:数据资产(数值)、组件(状态机与广播)、表现基类(动画事件对接)。
- 数值零硬编码;表现蓝图用软引用加载;UI 只订阅广播。
- 生效时机由美术用 Anim Notify 决定,程序不猜帧。
