在这一篇里,我们增加一些功能,就是技能击中敌人后,能够让敌人产生一些击退效果,如果敌人死亡,能够产生较大幅度的击退效果。
我们将首先将实现敌人死亡被击退的效果,然后再此基础上,实现攻击击退的效果。
要实现敌人死亡时,受到技能的冲击,我们需要可以在技能上设置技能的冲击力,并且修改死亡函数,可以在死亡时给死亡角色模型应用冲击力。
修改死亡函数
首先,我们在战斗接口中修改死亡函数,增加可以配置技能的冲击向量值,这个之前可以获取冲击的朝向和力度。
virtual void Die(const FVector& DeathImpulse) = 0;
然后我们在角色基类里也修改相关覆写
virtual void Die(const FVector& DeathImpulse) override;
并在实际执行的多播函数里添加参数
UFUNCTION(NetMulticast, Reliable)
virtual void MulticastHandleDeath(const FVector& DeathImpulse);
然后调用时传值
void ARPGCharacter::Die(const FVector& DeathImpulse)
{
//将武器从角色身上分离
Weapon->DetachFromComponent(FDetachmentTransformRules(EDetachmentRule::KeepWorld, true));
MulticastHandleDeath(DeathImpulse);
}
在多播里,我们将角色模型设置了物理模拟以后,通过调用AddImpulse给其一个冲击力,由于武器的质量比模型要小,我们只给其百分之一的冲击,第二个参数是接收冲击的骨骼,默认值NAME_None就是应用根骨骼,这对于复杂的骨骼动画系统尤为重要,可以精确控制冲量的作用位置。最后一个值如果设置为 true,则冲量的作用将被视为速度的改变,而不是一个基于质量的冲量。
void ARPGCharacter::MulticastHandleDeath_Implementation(const FVector& DeathImpulse)
{
//播放死亡音效
UGameplayStatics::PlaySoundAtLocation(this, DeathSound, GetActorLocation());
//开启武器物理效果
Weapon->SetSimulatePhysics(true); //开启模拟物理效果
Weapon->SetEnableGravity(true); //开启重力效果
Weapon->SetCollisionEnabled(ECollisionEnabled::PhysicsOnly); //开启物理碰撞通道
//Weapon->AddImpulse(DeathImpulse * 0.01f, NAME_None, true);
//开启角色物理效果
GetMesh()->SetSimulatePhysics(true); //开启模拟物理效果
GetMesh()->SetEnableGravity(true); //开启重力效果
GetMesh()->SetCollisionEnabled(ECollisionEnabled::PhysicsOnly); //开启物理碰撞通道
GetMesh()->SetCollisionResponseToChannel(ECC_WorldStatic, ECR_Block); //开启角色与静态物体产生碰撞
GetMesh()->AddImpulse(DeathImpulse, NAME_None, true);
//关闭角色碰撞体碰撞通道,避免其对武器和角色模拟物理效果产生影响
GetCapsuleComponent()->SetCollisionEnabled(ECollisionEnabled::NoCollision);
//设置角色溶解
Dissolve();
//设置死亡状态
bDead = true;
//触发死亡委托
OnDeath.Broadcast(this);
}
GE增加冲击参数
接着我们修改GE上下文,在里面可以保存对应的参数,在RPGAbilityTypes.h文件里,我们在创建的FDamageEffectParams结构体里增加两个用于传递使用的参数,我们可以在生成结构体时,对其设置参数。
UPROPERTY()
float DeathImpulseMagnitude = 0.f; //死亡时受到的冲击力
UPROPERTY()
FVector DeathImpulse = FVector::ZeroVector; //死亡时受到冲击的朝向
接着我们在上下文里增加一个参数,用于保存冲击力
UPROPERTY()
FVector DeathImpulse = FVector::ZeroVector; //死亡时冲击的方向
并增加get和set方法,方便设置冲击力
FVector GetDeathImpulse() const { return DeathImpulse; } //获取到死亡冲击的方向和力度
void SetDeathImpulse(const FVector& InImpulse) { DeathImpulse = InImpulse; } //设置死亡冲击的方向和力度
然后修改序列化,能够让值通过序列化传递到服务器
if(!DeathImpulse.IsZero())
{
RepBits |= 1 << 14;
}
}
//使用了多少长度,就将长度设置为多少
Ar.SerializeBits(&RepBits, 15);
...
if (RepBits & (1 << 14))
{
DeathImpulse.NetSerialize(Ar, Map, bOutSuccess);
}
接下来,为了方便设置,我们还需要在函数库里增加设置和获取的函数
//获取当前GE死亡冲击的方向和力度
UFUNCTION(BlueprintPure, Category="RPGAbilitySystemLibrary|GameplayEffects")
static FVector GetDeathImpulse(const FGameplayEffectContextHandle& EffectContextHandle);
//设置GE是否触发暴击
UFUNCTION(BlueprintCallable, Category="RPGAbilitySystemLibrary|GameplayEffects")
static void SetDeathImpulse(UPARAM(ref) FGameplayEffectContextHandle& EffectContextHandle, const FVector& bInDeathImpulse);
FVector URPGAbilitySystemBlueprintLibrary::GetDeathImpulse(const FGameplayEffectContextHandle& EffectContextHandle)
{
if(const FRPGGameplayEffectContext* RPGEffectContext = static_cast<const FRPGGameplayEffectContext*>(EffectContextHandle.Get()))
{
return RPGEffectContext->GetDeathImpulse();
}
return FVector::ZeroVector;
}
void URPGAbilitySystemBlueprintLibrary::SetDeathImpulse(FGameplayEffectContextHandle& EffectContextHandle, const FVector& bInDeathImpulse)
{
FRPGGameplayEffectContext* RPGEffectContext = static_cast<FRPGGameplayEffectContext*>(EffectContextHandle.Get());
RPGEffectContext->SetDeathImpulse(bInDeathImpulse);
}
并在应用配置项时,增加应用冲击力配置
接下来,我们在伤害技能上增加一个可配置参数,用来配置当前技能的冲击力
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category="Damage")
float DeathImpulseMagnitude = 60.f; //死亡时,受到的冲击的数值
并在生成配置项函数里,去设置配置结构体的冲击力
实现冲击的应用
火球术发射时,我们通过技能创建对应的发射物,并将配置结构体存储到了发射物身上,在碰撞触发时,我们将为结构体设置冲击的朝向
这样,我们应用时,就可以准确的获取到对应的冲击力和朝向。
我们在调用蓝图函数库URPGAbilitySystemBlueprintLibrary::ApplyDamageEffect(DamageEffectParams);的函数将配置结构体内的值保存的GE,并应用到目标。应用的操作是在服务器端实现的,我们通过蓝图库函数存到了GE的上下文里,并通过序列化上传到了服务器端,在服务器端的AttributeSet函数处理时,就可以获取到对应的函数。
接下来,我们在AS里处理,在调用死亡函数时,将通过函数库获取到GE上下文里的冲击的值传入。
这里有个bug解决了一下,就是如果当前攻击造成了角色死亡,有可能还会触发应用负面效果,我感觉这是没必要的,所以,如果角色生命值小于0,我将不再进行负面效果相关处理。
功能实现完成,我们要进行多次测试:
- 将负面效果应用概率降为0,防止负面效果应用伤害导致致命一击时负面效果造成的,测试击退效果。
- 将负面效果设置为100,保证每次攻击都能够应用效果,将伤害拉高,保证致命一击由火球术造成,查看是否在死亡时是否能够还会应用负面效果。
增加攻击击退效果参数
接下来,我们实现攻击击退效果,增加参数和死亡冲击时的参数雷同,这里我不做讲解
生成配置参数增加两个参数
UPROPERTY()
FVector KnockbackForce = FVector::ZeroVector; //攻击时击退的方向
UPROPERTY()
float KnockbackChance = 0.f; //攻击时击退概率
伤害技能基类增加两个参数,用于设置概率和力度
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category="Damage")
float KnockbackForceMagnitude = 1000.f; //技能击中敌人后,敌人受到的击退的力度
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category="Damage")
float KnockbackChance = 0.f; //技能命中敌人触发击退的概率
在生成配置时,设置参数
自定义GE上下文增加设置函数
FVector GetKnockbackForce() const { return KnockbackForce; } //获取到攻击击退的方向和力度
void SetKnockbackForce(const FVector& InKnockbackForce) { KnockbackForce = InKnockbackForce; } //设置攻击击退的方向和力度
...
UPROPERTY()
FVector KnockbackForce = FVector::ZeroVector; //攻击时击退的方向
序列化时,对击退力度进行序列化
...
if(!KnockbackForce.IsZero())
{
RepBits |= 1 << 15;
}
}
//使用了多少长度,就将长度设置为多少
Ar.SerializeBits(&RepBits, 16);
...
if (RepBits & (1 << 15))
{
KnockbackForce.NetSerialize(Ar, Map, bOutSuccess);
}
蓝图函数库增加对应函数,方便调用
//获取当前GE攻击击退的方向和力度
UFUNCTION(BlueprintPure, Category="RPGAbilitySystemLibrary|GameplayEffects")
static FVector GetKnockbackForce(const FGameplayEffectContextHandle& EffectContextHandle);
//设置GE攻击击退的方向和力度
UFUNCTION(BlueprintCallable, Category="RPGAbilitySystemLibrary|GameplayEffects")
static void SetKnockbackForce(UPARAM(ref) FGameplayEffectContextHandle& EffectContextHandle, const FVector& InKnockbackForce);
cpp实现一下
FVector URPGAbilitySystemBlueprintLibrary::GetKnockbackForce(const FGameplayEffectContextHandle& EffectContextHandle)
{
if(const FRPGGameplayEffectContext* RPGEffectContext = static_cast<const FRPGGameplayEffectContext*>(EffectContextHandle.Get()))
{
return RPGEffectContext->GetKnockbackForce();
}
return FVector::ZeroVector;
}
void URPGAbilitySystemBlueprintLibrary::SetKnockbackForce(FGameplayEffectContextHandle& EffectContextHandle, const FVector& InKnockbackForce)
{
FRPGGameplayEffectContext* RPGEffectContext = static_cast<FRPGGameplayEffectContext*>(EffectContextHandle.Get());
RPGEffectContext->SetKnockbackForce(InKnockbackForce);
}
函数库给目标应用GE时,将击退的值存入GE实例,用于复制到服务器端
实现应用击退逻辑
接下来,就是实现击退的逻辑,在发射火球术时,我们可以通过火球术技能生成配置项传递给发射的火球,在火球碰撞时,实现修改GE实例
这里,我们获取到火球飞行的方向,并将角度向上斜45度,让其可以有一个击飞的效果
在AS里处理时,我们就可以获取到击退的值,通过这个值就可以实现击退效果。
在受到伤害时,如果目标角色没有死亡,我们就获取到击退值,如果值不接近于0,我们将值应用给角色
LaunchCharacter将为角色设置一个待处理的发射速度 (LaunchVelocity),并在角色的 CharacterMovementComponent 下一次更新时应用这个速度。角色会被设置为“falling”(下落)状态,并触发 OnLaunched 事件。这通常用于角色跳跃、被抛出或其他瞬时位移的情况。
LaunchVelocity: 这是一个 FVector 类型的参数,表示施加给角色的速度。这是一个三维向量,决定了角色沿 X、Y、Z 方向的速度。
bXYOverride: 这是一个布尔值参数。如果设置为 true,将替换角色当前的 X 和 Y 方向速度,而不是在现有速度的基础上添加。这意味着角色在 X 和 Y 方向上的速度会被直接设置为 LaunchVelocity 中的对应值。
bZOverride: 这是一个布尔值参数。如果设置为 true,将替换角色当前的 Z 方向速度(通常与垂直跳跃有关),而不是在现有速度的基础上添加。角色在 Z 方向的速度会被设置为 LaunchVelocity 中的 Z 值。
接下来我们测试效果,可以根据需求调整效果。
实现近战攻击的击退逻辑
我们在火球术等投掷类的技能是将配置项传递给投掷物生成的,而近战攻击类型的技能不需要多此一举,而是直接在技能里应用的伤害。
下图为近战攻击实现的蓝图逻辑,可以看到,我们会遍历所有可以攻击的角色,然后通过CauseDamage函数应用的伤害。
如果在c++里实现,我们只需要修改CauseDamage函数,实现投掷技能的那一套即可。
以下是修改过的CauseDamage函数,通过MakeDamageEffectParamsFromClassDefaults配置项,然后再生成击退使用的角度和力度,实现对应的效果。
void URPGDamageGameplayAbility::CauseDamage(AActor* TargetActor)
{
//生成配置
FDamageEffectParams Params = MakeDamageEffectParamsFromClassDefaults(TargetActor);
//设置死亡冲击和击退
if(IsValid(TargetActor))
{
//获取到攻击对象和目标的朝向,并转换成角度
FRotator Rotation = (TargetActor->GetActorLocation() - GetAvatarActorFromActorInfo()->GetActorLocation()).Rotation();
Rotation.Pitch = 45.f; //设置击退角度垂直45度
const FVector ToTarget = Rotation.Vector();
Params.DeathImpulse = ToTarget * DeathImpulseMagnitude;
//判断攻击是否触发击退
if(FMath::RandRange(1, 100) < Params.KnockbackChance)
{
Params.KnockbackForce = ToTarget * KnockbackForceMagnitude;
}
}
//通过配置项应用给目标ASC
URPGAbilitySystemBlueprintLibrary::ApplyDamageEffect(Params);
}
如果要在蓝图里实现,我们可以将MakeDamageEffectParamsFromClassDefaults设置为BlueprintPure(没有调用的函数)
然后将配置项里的参数设置为BluePrintReadWrite可以在蓝图内获取和修改
接着,我们就可以在蓝图里,创建一个配置项,然后再修改配置项结构体内的一些内容,重新生成配置项,应用
解决角色击退移动的问题
我们发现,在角色被击退漂浮在空中时,她的腿时移动的,而不是那种悬浮浮空的效果。所以,这个问题,我们需要去动画蓝图里去修改。
在击退时,角色会被应用浮空效果,直到坠落到地面,所以,我们在角色更新动画时,去获取角色是否处于浮空状态即可
接着修改状态机,增加浮空状态
Alias是状态别名,可以指定某些状态通过条件可以切换到浮空状态
切换到浮空的条件就是通过判断浮空变量是否设置为true
退出的条件就是变量变为了false,默认切换到idle状态
切换移动的条件,我们也可以添加对浮空状态的判断