六、RPGTargetType.h/cpp & Targeting

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)提供的是InstantUser ConfirmedUser 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) override
  • virtual 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中)。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容