4.11 目标 - Targeting

4.11.1 目标数据 - Target Data

FGameplayAbilityTargetData是一个通用的结构体,专为那些可以在网络上传递的目标数据使用。TargetData通常会保存AActor/UObject的引用,FHitResults以及其他一些通用的位置/方向/原点的信息。当然,你也可以对他进行派生,把你想要的任何东西塞到它里面,这是一种简单的通过GameplayAbilities来在客户端和服务器之间传递数据的方式。FGameplayAbilityTargetData结构体需要去派生出子类来进行使用,不要直接去进行使用。GameplayAbilityTargetTypes.h中罗列了一些为GAS准备的开箱即用的的FGameplayAbilityTargetData子类结构体。

TargetData通常是由Target Actors产生,或是手动创建,并且由AbilityTasksGameplayEffects通过EffectContext来进行使用。当TargetData作为EffectContext的结果时,ExecutionsMMCsGameplayCues以及AttributeSet的一些默认方法都可以对其进行访问。

通常我们不会去直接传递FGameplayAbilityTargetData,相对的,我们会去利用FGameplayAbilityTargetDataHandle,其内部存有一个FGameplayAbilityTargetData的数组指针。这个中间结构体会为TargetData的多态性提供支持。

4.11.2 目标Actor - Target Actors

GameplayAbilities是利用WaitTargetDataAbilityTask来生成TargetActors,以显示和捕捉世界中的目标信息。TargetActors可以使用GameplayAbilityWorldReticles来显示当前的目标。在目标确认后,目标的信息会以TargetData的形式返回,然后可以进一步传递给GameplayEffects

TargetActors是派生自AActor,因此他们可以拥有任意类型的渲染组件(比如static mesh静态网格体或者decal贴花),来表示他们的位置以及他们如何进行目标选择。静态网格体可以用来显示你的角色所构建的物体。贴花可以用来显示地面上的相应作用区域。示例工程中使用了一个带有地面贴花的AGameplayAbilityTargetActor_GroundTrace,用以表示Meteor流星技能的伤害效果区域。TargetActors也可以不去显示任何东西。例如,GASShooter项目中的枪械弹药需要使用射线检测目标,但是这些一瞬间的检测并不需要显示任何东西。

TargetActors会利用基本的射线或者碰撞检测来捕获目标信息,并且可以根据TargetActor的具体实现将结果从FHitResults或者AActor数组转换为TargetDataWaitTargetDataAbilityTask可以根据TEnumAsByte<EGameplayTargetingConfirmation::Type> ConfirmationType的参数来决定目标在什么时候确定。当不是TEnumAsByte<EGameplayTargetingConfirmation::Type::Instant时,TargetActor通常会在Tick()中去作射线碰撞检测,并且根据其具体实现去将其位置更新到FHitResult中。尽管这是一个在Tick()中执行的射线碰撞检测,但是实际上你并不太需要为此担心性能问题,意味它并不需要进行网络复制,而且你通常也同一时间也不会有超过一个的TargetActor(这说的是更通常的情况,当然系统并没有限制同一时间的数量)。但是这件事你还是需要知道的,有些时候一些复杂的TargetActors可能会在Tick()中做非常多的操作,比如GASShooter项目中火箭发射器的第二个技能。尽管在客户端Tick()是非常灵敏的(即其执行次数高于服务端),但是当其开始对性能产生较大影响时你可能需要考虑降低TargetActorTick()频率。在TEnumAsByte<EGameplayTargetingConfirmation::Type::Instant时,TargetActor会瞬间生成,产生TargetData数据,然后销毁。这样,就永远不会涉及到Tick()的调用。

EGameplayTargetingConfirmation::Type目标确认的时机
Instant没有特殊逻辑或者用户输入决定何时去“开火”的情况下,目标的选取会立即发生。
UserConfirmed在用户通过技能绑定到Confirm的输入进而确认了目标亦或是通过调用UAbilitySystemComponent::TargetConfirm()表明目标的确认时,目标的选取就发生了。TargetActor同样也会去相应Cancel的输入或者UAbilitySystemComponent::TargetCancel()的调用从而取消目标的选择。
Custom技能中通过调用UGameplayAbility::ConfirmTaskByInstanceName()来决定具体的targeting data准备好的时机。同样,TargetActor也会响应UGameplayAbility::CancelTaskByInstanceName()来取消目标的选择。
CustomMulti同上,其不同之处在于数据生成时不会去结束掉这个AbilityTask

并不是每个TargetActor都支持所有的EGameplayTargetingConfirmation::Type。例如,AGameplayAbilityTargetActor_GroundTrace就不支持Instant类型的确认。

