UE战棋游戏的制作流程(使用GAS来制作技能系统)

文章介绍了在UE4中利用GameplayAbilitySystem(GAS)插件构建战棋游戏的角色属性、行为系统和战斗逻辑。作者详细讲解了如何设置角色的基础属性、使用GAS配置技能、计算角色行动顺序、实现A*寻路和FillFlood算法以确定移动范围。此外,还涵盖了角色死亡、AI决策等机制的实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档


前言

本篇文章记录了在学习GAS系统之后将其应用到战棋游戏的一个案例,供自己复习使用第三者观看需有一定UE的cpp基础。

在本demo中实现了战棋游戏的角色属性设计,等级成长系统,战斗系统(包含回合切换,FillFloud算法来计算移动范围,A*算法来实现寻路功能,结合蓝图实现平滑移动,根据角色AP值进行行动间的转换)等战棋游戏的大多数内容设计,其中技能系统采用GAS实现,将GAS的秒计时器改造为回合计时器,从而使得GAS的cooldown可以应用到回合制上,参考虚幻GAS回合制


角色基础功能

此处不再重复实现基础功能,基本在蓝图中实现,感兴趣的小伙伴可以自行搜索
参考链接

GAS插件的使用

配置GameplaySystemAbility

1.先去UE中下载插件GameplayAbility和GameplayTagsEditor
2.重启后在VS工程中的bulid.cs文件中配置数据(添加"GameplayAbilities",“GameplayTags”)

using UnrealBulidTool;

public class Study : ModuleRules
{
	public Study(ReadOnlyTargetRules Target):base(Target)
	{
		PCHUsage = PCHUsageMode.UseExplicitOrSHaredPCHs;

		PublicDependencyModuleNames.AddRange(new string[]{"Core","CoreUObject","Engine","InputCore","HeadMountedDisplay","GameplayAbilities","GameplayTags"});
	}
};

3.在CharacterBase.h的代码如下

#include "CoreMinimal.h"
#include "GameFrameWork/Character.h"
#include "AbilitySystemInterface.h"
#include "AbilitySystemComponent.h"
#include "Abilities/GameplayAbility.h"//这三个头文件是配置GAS的必要文件

#include "CharacterBase.generated.h"//此处头文件包含为系统自带,且之后要包含的头文件都必须在此头文件之前
UCLASS()
class STUDY_API ACharacterBase:public ACharacter,public IAbilitySystemInterface
{
	GENERATED_BODY();
public:
	ACharacterBase();
protected:
	virtual void BeginPlay();
public:
	virtual void Tick(float DeltaTime)override;
	
	virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent)override;
	
	UPROPERTY(VisibleAnywhere,BlueprintReadWrite,Category = "Character")
		UAbilitySystemComponent* AbilitySystemComp;
		
	virtual UAbilitySystemComponent* GetAbilitySystemComponent()const;
	
	UFUNCTION(BlueprintCallable,Category = "CharacterBase")
		void AquireAbility(TSubclassOf<UGameplayAbility> AbilityToAquire);
};
  1. Character.cpp
#include "CharacterBase.h"

