Targeting
技能系統(tǒng)中很重要的一環(huán)就是目標(biāo)的選取,比如LOL中的各色技能。

在GameplayAbilities and You 中,將Targeting放在了Advanced話題中。我主要參考這篇文章來講GAS中的Targeting。
文中以Wait Target Data這個(gè)AbilityTask為例。這個(gè)task在GAS中有舉足輕重的地位,原因如:
- 提供系統(tǒng)用以可視化技能目標(biāo)選擇
- 提供玩家發(fā)送信息到服務(wù)器的框架(Client to Server)

Wait Target Data通常放在Commit Ability之前,玩家在確認(rèn)釋放技能前可以看到技能指示器(參考LOL)。
Class必須是
GameplayAbilityTargetActor的子類。共有4種Confirmation type,后兩種是Custom,默認(rèn)提供的是Instant和User Confirmed。User Confirmed相較于Instant來說,需要額外調(diào)用UAbilitySystemComponent::TargetConfirm()或?qū)omfirm綁定在輸入上,取消也是類似的,需要調(diào)用UAbilitySystemComponent::TargetCancel(),或?qū)ancel綁定在輸入上。最后記得不要忘記調(diào)用EndAbility。
需要注意,task右側(cè)的引腳在客戶端和服務(wù)端都有效,Valid Data Delegate都會(huì)調(diào)用。后端關(guān)于它的實(shí)現(xiàn)采取一種比較有趣的方式: task會(huì)生成actor(GameplayAbilityTargetActor)實(shí)例,而這些實(shí)例不是replicated。(應(yīng)該是客戶端和服務(wù)端都生成actor)實(shí)際上,服務(wù)端通過AbilitySystemComponent將數(shù)據(jù)發(fā)送給客戶端。這種方案導(dǎo)致targeting actor的設(shè)置有些奇特。(暫時(shí)我還沒get到)
Target Actor
/**
* TargetActors are spawned to assist with ability targeting. They are spawned by ability tasks and create/determine the outgoing targeting data passed from one task to another
*
* WARNING: These actors are spawned once per ability activation and in their default form are not very efficient
* For most games you will need to subclass and heavily modify this actor, or you will want to implement similar functions in a game-specific actor or blueprint to avoid actor spawn costs
* This class is not well tested by internal games, but it is a useful class to look at to learn how target replication occurs
*/
我們通過繼承AGameplayAbilityTargetActor創(chuàng)建自定義的TargetActor。其中,有兩個(gè)主要的函數(shù)需要我們重寫:
virtual void StartTargeting(UGameplayAbility* Ability) overridevirtual void ConfirmTargetingAndContinue() override
StartTarget
在這里,你可以訪問Ability實(shí)例,因此你可以使用Ability類里的數(shù)據(jù)。比如:假如你有一個(gè)造墻的技能,那么在目標(biāo)選擇時(shí),需要從Ability實(shí)例中得到墻的mesh數(shù)據(jù),這樣TargetActor才能正確顯示墻體指示器。Ability實(shí)例中還包含釋放該技能的Character引用,因此TargetActor還能訪問到Character的信息(人物的tag和attribute可能影響targeting)。
ConfirmTargetingAndContinue
這里有些較難理解的地方,但是我們化繁為簡(jiǎn),這個(gè)函數(shù)最核心的功能是調(diào)用TargetDataReadyDelegate,同時(shí)傳遞攜帶包含我們target data的負(fù)載。因此,如果我們想傳遞兩個(gè)transforms,分別包含技能施放者位置和目標(biāo)物體位置,那么代碼如下:
// 繼承自FGameplayAbilityTargetData
FGameplayAbilityTargetData_LocationInfo *ReturnData = new FGameplayAbilityTargetData_LocationInfo();
// Source Transform
ReturnData->SourceLocation.LocationType = EGameplayAbilityTargetingLocationType::LiteralTransform;
ReturnData->SourceLocation.LiteralTransform = FTransform(SourceLocation);
// Destination Transform
ReturnData->TargetLocation.LocationType = EGameplayAbilityTargetingLocationType::LiteralTransform;
ReturnData->TargetLocation.LiteralTransform = FTransform((TargetLocation - SourceLocation).ToOrientationQuat(), TargetLocation);
// Handle !! 這就是我們?yōu)槭裁磏ew了卻不要delete的原因,里面用了智能指針
FGameplayAbilityTargetDataHandle Handle(ReturnData);
// Fire delegate with data handle !!!
TargetDataReadyDelegate.Broadcast(Handle);
關(guān)鍵的數(shù)據(jù)結(jié)構(gòu)是FGameplayAbilityTargetData(你的TargetingSystem運(yùn)行后最終的結(jié)果數(shù)據(jù)),引擎里已經(jīng)提供了一系列的子類用于不同的需求:
- FGameplayAbilityTargetData_LocationInfo :包含最多兩個(gè)位置信息數(shù)據(jù)
- FGameplayAbilityTargetData_ActorArray : 包含一個(gè)Actor數(shù)組
- FGameplayAbilityTargetData_SingleTargetHit : 包含一個(gè)碰撞結(jié)果(HitResult)
特別注意: 這個(gè)方法是將數(shù)據(jù)從Client端發(fā)往Server端。雖然Server端也有一個(gè)版本的數(shù)值,但它并不準(zhǔn)確。因?yàn)槭菑腃lient端發(fā)往Server端,所以數(shù)據(jù)有被用戶篡改的風(fēng)險(xiǎn),記得要添加驗(yàn)證。
接下來我們自定義一個(gè)FGameplayAbilityTargetData類,它包含兩個(gè)location,一個(gè)float,一個(gè)int。這個(gè)數(shù)據(jù)類依舊是用于造墻技能,只是這次,我們用鼠標(biāo)按下的時(shí)間(float)來決定墻的高度。代碼如下:
USTRUCT(BlueprintType)
struct FGameplayAbilityCastingTargetingLocationInfo: public FGameplayAbilityTargetData
{
GENERATED_USTRUCT_BODY()
/**
如果沒記錯(cuò),屬性需要標(biāo)記UPROPERTY(),才能被序列化
*/
/** Amount of time the ability has been charged */
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = Targeting)
float ChargeTime;
/** The ID of the Ability that is performing targeting */
UPROPERTY()
uint32 UniqueID;
/** Generic location data for source */
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = Targeting)
FGameplayAbilityTargetingLocationInfo SourceLocation;
/** Generic location data for target */
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = Targeting)
FGameplayAbilityTargetingLocationInfo TargetLocation;
// -------------------------------------
virtual bool HasOrigin() const override
{
return true;
}
virtual FTransform GetOrigin() const override
{
return SourceLocation.GetTargetingTransform();
}
// -------------------------------------
virtual bool HasEndPoint() const override
{
return true;
}
virtual FVector GetEndPoint() const override
{
return TargetLocation.GetTargetingTransform().GetLocation();
}
virtual FTransform GetEndPointTransform() const override
{
return TargetLocation.GetTargetingTransform();
}
// -------------------------------------
virtual UScriptStruct* GetScriptStruct() const override
{
return FGameplayAbilityCastingTargetingLocationInfo::StaticStruct();
}
virtual FString ToString() const override
{
return TEXT("FGameplayAbilityCastingTargetingLocationInfo");
}
bool NetSerialize(FArchive& Ar, class UPackageMap* Map, bool& bOutSuccess);
};
/** 這一部分具體的運(yùn)作機(jī)制不明,但必須要有 */
template<>
struct TStructOpsTypeTraits<FGameplayAbilityCastingTargetingLocationInfo> : public TStructOpsTypeTraitsBase2<FGameplayAbilityCastingTargetingLocationInfo>
{
enum
{
WithNetSerializer = true // For now this is REQUIRED for FGameplayAbilityTargetDataHandle net serialization to work
};
};
NetSerialize的實(shí)現(xiàn):
bool FGameplayAbilityCastingTargetingLocationInfo::NetSerialize(FArchive& Ar, class UPackageMap* Map, bool& bOutSuccess)
{
SourceLocation.NetSerialize(Ar, Map, bOutSuccess);
TargetLocation.NetSerialize(Ar, Map, bOutSuccess);
Ar << ChargeTime;
Ar << UniqueID;
bOutSuccess = true;
return true;
}
最后,正如官方頭文件所述,TargetActor默認(rèn)是每執(zhí)行一次Ability都會(huì)創(chuàng)建,然后銷毀。為了提高性能,要考慮重用TargetActor。當(dāng)然,你甚至可以不使用TargetActor,ActionRPG中就是這樣。對(duì)于造墻這種技能,墻體指示器可能要跟隨玩家的移動(dòng),所以你需要保存在StartTarget中獲得Character的指針或引用,然后在TargetActor的Tick函數(shù)中獲取Character的位置信息來更新墻體指示器的位置。
RPGTargetType.h/cpp
我們終于可以回到ActionRPG項(xiàng)目上來了。很不幸的是,前面說了一大段的知識(shí)點(diǎn)。這個(gè)項(xiàng)目并沒有用到,而是另辟蹊徑。
頭文件:
// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved.
#pragma once
#include "ActionRPG.h"
#include "Abilities/GameplayAbilityTypes.h"
#include "Abilities/RPGAbilityTypes.h"
#include "RPGTargetType.generated.h"
class ARPGCharacterBase;
class AActor;
struct FGameplayEventData;
/**
* Class that is used to determine targeting for abilities
* It is meant to be blueprinted to run target logic
* This does not subclass GameplayAbilityTargetActor because this class is never instanced into the world
* This can be used as a basis for a game-specific targeting blueprint
* If your targeting is more complicated you may need to instance into the world once or as a pooled actor
*/
UCLASS(Blueprintable, meta = (ShowWorldContextPin))
class ACTIONRPG_API URPGTargetType : public UObject
{
GENERATED_BODY()
public:
// Constructor and overrides
URPGTargetType() {}
/** Called to determine targets to apply gameplay effects to */
UFUNCTION(BlueprintNativeEvent)
void GetTargets(ARPGCharacterBase* TargetingCharacter, AActor* TargetingActor, FGameplayEventData EventData, TArray<FHitResult>& OutHitResults, TArray<AActor*>& OutActors) const;
};
/** Trivial target type that uses the owner */
UCLASS(NotBlueprintable)
class ACTIONRPG_API URPGTargetType_UseOwner : public URPGTargetType
{
GENERATED_BODY()
public:
// Constructor and overrides
URPGTargetType_UseOwner() {}
/** Uses the passed in event data */
virtual void GetTargets_Implementation(ARPGCharacterBase* TargetingCharacter, AActor* TargetingActor, FGameplayEventData EventData, TArray<FHitResult>& OutHitResults, TArray<AActor*>& OutActors) const override;
};
/** Trivial target type that pulls the target out of the event data */
UCLASS(NotBlueprintable)
class ACTIONRPG_API URPGTargetType_UseEventData : public URPGTargetType
{
GENERATED_BODY()
public:
// Constructor and overrides
URPGTargetType_UseEventData() {}
/** Uses the passed in event data */
virtual void GetTargets_Implementation(ARPGCharacterBase* TargetingCharacter, AActor* TargetingActor, FGameplayEventData EventData, TArray<FHitResult>& OutHitResults, TArray<AActor*>& OutActors) const override;
};
源文件:
// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved.
#include "Abilities/RPGTargetType.h"
#include "Abilities/RPGGameplayAbility.h"
#include "RPGCharacterBase.h"
void URPGTargetType::GetTargets_Implementation(ARPGCharacterBase* TargetingCharacter, AActor* TargetingActor, FGameplayEventData EventData, TArray<FHitResult>& OutHitResults, TArray<AActor*>& OutActors) const
{
return;
}
void URPGTargetType_UseOwner::GetTargets_Implementation(ARPGCharacterBase* TargetingCharacter, AActor* TargetingActor, FGameplayEventData EventData, TArray<FHitResult>& OutHitResults, TArray<AActor*>& OutActors) const
{
OutActors.Add(TargetingCharacter);
}
void URPGTargetType_UseEventData::GetTargets_Implementation(ARPGCharacterBase* TargetingCharacter, AActor* TargetingActor, FGameplayEventData EventData, TArray<FHitResult>& OutHitResults, TArray<AActor*>& OutActors) const
{
const FHitResult* FoundHitResult = EventData.ContextHandle.GetHitResult();
if (FoundHitResult)
{
OutHitResults.Add(*FoundHitResult);
}
else if (EventData.Target)
{
OutActors.Add(const_cast<AActor*>(EventData.Target));
}
}
不同于TargetActor,這里定義的TargetType(繼承自UObject)不會(huì)被也不能實(shí)例化到World中(Actor才能被放置到World中)。