【UE4C++-ActionRougelike-12】作业3


【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
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血量条一节设计伤害动画

伤害UI蓝图

2、调用伤害显示UI

在MinionRanged_BP中当血量变化时显示伤害UI(这里需要对AICharacter进行添加属性组件来获取OnHealthChange事件,具体会放在下一章内容中)

调用伤害显示UI

3、演示效果

伤害显示UI演示

三、生命恢复药水

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。演示效果如下:

生命恢复药水演示效果


文章作者: Woilin
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Woilin !
评论
  目录