ACharacterBase::ACharacterBase()
{
	PeimaryActorTick.bCanEverTick = true;

	AbilitySystemComp = CreateDefaultSubobject<UAbilitySystemComponent>(TEXT("AbilitySystemComp));
}
void ACharacterBase::SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent)
{
	Super::SetupPlayerInputComponent(PlayerInputComponent);
}

void ACharacterBase::AquireAbility(TSubclassOf<UGameplayAbility> AbilityToAquire)
{
	if(AbilitySystemComp)
	{
		if(HasAuthority() && AbilityToAquire)
		{
			AbilitySystemComp->GiveAbility(FGameplayAbilitySpec(AbilityToAquire,1,0);
		}
		AbilitySystemComp->InitAbilityActorInfo(this,this);
	}
}

角色的基本属性

AttributeSetBase.h

#include "CoreMinimal.h"
#include "AttributeSet.h"
#include "AttributeSetBase.generated.h"

//创建一个名为FOnHealthChangeDelegate的委托,具有两个浮点型参数
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnHealthChangeDelegate,float,Health,float,MaxHealth);

UCLASS()
class STUDY_API UAttributeSetBase : public UAttributeSet
{
	GENERATED_BODY();
public:
	UAttributeSetBase();
	UPROPERTY(EditAnywhere,BlueprintReadWrite,Category = "AttributeSetBase")
		FGameplayAttributeData Health;
	UPROPERTY(EditAnywhere,BlueprintReadWrite,Category = "AttributeSetBase")
		FGameplayAttributeData MaxHealth;
	UFUNCTION(BlueprintCallable,Category = "Property")
		void SetHP(float HP,float MaxHP);//供蓝图将表中的数据传入到cpp中所使用的函数

	UPROPERTY(EditAnywhere,BlueprintReadWrite,Category = "AttributeSetBase")
		FGameplayAttributeData Mana;
	UPROPERTY(EditAnywhere,BlueprintReadWrite,Category = "AttributeSetBase")
		FGameplayAttributeData MaxMana;
	UFUNCTION(BlueprintCallable,Category = "Property")
		void SetMP(float MP,float MaxMP);

	UPROPERTY(EditAnywhere,BlueprintReadWrite,Category = "AttributeSetBase")
		FGameplayAttributeData ActionPoint;
	UPROPERTY(EditAnywhere,BlueprintReadWrite,Category = "AttributeSetBase")
		FGameplayAttributeData MaxActionPoint;
	UFUNCTION(BlueprintCallable,Category = "Property")
		void SetAP(float AP,float MaxAP);//行动点数,每次行动均会消耗,AP越少速度越慢,行动次数也就越少

	UPROPERTY(EditAnywhere,BlueprintReadWrite,Category = "AttributeSetBase")
		FGameplayAttributeData AttackDamage;
	UFUNCTION(BlueprintCallable,Category = "Property")
		void SetFinalATK(float FinalATK);//最终攻击力,指经过一系列运算后攻击力的最终值,减去防御力最终值才是最终造成的伤害

	UPROPERTY(EditAnywhere,BlueprintReadWrite,Category = "AttributeSetBase")
		FGameplayAttributeData Armor;
	UFUNCTION(BlueprintCallable,Category = "Property")
		void SetFinalDEF(float FinalDEF);//最终防御力,指经过各种buff叠加后计算得到的防御力的最终值

	void PostGameplayEffectExcute(const struct FGameplayEffectModCallbackData& Data)override;

	//创建三个委托,在角色类中绑定相应改变血条等的函数
	FOnHealthChangeDelegate OnHealthChange;
	FOnHealthChangeDelegate OnManaChange;
	FOnHealthChangeDelegate OnActionPointChange;
};

AttributeSetBase.cpp

#include "AttributeSetBase.h"
#include "GameplayEffectExtension.h"
#include "GameplayEffect.h"
#include "CharacterBase.h"

UAttributeSetBase::UAttributeSetBase()
	:Health(200.0f),
	MaxHealth(200.0f),
	Mana(150.0f),
	MaxMana(150.0f),
	ActionPoint(10.0f),
	MaxActionPoint(10.0f),
	AttackDamage(20.0f),
	Armor(5.0f)
{
}


void UAttributeSetBase::SetHP(float HP,float MaxHP)
{
	Health.SetCurrentValue(HP);
	Health.SetBaseValue(HP);
	MaxHealth.SetCurrentValue(MaxHP);
	MaxHealth.SetBaseValue(MaxHP);
}

void UAttributeSetBase::SetMP(float MP,float MaxMP)
{
	Mana.SetCurrentValue(MP);
	Mana.SetBaseValue(MP);
	MaxMana.SetCurrentValue(MaxMP);
	MaxMana.SetBaseValue(MaxMP);
}

void UAttributeSetBase::SetAP(float AP,float MaxAP)
{
	ActionPoint.SetCurrentValue(AP);
	ActionPoint.SetBaseValue(AP);
	MaxActionPoint.SetCurrentValue(MaxAP);
	MaxActionPoint.SetBaseValue(MaxAP);
}

void UAttributeSetBase::SetFinalATK(float FinalATK)
{
	AttackDamage.SetCurrentValue(FinalATK);
	AttackDamage.SetBaseValue(FinalATK);
}

void UAttributeSetBase::SetFinalDEF(float FinalDEF)
{
	Armor.SetCurrentValue(FinalDEF);
	Armor.SetBaseValue(FinalDEF);
}

//此函数是在AttributeSetBase中的属性值发生变化的时候会调用一次
void UAttributeSetBase::PostGameplayEffectExcute(const struct FGameplayEffectModCallbackData& Data)
{
	//若发生变化的值是Health
	if(Data.EvaluatedData.Attribute.GetUProperty()==FindFieldChecked<UProperty>(UAttributeSetBase::StaticClass(),GET_MEMBER_NAME_CHECKED(UAttributeSetBase,Health)))
	{
		//限制Health改变的范围,并且调用委托(即依次调用该委托下绑定的所有函数)
		Health.SetCurrentValue(FMath::Clamp(Health.GetCurrentValue(),0.0f,MaxHealth.GetCurrentValue()));
		Health.SetBaseValue(FMath::Clamp(Health.GetBasetValue(),0.0f,MaxHealth.GetCurrentValue()));
		OnHealthChange.Broadcast(Health.GetCurrentValue(),MaxHealth.GetCurrentValue());

		ACharacterBase* CharacterOwner = Cast<ACharacterBase>(GetOwingActor());
		if(Health.GetCurrentValue() == MaxHealth.GetCurrentValue())
		{
			if(CharacterOwner)
			{
				CharacterOwner->AddGameplayTag(CharacterOwner->FullHealthTag);
			}
		}
		else
		{	
			if(CharacterOwner)
			{
				CharacterOwner->RemoveGameplayTag(CharacterOwner->FullHealthTag);
			}
		}
	}
	
	if(Data.EvaluatedData.Attribute.GetUProperty()==FindFieldChecked<UProperty>(UAttributeSetBase::StaticClass(),GET_MEMBER_NAME_CHECKED(UAttributeSetBase,Mana)))
	{
		Mana.SetCurrentValue(FMath::Clamp(Mana.GetCurrentValue(),0.0f,MaxMana.GetCurrentValue()));
		Mana.SetBaseValue(FMath::Clamp(Mana.GetBasetValue(),0.0f,MaxMana.GetCurrentValue()));
		OnManaChange.Broadcast(Mana.GetCurrentValue(),MaxMana.GetCurrentValue());
	}

	if(Data.EvaluatedData.Attribute.GetUProperty()==FindFieldChecked<UProperty>(UAttributeSetBase::StaticClass(),GET_MEMBER_NAME_CHECKED(UAttributeSetBase,ActionPoint)))
	{
		ActionPoint.SetCurrentValue(FMath::Clamp(ActionPoint.GetCurrentValue(),0.0f,MaxActionPoint.GetCurrentValue()));
		ActionPoint.SetBaseValue(FMath::Clamp(ActionPoint.GetBasetValue(),0.0f,MaxActionPoint.GetCurrentValue()));
		OnActionPointChange.Broadcast(ActionPoint.GetCurrentValue(),MaxActionPoint.GetCurrentValue());
	}
}

创建结构体用于储存GE中的数据

AbilityTypes.h

#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "AbilityTypes.generated.h"

class UGameplayAbilityBase;

//技能花费消耗的类型,基本不会改动此处代码
UENUM(BlueprintType)
enum class EAbilityCostType : uint8
{
	Health,
	Mana,
	ActionPoint
};
//攻击范围类型,目前有点,线,扇形,面,方形五种范围,可能会根据技能的需求增加类型
UENUM(BlueprintType)
enum class EAbilityRangeType : uint8
{
	Point,
	Line,
	AcrossLine,
	Surface,
	Square
};

USTRUCT(BlueprintType)
struct FGameplayAbilityInfo
{
	GENERATED_BODY()

	UPROPERTY(EditAnywhere,BlueprintReadWrite,Category = "AbilityInfo")
		float CooldownDuration;
	UPROPERTY(EditAnywhere,BlueprintReadWrite,Category = "AbilityInfo")
		float Cost;
	UPROPERTY(EditAnywhere,BlueprintReadWrite,Category = "AbilityInfo")
		EAbilityCostType CostType;
		
	UPROPERTY(EditAnywhere,BlueprintReadWrite,Category = "AbilityInfo")
		UMaterialInstance* UIMat;
		
	UPROPERTY(EditAnywhere,BlueprintReadWrite,Category = "AbilityInfo")
		EAbilityRangeType RangeType;
	UPROPERTY(EditAnywhere,BlueprintReadWrite,Category = "AbilityInfo")
		float CastDistance;
	UPROPERTY(EditAnywhere,BlueprintReadWrite,Category = "AbilityInfo")
		float EffectRange;
		
	UPROPERTY(EditAnywhere,BlueprintReadWrite,Category = "AbilityInfo")
		TSubclassOf<UGameplayAbilityBase> AbilityClass;

	FGameplayAbilityInfo();
	FGameplayAbilityInfo(float InCooldownDuration,float InCost,EAbilityCostType InCostType,
							UMaterialInstance* InUIMat,
							EAbilityRangeType InRangeType,float InCastDistance,float InEffectRange,
							TSubclassOf<UGameplayAbilityBase> InAbilityClass);
};
	

AbilityTypes.cpp

#include "AbilityTypes.h"

FGameplayAbilityInfo::FGameplayAbilityInfo()
	:CooldownDuration(0),
	Cost(0),
	CostType(EAbilityCostType::Mana),
	UIMat(nullptr),
	RangeType(EAbilityRangeType::Point),CastDistance(1),EffectRange(1),
	AbilityClass(nullptr)
{
}

FGameplayAbilityInfo::FGameplayAbilityInfo(float InCooldownDuration,float InCost,EAbilityCostType InCostType,
							UMaterialInstance* InUIMat,
							EAbilityRangeType InRangeType,float InCastDistance,float InEffectRange,
							TSubclassOf<UGameplayAbilityBase> InAbilityClass)
	:CooldownDuration(InCooldownDuration),
	Cost(InCost),
	CostType(InCostType),
	UIMat(InUIMat),
	RangeType(InRangeType),CastDistance(InCastDistance),EffectRange(InEffectRange),
	AbilityClass(InAbilityClass)
{
}

将GE中的数据传入结构体中方便蓝图直接调用

GameplayAbilityBase.h

#include "CoreMinimal.h"
#include "Abilities/GameplayAbility.h"
#include "AbilityTypes.h"
#include "GameplayAbilityBase.generated.h"

UCLASS()
class STUDY_API UGameplayAbilityBase: public UGameplayAbility
{
	GENERATED_BODY();
public:
	UPROPERTY(EditAnywhere,BlueprintReadWrite,Category = "AbilityBase")
		UMaterialInstance* UIMaterial;
	
	UPROPERTY(EditAnywhere,BlueprintReadWrite,Category = "AbilityBase")
		EAbilityRangeType RangeType;
	UPROPERTY(EditAnywhere,BlueprintReadWrite,Category = "AbilityBase")
		float CastDistance;
	UPROPERTY(EditAnywhere,BlueprintReadWrite,Category = "AbilityBase")
		float EffectRange;
		
	UFUNCTION(BlueprintCallable,Category = "AbilityBase")
		FGameplayAbilityInfo GetAbilityInfo();
};

GameplayAbilityBase.cpp

FGameplayAbilityInfo UGameplayAbilityBase::GetAbilityInfo()
{
	UGameplayEffect* CoolDownEffect = GetCooldownGameplayEffect();
	UGameplayEffect* CostEffect = GetCostGameplayEffect();
	if(CoolDownEffect && CostEffect)
	{
		float CoolDownDuration = 0;
		CoolDownEffect->DurationMagnitude.GetStaticMagnitudeIfPossible(1,CoolDownDuration);
		float Cost = 0;
		EAbilityCostType CostType;
		if(CostEffect->Modifiers.Num() > 0)
		{
			FGameplayModifierinfo EffectInfo = CostEffect->Modifiers[0];
			EffectInfo.ModifierMagnitude.GetStaticMagnitudeIfPossible(1,Cost);
			FGameplayAttribute CostAttr = EffectInfo.Attribute;
			FString AttributeName = CostAttr.AttributeName;
			if(AttributeName == "Health")
			{
				CostType = EAbilityCostType::Health;
			}
			else if(AttributeName == "Mana")
			{
				CostType = EAbilityCostType::Mana;
			}
			else if(AttributeName == "ActionPoint")
			{
				CostType = EAbilityCostType::ActionPoint;
			}
			return FGameplayAbilityInfo(CoolDownDuration,Cost,CostType,
				UIMaterial,
				RangeType,CastDistance,EffectRange,
				GetClass());
		}
	}
	return FGameplayAbilityInfo();
}

创建接口作为交流层

GetAttributeInterface.h

方便日后管理,MyTurnMap中有多少属性与Character进行了交流

#include "CoreMinimal.h"
#include "UObject/Interface.h"
#include "GetAttributeInterface.generated.h"

UINTERFACE(MinimalAPI)
class UGetAttributeInterface : public UInterface
{
	GENERATED_BODY()
};

class STUDY_API IGetAttributeInterface
{
	GENERATED_BODY();
public:
	virtual void SetTeamID(int id) = 0;
	virtual int GetTeamID() = 0;
	virtual float GetCurAP() = 0;
	virtual void SetAP() = 0;
	virtual bool IsOtherHostile(int id) = 0;
};

角色基类

CharacterBase.h

包含了角色相关的信息,其中响应按键输入以及涉及资产引用的操作在蓝图中实现

#include "CoreMinimal.h"
#include "GameFrameWork/Character.h"
#include "AbilitySystemInterface.h"
#include "AbilitySystemComponent.h"
#include "Abilities/GameplayAbility.h"//这三个头文件是配置GAS的必要文件
#include "GetAttributeInterface.h"

#include "CharacterBase.generated.h"//此处头文件包含为系统自带,且之后要包含的头文件都必须在此头文件之前

//以下三个类不包含在头文件的原因是这三个类在自己的头文件中包含了此角色类
//为避免重复包含头文件,此处改为添加一个引用
//此时,若cpp文件想要使用则需要重新包含头文件
class UAttributeSetBase;
class UGameplayAbilityBase;
class AOriginCell;
UCLASS()
class STUDY_API ACharacterBase:public ACharacter,public IAbilitySystemInterface,public IGetAttributeInterface
{
	GENERATED_BODY();
public:
	ACharacterBase();
protected:
	virtual void BeginPlay();
public:
	virtual void Tick(float DeltaTime)override;

	void SetCurrentCell(AOriginCell* originCell);
	AOriginCell* GerCurrentCell();
	
	//以下是重写的接口中的函数
	virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent)override;
	virtual float GetCurAP() override;
	virtual void SetAP() override;
	virtual void SetTeamID(int id) override;
	UFUNCTION(BlueprintCallable,Category = "CharacterBase")
		virtual int GetTeamID() override;
	virtual bool IsOtherHostile(int id)override;
	
	UPROPERTY(VisibleAnywhere,BlueprintReadWrite,Category = "CharacterBase")
		UAbilitySystemComponent* AbilitySystemComp;
	UPROPERTY(VisibleAnywhere,BlueprintReadWrite,Category = "CharacterBase")
		UAttributeSetBase* AttributeSetBaseComp;
		
	virtual UAbilitySystemComponent* GetAbilitySystemComponent()const;
	
	UFUNCTION(BlueprintCallable,Category = "CharacterBase")
		void AquireAbility(TSubclassOf<UGameplayAbility> AbilityToAquire);
	UFUNCTION(BlueprintCallable,Category = "CharacterBase")
		void AquireAbilities(TArray<TSubclassOf<UGameplayAbility>> AbilityToAquire);

	UFUNCTION()
		void OnHealthChanged(float Health,float MaxHealth);
	UFUNCTION(BlueprintImplementableEvent,Category = "CharacterBase",meta = (DisplayName = "OnHealthChanged"))
		void BP_OnHealthChanged(float Health,float MaxHealth);
	UFUNCTION()
		void OnManaChanged(float Mana,float MaxMana);
	UFUNCTION(BlueprintImplementableEvent,Category = "CharacterBase",meta = (DisplayName = "OnManaChanged"))
		void BP_OnManaChanged(float Mana,float MaxMana);
	UFUNCTION()
		void OnActionPointChanged(float ActionPoint,float MaxActionPoint);
	UFUNCTION(BlueprintImplementableEvent,Category = "CharacterBase",meta = (DisplayName = "OnActionPointChanged"))
		void BP_OnActionPointChanged(float ActionPoint,float MaxActionPoint);
		
	UFUNCTION(BlueprintImplementableEvent,Category = "CharacterBase",meta = (DisplayName = "Die"))
		void BP_Die();
	
	UFUNCTION(BlueprintCallable,Category = "CharacterBase")
		void AddGameplayTag(FGameplayTag& TagToAdd);
	UFUNCTION(BlueprintCallable,Category = "CharacterBase")
		void RemoveGameplayTag(FGameplayTag& TagToRemove);
	UPROPERTY(EditAnywhere,BlueprintReadWrite,Category = "CharacterBase")
		FGameplayTag FullHealthTag;

protected:
	bool bIsDead;
	int TeamID;
	AOriginCell* currentCell;
	void Dead();
	void DisableInputControl();
	void EnableInputControl();
	void AddAbilityToUI(TSubclassOf<UGameplayAbilityBase> AbilityToAdd);
};

