准备工作
本文仅适用于同时购买了我的多个产品的用户,如GCS,GMS,或者GIS。
由于战斗系统本身最复杂,牵扯的游戏机制也更多。因此,建议的流程是将GIS或GMS整合到GCS中。
前置知识
在此之前,你应该至少了解各系统的基础用法,以及GCS的BC_DemoCombatEntity的实现方式。
背景
首先,GCS和GMS,以及GIS都属于GGF(通用游戏框架插件,我的All-In-One解决方案,但它还未发布)。
我希望我的产品能够保持相互之间的独立,模块化。因此GCS和GMS以及GIS都有自己对应的配套项目,它们都是从我当前的All-In-One中分离出来的子集。
想了解更多请看这里。
合并项目
你可以手动将各插件对应版本的最新配套项目进行手动合并,或者将项目内容同时迁移到你自己的项目。
注意
配套项目均由我自己的项目拆分而成,因此你可以通过复制粘贴自由地再合并到一起。配套项目会共享一些内容,因此合并过程中的重复文件直接进行覆盖即可。
同时要注意配套项目的DefaultGame.ini和DefaultEngine.ini中的内容(若存在),也要进行合并。
安装所需插件
所有的配套项目都共享通用游戏系统(GGS),插件不需要合并,你只需从Fab安装好对应版本的插件即可。
整合GMS到GCS
GCS的配套项目中的Demo部分包含一个简单的运动系统,其演示了如何在GCS中引入运动系统,并包含:
- BC_Demo_MovementManager:位于/GenericDemo/GCS/Blueprints/MovementManager,是一个简单的运动管理组件。
- 若干动画蓝图:位于/GenericDemo/GCS/Animations/下,是几个轻量的动画蓝图,可以说是GMS的极端简化版本。
你可以将其替换为成别的实现,如:使用ALS/GASP或者我的GMS去替换GCS中的运动系统。
这里会介绍如何将GMS整合进GCS。
完成运动系统的基础设置
替换运动系统
在将GMS整合到GCS之前,你至少应该已经熟悉和了解GMS本身的使用。你可以参考运动系统的配置,在GCS中完成运动系统的基础组件设置。
关键点如下:
- 将GCS角色中的BC_Demo_MovementManager替换为GMS的运动系统组件,GMS的运动系统组件提供与BC_Demo_MovementManager相似的API/事件。
- 将GCS角色中的SkeletalMesh组件的动画蓝图从ABP_CombatCharacter替换为GMS提供的ABPT_GMS_Main。
- 参考GMS中的ABP_Mannequin_Ext在GMS中的用法,在GCS角色的Beginplay中,将其Link到SkeletalMesh组件的主动画实例中,ABP_Mannequin_Ext是设置角色Montage布局的动画层,它与GCS的Demo提供的ABP_CombatCharacter_Ext提供相同功能。
Loading Blueprint
Initializing BlueprintUE renderer...
更替运动API
GCS的战斗实体接口中,有几个运动相关的API与GMS的API兼容,因此你可以继承自BC_GCS_CombatEntity或者修改BC_GCS_DemoCombatEntity,并按照指示替换API调用。
DemoCombatEntity中的案例代码。
SetMovementSet/SetMovementState/SetRotationMode由GMS实现。
GetRotationMode/GetMovementState/GetMovementSet由GMS实现。
武器与运动定义整合
在GCS_Demo中,DemoWeapon是通过WeaponAnimLayerSelections 来配置了不同的动画层,并通过在合适的时候调用RefreshWeaponAnimLayer和UnlinkWeaponAnimLayer来实现针对角色的状态,根据当前武器切换不同的动画层(不同的动画姿态)。
若采用GMS,你无需再手动调用武器上的动画层的刷新,而是在你的武器类中新增运动定义变量,然后在武器激活/取消激活的时候,为GMS运动系统组件设置运动定义。
当运动API调用时,GMS运动系统组件会自动刷新对应的控制/动画层逻辑。
取决于你的游戏设计,你可能需要更复杂的逻辑来决定每个武器在激活时,为运动系统设置什么样的运动定义(比如根据角色的性别,采用不同的运动定义)。
总结
理想情况下,当你在GCS中切换武器时,会切换GMS组件上的运动定义。当你通过CombatEntityInterface调用运动相关API时,会通过GMS组件来执行具体的运动控制/动画替换。
整合GIS到GCS
jing'gao
此部分内容仍在编写中...
GCS的配套项目中的包含一个简单的武器管理组件,它演示了如何在GCS中引入装备管理系统。
你可以使用别的库存/装备系统去替换GCS中的武器管理功能等。
而这里会介绍如何使用GIS来管理GCS中的武器。
关键点
GIS是数据驱动的库存系统,若要用GIS来代替GCS中的装备管理功能,你需要从武器接口层去实现你的武器,而不是使用GCS_WeaponActor。
数据层
定义武器相关的道具Fragment,这部分是将GCS武器中的一些静态配置,以道具Fragment的形式制作。
/**
* The primary weapon setting.
*/
UCLASS(Blueprintable, BlueprintType, Const, meta=(DisplayName="Weapon Settings"))
class UGGF_ItemFragment_WeaponSettings : public UGIS_ItemFragment
{
GENERATED_BODY()
public:
/** Associated montages for abilities. */
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category="Weapon")
TSoftObjectPtr<UGCS_AbilityActionSetSettings> AbilityActionsLibrary;
/** Please add a variable description */
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category="Weapon")
TSoftObjectPtr<UGMS_MovementDefinition> MovementDefinition;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="Weapon")
TObjectPtr<UStaticMesh> StaticMesh;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="Weapon")
TObjectPtr<USkeletalMesh> SkeletalMesh;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="Weapon")
TSubclassOf<UAnimInstance> AnimInstanceClass;
/**
* Socket to attach to(will try one by one) while active.
* 当激活时,会附加到的插槽(一个个尝试)。
*/
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category="Weapon")
TArray<FName> ActiveSockets{};
/**
* Socket to attach to(will try one by one) while not active.
* 当没激活时,会附加到的插槽(一个个尝试)。
*/
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category="Weapon")
TArray<FName> InactiveSockets{};
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category="Weapon", meta=(RequiredAssetDataTags = "RowStructure=/Script/GenericCombatSystem.GCS_ComboDefinition"))
TObjectPtr<const UDataTable> ComboDefinitionTable;
/**
* Weapon trace definitions.
* 武器的检测定义
*/
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="Weapon", meta=(TitleProperty="TraceTag"))
TArray<FGCS_TraceDefinition> TraceDefinitions;
#if WITH_EDITOR
virtual bool FragmentDataValidation_Implementation(FText& OutMessage) const override;
#endif
};
/**
* The offhand weapon setting. The second equipment actor will be treated as OffhandWeapon.
*/
UCLASS(Blueprintable, BlueprintType, Const, meta=(DisplayName="Offhand Weapon Settings"))
class UGGF_ItemFragment_OffHandWeaponSettings : public UGGF_ItemFragment_WeaponSettings
{
GENERATED_BODY()
};逻辑层
装备实例
首先抽象出装备实例,负责所有装备的共通逻辑。
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "GIS_EquipmentInstance.h"
#include "GGF_EquipmentInstance.generated.h"
struct FGGA_AbilitySet_GrantedHandles;
/** Base class for Equipment Instance, contains shared feature of ""Equipment */
UCLASS(Blueprintable, BlueprintType)
class GENERICGAMEFRAMEWORK_API UGGF_EquipmentInstance : public UGIS_EquipmentInstance
{
GENERATED_BODY()
protected:
virtual void OnEquipmentBeginPlay_Implementation() override;
virtual void OnEquipmentEndPlay_Implementation() override;
virtual void OnActiveStateChanged_Implementation(bool bNewActiveState) override;
#pragma region AbilitySets
UFUNCTION(BlueprintCallable, Category="GGF|EquipmentInstance", meta=(BlueprintProtected))
virtual void GrantAbilitySets();
UFUNCTION(BlueprintCallable, Category="GGF|EquipmentInstance", meta=(BlueprintProtected))
virtual void GrantActiveAbilitySets();
UFUNCTION(BlueprintCallable, Category="GGF|EquipmentInstance", meta=(BlueprintProtected))
virtual void TakeAwayGrantedAbilitySets();
UFUNCTION(BlueprintCallable, Category="GGF|EquipmentInstance", meta=(BlueprintProtected))
virtual void TakeAwayGrantedActiveAbilitySets();
UFUNCTION(BlueprintCallable, Category="GGF|EquipmentInstance", meta=(BlueprintProtected))
virtual int32 GetEquipmentLevel() const;
protected:
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Transient, Category="EquipmentInstance")
TArray<FGGA_AbilitySet_GrantedHandles> GrantedAbilitySetHandles;
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Transient, Category="EquipmentInstance")
TArray<FGGA_AbilitySet_GrantedHandles> GrantedActiveAbilitySetHandles;
#pragma endregion
};
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
#include "Inventory/GGF_EquipmentInstance.h"
#include "AbilitySystemBlueprintLibrary.h"
#include "GIS_ItemInstance.h"
#include "Inventory/GGF_ItemFragments.h"
#include "Utilities/GGA_AbilitySystemFunctionLibrary.h"
void UGGF_EquipmentInstance::OnEquipmentBeginPlay_Implementation()
{
Super::OnEquipmentBeginPlay_Implementation();
}
void UGGF_EquipmentInstance::OnEquipmentEndPlay_Implementation()
{
Super::OnEquipmentEndPlay_Implementation();
}
void UGGF_EquipmentInstance::OnActiveStateChanged_Implementation(bool bNewActiveState)
{
Super::OnActiveStateChanged_Implementation(bNewActiveState);
}
void UGGF_EquipmentInstance::GrantAbilitySets()
{
if (OwningPawn && OwningPawn->HasAuthority() && SourceItem)
{
if (auto Fragment = SourceItem->FindFragmentByClass<UGGF_ItemFragment_AbilitySets>())
{
if (auto ASC = UAbilitySystemBlueprintLibrary::GetAbilitySystemComponent(OwningPawn))
{
GrantedAbilitySetHandles = UGGA_AbilitySet::GiveAbilitySetsToAbilitySystem(Fragment->AbilitySetsToGrant, ASC, this);
}
}
}
}
void UGGF_EquipmentInstance::GrantActiveAbilitySets()
{
if (OwningPawn && OwningPawn->HasAuthority() && SourceItem)
{
if (auto Fragment = SourceItem->FindFragmentByClass<UGGF_ItemFragment_AbilitySets>())
{
if (auto ASC = UAbilitySystemBlueprintLibrary::GetAbilitySystemComponent(OwningPawn))
{
GrantedActiveAbilitySetHandles = UGGA_AbilitySet::GiveAbilitySetsToAbilitySystem(Fragment->ActiveAbilitySetsToGrant, ASC, this, GetEquipmentLevel());
}
}
}
}
void UGGF_EquipmentInstance::TakeAwayGrantedAbilitySets()
{
if (OwningPawn && OwningPawn->HasAuthority())
{
if (auto ASC = UAbilitySystemBlueprintLibrary::GetAbilitySystemComponent(OwningPawn))
{
UGGA_AbilitySet::TakeAbilitySetsFromAbilitySystem(GrantedAbilitySetHandles, ASC);
GrantedAbilitySetHandles.Reset();
}
}
}
void UGGF_EquipmentInstance::TakeAwayGrantedActiveAbilitySets()
{
if (OwningPawn && OwningPawn->HasAuthority())
{
if (auto ASC = UAbilitySystemBlueprintLibrary::GetAbilitySystemComponent(OwningPawn))
{
for (auto& Handle : GrantedActiveAbilitySetHandles)
{
UGGA_AbilitySet::TakeAbilitySetFromAbilitySystem(Handle, ASC);
}
GrantedActiveAbilitySetHandles.Reset();
}
}
}
int32 UGGF_EquipmentInstance::GetEquipmentLevel() const
{
return 1;
}
武器实例
然后基于装备实例,实现武器实例,负责所有武器相关的逻辑。武器实例通过武器接口将GCS的武器功能桥接在一起。
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "GGF_EquipmentInstance.h"
#include "Collision/GCS_TraceDelegates.h"
#include "Weapon/GCS_WeaponInterface.h"
#include "GGF_WeaponInstance.generated.h"
class UGGF_ItemFragment_WeaponSettings;
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FGGF_OnWeaponTrailStateChangedSignature, bool, bEnable);
/**
* An equipment instance bridge to GCS's weapon interface.
*/
UCLASS()
class GENERICGAMEFRAMEWORK_API UGGF_WeaponInstance : public UGGF_EquipmentInstance, public IGCS_WeaponInterface
{
GENERATED_BODY()
public:
// Begin Weapon Interface
virtual APawn* GetWeaponOwner_Implementation() const override final;
virtual const FGameplayTagContainer GetWeaponTags_Implementation() const override;
virtual void SetWeaponActive_Implementation(bool bNewActive) override final;
virtual bool IsWeaponActive_Implementation() const override final;
virtual void ToggleTrail_Implementation(bool bEnable) override;
virtual UPrimitiveComponent* GetPrimitiveComponent_Implementation() const override;
// End WeaponInterface
// Begin Equipment Interface
virtual void OnEquipmentBeginPlay_Implementation() override;
virtual void OnEquipmentEndPlay_Implementation() override;
// End Equipment Interface
UPROPERTY(BlueprintAssignable, Category="Events")
FGCS_OnTraceHitSignature OnTraceHitEvent;
UPROPERTY(BlueprintAssignable, Category="Events")
FGCS_OnTraceStateChangedSignature OnTraceStateChangedEvent;
UPROPERTY(BlueprintAssignable, Category="Events")
FGGF_OnWeaponTrailStateChangedSignature OnTrailStateChangedEvent;
protected:
virtual void OnActiveStateChanged_Implementation(bool bNewActiveState) override;
#pragma region Settings
public:
/**
* Gets the default native weapon setting.
* @return Setting fragment.
*/
UFUNCTION(BlueprintCallable, BlueprintPure, Category="GGF|EquipmentInstance")
const UGGF_ItemFragment_WeaponSettings* GetWeaponSettings() const;
UFUNCTION(BlueprintCallable, BlueprintPure, Category="GGF|EquipmentInstance")
const UGGF_ItemFragment_WeaponSettings* GetOffhandWeaponSettings() const;
#pragma endregion
#pragma region Movements
protected:
UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category="GGF|EquipmentInstance", meta=(BlueprintProtected))
void RefreshMovementSet();
virtual void RefreshMovementSet_Implementation();
#pragma endregion
};
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
#include "Inventory/GGF_WeaponInstance.h"
#include "GIS_EquipmentActorInterface.h"
#include "GIS_ItemInstance.h"
#include "GIS_LogChannels.h"
#include "GMS_MovementSystemComponent.h"
#include "Inventory/GGF_ItemFragments.h"
APawn* UGGF_WeaponInstance::GetWeaponOwner_Implementation() const
{
return OwningPawn;
}
const FGameplayTagContainer UGGF_WeaponInstance::GetWeaponTags_Implementation() const
{
return IsValid(SourceItem) ? SourceItem->GetItemTags() : FGameplayTagContainer::EmptyContainer;
}
void UGGF_WeaponInstance::SetWeaponActive_Implementation(bool bNewActive)
{
bIsActive = true;
}
bool UGGF_WeaponInstance::IsWeaponActive_Implementation() const
{
return bIsActive;
}
//UPrimitiveComponent* UGGF_WeaponInstance::GetPrimitiveComponent_Implementation() const
//{
// checkf(0, TEXT("%S %s Not implemented, Should implemented by sub class!"), __FUNCTION__, *GetClass()->GetName());
// return nullptr;
//}
void UGGF_WeaponInstance::ToggleTrail_Implementation(bool bEnable)
{
OnTrailStateChangedEvent.Broadcast(bEnable);
}
UPrimitiveComponent* UGGF_WeaponInstance::GetPrimitiveComponent_Implementation() const
{
for (int32 i = 0; i < EquipmentActors.Num(); i++)
{
if (IsValid(EquipmentActors[i]))
{
if (UPrimitiveComponent* WeaponPrimitive = IGIS_EquipmentActorInterface::Execute_GetPrimitiveComponent(EquipmentActors[i]))
{
return WeaponPrimitive;
}
}
}
GIS_OWNED_CLOG(OwningPawn, Warning, "Weapon(%ss) failed to get primitive component on EquipmentActors!", *GetNameSafe(this));
return nullptr;
}
void UGGF_WeaponInstance::OnEquipmentBeginPlay_Implementation()
{
Super::OnEquipmentBeginPlay_Implementation();
}
void UGGF_WeaponInstance::OnEquipmentEndPlay_Implementation()
{
Super::OnEquipmentEndPlay_Implementation();
}
void UGGF_WeaponInstance::OnActiveStateChanged_Implementation(bool bNewActiveState)
{
Super::OnActiveStateChanged_Implementation(bNewActiveState);
}
const UGGF_ItemFragment_WeaponSettings* UGGF_WeaponInstance::GetWeaponSettings() const
{
return SourceItem->FindFragmentByClass<UGGF_ItemFragment_WeaponSettings>();
}
const UGGF_ItemFragment_WeaponSettings* UGGF_WeaponInstance::GetOffhandWeaponSettings() const
{
return SourceItem->FindFragmentByClass<UGGF_ItemFragment_OffHandWeaponSettings>();
}
void UGGF_WeaponInstance::RefreshMovementSet_Implementation()
{
if (OwningPawn && OwningPawn->HasAuthority())
{
if (bIsActive)
{
if (auto MSC = UGMS_MovementSystemComponent::GetMovementSystemComponent(OwningPawn))
{
MSC->SetMovementDefinition(GetWeaponSettings()->MovementDefinition);
}
}
}
}
物理层
GIS中,一个装备可以有多个物理呈现,这里仅讨论武器Actor本身。
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "GIS_EquipmentActorInterface.h"
#include "Collision/GCS_TraceStructLibrary.h"
#include "GameFramework/Actor.h"
#include "GGF_Weapon.generated.h"
class UGGF_WeaponInstance;
class UGIS_EquipmentInstance;
class UGIS_EquipmentSystemComponent;
class UGGF_ItemFragment_WeaponSettings;
/**
* Physical representation for a weapon instance.
* 武器实例的物理呈现。
*/
UCLASS(BlueprintType, Blueprintable, Abstract, ClassGroup=(GGF))
class GENERICGAMEFRAMEWORK_API AGGF_Weapon : public AActor, public IGIS_EquipmentActorInterface
{
GENERATED_BODY()
public:
/**
* Default constructor.
* 默认构造函数。
*/
AGGF_Weapon();
// Begin EquipmentActorInterface
virtual UGIS_EquipmentInstance* GetSourceEquipment_Implementation() const override;
virtual UPrimitiveComponent* GetPrimitiveComponent_Implementation() const override;
// End EquipmentActorInterface
protected:
// Begin EquipmentActorInterface
virtual void ReceiveSourceEquipment_Implementation(UGIS_EquipmentInstance* NewEquipmentInstance, int32 Idx) override;
virtual void ReceiveEquipmentBeginPlay_Implementation() override;
virtual void ReceiveEquipmentEndPlay_Implementation() override;
// End EquipmentActorInterface
//是否是副手武器。
UFUNCTION(BlueprintCallable, BlueprintPure, Category="GIS|EquipmentActor")
virtual bool IsOffhandWeapon() const;
/**
* Get correct weapon settings based on if offhand / primary weapon.
* @return The actual weapon settings.
*/
UFUNCTION(BlueprintCallable, BlueprintPure, Category="GIS|EquipmentActor")
virtual const UGGF_ItemFragment_WeaponSettings* GetWeaponSettings() const;
#pragma region Traces
UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category="GCS|WeaponTrace", meta=(BlueprintProtected))
void AddWeaponTraces();
UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category="GCS|WeaponTrace", meta=(BlueprintProtected))
void RemoveWeaponTraces();
/**
* Allow you to customize the source object used for weapon traces.
* 允许你自定义用于武器碰撞检测的源对象。
* @note The source object is the weapon itself by default. 默认是武器本身就是源对象.
* @return The object used as source object for weapon trace.
*/
UFUNCTION(BlueprintNativeEvent, Category="GCS|WeaponTrace", meta=(BlueprintProtected))
UObject* GetSourceObjectForTrace();
virtual UObject* GetSourceObjectForTrace_Implementation();
/**
* Allow you to customize the source component used for weapon traces.
* 允许你自定义用于武器碰撞检测的源组件。
* @note The source component is the weapon primitive component by default. 默认是武器的PrimitiveComponent就是源组件.
* @return The component used as source component for weapon trace.
*/
UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category="GCS|WeaponTrace", meta=(BlueprintProtected))
UPrimitiveComponent* GetSourceComponentForTrace(const FGameplayTag& TraceTag) const;
virtual UPrimitiveComponent* GetSourceComponentForTrace_Implementation(const FGameplayTag& TraceTag) const;
UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category="GCS|WeaponTrace")
void OnWeaponActiveStateChanged(bool bNewActiveState);
UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category="GCS|WeaponTrace")
void OnWeaponTrailStateChanged(bool bEnable);
UFUNCTION()
void OnAnyTraceHit(const FGCS_TraceHandle& TraceHandle, const FHitResult& HitResult);
/**
* Handles weapon trace hits.
* 处理武器碰撞命中。
* @param TraceHandle The collision trace instance. 碰撞检测实例。
* @param HitResult The hit result. 命中结果。
*/
UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category="GCS|WeaponTrace")
void OnWeaponTraceHit(const FGCS_TraceHandle& TraceHandle, const FHitResult& HitResult);
UFUNCTION()
void OnAnyTraceStateChanged(const FGCS_TraceHandle& TraceHandle, bool NewState);
/**
* Handles trace state changes.
* 处理碰撞状态变化。
* @param TraceHandle The collision trace instance. 碰撞检测实例。
* @param NewState The new state. 新状态。
*/
UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category="GCS|WeaponTrace")
void OnWeaponTraceStateChanged(const FGCS_TraceHandle& TraceHandle, bool NewState);
/**
* Traces associated with this weapon.
* 与该武器关联的碰撞检测.
*/
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Transient, Category="EquipmentInstance")
TArray<FGCS_TraceHandle> TraceHandles;
#pragma endregion
#pragma region Visuals
protected:
UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category="GCS|WeaponVisual", meta=(BlueprintProtected))
void ApplyWeaponVisual();
virtual void ApplyWeaponVisual_Implementation();
UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category="GCS|WeaponVisual", meta=(BlueprintProtected))
void ResetWeaponVisual();
virtual void ResetWeaponVisual_Implementation();
#pragma endregion
#pragma region Movements
UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category="GCS|Weapon", meta=(BlueprintProtected))
void RefreshMovementSet();
virtual void RefreshMovementSet_Implementation();
#pragma endregion
protected:
//The equipment instance who created this equipment actor.
UPROPERTY(VisibleAnywhere, Transient, Category="WeaponState")
TObjectPtr<UGGF_WeaponInstance> SourceEquipment{nullptr};
};
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
#include "Inventory/GGF_Weapon.h"
#include "AbilitySystemBlueprintLibrary.h"
#include "Components/PrimitiveComponent.h"
#include "GameFramework/Pawn.h"
#include "GCS_LogChannels.h"
#include "GIS_EquipmentInstance.h"
#include "GIS_LogChannels.h"
#include "GMS_MovementSystemComponent.h"
#include "Collision/GCS_TraceSystemComponent.h"
#include "Components/PoseableMeshComponent.h"
#include "Inventory/GGF_ItemFragments.h"
#include "Inventory/GGF_WeaponInstance.h"
AGGF_Weapon::AGGF_Weapon()
{
PrimaryActorTick.bCanEverTick = false;
bReplicates = true;
bOnlyRelevantToOwner = true;
}
UGIS_EquipmentInstance* AGGF_Weapon::GetSourceEquipment_Implementation() const
{
return SourceEquipment;
}
UPrimitiveComponent* AGGF_Weapon::GetPrimitiveComponent_Implementation() const
{
GCS_CLOG(Error, "Doesn't implement this function to get properly PrimitiveComponent For Weapon.")
return nullptr;
}
void AGGF_Weapon::ReceiveSourceEquipment_Implementation(UGIS_EquipmentInstance* NewEquipmentInstance, int32 Idx)
{
if (!IsValid(NewEquipmentInstance))
{
GIS_CLOG(Error, "received invalid equipment")
return;
}
if (UGGF_WeaponInstance* WeaponInstance = Cast<UGGF_WeaponInstance>(NewEquipmentInstance))
{
SourceEquipment = WeaponInstance;
}
else
{
GIS_CLOG(Error, "failed to received source equipment, expecting type of %s, got %s", *UGGF_WeaponInstance::StaticClass()->GetName(), *GetNameSafe(NewEquipmentInstance->GetClass()))
}
}
void AGGF_Weapon::ReceiveEquipmentBeginPlay_Implementation()
{
if (IsValid(SourceEquipment))
{
SourceEquipment->OnActiveStateChangedEvent.AddDynamic(this, &ThisClass::OnWeaponActiveStateChanged);;
SourceEquipment->OnTrailStateChangedEvent.AddDynamic(this, &ThisClass::OnWeaponTrailStateChanged);
}
else
{
GIS_CLOG(Error, "Missing source equipment!")
}
}
void AGGF_Weapon::ReceiveEquipmentEndPlay_Implementation()
{
if (IsValid(SourceEquipment))
{
SourceEquipment->OnActiveStateChangedEvent.RemoveDynamic(this, &ThisClass::OnWeaponActiveStateChanged);
SourceEquipment->OnTrailStateChangedEvent.RemoveDynamic(this, &ThisClass::OnWeaponTrailStateChanged);
}
}
bool AGGF_Weapon::IsOffhandWeapon() const
{
if (IsValid(SourceEquipment))
{
if (int32 Idx = SourceEquipment->GetIndexOfEquipmentActor(this))
{
// the second equipment actor are considered as Offhand Weapon.
return Idx == 1;
}
}
return false;
}
const UGGF_ItemFragment_WeaponSettings* AGGF_Weapon::GetWeaponSettings() const
{
if (IsOffhandWeapon())
{
return SourceEquipment->GetOffhandWeaponSettings();
}
return SourceEquipment->GetWeaponSettings();
}
void AGGF_Weapon::AddWeaponTraces_Implementation()
{
APawn* Pawn = Cast<APawn>(GetOwner());
if (!IsValid(Pawn))
{
GCS_OWNED_CLOG(this, Warning, "invalid weapon owner!")
return;
}
UGCS_TraceSystemComponent* TSC = UGCS_TraceSystemComponent::GetTraceSystemComponent(Pawn);
if (!IsValid(TSC))
{
GCS_OWNED_CLOG(Pawn, Warning, "missing trace system on weapon owner(%s)!", *GetNameSafe(Pawn))
return;
}
TraceHandles.Reset();
for (const FGCS_TraceDefinition& TraceDefinition : GetWeaponSettings()->TraceDefinitions)
{
UPrimitiveComponent* Primitive = GetSourceComponentForTrace(TraceDefinition.TraceTag);
if (Primitive == nullptr)
{
GCS_OWNED_CLOG(this, Error, "No SourceComponent provided for trace:%s,check your GetSourceComponentForTrace implementation.", *TraceDefinition.TraceTag.ToString());
continue;
}
FGCS_TraceHandle Handle = TSC->AddTrace(TraceDefinition, Primitive, GetSourceObjectForTrace());
if (Handle.IsValidHandle())
{
TraceHandles.Add(Handle);
}
}
TSC->OnTraceHitEvent.AddDynamic(this, &ThisClass::OnAnyTraceHit);
TSC->OnTraceStateChangedEvent.AddDynamic(this, &ThisClass::OnAnyTraceStateChanged);
}
void AGGF_Weapon::RemoveWeaponTraces_Implementation()
{
APawn* Pawn = Cast<APawn>(GetOwner());
if (!IsValid(Pawn))
{
GCS_OWNED_CLOG(this, Warning, "invalid weapon owner!")
return;
}
if (UGCS_TraceSystemComponent* TSC = UGCS_TraceSystemComponent::GetTraceSystemComponent(Pawn))
{
TSC->OnTraceHitEvent.RemoveDynamic(this, &ThisClass::OnAnyTraceHit);
TSC->OnTraceStateChangedEvent.RemoveDynamic(this, &ThisClass::OnAnyTraceStateChanged);
TSC->RemoveTraces(TraceHandles);
}
}
UObject* AGGF_Weapon::GetSourceObjectForTrace_Implementation()
{
return SourceEquipment;
}
UPrimitiveComponent* AGGF_Weapon::GetSourceComponentForTrace_Implementation(const FGameplayTag& TraceTag) const
{
GCS_CLOG(Error, "Doesn't implement this function to get properly SourceComponent For Trace.")
return nullptr;
}
void AGGF_Weapon::OnAnyTraceHit(const FGCS_TraceHandle& TraceHandle, const FHitResult& HitResult)
{
if (TraceHandles.Contains(TraceHandle))
{
OnWeaponTraceHit(TraceHandle, HitResult);
}
}
void AGGF_Weapon::OnAnyTraceStateChanged(const FGCS_TraceHandle& TraceHandle, bool NewState)
{
if (TraceHandles.Contains(TraceHandle))
{
OnWeaponTraceStateChanged(TraceHandle, NewState);
}
}
void AGGF_Weapon::OnWeaponActiveStateChanged_Implementation(bool bNewActiveState)
{
GCS_CLOG(VeryVerbose, "weapon active state changed:%s", bNewActiveState?TEXT("True"):TEXT("False"))
}
void AGGF_Weapon::OnWeaponTrailStateChanged_Implementation(bool bEnable)
{
GCS_CLOG(VeryVerbose, "weapon trail state changed:%s", bEnable?TEXT("True"):TEXT("False"))
}
void AGGF_Weapon::OnWeaponTraceHit_Implementation(const FGCS_TraceHandle& TraceHandle, const FHitResult& HitResult)
{
GCS_CLOG(VeryVerbose, "weapon hit %s", *HitResult.ToString())
}
void AGGF_Weapon::OnWeaponTraceStateChanged_Implementation(const FGCS_TraceHandle& TraceHandle, bool NewState)
{
}
void AGGF_Weapon::ApplyWeaponVisual_Implementation()
{
UPrimitiveComponent* Primitive = Execute_GetPrimitiveComponent(this);
if (!IsValid(Primitive))
{
GCS_CLOG(Error, "Missing primitive component!")
return;
}
if (!IsValid(SourceEquipment))
{
GCS_CLOG(Error, "Missing weapon settings!")
return;
}
if (Primitive->IsA<USkeletalMeshComponent>())
{
CastChecked<USkeletalMeshComponent>(Primitive)->SetSkeletalMeshAsset(GetWeaponSettings()->SkeletalMesh);
}
else if (Primitive->IsA<UStaticMeshComponent>())
{
CastChecked<UStaticMeshComponent>(Primitive)->SetStaticMesh(GetWeaponSettings()->StaticMesh);
}
else if (Primitive->IsA<UPoseableMeshComponent>())
{
CastChecked<UPoseableMeshComponent>(Primitive)->SetSkinnedAsset(GetWeaponSettings()->SkeletalMesh);
}
}
void AGGF_Weapon::ResetWeaponVisual_Implementation()
{
UPrimitiveComponent* Primitive = Execute_GetPrimitiveComponent(this);
if (!IsValid(Primitive))
{
GCS_CLOG(Error, "Missing primitive component!")
return;
}
if (Primitive->IsA<USkeletalMeshComponent>())
{
CastChecked<USkeletalMeshComponent>(Primitive)->SetSkeletalMeshAsset(nullptr);
}
else if (Primitive->IsA<UStaticMeshComponent>())
{
CastChecked<UStaticMeshComponent>(Primitive)->SetStaticMesh(nullptr);
}
else if (Primitive->IsA<UPoseableMeshComponent>())
{
CastChecked<UPoseableMeshComponent>(Primitive)->SetSkinnedAsset(nullptr);
}
}
void AGGF_Weapon::RefreshMovementSet_Implementation()
{
if (APawn* OwningPawn = IGIS_EquipmentInterface::Execute_GetOwningPawn(SourceEquipment))
{
if (OwningPawn->HasAuthority() && GetWeaponSettings())
{
if (IGIS_EquipmentInterface::Execute_IsEquipmentActive(SourceEquipment))
{
if (auto MSC = UGMS_MovementSystemComponent::GetMovementSystemComponent(OwningPawn))
{
MSC->SetMovementDefinition(GetWeaponSettings()->MovementDefinition);
}
}
}
}
}
TODO...