WaitTargetDataAbilityTask中有一个AGameplayAbilityTargetActor类作为参数,其会在每次这个技能任务激活时生成一个TargetActor的实例,而在AbilityTask结束时销毁这个实例。WaitTargetDataUsingActorAbilityTask可以传入一个已经生成好的TargetActor,同样会在技能任务结束时销毁该实例。这两种AbilityTasks都是低效的,在每次使用时都需要生成或者需要一个生成好的TargetActor。对于开发游戏原型来说他们是可以开箱即用的,但是正式的开发可能就需要进行进一步优化,比如某些情况下你需要不间断得生成TargetData,像那种自动来复枪。GASShooter项目中有一个自定义的AGameplayAbilityTargetActor的派生类,以及一个全新的WaitTargetDataWithReusableActorAbilityTask,在这个任务中你可以重复使用TargetActor而无需一直去处理销毁的问题。

TargetActors在默认情况下是不进行复制的。但是,如果你的游戏中有需要向其他玩家公开你所选目标的这样的需求的话,就需要用到TargetActors的复制了,当然这完全是可以实现的。WaitTargetDataAbilityTask中也包含了利用RPC和服务器进行通信的默认功能。如果TargetActorShouldProduceTargetDataOnServer属性设置为false,那么客户端在确定目标时会通过UAbilityTask_WaitTargetData::OnTargetDataReadyCallback()中的CallServerSetReplicatedTargetData()来将TargetData利用RPC发送给服务器。如果ShouldProduceTargetDataOnServertrue,客户端将发送一个通用的确认事件,即EAbilityGenericReplicatedEvent::GenericConfirm,在UAbilityTask_WaitTargetData::OnTargetDataReadyCallback()利用RPC方法传递给服务器,然后服务器将会基于接收到的RPC来做射线和碰撞检测,进而在服务器上生成数据。如果客户端取消了目标选择,它会发出一个通用的取消事件,即EAbilityGenericReplicatedEvent::GenericCancel,在UAbilityTask_WaitTargetData::OnTargetDataCancelledCallback中利用RPC的方法传递给客户端。如你所见,TargetActorWaitTargetDataAbilityTask上都存在着非常多的委托。TargetActor响应输入并且广播TargetData准备就绪、确认或者是取消的委托。WaitTargetData监听TargetActorTargetData准备就绪、确认以及取消的委托,并且将这些信息返回给GameplayAbility和服务器。如果你发送TargetData给服务器,你可能希望在服务器上进行确认操作,以便确认TargetData是否是合理的(防止作弊的发生)。直接在服务器上生成TargetData的话会完全避免这个问题,但是可能会导致所属客户端的预测的失败。

根据你所使用的AGameplayAbilityTargetActor的特定派生类的不同,WaitTargetDataAbilityTask节点上也会相应的暴露不同的ExposeOnSpawn参数。其中共享的部分有:

共享的 TargetActor 上的参数定义
Debug如果为true时,那么在非发行版的版本中它会去帮我们绘制所有的射线和碰撞检测的信息。请记住,非InstantTargetActors将会在Tick()中去做检测,那么自然,这些绘制内容也是在Tick()中完成的(译者注:相对来说射线检测的消耗还可以接收,但是其绘制的消耗可能会非常爆炸)。
Filter[可选] 一个特定的结构体,用来在射线检测时对Actors进行过滤。通常的用法是去过滤玩家的Pawn,只允许某个特定类型的目标。参考Target Data Filters小节获取更多相关内容。
Reticle Class[可选] TargetActor所要创建的AGameplayAbilityWorldReticle的派生类。
Reticle Parameters[可选] 配置光标。参考Reticles的小结。
Start Location一个特定的结构体,其中包含了射线检测的开始位置的信息。通常会是玩家的视口、武器枪口,亦或是Pawn的位置。

默认的TargetActor类中,Actors只有在被射线检测到或者碰撞检测到才是有效的目标。如果他们离开射线和碰撞检测的范围(他们移动了或者你的移开了视线),那么他们就不再有效。如果你希望TargetActor记住之前的有效目标(们),那么你需要去自己实现自定义的TargetActor类。我称其为永久目标,因为他们的存在会一直持续到TargetActor接收到目标的确认和取消事件,或者是TargetActor找到了一个新的有效目标,还有或者是目标不再有效(即其被销毁了)。GASShooter项目中将这种方式应用到火箭发射器的第二个技能上。

4.11.3 目标数据过滤器 - Target Data Filters

通过使用Make GameplayTargetDataFilterMake Filter Handle节点,你可以过滤玩家的Pawn或者只去选择某个特定的类。如果你需要更多高级的过滤效果,你可以在FGameplayTargetDataFilter基础上进一步派生,并且重写其中的FilterPassesForActor函数。

USTRUCT(BlueprintType)
struct GASDOCUMENTATION_API FGDNameTargetDataFilter : public FGameplayTargetDataFilter
{
	GENERATED_BODY()

	/** Returns true if the actor passes the filter and will be targeted */
	virtual bool FilterPassesForActor(const AActor* ActorToBeFiltered) const override;
};

但是,这并不会直接令其在Wait Target Data节点上生效,因为它还需要一个FGameplayTargetDataFilterHandle。还需要再构建一个新的自定义的Make Filter Handle来收扩展出来的派生类:

