六、RPGTargetType.h/cpp & Targeting

Targeting

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


六、RPGTargetType.h/cpp & Targeting_第1张图片

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

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

六、RPGTargetType.h/cpp & Targeting_第2张图片

Wait Target Data通常放在 Commit Ability之前,玩家在确认释放技能前可以看到技能指示器(参考LOL)。
六、RPGTargetType.h/cpp & Targeting_第3张图片

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 : public TStructOpsTypeTraitsBase2
{
    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& OutHitResults, TArray& 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& OutHitResults, TArray& 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& OutHitResults, TArray& 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& OutHitResults, TArray& OutActors) const
{
    return;
}

void URPGTargetType_UseOwner::GetTargets_Implementation(ARPGCharacterBase* TargetingCharacter, AActor* TargetingActor, FGameplayEventData EventData, TArray& OutHitResults, TArray& OutActors) const
{
    OutActors.Add(TargetingCharacter);
}

void URPGTargetType_UseEventData::GetTargets_Implementation(ARPGCharacterBase* TargetingCharacter, AActor* TargetingActor, FGameplayEventData EventData, TArray& OutHitResults, TArray& OutActors) const
{
    const FHitResult* FoundHitResult = EventData.ContextHandle.GetHitResult();
    if (FoundHitResult)
    {
        OutHitResults.Add(*FoundHitResult);
    }
    else if (EventData.Target)
    {
        OutActors.Add(const_cast(EventData.Target));
    }
}

不同于TargetActor,这里定义的TargetType(继承自UObject)不会被也不能实例化到World中(Actor才能被放置到World中)。

你可能感兴趣的:(六、RPGTargetType.h/cpp & Targeting)