前言
使用GAS时一定会用到GE,那么就会面临
- 巨量GE
- 维护困难
- 重复性配置工作
- 无法通过简单配置多属性与临时值混合计算
一. GE实例与CDO Apply的区别
CDO直接Apply会比较直观和方便
GE实例Apply可以合并一些较小差异的GE,避免大量GE文件
二. GE属性值捕捉方案
因为GE的CDO上只能支持一些简单的属性计算配置,要想实现复杂一些的,就需要自己去实现GEEC或者MMC,而实现计算,首先就需要对属性值进行捕捉。以下是可实行的一些方案。
方案1(官方):直接在GEEC或MMC的h文件中定义结构体,静态确定需要捕获的属性值
- 实现思路
- 定义结构体GDDamageStatics,声明需要捕捉的属性值
- 声明静态方法DamageStatics
- 在GEEC或MMC的构造函数中往关联属性数组中添加需要捕获的属性定义
- 参考代码
// Declare the attributes to capture and define how we want to capture them from the Source and Target.
struct GDDamageStatics
{
DECLARE_ATTRIBUTE_CAPTUREDEF(Armor);
DECLARE_ATTRIBUTE_CAPTUREDEF(PhysicsAttack)
DECLARE_ATTRIBUTE_CAPTUREDEF(Damage);
GDDamageStatics()
{
// Snapshot happens at time of GESpec creation
// We're not capturing anything from the Source in this example, but there could be like AttackPower attributes that you might want.
// Capture optional Damage set on the damage GE as a CalculationModifier under the ExecutionCalculation
DEFINE_ATTRIBUTE_CAPTUREDEF(UGDAttributeSetBase, Damage, Source, true);
DEFINE_ATTRIBUTE_CAPTUREDEF(UGDAttributeSetBase, PhysicsAttack, Source, false);
// Capture the Target's Armor. Don't snapshot.
DEFINE_ATTRIBUTE_CAPTUREDEF(UGDAttributeSetBase, Armor, Target, false);
}
};
static const GDDamageStatics& DamageStatics()
{
static GDDamageStatics DStatics;
return DStatics;
}
UGDDamageExecCalculation::UGDDamageExecCalculation()
{
RelevantAttributesToCapture.Add(DamageStatics().DamageDef);
RelevantAttributesToCapture.Add(DamageStatics().ArmorDef);
RelevantAttributesToCapture.Add(DamageStatics().PhysicsAttackDef);
}
方案2:通过Caller逐一传入
因为GE实例,可以通过Caller来进行float值的设置与获取
- 实现思路
- 创建GESpecHandle
- 封装一个需要传递的数据结构
- 封装通过Caller传入,传出解析数据结构的函数(进一步可以使用反射来实现)
- 创建GESpecHandle
- 参考代码
USTRUCT()
struct FCustomAttributeStruct
{
GENERATED_USTRUCT_BODY()
public:
float HP;
float MP;
float PhyAttack;
};
void UCalmerBlueprintLibrary::SetCustomAttribute(const FGameplayEffectSpecHandle& Handle,
const FCustomAttributeStruct& AttributeStruct)
{
Handle.Data->SetSetByCallerMagnitude(TEXT("HP"), AttributeStruct.HP);
Handle.Data->SetSetByCallerMagnitude(TEXT("MP"), AttributeStruct.MP);
Handle.Data->SetSetByCallerMagnitude(TEXT("PhyAttack"), AttributeStruct.PhyAttack);
}
void UCalmerBlueprintLibrary::GetCustomAttribute(const FGameplayEffectSpecHandle& Handle,
FCustomAttributeStruct& AttributeStruct)
{
AttributeStruct.HP = Handle.Data->GetSetByCallerMagnitude(TEXT("HP"));
AttributeStruct.MP = Handle.Data->GetSetByCallerMagnitude(TEXT("MP"));
AttributeStruct.PhyAttack = Handle.Data->GetSetByCallerMagnitude(TEXT("PhyAttack"));
}
方案3:通过EffectContext传递(推荐)
坑点:赋值时序可能导致SourceObject值未被设置,可能是蓝图蜜糖陷阱导致
1.通过SourceObject(简单)
- 自定义UObject
UCLASS(BlueprintType)
class GASDOCUMENTATION_API UEffectSourceDataObject : public UObject
{
GENERATED_BODY()
public:
UPROPERTY(BlueprintReadWrite)
float Damage;
UPROPERTY(BlueprintReadWrite)
float PhyAttack;
UPROPERTY(BlueprintReadWrite)
float DamageMagnification;
};
- 封装蓝图方法
void UGDBlueprintLibrary::SetEffectContextSourceObject(FGameplayEffectContextHandle GameplayEffectContextHandle,
UObject* Object)
{
GameplayEffectContextHandle.Get()->AddSourceObject(Object);
}
2. 自定义GameplayEffectContext, 继承GameplayEffectContext, 并重写Globals中的分配函数
- SubClass GameplayEffectContext
FCaGamePlayEffectContext.h
USTRUCT()
struct FCaGamePlayEffectContext : public FGameplayEffectContext
{
GENERATED_USTRUCT_BODY()
public:
virtual FGameplayAbilityTargetDataHandle GetTargetData()
{
return TargetData;
}
virtual void AddTargetData(const FGameplayAbilityTargetDataHandle& TargetDataHandle)
{
TargetData.Append(TargetDataHandle);
}
virtual FCommonTargetData* GetCommonData()
{
return CommonTargetData.Get();
}
virtual const FCommonTargetData* GetCommonData() const
{
return const_cast<FCaGamePlayEffectContext*>(this)->GetCommonData();
}
virtual void AddCommonTargetData(const FCommonTargetData& InCommonTargetData)
{
if (CommonTargetData.IsValid())
{
CommonTargetData.Reset();
}
check(!CommonTargetData.IsValid());
CommonTargetData = TSharedPtr<FCommonTargetData>(new FCommonTargetData(InCommonTargetData));
}
virtual UScriptStruct* GetScriptStruct() const override
{
return FCaGamePlayEffectContext::StaticStruct();
}
virtual FCaGamePlayEffectContext* Duplicate() const override
{
FCaGamePlayEffectContext* NewContext = new FCaGamePlayEffectContext();
*NewContext = *this;
NewContext->AddActors(Actors);
if (GetHitResult())
{
// Does a deep copy of the hit result
NewContext->AddHitResult(*GetHitResult(), true);
}
//参考HitResult
if(GetCommonData())
{
NewContext->AddCommonTargetData(*GetCommonData());
}
// Shallow copy of TargetData, is this okay?
NewContext->TargetData.Append(TargetData);
return NewContext;
}
virtual bool NetSerialize(FArchive& Ar, class UPackageMap* Map, bool& bOutSuccess) override;
protected:
FGameplayAbilityTargetDataHandle TargetData;
TSharedPtr<FCommonTargetData> CommonTargetData;
};
template<>
struct TStructOpsTypeTraits< FCaGamePlayEffectContext > : public TStructOpsTypeTraitsBase2< FCaGamePlayEffectContext >
{
enum
{
WithNetSerializer = true,
WithCopy = true // Necessary so that TSharedPtr<FHitResult> Data is copied around
};
};
注意: FCommonTargetData为自定义数据结构体, FGameplayAbilityTargetDataHandle为官方提供结构体
.cpp
bool FCaGamePlayEffectContext::NetSerialize(FArchive& Ar, UPackageMap* Map, bool& bOutSuccess)
{
return Super::NetSerialize(Ar, Map, bOutSuccess) && TargetData.NetSerialize(Ar, Map, bOutSuccess);
}
- 继承UAbilitySystemGlobals,修改配置
UCaAbilitySystemGlobals.h
#pragma once
#include "CoreMinimal.h"
#include "AbilitySystemGlobals.h"
#include "CaAbilitySystemGlobals.generated.h"
UCLASS()
class GASDOCUMENTATION_API UCaAbilitySystemGlobals : public UAbilitySystemGlobals
{
GENERATED_BODY()
public:
UCaAbilitySystemGlobals();
static UCaAbilitySystemGlobals& GSGet()
{
return dynamic_cast<UCaAbilitySystemGlobals&>(Get());
}
virtual FGameplayEffectContext* AllocGameplayEffectContext() const override;
};
.cpp
#include "GAS/CaAbilitySystemGlobals.h"
#include "Data/EffectSourceDataObject.h"
UCaAbilitySystemGlobals::UCaAbilitySystemGlobals()
{
}
FGameplayEffectContext* UCaAbilitySystemGlobals::AllocGameplayEffectContext() const
{
return new FCaGamePlayEffectContext();
}
配置修改
[/Script/GameplayAbilities.AbilitySystemGlobals]
GameplayCueNotifyPaths="/Game/GASDocumentation/Characters"
AbilitySystemGlobalsClassName="/Script/GASDocumentation.CaAbilitySystemGlobals"
- 自定义数据结构,参考HitResult
USTRUCT(BlueprintType)
struct FCommonTargetData:public FGameplayAbilityTargetData
{
GENERATED_USTRUCT_BODY()
public:
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = Targeting)
float Damage;
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = Targeting)
float PhyAttack;
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = Targeting)
float DamageMagnification;
// FCommonTargetData& operator=(FCommonTargetData&& Other) { Damage = Other.Damage; PhyAttack= Other.PhyAttack; DamageMagnification = Other.DamageMagnification; return *this; }
// FCommonTargetData& operator=(const FCommonTargetData& Other) { Damage = Other.Damage; PhyAttack= Other.PhyAttack; DamageMagnification = Other.DamageMagnification; return *this; }
virtual UScriptStruct* GetScriptStruct() const override
{
return FCommonTargetData::StaticStruct();
}
virtual FString ToString() const override
{
return TEXT("FCommonTargetData");
}
bool NetSerialize(FArchive& Ar, class UPackageMap* Map, bool& bOutSuccess);
};
template<>
struct TStructOpsTypeTraits<FCommonTargetData> : public TStructOpsTypeTraitsBase2<FCommonTargetData>
{
enum
{
WithNetSerializer = true // For now this is REQUIRED for FGameplayAbilityTargetDataHandle net serialization to work
};
};
- 添加蓝图静态方法,以添加自定义数据
.h
#pragma once
#include "CoreMinimal.h"
#include "Kismet/BlueprintFunctionLibrary.h"
#include "GameplayEffect.h"
#include "Data/EffectSourceDataObject.h"
#include "GDBlueprintLibrary.generated.h"
/**
*
*/
UCLASS()
class GASDOCUMENTATION_API UGDBlueprintLibrary : public UBlueprintFunctionLibrary
{
GENERATED_BODY()
public:
//方案1:通过SourceObject在context中去传递内容
UFUNCTION(BlueprintCallable)
static void SetEffectContextSourceObject(FGameplayEffectContextHandle GameplayEffectContextHandle, UObject* Object);
//通过自定义Context,自定义字段传递
UFUNCTION(BlueprintCallable)
static void SetEffectContextCommonTargetData(FGameplayEffectContextHandle GameplayEffectContextHandle,
const FCommonTargetData& CommonTargetData);
UFUNCTION(BlueprintCallable)
static void AddEffectContextTargetData(FGameplayEffectContextHandle GameplayEffectContextHandle,
const FGameplayAbilityTargetDataHandle& GameplayAbilityTargetDataHandle);
UFUNCTION(BlueprintCallable)
static FGameplayAbilityTargetDataHandle GetEffectContextTargetData(
FGameplayEffectContextHandle GameplayEffectContextHandle);
};
.cpp
#include "GDBlueprintLibrary.h"
void UGDBlueprintLibrary::SetEffectContextSourceObject(FGameplayEffectContextHandle GameplayEffectContextHandle,
UObject* Object)
{
GameplayEffectContextHandle.Get()->AddSourceObject(Object);
}
void UGDBlueprintLibrary::SetEffectContextCommonTargetData(FGameplayEffectContextHandle GameplayEffectContextHandle,
const FCommonTargetData& CommonTargetData)
{
FCaGamePlayEffectContext* CaGamePlayEffectContext = static_cast<FCaGamePlayEffectContext*>(GameplayEffectContextHandle.Get());
if(CaGamePlayEffectContext)
{
CaGamePlayEffectContext->AddCommonTargetData(CommonTargetData);
}
}
void UGDBlueprintLibrary::AddEffectContextTargetData(FGameplayEffectContextHandle GameplayEffectContextHandle,
const FGameplayAbilityTargetDataHandle& GameplayAbilityTargetDataHandle)
{
FCaGamePlayEffectContext* CaGamePlayEffectContext = static_cast<FCaGamePlayEffectContext*>(GameplayEffectContextHandle.Get());
if (CaGamePlayEffectContext)
{
CaGamePlayEffectContext->AddTargetData(GameplayAbilityTargetDataHandle);
}
}
FGameplayAbilityTargetDataHandle UGDBlueprintLibrary::GetEffectContextTargetData(
FGameplayEffectContextHandle GameplayEffectContextHandle)
{
FCaGamePlayEffectContext* CaGamePlayEffectContext = static_cast<FCaGamePlayEffectContext*>(GameplayEffectContextHandle.Get());
if (CaGamePlayEffectContext)
{
return CaGamePlayEffectContext->GetTargetData();
}
return FGameplayAbilityTargetDataHandle();
}
三. GE MMC与GEEC的封装
1. MMC的封装(单属性修改规则)
.h文件
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "GameplayModMagnitudeCalculation.h"
#include "WMModMagnitudeCalculationBase.generated.h"
/**
*
*/
UCLASS()
class WATCHMAN_API UWMModMagnitudeCalculationBase : public UGameplayModMagnitudeCalculation
{
GENERATED_BODY()
public:
FGameplayEffectSpec* GameplayEffectSpec;
virtual float CalculateBaseMagnitude_Implementation(const FGameplayEffectSpec& Spec) const override;
UFUNCTION(BlueprintCallable, BlueprintPure, Category="UWMModMagnitudeCalculationBase", meta=(HideSelfPin="true", AdvancedDisplay="bSnapshot"))
bool GetCapturedAttr(FGameplayAttribute Attribute, EGameplayEffectAttributeCaptureSource CaptureSource, float& Value, bool bSnapshot=true);
UFUNCTION(BlueprintCallable, BlueprintPure, Category="UWMModMagnitudeCalculationBase", meta=(HideSelfPin="true"))
void GetGESpec(FGameplayEffectSpec& Spec);
UFUNCTION(BlueprintCallable, BlueprintPure, Category="UWMModMagnitudeCalculationBase", meta=(HideSelfPin="true"))
void GetCallerValueByTag(FGameplayTag GameplayTag, float& OutValue);
UFUNCTION(BlueprintCallable, BlueprintPure, Category="UWMModMagnitudeCalculationBase", meta=(HideSelfPin="true"))
void GetCallerValueByName(FName GameName, float& OutValue);
UFUNCTION(BlueprintImplementableEvent)
float GetFinalCalculationValue();
};
.cpp文件
// Fill out your copyright notice in the Description page of Project Settings.
#include "WMModMagnitudeCalculationBase.h"
float UWMModMagnitudeCalculationBase::CalculateBaseMagnitude_Implementation(const FGameplayEffectSpec& Spec) const
{
UWMModMagnitudeCalculationBase* WMModMagnitudeCalculationBase = const_cast<UWMModMagnitudeCalculationBase*>(this);
WMModMagnitudeCalculationBase->GameplayEffectSpec = const_cast<FGameplayEffectSpec*>(&Spec);
return WMModMagnitudeCalculationBase->GetFinalCalculationValue();
}
bool UWMModMagnitudeCalculationBase::GetCapturedAttr(FGameplayAttribute Attribute,
EGameplayEffectAttributeCaptureSource CaptureSource, float& Value,
bool bSnapshot)
{
const FGameplayTagContainer* SourceTags = GameplayEffectSpec->CapturedSourceTags.GetAggregatedTags();
const FGameplayTagContainer* TargetTags = GameplayEffectSpec->CapturedTargetTags.GetAggregatedTags();
FAggregatorEvaluateParameters EvaluationParameters;
EvaluationParameters.SourceTags = SourceTags;
EvaluationParameters.TargetTags = TargetTags;
bool const IsGetSuccess = GetCapturedAttributeMagnitude(
FGameplayEffectAttributeCaptureDefinition(Attribute, CaptureSource, bSnapshot), *GameplayEffectSpec,
EvaluationParameters, Value);
if (!IsGetSuccess)
{
UE_LOG(LogTemp, Error,
TEXT(
"UWMModMagnitudeCalculationBase::GetCapturedAttr Failed, AttrName: %s, CaptureSource:%d, bSnapShot:%d"
), *Attribute.AttributeName, CaptureSource, bSnapshot)
}
return IsGetSuccess;
}
void UWMModMagnitudeCalculationBase::GetCallerValueByTag(FGameplayTag GameplayTag, float& OutValue)
{
OutValue = GameplayEffectSpec->GetSetByCallerMagnitude(GameplayTag);
}
void UWMModMagnitudeCalculationBase::GetCallerValueByName(FName GameName, float& OutValue)
{
OutValue = GameplayEffectSpec->GetSetByCallerMagnitude(GameName);
}
void UWMModMagnitudeCalculationBase::GetGESpec(FGameplayEffectSpec& Spec)
{
Spec = *GameplayEffectSpec;
}
2. GEEC的封装(多属性修改规则)
.h文件
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "GameplayEffectExecutionCalculation.h"
#include "WMGEExecutionCalculationBase.generated.h"
/**
*
*/
UCLASS()
class WATCHMAN_API UWMGEExecutionCalculationBase : public UGameplayEffectExecutionCalculation
{
GENERATED_BODY()
public:
UWMGEExecutionCalculationBase();
FGameplayEffectCustomExecutionParameters* ExecutionParameters;
FGameplayEffectCustomExecutionOutput* CurrentOutExecutionOutput;
UFUNCTION(BlueprintCallable, BlueprintPure, Category="WMGEExecutionCalculation", meta=(HideSelfPin="true"))
void GetGESpec(FGameplayEffectSpec& GameplayEffectSpec);
UFUNCTION(BlueprintCallable, BlueprintPure, Category="WMGEExecutionCalculation", meta=(HideSelfPin="true"))
void GetCallerValueByTag(FGameplayTag GameplayTag, float& OutValue);
UFUNCTION(BlueprintCallable, BlueprintPure, Category="WMGEExecutionCalculation", meta=(HideSelfPin="true"))
void GetCallerValueByName(FName GameName, float& OutValue);
UFUNCTION(BlueprintCallable, BlueprintPure, Category="WMGEExecutionCalculation", meta=(HideSelfPin="true", AdvancedDisplay="bSnapshot"))
bool GetCapturedAttr(FGameplayAttribute Attribute, EGameplayEffectAttributeCaptureSource CaptureSource, float& Value, bool bSnapshot=true);
UFUNCTION(BlueprintCallable, Category="WMGEExecutionCalculation", meta=(HideSelfPin="true", AdvancedDisplay="ModOp"))
void AddExecutionOutput(FGameplayAttribute GameplayAttribute, float Magnitude, TEnumAsByte<EGameplayModOp::Type> ModOp = EGameplayModOp::Additive);
virtual void Execute_Implementation(const FGameplayEffectCustomExecutionParameters& ExecutionParams, FGameplayEffectCustomExecutionOutput& OutExecutionOutput) const override;
UFUNCTION(BlueprintImplementableEvent)
void ExecuteInBlueprint();
};
.cpp
// Fill out your copyright notice in the Description page of Project Settings.
#include "WMGEExecutionCalculationBase.h"
UWMGEExecutionCalculationBase::UWMGEExecutionCalculationBase()
{
#if WITH_EDITOR
UEnum* EnumPtr = FindObject<UEnum>(ANY_PACKAGE, TEXT("EGameplayEffectAttributeCaptureSource"), true);
if(EnumPtr)
{
EnumPtr->SetMetaData(TEXT("DisplayName"), TEXT("攻击者"), 0);
EnumPtr->SetMetaData(TEXT("DisplayName"), TEXT("受击者"), 1);
}
#endif
}
void UWMGEExecutionCalculationBase::GetGESpec(FGameplayEffectSpec& GameplayEffectSpec)
{
GameplayEffectSpec = ExecutionParameters->GetOwningSpec();
}
void UWMGEExecutionCalculationBase::GetCallerValueByTag(FGameplayTag GameplayTag, float& OutValue)
{
const FGameplayEffectSpec& Spec = ExecutionParameters->GetOwningSpec();
OutValue = Spec.GetSetByCallerMagnitude(GameplayTag);
}
void UWMGEExecutionCalculationBase::GetCallerValueByName(FName GameName, float& OutValue)
{
const FGameplayEffectSpec& Spec = ExecutionParameters->GetOwningSpec();
OutValue = Spec.GetSetByCallerMagnitude(GameName);
}
bool UWMGEExecutionCalculationBase::GetCapturedAttr(FGameplayAttribute Attribute,
EGameplayEffectAttributeCaptureSource CaptureSource,
float& Value, bool bSnapshot)
{
Value = 0.0f;
const FGameplayEffectSpec& Spec = ExecutionParameters->GetOwningSpec();
const FGameplayTagContainer* SourceTags = Spec.CapturedSourceTags.GetAggregatedTags();
const FGameplayTagContainer* TargetTags = Spec.CapturedTargetTags.GetAggregatedTags();
FAggregatorEvaluateParameters EvaluationParameters;
EvaluationParameters.SourceTags = SourceTags;
EvaluationParameters.TargetTags = TargetTags;
bool const IsGetSuccess = ExecutionParameters->AttemptCalculateCapturedAttributeMagnitude(
FGameplayEffectAttributeCaptureDefinition(Attribute, CaptureSource, bSnapshot), EvaluationParameters, Value);
if(!IsGetSuccess)
{
UE_LOG(LogTemp, Error, TEXT("UWMGEExecutionCalculationBase::GetCapturedAttr Failed, AttrName: %s, CaptureSource:%d, bSnapShot:%d"), *Attribute.AttributeName, CaptureSource, bSnapshot)
}
return IsGetSuccess;
}
void UWMGEExecutionCalculationBase::AddExecutionOutput(
FGameplayAttribute GameplayAttribute, float Magnitude, TEnumAsByte<EGameplayModOp::Type> ModOp)
{
CurrentOutExecutionOutput->AddOutputModifier(FGameplayModifierEvaluatedData(GameplayAttribute, ModOp, Magnitude));
}
void UWMGEExecutionCalculationBase::Execute_Implementation(
const FGameplayEffectCustomExecutionParameters& ExecutionParams,
FGameplayEffectCustomExecutionOutput& OutExecutionOutput) const
{
// Super::Execute_Implementation(ExecutionParams, OutExecutionOutput);
UWMGEExecutionCalculationBase* WMExecutionCalculationBase = const_cast<UWMGEExecutionCalculationBase*>(this);
WMExecutionCalculationBase->ExecutionParameters =const_cast<FGameplayEffectCustomExecutionParameters*>(&ExecutionParams);
WMExecutionCalculationBase->CurrentOutExecutionOutput = &OutExecutionOutput;
WMExecutionCalculationBase->ExecuteInBlueprint();
}
3. 扩展:全属性捕捉封装(不推荐)
为了省去设置需要捕捉的值的步骤,需要在继承后,构造函数中去反射遍历Attribute类所有属性,然后添加到捕捉列表
- 核心代码,构造函数中
UCaGEECAllAttrBase::UCaGEECAllAttrBase()
{
for (TFieldIterator<FProperty> It(UCaAttributeSetBase::StaticClass()); It; ++It)
{
FGameplayAttribute GameplayAttribute = FGameplayAttribute(*It);
if(GameplayAttribute.IsValid())
{
RelevantAttributesToCapture.Add(FGameplayEffectAttributeCaptureDefinition(GameplayAttribute,EGameplayEffectAttributeCaptureSource::Source, true));
RelevantAttributesToCapture.Add(FGameplayEffectAttributeCaptureDefinition(GameplayAttribute,EGameplayEffectAttributeCaptureSource::Target, true));
}
UE_LOG(LogTemp,Log,TEXT("AttrbuteName:%s"), *It->GetName());
}
}
4. 简单使用方法
- 在蓝图中创建并继承上述基类(GEEC或MMC)
- 在ClassDefault配置需要捕捉的值
- 蓝图Graph中Get需要的值,可以是属性值,可以是Caller值,也可以通过EffectContext传递过来的值
- 书写计算公式
- AddExecutionOutPut,输出到对应属性上(MMC在GE外部就设置好了,只能对单个属性做修改,GEEC可以对多个属性做修改)
5. GEEC和MMC的区别
MMC做了预测而GEEC没有
MMC针对单一属性,GEEC对多属性
6. 蓝图库方法(操作GEEC与MMC)
略
四. GE的合并(优化)
- 结合传值和自定义MMC,GEEC,可以合并大量GE,减少GE数量
- GE也可以通过工具去生成,修改CDO
封装常见通用GE
- 赋予Tag的GE
- 冷却时间的GE
- 消耗的GE
五. 参考文档
https://github.com/tranek/GASDocumentation#concepts-ge-tags
《Lyra》
《ActionRPG》
《GASShotter》
《GASDocumentation》