CharacterBase.cpp

#include "CharacterBase.h"
#include "AttributeSetBase.h"
#include "GameFramework/PlayerController.h"
#include "GameplayAbilityBase.h"
#include "PlayerControllerBase.h"
#include "AIController.h"
#include "BrainComponent.h"
#include "OriginCell.h"

ACharacterBase::ACharacterBase()
{
	PeimaryActorTick.bCanEverTick = true;

	AbilitySystemComp = CreateDefaultSubobject<UAbilitySystemComponent>(TEXT("AbilitySystemComp));
	AttributeSetBaseComp = CreateDefaultSubobject<UAttributeSetBase>(TEXT("AttributeSetBaseComp ));
	bIsDead = false;
	TeamID = 255;
	currentCell = NULL;
}

void ACharacterBase::BeginPlay()
{
	Surper::BeginPlay();
	AttributeSetBaseComp->OnHealthChange.AddDynamic(this,&ACharacterBase::OnHealthChanged);
	AttributeSetBaseComp->OnManaChange.AddDynamic(this,&ACharacterBase::OnManaChanged);
	AttributeSetBaseComp->OnActionPointChange.AddDynamic(this,&ACharacterBase::OnActionPointChanged);

	AddGameplayTag(FullHealthTag);
}

void ACharacterBase::Tick(float DeltaTime)
{
	Surper::Tick(DeltaTime);
}

void ACharacterBase::SetCurrentCell(AOriginCell* originCell)
{
	currentCell = originCell;
}

AOriginCell*ACharacterBase::GerCurrentCell()
{
	return currentCell;
}

void ACharacterBase::SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent)
{
	Super::SetupPlayerInputComponent(PlayerInputComponent);
}

float ACharacterBase::GetCurAP()
{
	return AttributeSetBaseComp->ActionPoint.GetCurrentValue();
}

void ACharacterBase::SetAP()
{
	AttributeSetBaseComp->ActionPoint.SetCurrentValue(AttributeSetBaseComp->MaxActionPoint.GetCurrentValue());
}

void ACharacterBase::SetTeamID(int id)
{
	TeamID = id;
}

void ACharacterBase::GetTeamID()
{
	return TeamID;
}

bool ACharacterBase::IsOtherHostile(int id)
{
	return TeamID != id;
}

UAbilitySystemComponent* ACharacterBase::GetAbilitySystemComponent()const
{
	return AbilitySystemComp;
}

void ACharacterBase::AquireAbility(TSubclassOf<UGameplayAbility> AbilityToAquire)
{
	if(AbilitySystemComp)
	{
		if(HasAuthority() && AbilityToAquire)
		{
			AbilitySystemComp->GiveAbility(FGameplayAbilitySpec(AbilityToAquire,1,0);
		}
		AbilitySystemComp->InitAbilityActorInfo(this,this);
	}
}

void ACharacterBase::AquireAbilities(TArray<TSubclassOf<UGameplayAbility>> AbilityToAquire)
{
	for(TSubclassOf<UGameplayAbility> AbilityItem : AbilityToAquire)
	{
		AquireAbility(AbilityItem);
		if(AbilityItem->IsChildOf(UGameplayAbilityBase::StaticClass())
		{
			TSubclassOf<UGameplayAbilityBase> AbilityBaseClass = *AbilityItem;
			if(AbilityBaseClass != nullptr)
			{
				AddAbilityToUI(AbilityBaseClass);
			}
		}
	}
}

void ACharacterBase::OnHealthChanged(float Health,float MaxHealth)
{
	if(Health <= 0.0f && !bIsDead)
	{
		BP_Die();
		Dead();
		bIsDead = true;
	}
	BP_OnHealthChanged(Health,MaxHealth);
}

void ACharacterBase::OnManaChanged(float Mana,float MaxMana)
{
	BP_OnManaChanged(Mana,MaxMana);
}

void ACharacterBase::OnActionPointChanged(float ActionPoint,float MaxActionPoint)
{
	BP_OnActionPointChanged(ActionPoint,MaxActionPoint);
}

void ACharacterBase::AddGameplayTag(FGameplayTag& TagToAdd)
{
	GetAbilitySystemComponent()->AddLooseGameplayTag(TagToAdd);
	GetAbilitySystemComponent()->SetTagMapCount(TagToAdd,1);
}

void ACharacterBase::RemoveGameplayTag(FGameplayTag& TagToRemove)
{
	GetAbilitySystemComponent()->RemoveLooseGameplayTag(TagToRemove);
}

void ACharacterBase::Dead()
{
	DisableInputControl();
	if(currentCell)
	{
		currentCell->SerCurrentActor(NULL);
	}
}

void ACharacterBase::DisableInputControl()
{
	APlayerController* PC = Cast<APlayerController>(GetController());
	if(PC)
	{
		PC->DisableInput(PC);
	}
	AAIController* AIC = Cast<AAIController>(GetController());
	if(AIC)
	{
		AIC->GetBrainComponent()->StopLogic("Dead");
	}
}

void ACharacterBase::EnableInputControl()
{
	if(bIsDead)
	{
		return;
	}
	APlayerController* PC = Cast<APlayerController>(GetController());
	if(PC)
	{
		PC->EnableInput(PC);
	}
	AAIController* AIC = Cast<AAIController>(GetController());
	if(AIC)
	{
		AIC->GetBrainComponent()->RestartLogic("Dead");
	}
}

void ACharacterBase::AddAbilityToUI(TSubclassOf<UGameplayAbilityBase> AbilityToAdd)
{
	APlayerControllerBase* PlayerControllerBase = Cast<APlayerControllerBase>(GetController());
	if(PlayerControllerBase)
	{
		UGameplayABilityBase* AbilityInstance = AbilityToAdd.Get()->GetDefaultObject<UGameplayAbilityBase>();
		if(AbilityInstance)
		{
			FGameplayAbilityInfo AbilityInfo = AbilityInstance->GetAbilityInfo();
			PlayerControllerBase->AddAbilityToUI(AbilityInfo);
		}
	}
}

格子类

OriginCell.h

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "CharacterBase.h"
#include "Components/StaticMeshComponent.h"
#include "OriginCell.generated.h"

UCLASS()
class STUDY_API AOriginCell : public AActor
{
	GENERATED_BODY()
public:
	AOriginCell();

	UPROPERTY(EditAnywhere,BlueprintReadWrite,Category = "originCell")
		UStaticMeshComponent* originCell;
	UPROPERTY(EditAnywhere,Category = "originCell")
		UMaterial* highMaterial;
	UPROPERTY(EditAnywhere,Category = "originCell")
		UMaterial* normalMaterial;
	UPROPERTY(EditAnywhere,Category = "originCell")
		UMaterial* moveHighMaterial;
	UPROPERTY(EditAnywhere,Category = "originCell")
		UMaterial* castDistanceHighMaterial;
	UPROPERTY(EditAnywhere,Category = "originCell")
		UMaterial* attackHighMaterial;

	UPROPERTY(EditAnywhere,BlueprintReadWrite,Category = "originCell")
		AActor* currentActor;
	UPROPERTY(EditAnywhere,BlueprintReadWrite,Category = "originCell")
		bool isBlocked;
	bool hasCharacter;
	bool isBoundary;

	UFUNCTION(BlueprintCallable,Category = "originCell")
		void High();
	UFUNCTION(BlueprintCallable,Category = "originCell")
		void Normal();
	UFUNCTION(BlueprintCallable,Category = "originCell")
		void MoveHigh();
	UFUNCTION(BlueprintCallable,Category = "originCell")
		void AttackHigh();
	UFUNCTION(BlueprintCallable,Category = "originCell")
		void CastDistanceHigh();

	AActor* GetCurrentActor();
	void SetCurrentActor(AActor* actor);
	FRotator GetCurrentActorRotation();
	FVector GetCurrentActorLocation();

	//A*算法中的G,F
	int G;
	int F;
	AOriginCell* Parent;//用于记录通路
	
	//FillFloud算法
	int cellCost;//每个格子需要花费的AP
	int extraCost;//每个格子根据ZOC计算得出的额外消耗
	int surplusLimit;//到达这个格子时剩余的费用

protected:
	virtual void BeginPlay()override;
};

OriginCell.cpp

#include "OriginCell.h"

AOriginCell::AOriginCell()
{
	originCell = CreateDefaultSubobject<StaticMeshComponent>("originCell");
	originCell->SetupAttachment(RootComponent);
	highMaterial = CreateDefaultSubobject<UMaterial>("highMaterial");
	normalMaterial = CreateDefaultSubobject<UMaterial>("normalMaterial");
	moveHighMaterial = CreateDefaultSubobject<UMaterial>("moveHighMaterial");
	castDistanceHighMaterial = CreateDefaultSubobject<UMaterial>("castDistanceHighMaterial");
	attackHighMaterial = CreateDefaultSubobject<UMaterial>("attackHighMaterial");

	isBlocked = false;
	isBoundary = false;
	cellCost = 1;
	extraCost = 0;
}

void AOriginCell::BeginPlay()
{
	Super::BeginPlay();
	originCell->SetMaterial(0,normalMaterial);
}

AActor* AOriginCell::GetCurrentActor()
{
	return currentActor;
}

void AOriginCell::SetCurrentActor(AActor* actor)
{
	isBlocked = actor != NULL ? true : false;
	hasCharacter = Cast<ACharacterBase>(actor) ? true : false;
	currentActor = actor;
	ACharacterBase* character = Case<ACharacterBase>(actor);
	if(character)
	{
		character->SetCurrentCell(this);
	}
}

FRotator AOriginCell::GetCurrentActorRotation()
{
	if(currentActor)
	{
		return currentActor->GetActorRotation();
	}
	return FRotator();
}

FVector AOriginCell::GetCurrentActorLocation()
{
	return originCell->GetComponentLocation() + FVector(0,0,100);
}

void AOriginCell::High()
{
	originCell->SetMaterial(0,highMaterial);
}

void AOriginCell::Normal()
{
	originCell->SetMaterial(0,normalMaterial);
}

void AOriginCell::MoveHigh()
{
	originCell->SetMaterial(0,moveHighMaterial);
}

void AOriginCell::AttackHigh()
{
	originCell->SetMaterial(0,attackHighMaterial);
}

void AOriginCell::CastDistanceHigh()
{
	originCell->SetMaterial(0,castDistanceHighMaterial);
}

管理类(地图类)

MyTurnMaps.h

#pragma once

#include "Coreminimal.h"
#include "GameFramework/Actor.h"
#include "OriginCell.h"
#include "CharacterBase.h"
#include "AttributeSetBase.h"
#include "AbilityTypes.h"
#include "Abilities/GameplayAbility.h"
#include "Components/StaticMeshComponent.h"
#include "Components/SceneComponent.h"
#include "MyTurnMaps.generated.h"

UENUM(BlueprintType)
enum class ETurnState : uint8
{
	Set,
	Select,
	Move,
	Moving,
	Meau,
	Attack,
	Skill,
	Item,
	Rest
};
enum EMaterialType : uint8
{
	Normal,
	High,
	MoveHigh,
	CastDistanceHigh,
	AttackHigh
};
enume EHeroActionState : uint8
{
	WaitForAIChoose,
	WaitForPlayerChoose,
}
struct SkillType
{
	TSubclassOf<UGameplayAbility> AbilityType;
	float CastDistance;
	float EffectRange;
	EAbilityRangeType RangeType;
	int SkillID;
};

UCLASS()
class STUDY_API AMyTurnMaps : public AActor
{
	GENERATED_BODY()
public:
	AMyTurnMaps();
protected:
	virtual void BeginPlay()override;
	TArray<AOriginCell*> cellList;
	void InitMaps();
	void InitBoundaryCell();
	TArray<AOriginCell*> GetCharacterList();

public:
	virtual void Tick(float DeltaTime) override;

	UPROPERTY(EditAnywhere,BlueprintReadWrite,Category = "MyTurnMaps")
		UStaticMeshComponent* Maps;
	UPROPERTY(EditAnywhere,BlueprintReadOnly,Category = "MyTurnMaps")
		TSubclassOf<class AOriginCell> cell;
	UPROPERTY(EditAnywhere,BlueprintReadOnly,Category = "MyTurnMaps")
		TArray<TSubclassOf<class ACharacterBase>> chessList;
	UPROPERTY(BlueprintReadOnly,Category = "MyTurnMaps")
		int Turn;

	UPROPERTY(EditAnywhere,BlueprintReadOnly,Category = "MyTurnMaps")
		int col;
	UPROPERTY(EditAnywhere,BlueprintReadOnly,Category = "MyTurnMaps")
		int row;
	UPROPERTY(EditAnywhere,BlueprintReadOnly,Category = "MyTurnMaps")
		float cellW;

	UPROPERTY(EditAnywhere,BlueprintReadWrite,Category = "MyTurnMaps")
		ETurnState CurrentState;
	UPROPERTY(VisibleAnywhere,BlueprintReadOnly,Category = "MyTurnMaps")
		TArray<AOriginCell*> SeedList;
	UPROPERTY(VisibleAnywhere,BlueprintReadOnly,Category = "MyTurnMaps")
		TArray<ACharacterBase*> targetList;

	UFUNCTION(BlueprintCallable,Category = "MuTurnMaps")
		void MouseInput();
	UFUNCTION(BlueprintCallable,Category = "MuTurnMaps")
		void CancelMouseInput();
	UFUNCTION(BlueprintCallable,Category = "MuTurnMaps")
		void UpdateAPList();

	UFUNCTION()
		void Moving(AActor* character);
	UFUNCTION(BlueprintImplementableEvent,Category = "MyTurnMaps")
		void BP_Moving(AActor* character);
	UFUNCTION()
		void Battling(AActor* source,TSubclassOf<UGameplayAbility> abilityType);
	UFUNCTION(BlueprintImplementableEvent,Category = "MyTurnMaps")
		void BP_Battling(AActor* source,TSubclassOf<UGameplayAbility> abilityType);
		
	UFUNCTION(BlueprintCallable,Category = "MuTurnMaps")
		void GetSkillTypeInfo(TSubclassOf<UGameplayAbility> abilityType, float castDistance,
								float effectRange,EAbilityRangeType rangeType,int skillID);

protected:
	AOriginCell* CurrentCell;
	AoriginCell* SelectCell;
	bool canAccessed;
	int flag;
	EHeroActionState HeroActionState;

	//CancelMouseInput need variable
	TArray<AOriginCell*> PreCellList;
	int Index;
	
	//AICurrentState
	FTimerHandle TimerHandle;//此处使用一个计时器来模拟AI阶段行动,其中移动后攻击的代码在蓝图
	void AIDectect();
	void AISelectChess();
	void AIMoveChess();
	UFUNCTION(BlueprintCallable,Category = "MuTurnMaps")
		void AIBattleChess();

	//CurrentState
	void MouseDetect();
	void SetChess();
	void SelectChess();
	void MoveChess();
	void BattleChess();

	//CancelState
	void CancelSetChess();
	void CancelMoveChess();
	void CancelMeauChess();
	void CancelAttackChess();

	//Action sequence need variable
	TArray<ACharacterBase*> CurAPList;
	TArray<ACharacterBase*> NextAPList;
	TArray<ACharacterBase*> GetCurAPList();
	void SortCellList(TArray<ACharacterBase*>& curAPList);
	void ClearAPList(TArray<ACharacterBase*>& APList);//Remove not valid cell in APList

	//A* caculation
	TArray<AOriginCell*> openList;
	TArray<AOriginCell*> closeList;
	TArray<AOriginCell*> pathList;
	void CaculatePath(AOriginCell* from,AOriginCell* to);
	int CaculateH(AOriginCell* from,AOriginCell* to);
	void ClearPath();
	TArray<AOriginCell*> FindNeighbours(AOriginCell* neiCell);
	bool FindNeighboursIsBlocked(AOriginCell* neiCell);
	void GetPath(AOriginCell* to);
	void GetSeedPath();

	//Fill Flood caculation
	TArray<AOriginCell*> movePathList;
	TArray<AOriginCell*> battleRangeList;
	void GetMovePathList(AOriginCell* checkCell);
	void CheckCanMove(AOriginCell* checkCell,float limit);
	void ClearMovePathList();
	void SetListMaterial(TArray<AOriginCell*> list,EMaterialType type);

	//Battle Range caculation
	SkillType CurrentSkillType;
	TArray<AOriginCell*> attackRangeList;
	TArray<AOriginCell*> castDistanceList;
	void SetCharacterLookAtCell(AActor* source,AActor* target);
	void GetAttackRangeList(AOriginCell* center,EAbilityRangeType rangeType,float range);
	TArray<AOriginCell*> GetPointArea(AOriginCell* center);
	TArray<AOriginCell*> GetLineArea(AOriginCell* center,float range);
	TArray<AOriginCell*> GetAcrossLineArea(AOriginCell* center,float range);
	TArray<AOriginCell*> GetSurfaceArea(AOriginCell* center,float range);
	TArray<AOriginCell*> GetSquareArea(AOriginCell* center,float range);
	void ClearBattleList();

	AOriginCell* GetAIMoveCell(AOriginCell* sourece);
	AOriginCell* GetNearestTarget(AOriginCell* source);
};
	

MyTurnMaps.cpp

#include "MyTurnMap"

AMyTurnMaps::AMyTurnMaps()
{
	PrimaryActorTick.bCanEverTick = true;

	Maps = CreateDefaultSubobject<UStaticMeshComponent>("Maps");
	Maps->SetupAttachment(RootComponent);
	col = 5;
	row = 5;
	cellW = 125;
	Turn = 1;
	flag = 0;
}

void AMyTurnMaps::BeginPlay()
{
	Super::BeginPlay();
	InitMaps();
	CurrentState = ETurnState::Set;
	HeroActionState = EHeroActionState::WaitForPlayerChoose;
	Index = 0;
}

void AMyTurnMaps::Tick(float DeltaTime)
{
	Super::Tick(DeltaTime);
	switch(HeroActionState)
	{
	case EHeroActionState::WaitForAIChoose:
		if(flag == 0)//使用一个全局变量使得AI的函数每次只执行一次,相当于DoOnce
		{
			flag = 1;
			AIDectect();
		}
	}
		break;
	case EHeroActionState::WaitForPlayerChoose:
		MouseDetect();
		break;
	}
}
初始化地图
void AMyTurnMaps::InitMaps()
{
	FVector Location = Maps->GetComponentLocation();
	FRotator Rotation = Maps->GetComponentRotation();

	for(int i = 0;i < col;i++)
	{
		for(int j = 0;j < row;j++)
		{
			Location += FVector(cellW,0,0);
			if(GetWorld())
			{
				AOriginCell* cloneCell = GetWorld()->SpawnActor<AOriginCell>(cell,Location,Rotation);
				cellList.Add(cloneCell);
			}
		}
		Location += FVector(-row * cellW,cellW,0);//cellW为每个格子的长度
	}
	InitBoundaryCell();
}

//为地图设定边界,为了简化边界处理,减少边界溢出可能
void AMyTurnMaps::InitBoundaryCell()
{
	for(int index = 0;index < cellList.Num();index++)
	{
		if(index / col == 0 || index / col == row - 1 || index % col == 0 || (index + 1) % col == 0)
		{
			cellList[index]->isBoundary = true;
			cellList[index]->isBlocked = true;
		}
	}
}

TArray<AOriginCell*> AMyTurnMaps::GetCharacterList()
{
	TArray<AOriginCell*> list;
	for(inr i = 0;i < cellList.Num();i++)
	{
		if(cellList[i]->hasCharacter)
		{
			lise.Add(cellList[i]);
		}
	}
	return list;
}
鼠标点击事件和取消事件(在蓝图中调用)
void AMyTurnMaps::MouseInput()
{
	switch(CurrentState)
	{
	case ETurnState::Set:
		SetChess();
		break;
	case ETurnState::Select:
		SelectChess();
		break;
	case ETurnState::Move:
		MoveChess();
		break;
	case ETurnState::Attack:
		BattleChess();
		break;
	default:
		break;
	}
}

void AMyTurnMaps::CancelMouseInput()
{
	switch(CurrentState)
	{
	case ETurnState::Set:
		CancelSetChess();
		break;
	case ETurnState::Move:
		CancelMoveChess();
		break;
	case ETurnState::Meau:
		CancelMeauChess();
		break;
	case ETurnState::Attack:
		CancelAttackChess();
		break;
	default:
		break;
	}
}

void AMyTurnMaps::Moving(AActor* character)
{
	SetListMaterial(pathList,High);
	BP_Moving(character);
}

void AMyTurnMaps::Battling(AActor* source,TSubclassOf<UGameplayAbility> abilityType)
{
	SetCharacterLookAtCell(source,targetList[0]);//设置攻击者的朝向
	BP_Battling(source,abilityType);
}

void AMyTurnMaps::AIDectect()
{
	GetWorld()->GetTimerManger().SetTimer(TimerHandle,this,&AMyTurnMaps::AISelectChess,1.0f,false,1,0f);
}

void AMyTurnMaps::AISelectChess()
{
	SelectCell = CurAPList[0]->GetCurrentCell();
	GetMovePathList(SelectCell);

	GetWorld()->GetTimerManger().ClearTimer(TimerHandle);
	GetWorld()->GetTimerManger().SetTimer(TimerHandle,this,&AMyTurnMaps::AIMoveChess,1.0f,false,1,0f);
}

void AMyTurnMaps::AIMoveChess()
{
	CurrentCell = GetAIMoveCell(SelectCell);
	CaculatePath(SelectCell,CurrentCell);
	GetSeedPath();
	ClearMovePathList();
	CurrentCell->SetCurrentActor(SelectCell->GetCurrentActor());
	SelectCell->SetCurrentActor(NULL);
	SelectCell = CurrentCell;
	Moving(SelectCell->GetCurrentActor());

	GetWorld()->GetTimerManger().ClearTimer(TimerHandle);
}

void AMyTurnMaps::AIbattleChess()//该函数在蓝图中移动过后调用
{
	targetList.Add(CurAPList[0]);
	Battling(SelectCell->GetCurrentActor(),CurrentSkillType.AbilityType);
	ClearBattleList();
}
	
//每个阶段下每帧执行对应函数体
void AMyTurnMaps::MouseDetect()
{
	//射线检测获取当前鼠标点击位置
	FHitResult HitResult;
	APlayerController* pc = GetWorld()->GetFirstPlayerController();
	pc->GetHitResultUnderCursorByChannel(ETraceTypeQuery::TraceTypeQuery1,true,HitResult);
	AOriginCell* HitCell = Cast<AOriginCell>(HitResult.Actor);
	if(!HitCell)
	{
		return;
	}

	switch(CurrentState);
	{
	case ETurnState::Set:
	{
		if(CurrentCell && CurrentCell != HitCell)
		{
			CurrentCell->Normal();
		}
		CurrentCell = HitCell;
		CurrentCell->High();
	}
		break;
	case ETurnState::Select:
	{
		ClearPath();
		if(CurrentCell && CurrentCell != HitCell)
		{
			CurrentCell->Normal();
		}
		CurrentCell = HitCell;
		HitCell->High();
		if(CurrentCell->GetCurrentActor() == CurAPList[0])
		{
			GetMovePathList(CurrentCell);
		}
		else
		{
			ClearMovePathList();
		}
	}
		break;
	case ETurnState::Move:
	{
		if(!SelectCell)
		{
			return;
		}
		CurrentCell = HitCell;
		if(MovePathList.Contains(CurrentCell))
		{
			CaculatePath(SelectCell,CurrentCell);
		}
		else
		{
			canAccessed = false;
		}
		if(!canAccessed)
		{
			ClearPath();
			SetListMaterial(movePathList,MoveHigh);
		}
	}
		break;
	case ETurnState::Meau:
		ClearPath();
		break;
	case ETurnState::Attack:
	{
		if(castDistanceList.Contains(HitCell))
		{
			SetListMaterial(castDistanceList,CaseDistanceHigh);
			{
				SetListMaterial(attackRangeList,Normal);
				GetAttackRangeList(HitCell,CurrentSkillType.RangeType,CurrentSkillType.EffectRange);
				SetListMaterial(attackRangeList,AttackHigh);
			}
		}
		CurrentCell = HitCell;
	}
		break;
	default:
		break;
	}
}
回合中的每个阶段

//放置棋子阶段
void AMyTurnMaps::SetChess()
{
	if(CurrentCell && !CurrentCell->isBlocked && chessList.Num())
	{
		ACharacterBase* cloneChess = GetWorld()->SpawnActor<ACharacterBase>(chessList[Index],CurrentCell->GetCurrentActorLocation(),FRotator(0,0,0));
		CurrentCell->SetCurrentActor(cloneChess);
		PreCellList.Add(CurrentCell);
		Index++;

		//demo版代码,阵营划分采用先后落子的方法
		IGetAttributeInterface* inter = Cast<IGetAttributeInterface>(CurrentCell->GetCurrentActor());
		if(Index % 2)
		{
			inter->SetTeamID(1);
		}
		else
		{
			inter->SetTeamID(255);
		}

		//棋子放置完毕后,进入选择阶段
		if(Index == chessList.Num())
		{
			Index = 0;
			PreCellList.Empty();
			CurAPList = GetCurAPList();
			CurrentState = ETurnState::Select;
		}
	}
}

//选择阶段
void AMyTurnMaps::SelectChess()
{
	//若选中的是当前行动角色,则选中该角色,进入移动阶段
	if(CurrentCell && CurrentCell->GetCurrentActor() == CurAPList[0])
	{
		SelectCell = CurrentCell;
		GetMovePathList(SelectCell);

		CurrentState = ETurnState::Move;
	}
}

//移动阶段
void AMyTurnMaps::MoveChess()
{
	//若不移动则直接进入菜单界面,否则调用移动函数开始平滑移动
	if(CurrentCell == SelectCell)
	{
		ClearMovePathList();
		PreCellList.Add(SelectCell);
		CurrentState = ETurnState::Meau;
	}
	else if(CurrentCell && canAccessed && SelectCell)
	{
		CurrentState = ETurnState::Moving;
		GetSeedPath();
		ClearMovePathList();

		PreCellList.Add(SelectCell);
		CurrentCell->SetCurrentActor(SelectCell->GetCurrentActor());
		SelectCell->SetCurrentActor(NULL);
		SelectCell = CurrentCell;

		Moving(SelectCell->GetCurrentActor());
	}
}

//战斗阶段
void AMyTurnMaps::BattleChess()
{
	//若点击的格子在攻击范围之内,则进行攻击判断
	if(SelectCell && attackRangeList.Contains(CurrentCell))
	{
		//首先将攻击范围的敌人加入集合,在蓝图中传给EventData
		IGetAttributeInterface* selectInter = Cast<IGetAttributeInterface>(SelectCell->GetCurrentActor());
		int curID = selectInter->GetTeamID();
		for(int i = 0;i < attackRangeList.Num();i++)
		{
			IGetAttributeInterface* inter = Cast<IGetAttributeInterface>(attackRangeList[i]->GetCurrentActor());
			if(CurrentSkillType.SkillID == 0)
			{
				if(inter && inter->IsOtherHostile(curID))
				{
					ACharacterBase* character = Cast<ACharacterBase>(attackRangeList[i]->GetCurrentActor());
					tergetList.Add(character);
				}
			}
			else if(CurrentSkillType.SkillID == 255)
			{
				if(inter && !inter->IsOtherHostile(curID))
				{
					ACharacterBase* character = Cast<ACharacterBase>(attackRangeList[i]->GetCurrentActor());
					targetList.Add(character);
				}
			}
		}

		//判空
		if(targetList.Num() == 0)
		{
			UE_LOG(LogTemp,Warning,TEXT("targetList is NULL");
			return;
		}
		
		//调用蓝图中的战斗函数
		Battling(SelectCell->GetCurrentActor(),CurrentSkillType.AbilityType);

		ClearBattleList();
		PreCellList.Empty();
		CurrentState = ETurnState::Select;
	}
}

void AMyTurnMaps::CancelSetChess()
{
	//使用一个Index来维护撤回的操作,其中PreCellList用于存放已经放下的棋子,方便删除
	if(Index)
	{
		Index--;
		PreCellList[Index]->GetCurrentActor()->Destroy();
		PreCellList[Index]->SetCurrentActor(NULL);
		PreCellList.RemoveAt(Index);
	}
}

void AMyTurnMaps::CancelMoveChess()
{
	ClearMovePathList();
	ClearPath();
	CurrentState = ETurnState::Select;
}

void AMyTurnMaps::CancelMeauChess()
{
	if(PreCellList.Num() == 1)
	{
		if(SelectCell != PreCellList[0])
		{
			SelectCell->GetCurrentActor()->SetActorTransform(FTransform(SelectCell->GetCurrentActorRotation(),PreCellList[0]->GetCurrentActorLocation()));
			PreCellList[0]->SetCurrentActor(SelectCell->GetCurrentActor());
			SelectCell->SetCurrentActor(NULL);
			SelectCell = PreCellList[0];
		}
		PreCellList.Empty();
		CurrentState = ETurnState::Select;
	}
}

void AMyTurnMaps::CancelAttackChess()
{
	ClearBattleList();
	CurrentState = ETurnState::Meau;
}
插队算法根据AP计算棋子行动顺序

思路:
1:先对AP降序排列
2:由AP最大的行动,行动之后AP值减少,存入NextAPList
3:在CurAPList【0】行动之前,将NextAPList中每个元素与之比较,若AP相差为2倍以上,则插队
4:插队的方法是将Next中的元素插在Cur的元素之前
5:重复执行2-3
6:当AP为0时将其清除
7:重复1-6,直至Next和Cur全部为空,结束回合
8:回合开始,重新赋予AP

void AMyTurnMaps::UpdateAPList()
{
	//每次行动过后需要先删除本次行动的角色,但因为接下来要加入到Next中,所以先用一个临时变量储存
	ACharacterBase* curAPListCopy = CurAPList[0];
	CurAPList.RemoveAt(0);
	if(NextAPList.Num() + CurAPList.Num() == 0)
	{
		UE_LOG(LogTemp,Warning,TEXT("Turn End"));
		Turn++;
		CurAPList = GetCurAPList();
		return;
	}
	NextAPList.Add(curAPListCopy);

	ClearAPList(CurAPList);
	ClearAPList(NextAPList);
	if(CurAPList.Num() == 0)
	{
		CurAPList = NextAPList;
		NextAPList.Empty();
		SortCellList(CurAPList);
	}
	else
	{
		for(int i = 0;i < NextAPList();i++)
		{
			IGetAttributeInterface* Next_Inter = Cast<IGetAttributeInterface>(NextAPList[i]);
			IGetAttributeInterface* Cur_Inter = Cast<IGetAttributeInterface>(CurAPList[i]);
			if(Next_Inter->GetCurAP() >= 2.0f * Cur_Inter->GetCurAP())
			{
				CurAPList.Add(NextAPList[i]);
				NextAPList.RemoveAt(i);
				i--;
			}
		}
		SortCellList(CurAPList);
	}
}

//重置所有角色的AP
TArray<ACharacterBase*> AMyTurnMaps::GetCurAPList()
{
	TArray<ACharacterBase*> list;
	for(AOriginCell* item : cellList)
	{
		if(item->hasCharacter)
		{
			ACharacterBase* character = Cast<ACharacterBase>(item->GetCurrentActor());
			list.Add(character);
			IGetAttributeInterface* inter = Cast<IGetAttributeInterface>(character);
			inter->SetAP();
		}
	}
	SortCellList(list);
	return list;
}

//对所有角色当前AP进行排序,并且根据当前行动角色更新地图ZOC
void AMyTurnMaps::SortCellList(TArray<ACharacterBase*>& curAPList)
{
	int length = curAPList.Num();
	for(int i = 0;i < length - 1;i++)
	{
		for(int j = 0;j < length - i -1;j++)
		{
			IGetAttributeInterface* Next_Inter = Cast<IGetAttributeInterface>(curAPList[j]);
			IGetAttributeInterface* Cur_Inter = Cast<IGetAttributeInterface>(curAPList[j+1]);
			if(Cur_Inter->GetCurAP() < Next_inter->GetCurAP())
			{
				Swap(curAPList[j],curAPList[j+1]);
			}
		}
	}

	IGetAttributeInterface* inter = Cast<IGetAttributeInterface>(curAPList[0]);
	if(inter->GetTeamID() == 255)
	{
		HeroActionState = EHeroActionState::WaitForPlayerChoose;
		CurrentState = ETurnState::Select;
	}
	else
	{
		HeroActionState = EHeroActionState::WaitForAIChoose;
		flag = 0;
	}
	
	//根据当前角色阵营来设置当前地图ZOC(同阵营移动无额外消耗,其他阵营身边移动消耗加一)
	TArray<AOriginCell*> list = GetCharacterList();
	for(AOriginCell* item : list)
	{
		IGetAttributeInterface* Cur_inter = Cast<IGetAttributeInterface>(item->GetCurrentActor());
		if(Cur_inter->GetTeamID() == inter->GetTeamID())
		{
			TArray<AOriginCell*> neigh = FindNeighbours(item);
			for(int i = 0;i < neigh.Num(); i++)
			{
				neigh[i]->extraCost = 0;
			}
		}
		else
		{
			TArray<AOriginCell*> neigh = FindNeighbours(item);
			for(int i = 0;i < neigh.Num(); i++)
			{
				neigh[i]->extraCost = 1;
			}
		}
	}
	
	for(int i = 0;i < curAPList.Num();i++)
	{
		IGetAttributeInterface* I_Inter = Cast<IGetAttributeInterface>(curAPList[i]);
		UE_LOG(LogTemp,Warning,TEXT("curAPList[%d] AP = %f"),i,I_inter->GetCurAP());
	}
	UE_LOG(LogTemp,Warning,TEXT("-----------------------------------"));
	for(int i = 0;i < NextAPList.Num();i++)
	{
		IGetAttributeInterface* I_Inter = Cast<IGetAttributeInterface>(curAPList[i]);
		UE_LOG(LogTemp,Warning,TEXT("NextAPList[%d] AP = %f"),i,I_inter->GetCurAP());
	}
}

//清除所有AP为0的角色
void AMyTurnMaps::ClearAPList(TArray<ACharacterBase*>& APList)
{
	for(int i = 0;i < APList.Num();i++)
	{
		IGetAttributeInterface* inter = Cast<IGetAttributeInterface>(APList[i]);
		if(inter->GetCurAP() <= 0)
		{
			APList.RemoveAt(i);
			i--;
		}
	}
}
A*算法计算最短移动距离

思路:

1:首先创建两个集合openList和closeList(openList用于存放可以走的路,closeList存放已经走过的路(即不能走的
路,包含障碍物))

2:将起点加入openList中,使用一个workCell变量用于开拓道路。首先取出openList中的第一个元素
存放到workCell中。此时进行判断,若workCell等于终点,流程结束。否则进行3

3:将openList中的第一个元素即workCell从openList中删除,并加入到closeList中,表示这是走过的路。

4:找到workCell的所有邻居,用一个临时变量neighboursList存放,对neighboursList【i】进行判断,
若该邻居在closeList集合中,表示该邻居是走过的,直接跳过,否则进行5

5:若neighboursList【i】不在openList中,表示没有计算过F,计算F,并将neighboursList【i】的父
节点设置为workCell,然后将该邻居加入到openList中,加入的时候进行判断,当F最小时放openList
首位(保证最短路径)
否则,neighboursList【i】在openList中(表示曾经计算过F),执行6

6:重新计算F,若重新计算的值比之前计算的值小,则更新F和G以及父节点。

7:重复执行2-6,直到openList中没有元素(表示没有通路)

8:若成功找到通路,则用终点来回溯寻找父节点,将每个父节点存到pathList集合中,最终该集合就是通路。 
void AMyTurnMaps::CaculatePath(AOriginCell* from,AOriginCell* to)
{
	//使用一个canAccessed变量来达成目标为不可达到的地方时,不显示移动路径的目的
	if(to->isBlocked || FindNeighboursIsBlocked(to))
	{
		canAccessed = false;
		return;
	}

	ClearPath();
	SetListMaterial(movePathList,MoveHigh);
	SetListMaterial(battleRangeList,ArrackHigh);

	openList.Add(from);
	while(openList.Num() > 0)
	[
		AOriginCell* workCell = openList[0];
		if(workCell == to)
		{
			//找到路径
			cabAccessed = true;
			GetPath(to);
			break;
		}
		else
		{
			openList.RemoveAt(0);
			closeList.Add(workCell);
			TArray<AOriginCell*> neighbourList = FindNeighbours(workCell);
			for(int i = 0;i < neighbourList.Num();i++)
			{
				if(closeList.Contains(neighbourList[i]) || neighbourList[i]->isBlocked)
				{
					continue;
				}
				if(!openList.Contains(neighbourList[i]))
				{
					int G = workCell->G + 1;
					int H = CaculateH(neighbourList[i],to);
					int F = G + H;
					neighbourList[i]->Parent = workCell;
					if(openList.Num() == 0)
					{
						openList.Add(neighbourList[i]);
					}
					else
					{
						if(F < openList[0]->F)
						{
							openList.Insert(neighbourList[i],0);
						}
						else
						{
							openList.Add(neighbourList[i]);
						}
					}
				}
				else
				{
					int G = workCell->G + 1;
					int H = CaculateH(neighbourList[i],to);
					int F = G + H;
					if(F < neighbourList[i]->F)
					{
						neighbourList[i]->F = F;
						neighbourList[i]->G = G;
						neighbourList[i]->Parent = workCell;
					}
				}
			}
		}
	}
}

int AMyTurnMaps::CaculateH(AOriginCell* from,AOriginCell* to)
{
	int fromIndex = cellList.IndexOfByKey(from);
	int toIndex = cellList.IndexOfByKey(to);
	return abs(fromIndex / col - toIndex / col) + abs(fromIndex % col - toIndex % col);
}

void AMyTurnMaps::ClearPath()
{
	if(pathList.Num() && cloaeList.Num())
	{
		for(int i = 0;i < cellList.Num();i++)
		{
			cellList[i]->Parent = NULL;
			cellList[i]->F = 0;
			cellList[i]->G = 0;
			cellList[i]->Normal();
		}

		canAccessed = false;
		openList.Empty();
		closeList.Empty();
		pathList.Empty();
		SeedList.Empty();
	}
}

//返回目标周围四格,有边界处理
TArrayMAOriginCell*>) AMyTurnMaps::FIndNeighbours(AOriginCell* neiCell)
{
	TArray<AOriginCell*> neighbourList;
	int index = cellList.IndexOfByKey(neiCell);
	if(neiCell->isBoundary)
	{
		return neighbourList;
	}
	if(!cellList[index + 1]->isBoundary)
	{
		neighbourList.Add(cellList[index + 1]);
	}
	if(!cellList[index + col]->isBoundary)
	{
		neighbourList.Add(cellList[index + col]);
	}
	if(!cellList[index - col]->isBoundary)
	{
		neighbourList.Add(cellList[index - col]);
	}
	if(!cellList[index - 1]->isBoundary)
	{
		neighbourList.Add(cellList[index - 1]);
	}
	return neighbourList;
}

//若某个格子四周都不能到达,那么此格子默认不能到达
bool AMyTurnMaps::FindNeighBoursIsBlocked(AOriginCell* neiCell)
{
	TArray<AOriginCell*> neighbourList;
	neighbourList = FindNeighbours(neiCell);
	int i = 0;
	for(i = 0; i < neighbourList.Num();i++)
	{
		if(!neighbourList[i]->isBlocked || neighbourList[i] == SelectCell)
		{
			break;
		}
	}
	return i == neighbourList.Num();
}

//使用此函数递归寻找父节点,从而得到路径
void AMyTurnMaps::GetPath(AOriginCell* to)
{
	if(to->Parent)
	{
		GetPath(to->Path);
	}
	pathList.Add(to);
	to->High();
}

//得到路径的各个拐点,供AITOMove节点实现平滑移动
void AMyTurnMaps::GetSeedPath()
{
	int length = pathList.Num();
	if(length == 0)
	{
		return ;
	}
	SeedList.Add(pathList[0]);
	for(int i = 1;i < length - 1;i++)
	{
		int last = cellList.IndexOfByKey(pathList[i - 1]);
		int index = cellList.IndexOfByKey(pathList[i]);
		int next = cellList.IndexOfByKey(pathList[i + 1]);
		if(index - next != last - index)
		{
			SeedList.Add(cellList[index]);
		}
	}
	SeedList.Add(pathList[length - 1]);
}
FillFloud算法计算棋子可移动范围

思路:

1:定义一个movePathList作为存放可移动的范围,在originCell中添加CellCost作为走过该格子需要的消耗,添加
surplusLimit作为走到该格子还剩余的行动力

2:首先将起点加入movePathList'中,将起点的surplusLimit更新为传入的行动力

3:定义变量checkCell,顺序获得movePathList中的元素,求出checkCell的邻居

4:遍历邻居,对每个邻居进行一次检查,判断当前行动力是否可以移动到该邻居

5:如果可以,将该邻居加入到movePathList中,如果该邻居已经在movepathList中,判断此时的limit是否大于
movepathlist中的surplus,如果大于则将当前的limt更新进movepathlist

6:重复进行3-5,当movepathlist中的元素遍历完毕时,退出循环 
//获得移动路径
void AMyTurnMaps::GetMovePathList(AOriginCell* checkCell)
{
	ClearMovePathList();

	IGetAttributeInterface* inter = Cast<IGetAttributeInterface>(checkCell->GetCurrentActor());
	float limit = inter->GetCurAP();

	movePathList.Add(checkCell);
	checkCell->surplusLimit = limit;
	for(int i = 0;i < movePathList.Num(); i++)
	{
		checkCell = movePathList[i];
		TArray<AOriginCell*> neighbourList = FindNeighbours(checkCell);
		for(int j = 0;j < neighbourList.Num();j++)
		{
			CheckCanMove(neighbourList[j],checkCell->surplusLimit);
		}
		movePathList[i]->MoveHigh();
	}
}


void AMyTurnMaps::CheckCanMove(AOriginCell* checkCell,float limit)
{
	limit -= (checkCell->cellCost + checkCell->extraCost);
	if(limit < 0 || checkCell->isBlocked)
	{
		return ;
	}

	if(movePathList.Contains(checkCell))
	{
		if(limit > checkCell->surplusLimit)
		{
			checkCell->surplusLimit = limit;
		}
	}
	else
	{
		checkCell->surplusLimit = limit;
		movePathList.Add(checkCell);
	}
}

void AMyTrunMaps::ClearMovePathList()
{
	if(movePathList.Num())
	{
		for(int i = 0; i < pathList.Num();i++)
		{
			pathList[i]->surplusLimit = 0;
		}
		SetListMaterial(movePathList,Normal);
		SetListMaterial(battleRangeList,Normal);
	}
	movePathList.Empty();
	battleRangeList.Empty();
}

void AMyTurnMaps::SetListMaterial(TArray<AOriginCell*> list,EMaterialType type)
{
	for(int i = 0;i < list.Num();i++)
	{
		switch(type)
		{
		case EMaterialType::Normal:
			list[i]->Normal();
			break;
		case EMaterialType::High:
			list[i]->High();
			break;
		case EMaterialType::MoveHigh:
			list[i]->MoveHigh();
			break;
		case EMaterialType::CastDistanceHigh:
			list[i]->CastDistanceHigh();
			break;
		case EMaterialType::AttackHigh:
			list[i]->AttackHigh();
			break;
		}
	}
}

void AMyTurnMaps::SetCharacterLookAtCell(AActor* source,AActor* target)
{
	ACharacterBase* character = Case<ACharacterBase>(sourse);
	if(character)
	{
		FVector from = character->GetActorLocation();
		FVector to = target->GetActorLocation();
		character->SetActorRotation(FRotationMatrix::MakeFromX((to - from).GetSafeNormal()).Rotator());
	}
}
实现技能施法范围和影响范围
//将蓝图中表格的数据使用本函数传入cpp
void AMyTrunMaps::GetSkilltypeInfo(TSubclassOf<UGameplayAbility> abilityType,float castDistance,float effectRange,EAbilittyRangeType rangeType,int skillID)
{
	CurrentSkillType.AbilityType = abilityType;
	CurrentSkillType.CastDistance = castDistance;
	CurrentSkillType.EffectRange = effectRange;
	CurrentSkillType.RangeType = rangeType;
	CurrentSkillType.SkillID = skillID;

	//对一些数据的传入做一些限制,如范围为点的影响范围必定是1
	switch(rangeType)
	{
	case EAbilityRangeType::Point:
		CurrentSkillType.EffectRange = 1;
		break;
	case EAbilityRangeType::Line:
	case EAbilityRangeType::AcrossLine:
		CurrentSkillType.CaseDistance = 1;
		break;
	}

	ClearBattleList();
	caseDistanceList = GetSurfaceArea(SelcectCell.CurrentSkillType.CastDistance);
	SetListMaterial(castDistanceList,CastDistanceHigh);
}

//攻击范围管理函数,用于计算技能的影响范围和可施放范围
void AMyTurnMaps::GetAttackRangeList(AOriginCell* center,EAbilityRangeType rangeType,float range)
{
	attackRangeList.Empty();

	switch(rangeType)
	{
	case EAbilityRangeType::Point:
		attackRangeList = GetPointArea(center);
		break;
	case EAbilityRangeType::Line:
		attackRangeList = GetLineArea(center,range);
		break;
	case EAbilityRangeType::AcrossLine:
		attackRangeList = GetAcrossLineArea(center,range);
		break;
	case EAbilityRangeType::Surface:
		attackRangeList = GetSurfaceArea(center,range);
		break;
	case EAbilityRangeType::Square:
		attackRangeList = GetSquareArea(center,range);
		break;
	}
}

TArray<OriginCell*> AMyTurnMaps::GetPointArea(AOriginCell* center)
{
	TArray<OriginCell*> RangeList;
	if(castDistanceList.Contains(center))
	{
		RangeList.Add(center);
	}
	return RangeList;
}

//先计算施法点处于棋子的方向,沿该方向延伸影响范围
TArray<OriginCell*> AMyTurnMaps::GetLineArea(AOriginCell* center,float range)
{
	TArray<OriginCell*> RangeList;
	int indexSelf = cellList.IndexOfByKey(SelectCell);
	int indexCenter = cellList.IndexOfByKey(center);
	int sub = indexCenter - indexSelf;
	if(sub == 1)//right
	{
		for(int i = 0;i < range;i++)
		{
			RangeList.Add(cellList[indexCenter + i]);
			if((indexCenter + i + 1) % col == 0)
			{
				break;//边界处理
			}
		}
	}
	else if(sub == -1)//left
	{
		for(int i = 0;i < range;i++)
		{
			RangeList.Add(cellList[indexCenter - i]);
			if((indexCenter - i) % col == 0)
			{
				break;
			}
		}
	}
	else if(sub == col)//up
	{
		for(int i = 0;i < range;i++)
		{
			RangeList.Add(cellList[indexCenter + i*col]);
			if((indexCenter + i*col) / col == row - 1)
			{
				break;
			}
		}
	}
	else if(sub == -col)//down
	{
		for(int i = 0;i < range;i++)
		{
			RangeList.Add(cellList[indexCenter - i*col]);
			if((indexCenter - i*col) / col == 0)
			{
				break;
			}
		}
	}
	return RangeList;
}

//扇形范围,此处采用面形范围的一半处理
TArray<OriginCell*> AMyTurnMaps::GetAcrossLineArea(AOriginCell* center,float range)
{
	TArray<OriginCell*> RangeList;
	RangeList = GetSurfaceArea(center,range);

	int indexSelf = cellList.IndexOfByKey(SelectCell);
	int indexCenter = cellList.IndexOfByKey(center);
	int sub = inedxSelf - indexCenter;
	if(sub == 1)//right
	{
		for(int i = 0; i < RangeList.num();i++)
		{
			int index = cellList.IndexOfByKey(RangeList[i]);
			if(index % col < indexCenter % col)
			{
				//若在右,则删除中心及左边的范围,其余情况类似
				RangeList.RemoveAt(i);
				i--;
			}
		}
	}
	else if(sub == -1)//left
	{
		for(int i = 0; i < RangeList.num();i++)
		{
			int index = cellList.IndexOfByKey(RangeList[i]);
			if(index % col > indexCenter % col)
			{
				RangeList.RemoveAt(i);
				i--;
			}
		}
	}
	else if(sub == col)//up
	{
		for(int i = 0; i < RangeList.num();i++)
		{
			int index = cellList.IndexOfByKey(RangeList[i]);
			if(index / col < indexCenter / col)
			{
				RangeList.RemoveAt(i);
				i--;
			}
		}
	}
	else if(sub == -col)//down
	{
		for(int i = 0; i < RangeList.num();i++)
		{
			int index = cellList.IndexOfByKey(RangeList[i]);
			if(index / col > indexCenter / col)
			{
				RangeList.RemoveAt(i);
				i--;
			}
		}
	}
	else
	{
		RangeList.Empty();
	}
	return RangeList;
}

//面形范围,思路参考FillFloud算法,由中心点为中心蔓延
TArray<OriginCell*> AMyTurnMaps::GetSurfaceArea(AOriginCell* center,float range)
{
	TArray<OriginCell*> RangeList;
	TArray<OriginCell*> RangeListCopy;
	RangeListCopy.Add(center);

	for(int k = 0;k < range;k++)
	{
		for(int i = 0;i <RangeListCopy.Num();i++)
		{
			TArray<OriginCell*> neighbourList = FindNeighbours(RangeListCopy[i]);
			for(int j = 0;j < neighbourList.Num();j++)
			{
				RangeList.AddUnique(neighbourList[j]);
			}
		}
		RangeListCopy = RangeList;
	}
	RangeList.Add(center);
	return RangeList;
}

//获取中心点身边八格范围,为正方形范围
TArray<OriginCell*> AMyTurnMaps::GetSquareArea(AOriginCell* center,float range)
{
	TArray<OriginCell*> RangeList;
	int index = cellList.IndexOfBykey(center);
	if(center->isBoundary)
	{
		return RangeList;
	}
	RangeList = FindNeighbours(center);
	if(!cellList[index + col + 1]->isBoundary)
	{
		RangeList.Add(cellList[index + col + 1]);
	}
	if(!cellList[index - col + 1]->isBoundary)
	{
		RangeList.Add(cellList[index - col + 1]);
	}
	if(!cellList[index + col - 1]->isBoundary)
	{
		RangeList.Add(cellList[index + col - 1]);
	}
	if(!cellList[index - col - 1]->isBoundary)
	{
		RangeList.Add(cellList[index - col - 1]);
	}
	
	return RangeList;
}

void AMyTurnMaps::ClearBattleList()
{
	SetListMaterial(attackRangeList,Normal);
	SetListMaterial(castDistanceList,Normal);
	attackRangeList.Empty();
	castDistanceList.Empty();
	targetList.Empty();
}
AI的移动和战斗逻辑
AOriginCell* AMyTurnMaps::GetAIMoveCell(AOriginCell* source)
{
	AOriginCell* target;
	target = GetNearestTarget(source);
	return target;
}

AOriginCell* AMyTurnMaps::GetNearestTarget(AOriginCell* source)
{
	int min = 100000;
	AOriginCell* target = nullptr;
	TArray<AOriginCell*> list = GetCharacterList();
	IGetAttributeInterface* inter = Case<IGetAttributeInterface>(source->GetCurrentActor());
	if(Cur_inter->IsOtherHostile(inter->GetTeamID()))
	{
		TArray<AOriginCell*> neigh = FindNeighbours(item);
		for(int i = 0;i < neigh.Num();i++)
		{
			CaculatePath(source,neigh[i]);
			if(min > pathList.Num())
			{
				min = pathList.Num();
				target = neigh[i];
			}
		}
	}
	if(min == 100000)
	{
		UE_LOG(LogTemp,Warning,TEXT("GetNearnestTarget() target is null"));
		return nullptr;
	}
	else
	{
		return target;
	}
}


评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值