美文网首页
六、RPGTargetType.h/cpp & Targetin

六、RPGTargetType.h/cpp & Targetin

作者: 珏_Gray | 来源:发表于2019-06-13 10:16 被阅读0次

    Targeting

    技能系统中很重要的一环就是目标的选取,比如LOL中的各色技能。


    GameplayAbilities and You 中,将Targeting放在了Advanced话题中。我主要参考这篇文章来讲GAS中的Targeting。
    文中以Wait Target Data这个AbilityTask为例。这个task在GAS中有举足轻重的地位,原因如:

    • 提供系统用以可视化技能目标选择
    • 提供玩家发送信息到服务器的框架(Client to Server)

    Wait Target Data通常放在Commit Ability之前,玩家在确认释放技能前可以看到技能指示器(参考LOL)。

    Class必须是GameplayAbilityTargetActor的子类。共有4种Confirmation type,后两种是Custom,默认提供的是InstantUser ConfirmedUser Confirmed相较于Instant来说,需要额外调用UAbilitySystemComponent::TargetConfirm()或将comfirm绑定在输入上,取消也是类似的,需要调用UAbilitySystemComponent::TargetCancel(),或将cancel绑定在输入上。最后记得不要忘记调用EndAbility

    需要注意,task右侧的引脚在客户端和服务端都有效,Valid Data Delegate都会调用。后端关于它的实现采取一种比较有趣的方式: task会生成actor(GameplayAbilityTargetActor)实例,而这些实例不是replicated。(应该是客户端和服务端都生成actor)实际上,服务端通过AbilitySystemComponent将数据发送给客户端。这种方案导致targeting actor的设置有些奇特。(暂时我还没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创建自定义的TargetActor。其中,有两个主要的函数需要我们重写:

    • virtual void StartTargeting(UGameplayAbility* Ability) override
    • virtual void ConfirmTargetingAndContinue() override

    StartTarget

    在这里,你可以访问Ability实例,因此你可以使用Ability类里的数据。比如:假如你有一个造墙的技能,那么在目标选择时,需要从Ability实例中得到墙的mesh数据,这样TargetActor才能正确显示墙体指示器。Ability实例中还包含释放该技能的Character引用,因此TargetActor还能访问到Character的信息(人物的tag和attribute可能影响targeting)。

    ConfirmTargetingAndContinue

    这里有些较难理解的地方,但是我们化繁为简,这个函数最核心的功能是调用TargetDataReadyDelegate,同时传递携带包含我们target data的负载。因此,如果我们想传递两个transforms,分别包含技能施放者位置和目标物体位置,那么代码如下:

    // 继承自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 !! 这就是我们为什么new了却不要delete的原因,里面用了智能指针
    FGameplayAbilityTargetDataHandle Handle(ReturnData);
    // Fire delegate with data handle !!!
    TargetDataReadyDelegate.Broadcast(Handle);
    

    关键的数据结构是FGameplayAbilityTargetData(你的TargetingSystem运行后最终的结果数据),引擎里已经提供了一系列的子类用于不同的需求:

    • FGameplayAbilityTargetData_LocationInfo :包含最多两个位置信息数据
    • FGameplayAbilityTargetData_ActorArray : 包含一个Actor数组
    • FGameplayAbilityTargetData_SingleTargetHit : 包含一个碰撞结果(HitResult)

    特别注意: 这个方法是将数据从Client端发往Server端。虽然Server端也有一个版本的数值,但它并不准确。因为是从Client端发往Server端,所以数据有被用户篡改的风险,记得要添加验证。

    接下来我们自定义一个FGameplayAbilityTargetData类,它包含两个location,一个float,一个int。这个数据类依旧是用于造墙技能,只是这次,我们用鼠标按下的时间(float)来决定墙的高度。代码如下:

    USTRUCT(BlueprintType)
    struct FGameplayAbilityCastingTargetingLocationInfo: public FGameplayAbilityTargetData
    {
        GENERATED_USTRUCT_BODY()
    
            /**
              如果没记错,属性需要标记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);
    };
    
    /**  这一部分具体的运作机制不明,但必须要有 */
    template<>
    struct TStructOpsTypeTraits<FGameplayAbilityCastingTargetingLocationInfo> : public TStructOpsTypeTraitsBase2<FGameplayAbilityCastingTargetingLocationInfo>
    {
        enum
        {
            WithNetSerializer = true    // For now this is REQUIRED for FGameplayAbilityTargetDataHandle net serialization to work
        };
    };
    

    NetSerialize的实现:

    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默认是每执行一次Ability都会创建,然后销毁。为了提高性能,要考虑重用TargetActor。当然,你甚至可以不使用TargetActor,ActionRPG中就是这样。对于造墙这种技能,墙体指示器可能要跟随玩家的移动,所以你需要保存在StartTarget中获得Character的指针或引用,然后在TargetActor的Tick函数中获取Character的位置信息来更新墙体指示器的位置。


    RPGTargetType.h/cpp

    我们终于可以回到ActionRPG项目上来了。很不幸的是,前面说了一大段的知识点。这个项目并没有用到,而是另辟蹊径。

    头文件:

    // 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)不会被也不能实例化到World中(Actor才能被放置到World中)。

    相关文章

      网友评论

          本文标题:六、RPGTargetType.h/cpp & Targetin

          本文链接:https://www.haomeiwen.com/subject/dellfctx.html