跳转到正文

集成通用运动/库存系统

此文档知道你如何在通用战斗系统中集成通用运动系统。

作

作者

作者

创建时间: 2025年4月08日更新时间: 2026年4月14日

目录

准备工作

本文仅适用于同时购买了我的多个产品的用户,如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中引入运动系统,并包含:

  1. BC_Demo_MovementManager:位于/GenericDemo/GCS/Blueprints/MovementManager,是一个简单的运动管理组件。
  2. 若干动画蓝图:位于/GenericDemo/GCS/Animations/下,是几个轻量的动画蓝图,可以说是GMS的极端简化版本。

你可以将其替换为成别的实现,如:使用ALS/GASP或者我的GMS去替换GCS中的运动系统。

这里会介绍如何将GMS整合进GCS。

完成运动系统的基础设置

替换运动系统

在将GMS整合到GCS之前,你至少应该已经熟悉和了解GMS本身的使用。你可以参考运动系统的配置,在GCS中完成运动系统的基础组件设置。

关键点如下:

  1. 将GCS角色中的BC_Demo_MovementManager替换为GMS的运动系统组件,GMS的运动系统组件提供与BC_Demo_MovementManager相似的API/事件。
  2. 将GCS角色中的SkeletalMesh组件的动画蓝图从ABP_CombatCharacter替换为GMS提供的ABPT_GMS_Main。
  3. 参考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的形式制作。

cpp
/**
 * 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()
};

逻辑层

装备实例

首先抽象出装备实例,负责所有装备的共通逻辑。

cpp
// 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
};
cpp
// 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的武器功能桥接在一起。

cpp
// 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
};
cpp
// 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本身。

cpp
// 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};
};
cpp
// 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...