【UE4C++-ActionRougelike-12】作业3
第三次作业内容:
- 魔法攻击类:增加音频:飞行声、击中声
- 玩家角色:增加”击中闪光”效果
- 属性组件:增加”最大生命值”属性
- UI:增加伤害显示UI
- 生命药水:能够交互;10s后重新生成;满血情况下忽视交互
- 魔法攻击类:施法时增加特效;击中目标后镜头震动
下面将作业内容整理为三个模块进行记录
一、魔法攻击优化
1、添加音频 & 镜头震动
1.1 添加初始化组件
在MyProjectileBase中添加音频组件和碰撞效果音效
class ACTIONROGUELIKE_API AMyProjectileBase : public AActor
{
protected:
//碰撞效果震动的类
UPROPERTY(EditDefaultsOnly, Category = "Effects|Shake")
TSubclassOf<UCameraShakeBase> ImpactShake;
//碰撞效果震动的内部半径
UPROPERTY(EditDefaultsOnly, Category = "Effects|Shake")
float ImpactShakeInnerRadius;
//碰撞效果震动的外部半径
UPROPERTY(EditDefaultsOnly, Category = "Effects|Shake")
float ImpactShakeOuterRadius;
//碰撞效果音效
UPROPERTY(EditDefaultsOnly, Category = "Effects")
USoundCue* ImpactSound;
//碰撞效果粒子系统
UPROPERTY(EditDefaultsOnly, Category = "Effects")
UParticleSystem* ImpactVFX;
//添加音频组件
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Components")
UAudioComponent* AudioComp;
}
在MyProjectileBase.cpp中初始化各个组件,并且实现魔法攻击爆炸后的逻辑
AMyProjectileBase::AMyProjectileBase()
{
EffectComp = CreateDefaultSubobject<UParticleSystemComponent>("EffectComp");
EffectComp->SetupAttachment(RootComponent);
AudioComp = CreateDefaultSubobject<UAudioComponent>("AudioComp");
AudioComp->SetupAttachment(RootComponent);
ImpactShakeInnerRadius = 0.0f;
ImpactShakeOuterRadius = 1500.0f;
}
void AMyProjectileBase::Explode_Implementation()
{
if (ensure(!IsPendingKill()))
{
UGameplayStatics::SpawnEmitterAtLocation(this, ImpactVFX, GetActorLocation(), GetActorRotation());
//播放声音
UGameplayStatics::PlaySoundAtLocation(this, ImpactSound, GetActorLocation());
//播放一个镜头震动
UGameplayStatics::PlayWorldCameraShake(this, ImpactShake, GetActorLocation(), ImpactShakeInnerRadius, ImpactShakeOuterRadius);
Destroy();
}
}
1.2 绑定音频文件
在MagicProjectile_BP中对各个音频文件进行绑定
1.3 绑定镜头震动文件
在MagicProjectile_BP中对击中震动类进行绑定
2、施法特效 & 击中闪光效果
2.1 添加、初始化施法特效组件
在MyCharacter类中添加粒子特效组件
class ACTIONROGUELIKE_API AMyCharacter : public ACharacter
{
protected:
//手部插槽名
UPROPERTY(VisibleAnywhere, Category = "Effects")
FName HandSocketName;
//存储攻击动画的延迟时间
UPROPERTY(EditDefaultsOnly, Category = "Attack")
float AttackAnimDelay;
//攻击动画时生成的粒子效果
UPROPERTY(EditAnywhere, Category = "Attack")
UParticleSystem* CastingEffect;
}
在MyCharacter.cpp中对变量初始化。
AMyCharacter::AMyCharacter()
{
AttackAnimDelay = 0.2f;
HandSocketName = "Muzzle_01";
}
//对于多个不同种类的攻击类都会进行攻击特效的播放,因此将这部分代码整理为一个函数,提高代码的复用性
void AMyCharacter::StartAttackEffects()
{
PlayAnimMontage(AttackAnim);
UGameplayStatics::SpawnEmitterAttached(CastingEffect, GetMesh(), HandSocketName, FVector::ZeroVector, FRotator::ZeroRotator, EAttachLocation::SnapToTarget);
}
2.2 实现击中闪光
在MyAICharacter中添加TimeToHitParamName变量来表示HitFlash材质中的TimeToHit变量。游戏材质入门那节课创建的HitFlash材质,其实现原理我们现在简单回顾一下:当物体被Hit时,设置TimeToHit为当前系统时间,此时材质中的系统时间-TimeToHit会为0,随着系统时间增加,会进行颜色的变化,实现闪光效果。
class ACTIONROGUELIKE_API AMyAICharacter : public ACharacter
{
protected:
UPROPERTY(VisibleAnywhere, Category = "Effects")
FName TimeToHitParamName;
}
在MyAICharacter中初始化TimeToHitParamName,并且对HitFlash材质中的TimeToHit赋值(这里需要对AICharacter进行添加属性组件来获取OnHealthChange事件,具体会放在下一章内容中)
AMyAICharacter::AMyAICharacter()
{
PawnSensingComp = CreateDefaultSubobject<UPawnSensingComponent>("PawnSensingComp");
AttributeComp = CreateDefaultSubobject<UMyAttributeComponent>("AttributeComp");
AutoPossessAI = EAutoPossessAI::PlacedInWorldOrSpawned;
TimeToHitParamName = "TimeToHit";
}
void AMyAICharacter::OnHealthChanged(AActor* InstigatorActor, UMyAttributeComponent* OwningComp, float NewHealth, float Delta)
{
GetMesh()->SetScalarParameterValueOnMaterials(TimeToHitParamName, GetWorld()->TimeSeconds);
}
2.3 绑定特效和闪光材质
在PlayerCharacter_BP中绑定攻击特效
在项目资源自带的M_SimpleLaneMinion中添加HitFlash材质函数
3、效果演示
二、伤害显示UI
1、修改伤害显示UI Widget蓝图
修改DamagePopup_Widget,根据UMG血量条一节设计伤害动画
2、调用伤害显示UI
在MinionRanged_BP中当血量变化时显示伤害UI(这里需要对AICharacter进行添加属性组件来获取OnHealthChange事件,具体会放在下一章内容中)
3、演示效果
三、生命恢复药水
1、创建拾取类道具基类
新建C++类MyPickup,表示拾取类道具的基类
class ACTIONROGUELIKE_API AMyPickup : public AActor, public ISInteractionInterface
{
GENERATED_BODY()
public:
AMyPickup();
void Interact_Implementation(APawn* InstigatorPawn) override;
protected:
//用于处理道具的碰撞和交互
UPROPERTY(VisibleAnywhere, Category = "Component")
USphereComponent* SphereComp;
//用于渲染道具的外观
UPROPERTY(VisibleAnywhere, Category = "Components")
UStaticMeshComponent* MeshComp;
//道具重生的时间间隔
UPROPERTY(EditAnywhere, Category = "PowerUp")
float RespawnTime;
//用于管理物品重生的定时器
FTimerHandle TimerHandle_RespawnTimer;
//用于显示道具
UFUNCTION()
void ShowPickup();
//用于隐藏道具
void HideAndCooldownPickup();
//设置道具的激活状态(是否可交互和渲染)
void SetPickupState(bool bNewIsActive);
};
在MyPickup.cpp中定义道具的显示、隐藏和重生,具体交互行为需要在派生类中对接口函数Interact_Implementation进行实现。
AMyPickup::AMyPickup()
{
SphereComp = CreateDefaultSubobject<USphereComponent>("SphereComp");
SphereComp->SetCollisionProfileName("Pickup");
RootComponent = SphereComp;
MeshComp = CreateDefaultSubobject<UStaticMeshComponent>("MeshComp");
MeshComp->SetCollisionEnabled(ECollisionEnabled::NoCollision);
MeshComp->SetupAttachment(RootComponent);
RespawnTime = 10.0f;
}
void AMyPickup::Interact_Implementation(APawn* InstigatorPawn)
{
//不同道具子类根据其效果实现不同功能
}
//用于显示物品的函数,通过调用 SetPickupState() 函数将物品设置为可交互和可渲染的状态
void AMyPickup::ShowPickup()
{
SetPickupState(true);
}
//用于隐藏物品并设置物品的重生定时器
void AMyPickup::HideAndCooldownPickup()
{
SetPickupState(false);
//设置了一个定时器,定时触发 ShowPickup() 函数用于重生物品
GetWorldTimerManager().SetTimer(TimerHandle_RespawnTimer, this, &AMyPickup::ShowPickup, RespawnTime);
}
//用于设置物品的激活状态(是否可交互和渲染)
void AMyPickup::SetPickupState(bool bNewIsActive)
{
//设置物品是否可交互
SetActorEnableCollision(bNewIsActive);
//设置物品是否可渲染
RootComponent->SetVisibility(bNewIsActive, true);
}
2、角色添加最大生命值属性
在属性组件MyAttributeComponent.h中添加最大生命值属性
class ACTIONROGUELIKE_API UMyAttributeComponent : public UActorComponent
{
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Attributes")
float MaxHealth;
//判断玩家是否存活
UFUNCTION(BlueprintCallable)
bool IsAlive() const;
//判断玩家是否满血
UFUNCTION(BlueprintCallable)
bool IsFullHealth() const;
//返回玩家最大生命值
UFUNCTION(BlueprintCallable)
float GetHealthMax() const;
}
在MyAttributeComponent.cpp中修改生命值变化函数,添加生命值相关的功能函数
UMyAttributeComponent::UMyAttributeComponent()
{
MaxHealth = 100;
Health = MaxHealth;
}
//玩家是否满血
bool UMyAttributeComponent::IsFullHealth() const
{
return Health == MaxHealth;
}
//返回最大生命值
float UMyAttributeComponent::GetHealthMax() const
{
return MaxHealth;
}
bool UMyAttributeComponent::ApplyHealthChange(float Delta)
{
float OldHealth = Health;
//将 Health 的值限制在 0 到 MaxHealth 之间
Health = FMath::Clamp(Health + Delta, 0.0f, MaxHealth);
//计算实际的生命值变化量
float ActualDelta = Health - OldHealth;
OnHealthChange.Broadcast(nullptr,this, Health,ActualDelta);
return ActualDelta != 0;
}
3、创建生命恢复药水类
以MyPick类为父类创建生命恢复药水类MyPickup_Health
class ACTIONROGUELIKE_API AMyPickup_Health : public AMyPickup
{
public:
//重写接口函数,实现生命恢复药水的功能
virtual void Interact_Implementation(APawn* InstigatorPawn) override;
AMyPickup_Health();
};
在MyPickup_Health.cpp中给出接口函数的实现
void AMyPickup_Health::Interact_Implementation(APawn* InstigatorPawn)
{
if (!ensure(InstigatorPawn))
{
return;
}
UMyAttributeComponent* AttributeComp = Cast<UMyAttributeComponent>(InstigatorPawn->GetComponentByClass(UMyAttributeComponent::StaticClass()));
// 玩家是否满血
if (ensure(AttributeComp) && !AttributeComp->IsFullHealth())
{
//如果没有满血,则恢复到满血
if (AttributeComp->ApplyHealthChange(AttributeComp->GetHealthMax()))
{
//隐藏生命药水并启动其重生计时器
HideAndCooldownPickup();
}
}
}
4、效果演示
最后以MyPickup_Health为父类创建蓝图类PickupHealth_BP,并设置相应Mesh。演示效果如下: