在上一篇文章里,我们实现了范围技能的范围指示功能。范围指示是在释放技能前,确认技能的攻击范围,在确认位置后,通过额外按键进行触发技能释放。
在这一篇里,我们将先实现在技能里使用范围指示,并能够播放对应的动作,特效和音效。
创建技能蓝图
首先,我们创建一个基于伤害的技能蓝图
在c++里增加对应技能的标签
FGameplayTag Abilities_Arcane_ArcaneShards; //奥数爆发技能标签
然后注册到标签管理器
GameplayTags.Abilities_Arcane_ArcaneShards = UGameplayTagsManager::Get()
.AddNativeGameplayTag(
FName("Abilities.Arcane.ArcaneShards"),
FString("奥术技能奥术爆发标签")
);
接着编译打开UE,在技能蓝图里设置对应标签
在技能配置表里增加对技能配置
在技能面板里设置显示对应技能的按钮的标签
接下来,我们测试一下效果
接着,我在技能蓝图里边写一些测试功能,来测试技能范围标识的运行效果。
在技能激活时,我们触发技能的显示光圈,然后再次触发时,我们关闭光圈,后面就可以执行技能释放。
我们在显示光圈时将鼠标隐藏掉,这个需要在角色类里的函数里设置比较方便
解决移动光圈模糊的问题
这个问题的原因是因为抗锯齿的造成的,在某些游戏里也有相同的问题。
在项目设置里,找到渲染-默认设置,将抗锯齿修改为祖传的TAA,这种就不会出现残影
UE里内置了四中抗锯齿
FXAA:通过增加后处理,对图片进行边缘坚持进行模糊处理。
TAA:通过多帧之间的信息来平滑边缘,并结合运动矢量减少锯齿。
MSAA:在光栅化阶段对几何体边缘进行采样,只处理几何体的边缘像素。
TSR:是 UE5 新推出的一种抗锯齿与超分辨率结合的技术,用于在低分辨率渲染时生成高质量画面。
添加生成随机的技能生成位置
接下来,我们要实现一个功能,实现多个生成粒子特效的位置,我们需要同意创建一个Actor,然后在里面添加一些场景组件,作为记录对应的位置点来实现。
首先创建一个基于actor的类
命名
增加一些对应的配置项,增加十个场景组件和一个默认的actor根组件,创建一个获取场景组件位置的函数。
UCLASS()
class RPG_API APointCollection : public AActor
{
GENERATED_BODY()
public:
APointCollection();
virtual void Tick(float DeltaTime) override;
/**
* 获取到actor当前世界位置下的多个数量的位置
* @param GroundLocation 地面位置
* @param NumPoints 需要返回的数量最大10个
* @param YawOverride 设置actor旋转的值
* @return
*/
UFUNCTION(BlueprintPure)
TArray<USceneComponent*> GetGroundPoints(const FVector& GroundLocation, int32 NumPoints, float YawOverride = 0.f);
protected:
virtual void BeginPlay() override;
/**
* 初始化时使用,减少重复代码,创建多个点并添加到根组件下面
* @param Pt 创建的场景组件引用
* @param Name 场景组件名称
*/
void CreateSceneComponent(TObjectPtr<USceneComponent>& Pt, FName Name);
//存储下面场景组件的数组
UPROPERTY(BlueprintReadOnly, VisibleAnywhere)
TArray<USceneComponent*> ImmutablePts;
//场景的根组件,作为检测位置的根
UPROPERTY(BlueprintReadOnly, VisibleAnywhere)
TObjectPtr<USceneComponent> Pt_0;
UPROPERTY(BlueprintReadOnly, VisibleAnywhere)
TObjectPtr<USceneComponent> Pt_1;
UPROPERTY(BlueprintReadOnly, VisibleAnywhere)
TObjectPtr<USceneComponent> Pt_2;
UPROPERTY(BlueprintReadOnly, VisibleAnywhere)
TObjectPtr<USceneComponent> Pt_3;
UPROPERTY(BlueprintReadOnly, VisibleAnywhere)
TObjectPtr<USceneComponent> Pt_4;
UPROPERTY(BlueprintReadOnly, VisibleAnywhere)
TObjectPtr<USceneComponent> Pt_5;
UPROPERTY(BlueprintReadOnly, VisibleAnywhere)
TObjectPtr<USceneComponent> Pt_6;
UPROPERTY(BlueprintReadOnly, VisibleAnywhere)
TObjectPtr<USceneComponent> Pt_7;
UPROPERTY(BlueprintReadOnly, VisibleAnywhere)
TObjectPtr<USceneComponent> Pt_8;
UPROPERTY(BlueprintReadOnly, VisibleAnywhere)
TObjectPtr<USceneComponent> Pt_9;
UPROPERTY(BlueprintReadOnly, VisibleAnywhere)
TObjectPtr<USceneComponent> Pt_10;
};
在构造函数里,我们为场景组件创建实例,将Pt_0作为根节点,然后将其他节点添加到根组件上面
APointCollection::APointCollection()
{
PrimaryActorTick.bCanEverTick = false;
//设置Pt_0作为根节点
Pt_0 = CreateDefaultSubobject<USceneComponent>("Pt_0");
ImmutablePts.Add(Pt_0);
SetRootComponent(Pt_0);
//将其它的场景组件作为根组件的子节点添加,会在蓝图中去修改它的位置
CreateSceneComponent(Pt_1, "Pt_1");
CreateSceneComponent(Pt_2, "Pt_2");
CreateSceneComponent(Pt_3, "Pt_3");
CreateSceneComponent(Pt_4, "Pt_4");
CreateSceneComponent(Pt_5, "Pt_5");
CreateSceneComponent(Pt_6, "Pt_6");
CreateSceneComponent(Pt_7, "Pt_7");
CreateSceneComponent(Pt_8, "Pt_8");
CreateSceneComponent(Pt_9, "Pt_9");
CreateSceneComponent(Pt_10, "Pt_10");
}
为了减少重复代码,我们将重复代码抽离成一个函数,实现了场景组件的创建,并添加到数组中,方便遍历。
void APointCollection::CreateSceneComponent(TObjectPtr<USceneComponent>& Pt, const FName Name)
{
Pt = CreateDefaultSubobject<USceneComponent>(Name);
ImmutablePts.Add(Pt);
Pt->SetupAttachment(GetRootComponent());
}
接着就是获取顶点数量和旋转角度,这样可以防止技能特效同质化严重,我们最多支持11个位置获取,并且支持随机角度。
TArray<USceneComponent*> APointCollection::GetGroundPoints(const FVector& GroundLocation, int32 NumPoints, float YawOverride)
{
checkf(ImmutablePts.Num() >= NumPoints, TEXT("访问索引超过了数组的范围"));
TArray<USceneComponent*> ArrayCopy;
for(USceneComponent* Pt : ImmutablePts)
{
if(ArrayCopy.Num() >= NumPoints) return ArrayCopy;
if(Pt != Pt_0)
{
FVector ToPoint = Pt->GetComponentLocation() - Pt_0->GetComponentLocation(); //获取到节点基于根组件世界坐标系下的偏移
ToPoint = ToPoint.RotateAngleAxis(YawOverride, FVector::UpVector); //对偏移值进行垂直偏移
Pt->SetWorldLocation(Pt_0->GetComponentLocation() + ToPoint); //设置偏移后的坐标
}
//创建拾取坐标使用的起始点和最终位置
const FVector RaisedLocation = FVector(Pt->GetComponentLocation().X, Pt->GetComponentLocation().Y, Pt->GetComponentLocation().Z + 500.f);
const FVector LoweredLocation = FVector(Pt->GetComponentLocation().X, Pt->GetComponentLocation().Y, Pt->GetComponentLocation().Z - 500.f);
//获取到周围过滤的对象
TArray<AActor*> IgnoreActors;
URPGAbilitySystemLibrary::GetLivePlayersWithinRadius(this, IgnoreActors, TArray<AActor*>(), 1500.f, GetActorLocation());
//创建一条直线来拾取地面的坐标
FHitResult HitResult;
FCollisionQueryParams QueryParams;
QueryParams.AddIgnoredActors(IgnoreActors);
GetWorld()->LineTraceSingleByProfile(HitResult, RaisedLocation, LoweredLocation, FName("BlockAll"), QueryParams);
//通过结果修改节点的位置和朝向
const FVector AdjustedLocation = FVector(Pt->GetComponentLocation().X, Pt->GetComponentLocation().Y, HitResult.ImpactPoint.Z);
Pt->SetWorldLocation(AdjustedLocation);
Pt->SetWorldRotation(UKismetMathLibrary::MakeRotFromZ(HitResult.ImpactNormal));
//添加到返回数组内
ArrayCopy.Add(Pt);
}
return ArrayCopy;
}
接着编译打开UE,创建一个对应的蓝图
为了方便查看场景组件的位置,我们可以在每个场景组件下面添加一个公告板组件
按照需求摆好一些随机的位置
修改技能,通过使用通过类生成actor节点生成
以下是生成测试节点,并防止对应的节点重复调用,我们将节点尽量保存为变量,在获取到位置后,我们绘制一个调试球来查看位置。
最后运行测试效果。
延迟生成技能个体
接下来,我们修改技能,让技能能够岔开时间生成,不会造成瞬间生成多个actor导致出现卡顿现象
我们将获取生成个数存储为一个变量
然后通过使用定时器来每经过一段时间调用事件来实现奥数技能个体的添加,并在设置定时器后,调用一次函数,保证事件触发时能够生成第一个,然后调用debug显示,这里我们后续创建功能可以修改为实际调用。在生成测试球体后,我们判定索引是否超出了数组边界,如果没有超过,则等待下次函数调用,如果超过了,我们将结束技能。
在结束技能时,我们获取位置的actor销毁,并清除定时器。
实现奥数爆发个体的技能特效
接下来,我们要通过GameplayCue来实现技能的特效和音效
首先创建一个GameplayCue标签,用于激活
这里,我们创建一个新的GameplayCue,
GameplayCueNotify_Static适用于简单的、不需要复杂逻辑的 单次效果
GameplayCueNotify_Burst 是 GameplayCueNotify_Static 的一个增强版本,用于 瞬时的“爆发式”效果,并且支持更多的细节配置。
GameplayCueNotify_HitImpact 专门用于处理 击中(Hit)事件 的效果。它通常用来处理攻击命中目标后的反馈,如产生火花、击退、流血等效果。
对于奥术爆发特效,我们使用GameplayCueNotify_Burst 去实现。
我们可以查看源码,查看对应类拥有的内容
这个类包含两个内容,一个是在被执行时调用的回调,然后另一个是在蓝图中实现的回调
执行回调会在内部处理数据,然后调用OnBurst
我们打开刚才制作的GameplayCue蓝图,在右侧设置对应的标签
然后书写一些debug信息
在技能蓝图里,通过当前节点来调用
设置配置项
运行查看执行技能时,对应技能是否能够触发。
接下来,我们配置特效,Brust里带的配置可以让我们配置粒子,音效,相机震动等相关
我们设置所需的粒子特效和音效,这样,我们就不需要和在GameplayCueNotify_Static里一样,还需要自己手动实现对应效果
运行查看对应效果
添加转向和动画
接下来,我们按照之前的思路,实现转向和蒙太奇动画,这里就简短介绍一下,如果想完整查看的,可以看一下之前实现的技能。
创建一个通知标签
创建一个蒙太奇,并添加Motion Wraping通知和Game Events通知
在技能里添加对Motion Wraping的朝向设置,以及蒙太奇播放和事件接收通知。
这样,角色将在动画触发后播放对应的动画,并在接收到通知后触发后续技能特效播放。