FGameplayTargetDataFilterHandle UGDTargetDataFilterBlueprintLibrary::MakeGDNameFilterHandle(FGDNameTargetDataFilter Filter, AActor* FilterActor)
{
	FGameplayTargetDataFilter* NewFilter = new FGDNameTargetDataFilter(Filter);
	NewFilter->InitializeFilterContext(FilterActor);

	FGameplayTargetDataFilterHandle FilterHandle;
	FilterHandle.Filter = TSharedPtr<FGameplayTargetDataFilter>(NewFilter);
	return FilterHandle;
}

4.11.4 游戏技能的世界标线 - Gameplay Ability World Reticles

AGameplayAbilityWorldReticles,即Reticles,当你利用非Instant确定出TargetActors,它会帮你从视觉表现方面显示你当前正在选择的目标。TargetActors会负责Reticles从生成到销毁的整个生命周期。Reticles本质上是AActors,因此他们可以使用任意类型的可视化组件。GASShooter实现了一个它的常见的用法,即使用WidgetComponent来在屏幕空间中显示一个UMG Widget类型的控件(总是朝向玩家摄像机)。Reticles并不知道他们的目标是具体哪一个AActor,但是你可以在自定义的TargetActor进一步实现这个功能。TargetActors通常会在每个Tick()内去更新Reticle的位置到目标的位置(译者注:目标位置的世界控空间位置转到屏幕空间的二维空间位置)。

GASShooter中使用Reticles来显示被火箭发射器的第二个技能锁定的目标。敌人身上的红色的指示图标就是Reticle。而相同样式的白色的那个则是火箭发射器的准星。
Reticles in GASShooter

Reticles提供了一些对设计者可能有用的BlueprintImplementableEvents(他们就是被设计来在蓝图中进行拓展开发的):

/** Called whenever bIsTargetValid changes value. */
UFUNCTION(BlueprintImplementableEvent, Category = Reticle)
void OnValidTargetChanged(bool bNewValue);

/** Called whenever bIsTargetAnActor changes value. */
UFUNCTION(BlueprintImplementableEvent, Category = Reticle)
void OnTargetingAnActor(bool bNewValue);

UFUNCTION(BlueprintImplementableEvent, Category = Reticle)
void OnParametersInitialized();

UFUNCTION(BlueprintImplementableEvent, Category = Reticle)
void SetReticleMaterialParamFloat(FName ParamName, float value);

UFUNCTION(BlueprintImplementableEvent, Category = Reticle)
void SetReticleMaterialParamVector(FName ParamName, FVector value);

Reticles也可以使用由TargetActor提供的FWorldReticleParameters来进行配置。默认的结构体只提供了一个变量FVector AOEScale。尽管你可以去派生这个结构体,但是TargetActor只会去接收默认的基类结构体。从设计层面看,默认的TargetActorFWorldReticleParameters上的限制可能看起来显得有些目光短浅。但是,如果你自定义了自己的TargetActor,你也可以提供你自己自定义的标线参数结构体,然后在生成他们的时候手动将其传递给你的AGameplayAbilityWorldReticles的派生类中。

Reticles默认情况下不会被复制,但是如果你的游戏有类似的需求,比如给其他玩家显示本地玩家锁定的敌人是你的游戏的设计之一的话,也可以将复制的选项打开。

默认的TargetActors中,Reticles只会在当前有效的目标上显示。例如,如果你使用了一个AGameplayAbilityTargetActor_SingleLineTrace来检测一个目标,那么Reticle将只会在目标直接暴露在检测路径上时出现。如果你看向别的什么地方,那么此时敌人就不再是一个有效的目标,那么Reticle就自动消失了。如果你希望Reticle停留在最后一个有效目标身上,你就需要自定义你的TargetActor,让其记住最后一个有效目标并保留Reticle到目标身上。我建议是将他们作为永久目标,因为他们会一直存在直到TargetActor接收到确认或者取消的指令,或者TargetActor通过检测找到了一个新的有效目标,或者目标不再有效(被销毁了)。GASShooter项目中就应用这个方案到火箭发射器的第二个技能上了。

4.11.5 游戏效果容器的目标 - Gameplay Effect Containers Targeting

GameplayEffectContainers中提供了一个可选的,高效的生成TargetData的方法。在客户端和服务器上应用EffectContainer时就会立马进行目标的选择。这个方法要远比TargetActors的方式更加高效,因为它是直接运行在目标选择的对象的CDO(译者注:Class Default Object,可以理解为类的一个默认单例对象)上的,相对于需要去生成和销毁的Actors对象来说当然更加高效。但是它会缺少玩家的输入,没有确认取消的过程,也不能够从客户端往服务器发送数据(因为它在两端都会执行)。对于立即触发的射线或者碰撞检测来说这非常有效。Epic的Action RPG Sample Project项目中,在其容器中包含了两种类型的目标选择——选择技能的释放者,以及从某个事件中获取到TargetData。它也在蓝图中实现了一个去做球体的轨迹检测。你可以派生URPGTargetType来制作你自己的目标类型。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值