第一次提交

This commit is contained in:
不明不惑
2026-03-03 01:23:02 +08:00
commit 3e434877e8
1053 changed files with 102411 additions and 0 deletions

View File

@@ -0,0 +1,55 @@
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
using UnrealBuildTool;
public class GenericInputSystem : ModuleRules
{
public GenericInputSystem(ReadOnlyTargetRules Target) : base(Target)
{
PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs;
PublicIncludePaths.AddRange(
new string[] {
// ... add public include paths required here ...
}
);
PrivateIncludePaths.AddRange(
new string[] {
// ... add other private include paths required here ...
}
);
PublicDependencyModuleNames.AddRange(
new string[]
{
"Core",
"EnhancedInput",
"GameplayTags"
// ... add other public dependencies that you statically link with here ...
}
);
PrivateDependencyModuleNames.AddRange(
new string[]
{
"CoreUObject",
"Engine",
"InputCore",
"GameplayDebugger"
// ... add private dependencies that you statically link with here ...
}
);
DynamicallyLoadedModuleNames.AddRange(
new string[]
{
// ... add any modules that your module loads dynamically here ...
}
);
}
}

View File

@@ -0,0 +1,73 @@
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
#include "Actions/GIPS_AsyncAction_ListenInputEvent.h"
#include "Engine/Engine.h"
#include "GIPS_InputSystemComponent.h"
UGIPS_AsyncAction_ListenInputEvent* UGIPS_AsyncAction_ListenInputEvent::ListenInputEvent(UObject* WorldContextObject, UGIPS_InputSystemComponent* InputSystemComponent,
FGameplayTagContainer InputTagsToListen, TArray<ETriggerEvent> EventsToListen, bool bListenForBufferedInput,
bool bExactMatch)
{
if (!IsValid(InputSystemComponent))
{
FFrame::KismetExecutionMessage(TEXT("ListenInputEvent was passed a null InputSystemComponent"), ELogVerbosity::Error);
return nullptr;
}
UWorld* World = GEngine->GetWorldFromContextObject(WorldContextObject, EGetWorldErrorMode::LogAndReturnNull);
UGIPS_AsyncAction_ListenInputEvent* Action = NewObject<UGIPS_AsyncAction_ListenInputEvent>();
Action->Input = InputSystemComponent;
Action->InputTags = InputTagsToListen;
Action->bForBufferedInput = bListenForBufferedInput;
Action->TriggerEvents = EventsToListen;
Action->RegisterWithGameInstance(World);
return Action;
}
void UGIPS_AsyncAction_ListenInputEvent::Activate()
{
if (bForBufferedInput)
{
Input->OnFireBufferedInput.AddDynamic(this, &ThisClass::HandleInput);
}
else
{
Input->OnReceivedInput.AddDynamic(this, &ThisClass::HandleInput);
}
}
void UGIPS_AsyncAction_ListenInputEvent::HandleInput(const FInputActionInstance& ActionData, const FGameplayTag& InputTag, ETriggerEvent TriggerEvent)
{
if (bExact ? InputTags.HasTagExact(InputTag) : InputTags.HasTag(InputTag))
{
if (TriggerEvents.Contains(TriggerEvent))
{
OnReceivedInput.Broadcast(ActionData, InputTag, TriggerEvent);
}
}
}
void UGIPS_AsyncAction_ListenInputEvent::Cancel()
{
if (Input.IsValid())
{
if (bForBufferedInput)
{
if (Input->OnFireBufferedInput.IsAlreadyBound(this, &ThisClass::HandleInput))
{
Input->OnFireBufferedInput.RemoveDynamic(this, &ThisClass::HandleInput);
}
}
else
{
if (Input->OnFireBufferedInput.IsAlreadyBound(this, &ThisClass::HandleInput))
{
Input->OnFireBufferedInput.RemoveDynamic(this, &ThisClass::HandleInput);
}
}
Super::Cancel();
}
}

View File

@@ -0,0 +1,280 @@
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
#include "GIPS_GameplayDebugger.h"
#if WITH_GAMEPLAY_DEBUGGER
#include "GIPS_InputConfig.h"
#include "GIPS_InputControlSetup.h"
#include "GIPS_InputFunctionLibrary.h"
#include "GIPS_InputSystemComponent.h"
#include "Engine/Canvas.h"
#include "GameFramework/Actor.h"
#include "GameFramework/Pawn.h"
#include "GameFramework/PlayerController.h"
FGIPS_GameplayDebuggerCategory_Input::FGIPS_GameplayDebuggerCategory_Input()
{
SetDataPackReplication(&DataPack);
const FName KeyNameOne{"One"};
const FName KeyNameTwo{"Two"};
const FName KeyNameThree{"Three"};
const FName KeyNameFour{"Four"};
BindKeyPress(KeyNameOne, FGameplayDebuggerInputModifier::Shift, this, &FGIPS_GameplayDebuggerCategory_Input::OnShowInputBuffersToggle, EGameplayDebuggerInputMode::Local);
BindKeyPress(KeyNameTwo, FGameplayDebuggerInputModifier::Shift, this, &FGIPS_GameplayDebuggerCategory_Input::OnShowPassedInputEntriesToggle, EGameplayDebuggerInputMode::Local);
BindKeyPress(KeyNameThree, FGameplayDebuggerInputModifier::Shift, this, &FGIPS_GameplayDebuggerCategory_Input::OnShowBlockedInputEntriesToggle, EGameplayDebuggerInputMode::Local);
BindKeyPress(KeyNameFour, FGameplayDebuggerInputModifier::Shift, this, &FGIPS_GameplayDebuggerCategory_Input::OnShowBufferedInputEntriesToggle, EGameplayDebuggerInputMode::Local);
}
void FGIPS_GameplayDebuggerCategory_Input::CollectData(APlayerController* OwnerPC, AActor* DebugActor)
{
if (const UGIPS_InputSystemComponent* InputSystem = UGIPS_InputSystemComponent::GetInputSystemComponent(DebugActor))
{
if (UGIPS_InputControlSetup* InputControlSetup = InputSystem->GetCurrentInputSetup())
{
if (UGIPS_InputConfig* InputConfig = InputSystem->GetInputConfig())
{
DataPack.ActorName = OwnerPC->GetPawn()->GetName();
DataPack.InputConfig = InputConfig->GetName();
DataPack.InputControlSetup = InputControlSetup->GetName();
TMap<FGameplayTag, FGIPS_BufferedInput> ActiveWindows = InputSystem->GetActiveBufferWindows();
for (const FGIPS_InputBufferWindow& Definition : InputConfig->InputBufferDefinitions)
{
FRepData::FInputBuffersDebug ItemData;
ItemData.WindowName = UGIPS_InputFunctionLibrary::GetLastTagName(Definition.Tag);
ItemData.bIsActive = ActiveWindows.Contains(Definition.Tag);
ItemData.InputTagName = ItemData.bIsActive && ActiveWindows[Definition.Tag].InputTag.IsValid()
? UGIPS_InputFunctionLibrary::GetLastTagName(ActiveWindows[Definition.Tag].InputTag)
: NAME_None;
ItemData.InputEvent = ItemData.bIsActive ? ActiveWindows[Definition.Tag].TriggerEvent : ETriggerEvent::None;
DataPack.InputBuffers.Add(ItemData);
}
FGIPS_BufferedInput Input = InputSystem->GetLastBufferedInput();
DataPack.BufferedInputTag = Input.InputTag;
}
}
}
}
void FGIPS_GameplayDebuggerCategory_Input::DrawData(APlayerController* OwnerPC, FGameplayDebuggerCanvasContext& CanvasContext)
{
// Draw the sub-category bindings inline with the category header
{
CanvasContext.CursorX += 200.0f;
CanvasContext.CursorY -= CanvasContext.GetLineHeight();
const TCHAR* Active = TEXT("{green}");
const TCHAR* Inactive = TEXT("{grey}");
CanvasContext.Printf(TEXT("InputBuffers [%s%s{white}]\t PassedInputEntries [%s%s{white}]\t BlockedInputEntries [%s%s{white}]\t BufferedInputEntries [%s%s{white}]\t"),
bShowInputBuffers ? Active : Inactive, *GetInputHandlerDescription(0),
bShowPassedInputEntries ? Active : Inactive, *GetInputHandlerDescription(1),
bShowBlockedInputEntries ? Active : Inactive, *GetInputHandlerDescription(2),
bShowBufferedInputEntries ? Active : Inactive, *GetInputHandlerDescription(3)
);
}
if (LastDrawDataEndSize <= 0.0f)
{
// Default to the full frame size
LastDrawDataEndSize = CanvasContext.Canvas->SizeY - CanvasContext.CursorY - CanvasContext.CursorX;
}
constexpr FLinearColor BackgroundColor(0.1f, 0.1f, 0.1f, 0.8f);
const FVector2D BackgroundPos{CanvasContext.CursorX, CanvasContext.CursorY};
const FVector2D BackgroundSize(CanvasContext.Canvas->SizeX - (2.0f * CanvasContext.CursorX), LastDrawDataEndSize);
// Draw a transparent dark background so that the text is easier to look at
FCanvasTileItem Background(FVector2D(0.0f), BackgroundSize, BackgroundColor);
Background.BlendMode = SE_BLEND_Translucent;
CanvasContext.DrawItem(Background, BackgroundPos.X, BackgroundPos.Y);
CanvasContext.Printf(TEXT("{white}Actor name: {yellow}%s \t{white}Config: {yellow}%s \t{white}Control: {yellow}%s"), *DataPack.ActorName, *DataPack.InputConfig, *DataPack.InputControlSetup);
if (bShowInputBuffers)
{
DrawInputBuffers(CanvasContext, OwnerPC);
}
DrawInputEntries(CanvasContext, OwnerPC);
}
void FGIPS_GameplayDebuggerCategory_Input::FRepData::Serialize(FArchive& Ar)
{
Ar << ActorName;
Ar << InputConfig;
Ar << InputControlSetup;
}
TSharedRef<FGameplayDebuggerCategory> FGIPS_GameplayDebuggerCategory_Input::MakeInstance()
{
return MakeShareable(new FGIPS_GameplayDebuggerCategory_Input());
}
void FGIPS_GameplayDebuggerCategory_Input::OnShowInputBuffersToggle()
{
bShowInputBuffers = !bShowInputBuffers;
}
void FGIPS_GameplayDebuggerCategory_Input::OnShowPassedInputEntriesToggle()
{
bShowPassedInputEntries = !bShowPassedInputEntries;
}
void FGIPS_GameplayDebuggerCategory_Input::OnShowBlockedInputEntriesToggle()
{
bShowBlockedInputEntries = !bShowBlockedInputEntries;
}
void FGIPS_GameplayDebuggerCategory_Input::OnShowBufferedInputEntriesToggle()
{
bShowBufferedInputEntries = !bShowBufferedInputEntries;
}
void FGIPS_GameplayDebuggerCategory_Input::DrawInputBuffers(FGameplayDebuggerCanvasContext& CanvasContext, const APlayerController* OwnerPC) const
{
const float CanvasWidth = CanvasContext.Canvas->SizeX;
int32 NumActive = 0;
for (const FRepData::FInputBuffersDebug& ItemData : DataPack.InputBuffers)
{
NumActive += ItemData.bIsActive;
}
// Measure the individual string sizes, so that we can size the columns properly
// We're picking a long-enough name for the object name sizes
constexpr float Padding = 10.0f;
static float WindowNameSize = 0.0f, ActiveNameSize = 0.0f, InputTagSize = 0.0f, InputEventSize = 0.0f;
if (WindowNameSize <= 0.0f)
{
float TempSizeY = 0.0f;
// We have to actually use representative strings because of the kerning
CanvasContext.MeasureString(TEXT("WindowName: Combo"), WindowNameSize, TempSizeY);
CanvasContext.MeasureString(TEXT("Active: False"), ActiveNameSize, TempSizeY);
CanvasContext.MeasureString(TEXT("InputTag: InputTag.Attack"), InputTagSize, TempSizeY);
CanvasContext.MeasureString(TEXT("InputEvent: Triggered"), InputEventSize, TempSizeY);
WindowNameSize += Padding;
}
const float ColumnWidth = WindowNameSize * 2 + ActiveNameSize + InputTagSize + InputEventSize;
const int NumColumns = FMath::Max(1, FMath::FloorToInt(CanvasWidth / ColumnWidth));
CanvasContext.Print(TEXT("Input Buffers:"));
CanvasContext.CursorX += 200.0f;
CanvasContext.CursorY -= CanvasContext.GetLineHeight();
CanvasContext.Printf(TEXT("Legend: {yellow}Total [%d] {cyan}Active [%d]"), DataPack.InputBuffers.Num(), NumActive);
CanvasContext.CursorX += Padding;
for (const FRepData::FInputBuffersDebug& ItemData : DataPack.InputBuffers)
{
float CursorX = CanvasContext.CursorX;
float CursorY = CanvasContext.CursorY;
// Print positions manually to align them properly
CanvasContext.PrintAt(CursorX + WindowNameSize * 0, CursorY, ItemData.bIsActive ? FColor::Cyan : FColor::Yellow, ItemData.WindowName.ToString());
CanvasContext.PrintAt(CursorX + WindowNameSize * 1, CursorY, FString::Printf(TEXT("{grey}active: {white}%.35s"), ItemData.bIsActive ? TEXT("True") : TEXT("False")));
CanvasContext.PrintAt(CursorX + WindowNameSize * 2, CursorY, FString::Printf(TEXT("{grey}InputTag: {white}%s"), *ItemData.InputTagName.ToString()));
CanvasContext.PrintAt(CursorX + WindowNameSize * 3, CursorY,
FString::Printf(TEXT("{grey}InputEvent: {white}%s"), *UGIPS_InputFunctionLibrary::GetTriggerEventString(ItemData.InputEvent)));
// PrintAt would have reset these values, restore them.
CanvasContext.CursorX = CursorX + (CanvasWidth / NumColumns);
CanvasContext.CursorY = CursorY;
// If we're going to overflow, go to the next line...
if (CanvasContext.CursorX + ColumnWidth >= CanvasWidth)
{
CanvasContext.MoveToNewLine();
CanvasContext.CursorX += Padding;
}
}
// End the row with a newline
if (CanvasContext.CursorX != CanvasContext.DefaultX)
{
CanvasContext.MoveToNewLine();
}
CanvasContext.Printf(TEXT("{grey}Last triggered input: {white}%s"), *(DataPack.BufferedInputTag.IsValid() ? DataPack.BufferedInputTag.GetTagName().ToString() : TEXT("None")));
// End the category with a newline to separate from the other categories
CanvasContext.MoveToNewLine();
}
void FGIPS_GameplayDebuggerCategory_Input::DrawInputEntries(FGameplayDebuggerCanvasContext& CanvasContext, const APlayerController* OwnerPC) const
{
const UGIPS_InputSystemComponent* InputSystem = UGIPS_InputSystemComponent::GetInputSystemComponent(FindLocalDebugActor());
if (InputSystem == nullptr || (!bShowPassedInputEntries && !bShowBlockedInputEntries && !bShowBufferedInputEntries))
return;
const float CanvasWidth = CanvasContext.Canvas->SizeX;
constexpr float Padding = 10.0f;
constexpr float ColumnSize = 150;
constexpr float ColumnWidth = ColumnSize * 3;
const int NumColumns = FMath::Max(1, FMath::FloorToInt(CanvasContext.Canvas->SizeX / ColumnWidth));
auto DrawEntries = [&](const FString& Header, const TArray<FGIPS_BufferedInput>& Entries)
{
CanvasContext.Printf(TEXT("%s"), *Header);
CanvasContext.CursorX += Padding;
for (int i = 0; i < Entries.Num(); ++i)
{
const FGIPS_BufferedInput& Entry = Entries[i];
float CursorX = CanvasContext.CursorX;
float CursorY = CanvasContext.CursorY;
// Print positions manually to align them properly
CanvasContext.PrintAt(CursorX + ColumnSize * 0, CursorY, i == Entries.Num() - 1 ? FColor::Cyan : FColor::Yellow,
Entry.InputTag.IsValid() ? *UGIPS_InputFunctionLibrary::GetLastTagName(Entry.InputTag).ToString() : TEXT("None"));
CanvasContext.PrintAt(CursorX + ColumnSize * 1, CursorY, FString::Printf(TEXT("{grey}TriggerEvent: {white}%s"), *UGIPS_InputFunctionLibrary::GetTriggerEventString(Entry.TriggerEvent)));
CanvasContext.PrintAt(CursorX + ColumnSize * 2, CursorY, FString::Printf(TEXT("{grey}ActionValue: {white}%s"), *Entry.ActionData.GetValue().ToString()));
// PrintAt would have reset these values, restore them.
CanvasContext.CursorX = CursorX + (CanvasWidth / NumColumns);
CanvasContext.CursorY = CursorY;
CanvasContext.MoveToNewLine();
CanvasContext.CursorX += Padding;
}
// End the row with a newline
if (CanvasContext.CursorX != CanvasContext.DefaultX)
{
CanvasContext.MoveToNewLine();
}
};
if (bShowPassedInputEntries)
{
auto Entries = InputSystem->GetPassedInputEntries();
DrawEntries(TEXT("Passed Inputs:"), Entries);
}
if (bShowBlockedInputEntries)
{
auto Entries = InputSystem->GetBlockedInputEntries();
DrawEntries(TEXT("Blocked Inputs:"), Entries);
}
if (bShowBufferedInputEntries)
{
auto Entries = InputSystem->GetBufferedInputEntries();
DrawEntries(TEXT("Buffered Inputs:"), Entries);
}
// End the category with a newline to separate from the other categories
CanvasContext.MoveToNewLine();
}
#endif

View File

@@ -0,0 +1,94 @@
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
#include "GIPS_InputChecker.h"
#include "GameFramework/Actor.h"
#include "GameplayTagAssetInterface.h"
#include "GIPS_InputSystemComponent.h"
bool UGIPS_InputChecker::CheckInput(UGIPS_InputSystemComponent* IC, const FInputActionInstance& ActionData, FGameplayTag InputTag, ETriggerEvent TriggerEvent) const
{
return DoCheckInput(IC, ActionData, InputTag, TriggerEvent);
}
bool UGIPS_InputChecker::DoCheckInput_Implementation(UGIPS_InputSystemComponent* IC, const FInputActionInstance& ActionData, const FGameplayTag& InputTag,
const ETriggerEvent& TriggerEvent) const
{
return true;
}
FGameplayTagContainer UGIPS_InputChecker_TagRelationship::GetActorTags_Implementation(UGIPS_InputSystemComponent* IC) const
{
FGameplayTagContainer Tags;
if (const IGameplayTagAssetInterface* TagAssetInterface = Cast<IGameplayTagAssetInterface>(IC->GetOwner()))
{
TagAssetInterface->GetOwnedGameplayTags(Tags);
}
return Tags;
}
bool UGIPS_InputChecker_TagRelationship::DoCheckInput_Implementation(UGIPS_InputSystemComponent* IC, const FInputActionInstance& ActionData, const FGameplayTag& InputTag,
const ETriggerEvent& TriggerEvent) const
{
const FGameplayTagContainer ActorOwnedTags = GetActorTags(IC);
for (const FGIPS_InputTagRelationship& Relationship : InputTagRelationships)
{
// TagQuery > TagRequirements.
if (Relationship.ActorTagQuery.IsEmpty() || !Relationship.ActorTagQuery.Matches(ActorOwnedTags))
{
continue;
}
bool bNotAllowed = false;
bool bBlocked = false;
if (!Relationship.AllowedInputs.IsEmpty())
{
int32 Index = Relationship.IndexOfAllowedInput(InputTag, TriggerEvent);
if (Index == INDEX_NONE)
{
bNotAllowed = true;
}
}
if (!Relationship.BlockedInputs.IsEmpty())
{
int32 Index = Relationship.IndexOfBlockedInput(InputTag, TriggerEvent);
if (Index != INDEX_NONE)
{
bBlocked = true;
}
}
if (bNotAllowed || bBlocked)
{
return false;
}
}
return true;
}
#if WITH_EDITOR
#include "UObject/ObjectSaveContext.h"
void UGIPS_InputChecker_TagRelationship::PreSave(FObjectPreSaveContext SaveContext)
{
for (FGIPS_InputTagRelationship& InputTagRelationship : InputTagRelationships)
{
InputTagRelationship.EditorFriendlyName = InputTagRelationship.ActorTagQuery.GetDescription();
// TArray<FGameplayTag> TagArray;
// InputTagRelationship.InputTagsAllowed.GetGameplayTagArray(TagArray);
// InputTagRelationship.AllowedInputs.Empty();
// for (FGameplayTag Tag : TagArray)
// {
// FGIPS_AllowedInput AllowedInput;
// AllowedInput.TriggerEvents.Empty();
// AllowedInput.InputTag = Tag;
// InputTagRelationship.AllowedInputs.Add(AllowedInput);
// }
}
Super::PreSave(SaveContext);
}
#endif

View File

@@ -0,0 +1,26 @@
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
#include "GIPS_InputConfig.h"
#if WITH_EDITOR
#include "UObject/ObjectSaveContext.h"
#include "Misc/DataValidation.h"
void UGIPS_InputConfig::PreSave(FObjectPreSaveContext SaveContext)
{
Super::PreSave(SaveContext);
}
EDataValidationResult UGIPS_InputConfig::IsDataValid(FDataValidationContext& Context) const
{
if (InputActionMappings.IsEmpty())
{
Context.AddError(FText::FromString(TEXT("InputActionMappings can't be empty!")));
return EDataValidationResult::Invalid;
}
return Super::IsDataValid(Context);
}
#endif

View File

@@ -0,0 +1,133 @@
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
#include "GIPS_InputControlSetup.h"
#include "GIPS_LogChannels.h"
#include "GIPS_InputChecker.h"
#include "GIPS_InputSystemComponent.h"
#include "GIPS_InputFunctionLibrary.h"
#include "Misc/DataValidation.h"
void UGIPS_InputControlSetup::HandleInput(UGIPS_InputSystemComponent* IC, const FInputActionInstance& ActionData, const FGameplayTag& InputTag, ETriggerEvent TriggerEvent) const
{
TArray<TObjectPtr<UGIPS_InputProcessor>> Processors = FilterInputProcessors(InputTag, TriggerEvent);
for (int32 i = 0; i < Processors.Num(); i++)
{
UGIPS_InputProcessor* Processor = Processors[i];
if (Processor->CanHandleInput(IC, ActionData, InputTag, TriggerEvent))
{
Processor->HandleInput(IC, ActionData, InputTag, TriggerEvent);
if (InputProcessorExecutionType == EGIPS_InputProcessorExecutionType::FirstOnly)
{
return;
}
}
else
{
if (ShouldDebug(InputTag, TriggerEvent))
{
GIPS_OWNED_CLOG(IC, Verbose, "Input:%s can't be handled by processor:%s at index(%d), TriggerEvent:%s", *InputTag.ToString(), *Processor->GetClass()->GetName(),
i, *UGIPS_InputFunctionLibrary::GetTriggerEventString(TriggerEvent));
}
}
}
}
bool UGIPS_InputControlSetup::CheckInput(UGIPS_InputSystemComponent* IC, const FInputActionInstance& ActionData, const FGameplayTag& InputTag, ETriggerEvent TriggerEvent)
{
if (InternalCheckInput(IC, ActionData, InputTag, TriggerEvent))
{
if (ShouldDebug(InputTag, TriggerEvent))
{
GIPS_OWNED_CLOG(IC, Verbose, "Input:%s passed,TriggerEvent:%s", *InputTag.ToString(), *UGIPS_InputFunctionLibrary::GetTriggerEventString(TriggerEvent));
}
IC->RegisterPassedInputEntry({InputTag, ActionData, TriggerEvent});
return true;
}
if (bEnableInputBuffer)
{
if (IC->TrySaveInput(ActionData, InputTag, TriggerEvent))
{
if (ShouldDebug(InputTag, TriggerEvent))
{
GIPS_OWNED_CLOG(IC, Verbose, "Input:%s buffered,TriggerEvent:%s", *InputTag.ToString(), *UGIPS_InputFunctionLibrary::GetTriggerEventString(TriggerEvent));
}
IC->RegisterBufferedInputEntry({InputTag, ActionData, TriggerEvent});
return false;
}
}
if (ShouldDebug(InputTag, TriggerEvent))
{
GIPS_OWNED_CLOG(IC, Verbose, "Input:%s blocked,TriggerEvent:%s", *InputTag.ToString(), *UGIPS_InputFunctionLibrary::GetTriggerEventString(TriggerEvent));
}
IC->RegisterBlockedInputEntry({InputTag, ActionData, TriggerEvent});
return false;
}
bool UGIPS_InputControlSetup::ShouldDebug(const FGameplayTag& InputTag, const ETriggerEvent& TriggerEvent) const
{
return bEnableInputDebug && (DebugInputTags.IsEmpty() || DebugInputTags.HasTagExact(InputTag)) && (DebugTriggerEvents.IsEmpty() || DebugTriggerEvents.Contains(TriggerEvent));
}
bool UGIPS_InputControlSetup::InternalCheckInput(UGIPS_InputSystemComponent* IC, const FInputActionInstance& ActionData, const FGameplayTag& InputTag, ETriggerEvent TriggerEvent)
{
if (InputCheckers.IsEmpty())
{
return true;
}
int32 AlwaysAllowedInputIndex = INDEX_NONE;
for (int32 i = 0; i < AlwaysAllowedInputs.Num(); i++)
{
if (AlwaysAllowedInputs[i].InputTag == InputTag && (AlwaysAllowedInputs[i].TriggerEvents.IsEmpty() || AlwaysAllowedInputs[i].TriggerEvents.Contains(TriggerEvent)))
{
AlwaysAllowedInputIndex = i;
}
}
if (AlwaysAllowedInputIndex != INDEX_NONE)
{
return true;
}
for (const UGIPS_InputChecker* Checker : InputCheckers)
{
if (Checker == nullptr)
continue;
if (!Checker->CheckInput(IC, ActionData, InputTag, TriggerEvent))
return false;
}
return true;
}
TArray<TObjectPtr<UGIPS_InputProcessor>> UGIPS_InputControlSetup::FilterInputProcessors(const FGameplayTag& InputTag, const ETriggerEvent& TriggerEvent) const
{
return InputProcessors.FilterByPredicate([&](TObjectPtr<UGIPS_InputProcessor> Processor)
{
return Processor && !Processor->InputTags.IsEmpty() && Processor->InputTags.HasTagExact(InputTag) && Processor->TriggerEvents.Contains(TriggerEvent);
});
}
#if WITH_EDITOR
EDataValidationResult UGIPS_InputControlSetup::IsDataValid(FDataValidationContext& Context) const
{
for (int32 i = 0; i < InputProcessors.Num(); i++)
{
if (InputProcessors[i] == nullptr)
{
Context.AddError(FText::FromString(FString::Format(TEXT("Invalid processor at index:{0}"), {i})));
return EDataValidationResult::Invalid;
}
if (InputProcessors[i]->InputTags.IsEmpty())
{
Context.AddWarning(FText::FromString(FString::Format(TEXT("Invalid processor at index:{0} has empty InputTags!!!"), {i})));
return EDataValidationResult::Invalid;
}
}
return Super::IsDataValid(Context);
}
#endif

View File

@@ -0,0 +1,93 @@
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
#include "GIPS_InputFunctionLibrary.h"
#include "GameplayTagsManager.h"
FInputActionValue UGIPS_InputFunctionLibrary::GetInputActionValue(const FInputActionInstance& ActionDataData)
{
return ActionDataData.GetValue();
}
FName UGIPS_InputFunctionLibrary::GetLastTagName(FGameplayTag Tag)
{
if (!Tag.IsValid())
{
return FName(TEXT("Invalid Tag"));
}
TArray<FName> TagNames;
UGameplayTagsManager::Get().SplitGameplayTagFName(Tag, TagNames);
if (TagNames.IsEmpty())
{
return FName(TEXT("Invalid Tag"));
}
return TagNames.Last();
}
FString UGIPS_InputFunctionLibrary::GetSimpleStringOfTags(FGameplayTagContainer Tags)
{
return Tags.ToStringSimple();
}
TArray<FName> UGIPS_InputFunctionLibrary::GetLastTagNameArray(FGameplayTagContainer Tags)
{
TArray<FGameplayTag> TagArray;
Tags.GetGameplayTagArray(TagArray);
TArray<FName> NameArray;
for (const FGameplayTag& Tag : TagArray)
{
NameArray.Add(GetLastTagName(Tag));
}
return NameArray;
}
FString UGIPS_InputFunctionLibrary::GetLastTagNameString(FGameplayTagContainer Tags)
{
TArray<FGameplayTag> TagArray;
Tags.GetGameplayTagArray(TagArray);
FString Output;
for (const FGameplayTag& Tag : TagArray)
{
Output.Append(FString::Format(TEXT(" ({0}) "), {GetLastTagName(Tag).ToString()}));
}
return Output;
}
FString UGIPS_InputFunctionLibrary::GetTagQueryDescription(const FGameplayTagQuery& TagQuery)
{
if (TagQuery.IsEmpty())
{
return TEXT("Empty Query");
}
return TagQuery.GetDescription();
}
FString UGIPS_InputFunctionLibrary::GetTriggerEventString(ETriggerEvent TriggerEvent)
{
switch (TriggerEvent)
{
case ETriggerEvent::Started:
return TEXT("Start");
case ETriggerEvent::Triggered:
return TEXT("Triggered");
case ETriggerEvent::Canceled:
return TEXT("Canceled");
case ETriggerEvent::Ongoing:
return TEXT("Ongoing");
case ETriggerEvent::Completed:
return TEXT("Completed");
case ETriggerEvent::None:
default:
return TEXT("None");
}
}

View File

@@ -0,0 +1,90 @@
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
#include "GIPS_InputProcessor.h"
#include "EnhancedInputComponent.h"
#include "GIPS_InputChecker.h"
#include "GIPS_InputFunctionLibrary.h"
UGIPS_InputProcessor::UGIPS_InputProcessor()
{
}
bool UGIPS_InputProcessor::CanHandleInput(UGIPS_InputSystemComponent* IC, const FInputActionInstance& ActionData, FGameplayTag InputTag, ETriggerEvent TriggerEvent) const
{
return CheckCanHandleInput(IC, ActionData, InputTag, TriggerEvent);
}
void UGIPS_InputProcessor::HandleInput(UGIPS_InputSystemComponent* IC, const FInputActionInstance& ActionData, FGameplayTag InputTag, ETriggerEvent TriggerEvent) const
{
switch (TriggerEvent)
{
case ETriggerEvent::Triggered:
return HandleInputTriggered(IC, ActionData, InputTag);
case ETriggerEvent::Started:
return HandleInputStarted(IC, ActionData, InputTag);
case ETriggerEvent::Ongoing:
return HandleInputOngoing(IC, ActionData, InputTag);
case ETriggerEvent::Canceled:
return HandleInputCanceled(IC, ActionData, InputTag);
case ETriggerEvent::Completed:
return HandleInputCompleted(IC, ActionData, InputTag);
default:
return;
}
}
FInputActionValue UGIPS_InputProcessor::GetInputActionValue(const FInputActionInstance& ActionData) const
{
const FInputActionValue Value = ActionData.GetValue();
return Value;
}
bool UGIPS_InputProcessor::CheckCanHandleInput_Implementation(UGIPS_InputSystemComponent* IC, const FInputActionInstance& ActionData, FGameplayTag InputTag, ETriggerEvent TriggerEvent) const
{
return true;
}
FString UGIPS_InputProcessor::GetEditorFriendlyName_Implementation() const
{
return TEXT("");
}
void UGIPS_InputProcessor::HandleInputCanceled_Implementation(UGIPS_InputSystemComponent* IC, const FInputActionInstance& ActionData, FGameplayTag InputTag) const
{
}
void UGIPS_InputProcessor::HandleInputCompleted_Implementation(UGIPS_InputSystemComponent* IC, const FInputActionInstance& ActionData, FGameplayTag InputTag) const
{
}
void UGIPS_InputProcessor::HandleInputOngoing_Implementation(UGIPS_InputSystemComponent* IC, const FInputActionInstance& ActionData, FGameplayTag InputTag) const
{
}
void UGIPS_InputProcessor::HandleInputStarted_Implementation(UGIPS_InputSystemComponent* IC, const FInputActionInstance& ActionData, FGameplayTag InputTag) const
{
}
void UGIPS_InputProcessor::HandleInputTriggered_Implementation(UGIPS_InputSystemComponent* IC, const FInputActionInstance& ActionData, FGameplayTag InputTag) const
{
}
#if WITH_EDITOR
#include "UObject/ObjectSaveContext.h"
FString UGIPS_InputProcessor::NativeGetEditorFriendlyName() const
{
FString BPOverride = GetEditorFriendlyName();
return FString::Format(TEXT("Input:{0} {1}"), {UGIPS_InputFunctionLibrary::GetLastTagNameString(InputTags),BPOverride});
}
void UGIPS_InputProcessor::PreSave(FObjectPreSaveContext SaveContext)
{
EditorFriendlyName = NativeGetEditorFriendlyName();
UObject::PreSave(SaveContext);
}
#endif

View File

@@ -0,0 +1,546 @@
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
#include "GIPS_InputSystemComponent.h"
#include "EnhancedInputSubsystems.h"
#include "GameFramework/Pawn.h"
#include "GameFramework/PlayerController.h"
#include "Engine/LocalPlayer.h"
#include "GIPS_LogChannels.h"
#include "GIPS_InputConfig.h"
#include "GIPS_InputControlSetup.h"
#include "GIPS_InputFunctionLibrary.h"
#include "Engine/World.h"
#include "Misc/DataValidation.h"
UGIPS_InputSystemComponent::UGIPS_InputSystemComponent(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer)
{
}
void UGIPS_InputSystemComponent::OnRegister()
{
Super::OnRegister();
const UWorld* World = GetWorld();
if (World->IsGameWorld())
{
APlayerController* PCOwner = GetOwner<APlayerController>();
APawn* PawnOwner = GetOwner<APawn>();
OwnerType = PCOwner ? EGIPS_OwnerType::PC : EGIPS_OwnerType::Pawn;
if (OwnerType == EGIPS_OwnerType::Pawn)
{
if (ensure(PawnOwner))
{
PawnOwner->ReceiveRestartedDelegate.AddDynamic(this, &UGIPS_InputSystemComponent::OnPawnRestarted);
PawnOwner->ReceiveControllerChangedDelegate.AddDynamic(this, &UGIPS_InputSystemComponent::OnControllerChanged);
// If our pawn has an input component we were added after restart
if (PawnOwner->InputComponent)
{
OnPawnRestarted(PawnOwner);
}
}
}
if (OwnerType == EGIPS_OwnerType::PC)
{
if (ensure(PCOwner))
{
// TODO 支持放到PC上。
}
}
}
}
void UGIPS_InputSystemComponent::OnUnregister()
{
const UWorld* World = GetWorld();
if (World && World->IsGameWorld())
{
CleanupInputComponent();
if (OwnerType == EGIPS_OwnerType::Pawn)
{
APawn* PawnOwner = GetOwner<APawn>();
PawnOwner->ReceiveRestartedDelegate.RemoveAll(this);
PawnOwner->ReceiveControllerChangedDelegate.RemoveAll(this);
}
if (OwnerType == EGIPS_OwnerType::PC)
{
APlayerController* PCOwner = GetOwner<APlayerController>();
}
}
Super::OnUnregister();
}
APawn* UGIPS_InputSystemComponent::GetControlledPawn() const
{
if (OwnerType == EGIPS_OwnerType::Pawn)
{
return GetOwner<APawn>();
}
if (OwnerType == EGIPS_OwnerType::PC)
{
APlayerController* PC = GetOwner<APlayerController>();
return PC ? PC->GetPawn() : nullptr;
}
return nullptr;
}
UGIPS_InputSystemComponent* UGIPS_InputSystemComponent::GetInputSystemComponent(const AActor* Actor)
{
return IsValid(Actor) ? Actor->FindComponentByClass<UGIPS_InputSystemComponent>() : nullptr;
}
bool UGIPS_InputSystemComponent::FindInputSystemComponent(const AActor* Actor, UGIPS_InputSystemComponent*& Component)
{
Component = GetInputSystemComponent(Actor);
return Component != nullptr;
}
void UGIPS_InputSystemComponent::OnSetupPlayerInputComponent_Implementation(UEnhancedInputComponent* NewInputComponent)
{
BindInputActions();
}
void UGIPS_InputSystemComponent::OnCleanupPlayerInputComponent_Implementation(UEnhancedInputComponent* PrevInputComponent)
{
}
void UGIPS_InputSystemComponent::OnPawnRestarted(APawn* Pawn)
{
if (ensure(Pawn && Pawn == GetOwner()) && Pawn->InputComponent)
{
GIPS_CLOG(Verbose, "cleanup and setup input for Pawn: %s", Pawn ? *Pawn->GetName() : TEXT("NONE"))
CleanupInputComponent();
if (Pawn->InputComponent)
{
SetupInputComponent(Pawn->InputComponent);
}
}
}
void UGIPS_InputSystemComponent::OnControllerChanged(APawn* Pawn, AController* OldController, AController* NewController)
{
// Only handle releasing, restart is a better time to handle binding
if (ensure(Pawn && Pawn == GetOwner()) && OldController)
{
GIPS_CLOG(Verbose, "cleanup input component due to controller change. %s", Pawn ? *Pawn->GetName() : TEXT("NONE"))
CleanupInputComponent(OldController);
}
}
void UGIPS_InputSystemComponent::CleanInputActionValueBindings()
{
for (auto& Binding : InputActionValueBindings)
{
InputComponent->RemoveActionValueBinding(Binding.Value);
GIPS_CLOG(Verbose, "Clean input action value binding for InputTag:{%s}", *Binding.Key.ToString());
}
InputActionValueBindings.Empty();
}
void UGIPS_InputSystemComponent::SetupInputActionValueBindings()
{
check(InputConfig);
for (auto& Mapping : InputConfig->InputActionMappings)
{
if (Mapping.Value.bValueBinding)
{
FEnhancedInputActionValueBinding& Binding = InputComponent->BindActionValue(Mapping.Value.InputAction);
int32 BindingIndex = InputComponent->GetActionValueBindings().Find(Binding);
InputActionValueBindings.Emplace(Mapping.Key, BindingIndex);
GIPS_CLOG(Verbose, "Setup input action value binding for InputTag:{%s} ad index:{%d}", *Mapping.Key.ToString(), BindingIndex);
}
}
}
void UGIPS_InputSystemComponent::SetupInputComponent(UInputComponent* NewInputComponent)
{
InputComponent = Cast<UEnhancedInputComponent>(NewInputComponent);
if (ensureMsgf(InputComponent, TEXT("Project must use EnhancedInputComponent to support PlayerControlsComponent")))
{
UEnhancedInputLocalPlayerSubsystem* Subsystem = GetEnhancedInputSubsystem();
if (Subsystem && InputMappingContext)
{
Subsystem->AddMappingContext(InputMappingContext, InputPriority);
}
CleanInputActionValueBindings();
SetupInputActionValueBindings();
GIPS_CLOG(Verbose, "Setup for Pawn/PC: %s", GetOwner() ? *GetOwner()->GetName() : TEXT("NONE"))
OnSetupPlayerInputComponent(InputComponent);
SetupInputComponentEvent.Broadcast(InputComponent);
}
}
void UGIPS_InputSystemComponent::CleanupInputComponent(AController* OldController)
{
UEnhancedInputLocalPlayerSubsystem* Subsystem = GetEnhancedInputSubsystem(OldController);
if (Subsystem && InputComponent)
{
OnCleanupPlayerInputComponent(InputComponent);
CleanupInputComponentEvent.Broadcast(InputComponent);
if (InputMappingContext)
{
Subsystem->RemoveMappingContext(InputMappingContext);
}
CleanInputActionValueBindings();
}
InputComponent = nullptr;
}
UEnhancedInputLocalPlayerSubsystem* UGIPS_InputSystemComponent::GetEnhancedInputSubsystem(AController* OldController) const
{
if (OwnerType == EGIPS_OwnerType::Pawn && !GetOwner<APawn>())
{
return nullptr;
}
const APawn* PawnOwner = GetOwner<APawn>();
const APlayerController* PC = PawnOwner ? PawnOwner->GetController<APlayerController>() : GetOwner<APlayerController>();
if (!PC)
{
PC = Cast<APlayerController>(OldController);
if (!PC)
{
return nullptr;
}
}
const ULocalPlayer* LP = PC->GetLocalPlayer();
if (!LP)
{
return nullptr;
}
return LP->GetSubsystem<UEnhancedInputLocalPlayerSubsystem>();
}
void UGIPS_InputSystemComponent::BindInputActions()
{
check(InputConfig);
for (auto& Pair : InputConfig->InputActionMappings)
{
// Generic binding.
InputComponent->BindAction(Pair.Value.InputAction, ETriggerEvent::Triggered, this, &ThisClass::InputActionCallback, Pair.Key, ETriggerEvent::Triggered);
InputComponent->BindAction(Pair.Value.InputAction, ETriggerEvent::Started, this, &ThisClass::InputActionCallback, Pair.Key, ETriggerEvent::Started);
InputComponent->BindAction(Pair.Value.InputAction, ETriggerEvent::Ongoing, this, &ThisClass::InputActionCallback, Pair.Key, ETriggerEvent::Ongoing);
InputComponent->BindAction(Pair.Value.InputAction, ETriggerEvent::Completed, this, &ThisClass::InputActionCallback, Pair.Key, ETriggerEvent::Completed);
InputComponent->BindAction(Pair.Value.InputAction, ETriggerEvent::Canceled, this, &ThisClass::InputActionCallback, Pair.Key, ETriggerEvent::Canceled);
}
}
UGIPS_InputControlSetup* UGIPS_InputSystemComponent::GetCurrentInputSetup() const
{
if (InputControlSetups.IsValidIndex(InputControlSetups.Num() - 1))
{
return InputControlSetups[InputControlSetups.Num() - 1];
}
return nullptr;
}
UGIPS_InputConfig* UGIPS_InputSystemComponent::GetInputConfig() const
{
return InputConfig;
}
void UGIPS_InputSystemComponent::PushInputSetup(UGIPS_InputControlSetup* NewSetup)
{
if (!InputControlSetups.Contains(NewSetup))
{
InputControlSetups.Push(NewSetup);
}
}
void UGIPS_InputSystemComponent::PopInputSetup()
{
if (InputControlSetups.Num() > 1)
{
InputControlSetups.Pop();
}
}
bool UGIPS_InputSystemComponent::CheckInputAllowed(FGameplayTag InputTag, ETriggerEvent TriggerEvent)
{
FInputActionInstance ActionData;
return CheckInputAllowed(ActionData, InputTag, TriggerEvent);
}
bool UGIPS_InputSystemComponent::CheckInputAllowed(const FInputActionInstance& ActionData, FGameplayTag InputTag, ETriggerEvent TriggerEvent)
{
if (UGIPS_InputControlSetup* Setup = GetCurrentInputSetup())
{
return Setup->CheckInput(this, ActionData, InputTag, TriggerEvent);
}
return true;
}
void UGIPS_InputSystemComponent::InputActionCallback(const FInputActionInstance& ActionData, FGameplayTag InputTag, ETriggerEvent TriggerEvent)
{
if (InputTag.IsValid())
{
GIPS_CLOG(VeryVerbose, "Input(%s) triggered with event(%s) and value(%s)", *UGIPS_InputFunctionLibrary::GetLastTagName(InputTag).ToString(), *InputTag.ToString(),
*UGIPS_InputFunctionLibrary::GetTriggerEventString(TriggerEvent), *ActionData.GetValue().ToString());
if (!bProcessingInputExternally && CheckInputAllowed(ActionData, InputTag, TriggerEvent))
{
ProcessInput(ActionData, InputTag, TriggerEvent);
}
LastInputActionValues.Emplace(InputTag, ActionData.GetValue());
OnReceivedInput.Broadcast(ActionData, InputTag, TriggerEvent);
}
}
void UGIPS_InputSystemComponent::ProcessInput(const FInputActionInstance& ActionData, const FGameplayTag& InputTag, ETriggerEvent TriggerEvent)
{
if (UGIPS_InputControlSetup* Setup = GetCurrentInputSetup())
{
Setup->HandleInput(this, ActionData, InputTag, TriggerEvent);
}
}
UInputAction* UGIPS_InputSystemComponent::GetInputActionOfInputTag(FGameplayTag InputTag) const
{
if (InputTag.IsValid() && InputConfig->InputActionMappings.Contains(InputTag))
return InputConfig->InputActionMappings[InputTag].InputAction;
return nullptr;
}
FInputActionValue UGIPS_InputSystemComponent::GetInputActionValueOfInputTag(FGameplayTag InputTag) const
{
if (InputComponent)
{
if (UInputAction* IA = GetInputActionOfInputTag(InputTag))
{
return InputComponent->GetBoundActionValue(IA);
}
}
return FInputActionValue();
}
FInputActionValue UGIPS_InputSystemComponent::GetLastInputActionValueOfInputTag(FGameplayTag InputTag) const
{
if (InputTag.IsValid() && LastInputActionValues.Contains(InputTag))
{
return LastInputActionValues[InputTag];
}
return FInputActionValue();
}
void UGIPS_InputSystemComponent::RegisterPassedInputEntry(const FGIPS_BufferedInput& InputEntry)
{
if (PassedInputEntries.Num() >= MaxInputEntriesNum)
{
PassedInputEntries.RemoveAtSwap(0);
}
PassedInputEntries.Add(InputEntry);
}
void UGIPS_InputSystemComponent::RegisterBlockedInputEntry(const FGIPS_BufferedInput& InputEntry)
{
if (BlockedInputEntries.Num() >= MaxInputEntriesNum)
{
BlockedInputEntries.RemoveAtSwap(0);
}
BlockedInputEntries.Add(InputEntry);
}
void UGIPS_InputSystemComponent::RegisterBufferedInputEntry(const FGIPS_BufferedInput& InputEntry)
{
if (BufferedInputEntries.Num() >= MaxInputEntriesNum)
{
BufferedInputEntries.RemoveAtSwap(0);
}
BufferedInputEntries.Add(InputEntry);
}
#pragma region InputBuffer
bool UGIPS_InputSystemComponent::TrySaveInput(const FInputActionInstance& ActionData, const FGameplayTag& InputTag, ETriggerEvent TriggerEvent)
{
if (ActiveBufferWindows.IsEmpty())
{
// No any buffer window.
return false;
}
TArray<FGameplayTag> ActiveBufferWindowNames;
ActiveBufferWindows.GetKeys(ActiveBufferWindowNames);
// To see if any active buffer window can accept this input.
int32 Counter{0};
for (FGameplayTag& ActiveBufferWindowName : ActiveBufferWindowNames)
{
if (TrySaveAsBufferedInput(ActiveBufferWindowName, ActionData, InputTag, TriggerEvent))
{
Counter++;
}
}
return Counter > 0;
}
void UGIPS_InputSystemComponent::FireBufferedInput()
{
ProcessInput(CurrentBufferedInput.ActionData, CurrentBufferedInput.InputTag, CurrentBufferedInput.TriggerEvent);
OnFireBufferedInput.Broadcast(CurrentBufferedInput.ActionData, CurrentBufferedInput.InputTag, CurrentBufferedInput.TriggerEvent);
ResetBufferedInput();
CloseActiveInputBufferWindows();
}
void UGIPS_InputSystemComponent::OpenInputBufferWindow(FGameplayTag BufferWindowName)
{
if (!BufferWindowName.IsValid())
{
GIPS_CLOG(Warning, "invalid buffer name.");
return;
}
if (ActiveBufferWindows.Contains(BufferWindowName))
{
GIPS_CLOG(Warning, "Can't Open buffer window(%s) as it already active!", *BufferWindowName.ToString());
return;
}
if (!ActiveBufferWindows.Contains(BufferWindowName))
{
if (const FGIPS_InputBufferWindow* Window = InputConfig->InputBufferDefinitions.FindByKey(BufferWindowName))
{
ActiveBufferWindows.FindOrAdd(BufferWindowName);
GIPS_CLOG(Verbose, "Open buffer window:%s", *BufferWindowName.ToString());
InputBufferWindowStateChangedEvent.Broadcast(BufferWindowName, true);
}
}
}
void UGIPS_InputSystemComponent::CloseInputBufferWindow(FGameplayTag BufferWindowName)
{
if (ActiveBufferWindows.Contains(BufferWindowName))
{
CurrentBufferedInput = ActiveBufferWindows[BufferWindowName];
if (CurrentBufferedInput.InputTag.IsValid())
{
GIPS_CLOG(Verbose, "Fire buffered input(:%s,TriggerEvent:%s) from Window(%s)", *CurrentBufferedInput.InputTag.ToString(),
*UGIPS_InputFunctionLibrary::GetTriggerEventString(CurrentBufferedInput.TriggerEvent), *BufferWindowName.ToString());
FireBufferedInput();
}
ActiveBufferWindows.Remove(BufferWindowName);
GIPS_CLOG(Verbose, "Close buffer window:%s", *BufferWindowName.ToString());
InputBufferWindowStateChangedEvent.Broadcast(BufferWindowName, false);
}
}
void UGIPS_InputSystemComponent::CloseActiveInputBufferWindows()
{
ActiveBufferWindows.Empty();
}
FGIPS_BufferedInput UGIPS_InputSystemComponent::GetLastBufferedInput() const
{
return LastBufferedInput;
}
TMap<FGameplayTag, FGIPS_BufferedInput> UGIPS_InputSystemComponent::GetActiveBufferWindows() const
{
return ActiveBufferWindows;
}
void UGIPS_InputSystemComponent::ResetBufferedInput()
{
LastBufferedInput = CurrentBufferedInput;
CurrentBufferedInput = FGIPS_BufferedInput();
}
bool UGIPS_InputSystemComponent::TrySaveAsBufferedInput(const FGameplayTag BufferWindowName, const FInputActionInstance& ActionData, const FGameplayTag& InputTag, ETriggerEvent TriggerEvent)
{
if (!ActiveBufferWindows.Contains(BufferWindowName))
return false;
FGIPS_BufferedInput& BufferedInput = ActiveBufferWindows[BufferWindowName];
const FGIPS_InputBufferWindow* Definition = InputConfig->InputBufferDefinitions.FindByKey(BufferWindowName);
if (Definition == nullptr)
return false;
const int32 AllowedInputIndex = Definition->IndexOfAllowedInput(InputTag, TriggerEvent);
if (AllowedInputIndex == INDEX_NONE)
return false;
// Instance buffering.
if (Definition->BufferType == EGIPS_InputBufferType::Instant)
{
BufferedInput.InputTag = InputTag;
BufferedInput.ActionData = ActionData;
BufferedInput.TriggerEvent = TriggerEvent;
CurrentBufferedInput = BufferedInput;
GIPS_CLOG(Verbose, "Instantly fire buffered input(%s,TriggerEvent:%s) from Window(%s)", *InputTag.ToString(), *UGIPS_InputFunctionLibrary::GetTriggerEventString(TriggerEvent),
*BufferWindowName.ToString());
FireBufferedInput();
ActiveBufferWindows.Remove(BufferWindowName);
return true;
}
if (BufferedInput.InputTag.IsValid() && Definition->BufferType == EGIPS_InputBufferType::HighestPriority)
{
const int32 ExistingInputIndex = Definition->IndexOfAllowedInput(BufferedInput.InputTag, BufferedInput.TriggerEvent);
if (ExistingInputIndex != INDEX_NONE && AllowedInputIndex < ExistingInputIndex)
{
GIPS_CLOG(Verbose, "Record new buffered input(%s,TriggerEvent:%s) in Window(%s),Before was input(%s,TriggerEvent:%s)", *InputTag.ToString(),
*UGIPS_InputFunctionLibrary::GetTriggerEventString(TriggerEvent), *BufferWindowName.ToString(),
*BufferedInput.InputTag.ToString(),
*UGIPS_InputFunctionLibrary::GetTriggerEventString(BufferedInput.TriggerEvent));
BufferedInput.InputTag = InputTag;
BufferedInput.ActionData = ActionData;
BufferedInput.TriggerEvent = TriggerEvent;
return true;
}
}
GIPS_CLOG(Verbose, "Record buffered input(%s,TriggerEvent:%s) in Window(%s)", *InputTag.ToString(),
*UGIPS_InputFunctionLibrary::GetTriggerEventString(TriggerEvent), *BufferWindowName.ToString());
BufferedInput.InputTag = InputTag;
BufferedInput.ActionData = ActionData;
BufferedInput.TriggerEvent = TriggerEvent;
return true;
}
#pragma endregion
#pragma region DataValidation
#if WITH_EDITOR
EDataValidationResult UGIPS_InputSystemComponent::IsDataValid(FDataValidationContext& Context) const
{
if (!InputConfig)
{
Context.AddError(FText::FromString(TEXT("InputConfig is required.")));
return EDataValidationResult::Invalid;
}
if (InputControlSetups.IsEmpty())
{
Context.AddError(FText::FromString(TEXT("At least one InputControlSetup is required.")));
return EDataValidationResult::Invalid;
}
return Super::IsDataValid(Context);
}
#endif
#pragma endregion

View File

@@ -0,0 +1,25 @@
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
#include "GIPS_InputTypes.h"
#include "GIPS_InputFunctionLibrary.h"
bool FGIPS_InputBufferWindow::operator==(const FGameplayTag& OtherTag) const
{
return Tag == OtherTag;
}
bool FGIPS_InputBufferWindow::operator!=(const FGameplayTag& OtherTag) const
{
return Tag != OtherTag;
}
FString FGIPS_BufferedInput::ToString() const
{
return FString::Format(TEXT("Tag:{0},Event:{1},Source:{2}"), {
InputTag.IsValid() ? *UGIPS_InputFunctionLibrary::GetLastTagName(InputTag).ToString() : TEXT("None"),
*UGIPS_InputFunctionLibrary::GetTriggerEventString(TriggerEvent),
ActionData.GetSourceAction() ? ActionData.GetSourceAction()->GetName() : TEXT("None"),
});
}

View File

@@ -0,0 +1,36 @@
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
#include "GIPS_LogChannels.h"
#include "GameFramework/Actor.h"
#include "Components/ActorComponent.h"
DEFINE_LOG_CATEGORY(LogGIPS);
FString GetGIPSLogContextString(const UObject* ContextObject)
{
ENetRole Role = ROLE_None;
FString RoleName = TEXT("None");
FString Name = "None";
if (const AActor* Actor = Cast<AActor>(ContextObject))
{
Role = Actor->GetLocalRole();
Name = Actor->GetName();
}
else if (const UActorComponent* Component = Cast<UActorComponent>(ContextObject))
{
Role = Component->GetOwnerRole();
Name = Component->GetOwner()->GetName();
}
else if (IsValid(ContextObject))
{
Name = ContextObject->GetName();
}
if (Role != ROLE_None)
{
RoleName = (Role == ROLE_Authority) ? TEXT("Server") : TEXT("Client");
}
return FString::Printf(TEXT("[%s] (%s)"), *RoleName, *Name);
}

View File

@@ -0,0 +1,39 @@
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
#include "GenericInputSystem.h"
#if WITH_GAMEPLAY_DEBUGGER
#include "GameplayDebugger.h"
#include "GIPS_GameplayDebugger.h"
#endif // WITH_GAMEPLAY_DEBUGGER
#define LOCTEXT_NAMESPACE "FGenericInputSystemModule"
void FGenericInputSystemModule::StartupModule()
{
// This code will execute after your module is loaded into memory; the exact timing is specified in the .uplugin file per-module
#if WITH_GAMEPLAY_DEBUGGER
IGameplayDebugger& GameplayDebuggerModule = IGameplayDebugger::Get();
GameplayDebuggerModule.RegisterCategory("GenericInputSystem", IGameplayDebugger::FOnGetCategory::CreateStatic(&FGIPS_GameplayDebuggerCategory_Input::MakeInstance));
GameplayDebuggerModule.NotifyCategoriesChanged();
#endif // WITH_GAMEPLAY_DEBUGGER
}
void FGenericInputSystemModule::ShutdownModule()
{
// This function may be called during shutdown to clean up your module. For modules that support dynamic reloading,
// we call this function before unloading the module.
#if WITH_GAMEPLAY_DEBUGGER
if (IGameplayDebugger::IsAvailable())
{
IGameplayDebugger& GameplayDebuggerModule = IGameplayDebugger::Get();
GameplayDebuggerModule.UnregisterCategory("GenericInputSystem");
GameplayDebuggerModule.NotifyCategoriesChanged();
}
#endif // WITH_GAMEPLAY_DEBUGGER
}
#undef LOCTEXT_NAMESPACE
IMPLEMENT_MODULE(FGenericInputSystemModule, GenericInputSystem)

View File

@@ -0,0 +1,99 @@
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "GameplayTagContainer.h"
#include "InputAction.h"
#include "Engine/CancellableAsyncAction.h"
#include "GIPS_InputTypes.h"
#include "GIPS_AsyncAction_ListenInputEvent.generated.h"
class UGIPS_InputSystemComponent;
/**
* Async action for listening to input events.
* 用于监听输入事件的异步动作。
*/
UCLASS()
class GENERICINPUTSYSTEM_API UGIPS_AsyncAction_ListenInputEvent : public UCancellableAsyncAction
{
GENERATED_BODY()
public:
/**
* Creates an async action to listen for input events.
* 创建一个异步动作以监听输入事件。
* @param WorldContextObject The world context object. 世界上下文对象。
* @param InputSystemComponent The input system component. 输入系统组件。
* @param InputTagsToListen Tags to listen for. 要监听的标签。
* @param EventsToListen Trigger events to listen for. 要监听的触发事件。
* @param bListenForBufferedInput Whether to listen for buffered inputs. 是否监听缓冲输入。
* @param bExactMatch Whether to require exact tag matching. 是否要求精确标签匹配。
* @return The async action instance. 异步动作实例。
*/
UFUNCTION(BlueprintCallable, Category="GIPS|Input", meta=(WorldContext = "WorldContextObject", BlueprintInternalUseOnly="true"))
static UGIPS_AsyncAction_ListenInputEvent* ListenInputEvent(UObject* WorldContextObject, UGIPS_InputSystemComponent* InputSystemComponent,UPARAM(meta=(Categories="InputTag,GIPS.InputTag"))
FGameplayTagContainer InputTagsToListen,
TArray<ETriggerEvent> EventsToListen, bool bListenForBufferedInput = false, bool bExactMatch = true);
/**
* Activates the async action.
* 激活异步动作。
*/
virtual void Activate() override;
/**
* Event triggered when an input is received.
* 接收到输入时触发的事件。
*/
UPROPERTY(BlueprintAssignable)
FGIPS_ReceivedInputSignature OnReceivedInput;
/**
* Handles the input event.
* 处理输入事件。
* @param ActionData The input action data. 输入动作数据。
* @param InputTag The gameplay tag for the input. 输入的游戏标签。
* @param TriggerEvent The trigger event type. 触发事件类型。
*/
UFUNCTION()
void HandleInput(const FInputActionInstance& ActionData, const FGameplayTag& InputTag, ETriggerEvent TriggerEvent);
private:
/**
* Cancels the async action.
* 取消异步动作。
*/
virtual void Cancel() override;
/**
* Tags to listen for.
* 要监听的标签。
*/
FGameplayTagContainer InputTags;
/**
* Trigger events to listen for.
* 要监听的触发事件。
*/
TArray<ETriggerEvent> TriggerEvents;
/**
* Whether to listen for buffered inputs.
* 是否监听缓冲输入。
*/
bool bForBufferedInput{false};
/**
* Whether to require exact tag matching.
* 是否要求精确标签匹配。
*/
bool bExact{true};
/**
* Weak reference to the input system component.
* 输入系统组件的弱引用。
*/
TWeakObjectPtr<UGIPS_InputSystemComponent> Input;
};

View File

@@ -0,0 +1,69 @@
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
#pragma once
#include "GameplayTagContainer.h"
#if WITH_GAMEPLAY_DEBUGGER
#include "CoreMinimal.h"
#include "GameplayDebuggerCategory.h"
#include "InputTriggers.h"
class APlayerController;
class AActor;
class FGIPS_GameplayDebuggerCategory_Input : public FGameplayDebuggerCategory
{
public:
FGIPS_GameplayDebuggerCategory_Input();
void CollectData(APlayerController* OwnerPC, AActor* DebugActor) override;
void DrawData(APlayerController* OwnerPC, FGameplayDebuggerCanvasContext& CanvasContext) override;
static TSharedRef<FGameplayDebuggerCategory> MakeInstance();
void OnShowInputBuffersToggle();
void OnShowPassedInputEntriesToggle();
void OnShowBlockedInputEntriesToggle();
void OnShowBufferedInputEntriesToggle();
protected:
void DrawInputBuffers(FGameplayDebuggerCanvasContext& CanvasContext, const APlayerController* OwnerPC) const;
void DrawInputEntries(FGameplayDebuggerCanvasContext& CanvasContext, const APlayerController* OwnerPC) const;
struct FRepData
{
FString ActorName;
FString InputConfig;
FString InputControlSetup;
FGameplayTag BufferedInputTag;
struct FInputBuffersDebug
{
FName WindowName;
bool bIsActive;
FName InputTagName;
ETriggerEvent InputEvent;
};
TArray<FInputBuffersDebug> InputBuffers;
void Serialize(FArchive&Ar);
};
FRepData DataPack;
private:
// Save off the last expected draw size so that we can draw a border around it next frame (and hope we're the same size)
float LastDrawDataEndSize = 0.0f;
bool bShowInputBuffers = true;
bool bShowPassedInputEntries = true;
bool bShowBlockedInputEntries = true;
bool bShowBufferedInputEntries = true;
};
#endif // WITH_GAMEPLAY_DEBUGG

View File

@@ -0,0 +1,97 @@
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "GIPS_InputTypes.h"
#include "InputAction.h"
#include "UObject/Object.h"
#include "GIPS_InputChecker.generated.h"
class UGIPS_InputSystemComponent;
/**
* Base class for input validation, inheritable via Blueprint or C++.
* 输入验证的基类可通过蓝图或C++继承。
*/
UCLASS(Abstract, Blueprintable, BlueprintType, EditInlineNew, DefaultToInstanced, CollapseCategories, Const)
class GENERICINPUTSYSTEM_API UGIPS_InputChecker : public UObject
{
GENERATED_BODY()
public:
/**
* Checks if an input is valid.
* 检查输入是否有效。
* @param IC The input system component. 输入系统组件。
* @param ActionData The input action data. 输入动作数据。
* @param InputTag The gameplay tag for the input. 输入的游戏标签。
* @param TriggerEvent The trigger event type. 触发事件类型。
* @return True if the input is valid, false otherwise. 如果输入有效则返回true否则返回false。
*/
UFUNCTION(BlueprintCallable, Category="GIPS|Input")
bool CheckInput(UGIPS_InputSystemComponent* IC, const FInputActionInstance& ActionData, UPARAM(meta = (Categories="InputTag,GIPS.InputTag")) FGameplayTag InputTag,
ETriggerEvent TriggerEvent) const;
protected:
/**
* Blueprint-implementable input validation logic.
* 可通过蓝图实现的输入验证逻辑。
* @param IC The input system component. 输入系统组件。
* @param ActionData The input action data. 输入动作数据。
* @param InputTag The gameplay tag for the input. 输入的游戏标签。
* @param TriggerEvent The trigger event type. 触发事件类型。
* @return True if the input is valid, false otherwise. 如果输入有效则返回true否则返回false。
*/
UFUNCTION(BlueprintNativeEvent, Category="GIPS|Input", meta=(DisplayName="DoCheckInput"))
bool DoCheckInput(UGIPS_InputSystemComponent* IC, const FInputActionInstance& ActionData, const FGameplayTag& InputTag, const ETriggerEvent& TriggerEvent) const;
};
/**
* Input checker for tag-based relationships.
* 基于标签关系的输入检查器。
*/
UCLASS()
class GENERICINPUTSYSTEM_API UGIPS_InputChecker_TagRelationship : public UGIPS_InputChecker
{
GENERATED_BODY()
protected:
/**
* List of input tag relationships for validation.
* 用于验证的输入标签关系列表。
*/
UPROPERTY(EditAnywhere, Category="GIPS|Input", meta=(TitleProperty="EditorFriendlyName", ShowOnlyInnerProperties))
TArray<FGIPS_InputTagRelationship> InputTagRelationships;
/**
* Gets the actor's tags for validation.
* 获取用于验证的演员标签。
* @param IC The input system component. 输入系统组件。
* @return The actor's gameplay tag container. 演员的游戏标签容器。
*/
UFUNCTION(BlueprintCallable, Category="GIPS|Input", BlueprintNativeEvent)
FGameplayTagContainer GetActorTags(UGIPS_InputSystemComponent* IC) const;
virtual FGameplayTagContainer GetActorTags_Implementation(UGIPS_InputSystemComponent* IC) const;
/**
* Implementation of input validation logic.
* 输入验证逻辑的实现。
* @param IC The input system component. 输入系统组件。
* @param ActionData The input action data. 输入动作数据。
* @param InputTag The gameplay tag for the input. 输入的游戏标签。
* @param TriggerEvent The trigger event type. 触发事件类型。
* @return True if the input is valid, false otherwise. 如果输入有效则返回true否则返回false。
*/
virtual bool DoCheckInput_Implementation(UGIPS_InputSystemComponent* IC, const FInputActionInstance& ActionData, const FGameplayTag& InputTag,
const ETriggerEvent& TriggerEvent) const override;
#if WITH_EDITOR
/**
* Called before saving the object.
* 在保存对象之前调用。
* @param SaveContext The save context. 保存上下文。
*/
virtual void PreSave(FObjectPreSaveContext SaveContext) override;
#endif
};

View File

@@ -0,0 +1,54 @@
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "GameplayTagContainer.h"
#include "GIPS_InputTypes.h"
#include "Engine/DataAsset.h"
#include "GIPS_InputConfig.generated.h"
class UInputAction;
/**
* Configuration data asset for the input system component.
* 输入系统组件的配置数据资产。
*/
UCLASS(Const)
class GENERICINPUTSYSTEM_API UGIPS_InputConfig : public UDataAsset
{
GENERATED_BODY()
public:
/**
* Mapping of input tags to input action settings.
* 输入标签到输入动作设置的映射。
*/
UPROPERTY(EditAnywhere, Category="GIPS|Input", meta=(Categories="InputTag,GIPS.InputTag", ForceInlineRow))
TMap<FGameplayTag, FGIPS_InputActionSetting> InputActionMappings;
/**
* List of defined input buffer windows.
* 定义的输入缓冲窗口列表。
*/
UPROPERTY(EditAnywhere, Category="GIPS|Input", meta=(DisplayName="Input Buffer Windows", TitleProperty="Tag", NoElementDuplicate))
TArray<FGIPS_InputBufferWindow> InputBufferDefinitions;
protected:
#if WITH_EDITOR
/**
* Called before saving the object.
* 在保存对象之前调用。
* @param SaveContext The save context. 保存上下文。
*/
virtual void PreSave(FObjectPreSaveContext SaveContext) override;
/**
* Validates the data asset in the editor.
* 在编辑器中验证数据资产。
* @param Context The data validation context. 数据验证上下文。
* @return The validation result. 验证结果。
*/
virtual EDataValidationResult IsDataValid(FDataValidationContext& Context) const override;
#endif
};

View File

@@ -0,0 +1,143 @@
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "GIPS_InputTypes.h"
#include "GIPS_InputControlSetup.generated.h"
struct FInputActionInstance;
class UGIPS_InputChecker;
class UGIPS_InputProcessor;
class UGIPS_InputSystemComponent;
/**
* Data asset for defining input checkers and processors.
* 定义输入检查器和处理器的数据资产。
*/
UCLASS(BlueprintType, Const)
class GENERICINPUTSYSTEM_API UGIPS_InputControlSetup : public UDataAsset
{
GENERATED_BODY()
public:
/**
* Handles input actions for the input system.
* 为输入系统处理输入动作。
* @param IC The input system component. 输入系统组件。
* @param ActionData The input action data. 输入动作数据。
* @param InputTag The gameplay tag for the input. 输入的游戏标签。
* @param TriggerEvent The trigger event type. 触发事件类型。
*/
void HandleInput(UGIPS_InputSystemComponent* IC, const FInputActionInstance& ActionData, const FGameplayTag& InputTag, ETriggerEvent TriggerEvent) const;
/**
* Checks if an input is allowed.
* 检查输入是否被允许。
* @param IC The input system component. 输入系统组件。
* @param ActionData The input action data. 输入动作数据。
* @param InputTag The gameplay tag for the input. 输入的游戏标签。
* @param TriggerEvent The trigger event type. 触发事件类型。
* @return True if the input is allowed, false otherwise. 如果输入被允许则返回true否则返回false。
*/
bool CheckInput(UGIPS_InputSystemComponent* IC, const FInputActionInstance& ActionData, const FGameplayTag& InputTag, ETriggerEvent TriggerEvent);
protected:
/**
* Determines if debugging is enabled for the input.
* 确定是否为输入启用调试。
* @param InputTag The gameplay tag for the input. 输入的游戏标签。
* @param TriggerEvent The trigger event type. 触发事件类型。
* @return True if debugging is enabled, false otherwise. 如果启用调试则返回true否则返回false。
*/
bool ShouldDebug(const FGameplayTag& InputTag, const ETriggerEvent& TriggerEvent) const;
/**
* Internal logic for checking input validity.
* 检查输入有效性的内部逻辑。
* @param IC The input system component. 输入系统组件。
* @param ActionData The input action data. 输入动作数据。
* @param InputTag The gameplay tag for the input. 输入的游戏标签。
* @param TriggerEvent The trigger event type. 触发事件类型。
* @return True if the input is valid, false otherwise. 如果输入有效则返回true否则返回false。
*/
virtual bool InternalCheckInput(UGIPS_InputSystemComponent* IC, const FInputActionInstance& ActionData, const FGameplayTag& InputTag, ETriggerEvent TriggerEvent);
/**
* Filters input processors based on the input tag and trigger event.
* 根据输入标签和触发事件过滤输入处理器。
* @param InputTag The gameplay tag for the input. 输入的游戏标签。
* @param TriggerEvent The trigger event type. 触发事件类型。
* @return Array of filtered input processors. 过滤后的输入处理器数组。
*/
TArray<TObjectPtr<UGIPS_InputProcessor>> FilterInputProcessors(const FGameplayTag& InputTag, const ETriggerEvent& TriggerEvent) const;
/**
* List of input events will bypass all the input checkers.
* 此列表中的输入时间会绕过所有的输入检查器。
*/
UPROPERTY(EditAnywhere, Category="GIPS|Input", meta=(TitleProperty="InputTag"))
TArray<FGIPS_AllowedInput> AlwaysAllowedInputs;
/**
* List of input checkers to validate input events.
* 验证输入事件的一组输入检查器。
*/
UPROPERTY(EditAnywhere, Instanced, Category="GIPS|Input")
TArray<TObjectPtr<UGIPS_InputChecker>> InputCheckers;
/**
* If enabled, disallowed inputs are attempted to be stored in the input buffer.
* 如果启用,不允许的输入将尝试存储在输入缓冲区中。
* @attention Input buffering may not be needed for some setups, like UI. 对于某些设置如UI可能不需要输入缓冲。
*/
UPROPERTY(EditAnywhere, Category="GIPS|Input")
bool bEnableInputBuffer{false};
/**
* Controls the execution order of input processors for a single input event.
* 控制单个输入事件的处理器执行顺序。
*/
UPROPERTY(EditAnywhere, Category="GIPS|Input")
EGIPS_InputProcessorExecutionType InputProcessorExecutionType{EGIPS_InputProcessorExecutionType::MatchAll};
/**
* List of input processors to handle allowed input events sequentially.
* 处理允许的输入事件的一组输入处理器,按顺序执行。
*/
UPROPERTY(EditAnywhere, Instanced, Category="GIPS|Input", meta=(TitleProperty="EditorFriendlyName", ShowOnlyInnerProperties))
TArray<TObjectPtr<UGIPS_InputProcessor>> InputProcessors;
/**
* Enables debug logging for input events.
* 为输入事件启用调试日志。
* @attention Requires LogGIPS to be set to VeryVerbose to take effect. 需要将LogGIPS设置为VeryVerbose才能生效。
*/
UPROPERTY(EditAnywhere, Category="GIPS|Debug")
bool bEnableInputDebug{false};
/**
* Input tags to debug (logs all tags if empty).
* 要调试的输入标签(如果为空则记录所有标签)。
*/
UPROPERTY(EditAnywhere, Category="GIPS|Debug", meta=(EditCondition="bEnableInputDebug", Categories="InputTag,GIPS.InputTag"))
FGameplayTagContainer DebugInputTags{};
/**
* Trigger events to debug (logs all events if empty).
* 要调试的触发事件(如果为空则记录所有事件)。
*/
UPROPERTY(EditAnywhere, Category="GIPS|Debug", meta=(EditCondition="bEnableInputDebug"))
TArray<ETriggerEvent> DebugTriggerEvents{ETriggerEvent::Started, ETriggerEvent::Completed};
public:
#if WITH_EDITOR
/**
* Validates the data asset in the editor.
* 在编辑器中验证数据资产。
* @param Context The data validation context. 数据验证上下文。
* @return The validation result. 验证结果。
*/
virtual EDataValidationResult IsDataValid(FDataValidationContext& Context) const override;
#endif
};

View File

@@ -0,0 +1,81 @@
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "GameplayTagContainer.h"
#include "InputAction.h"
#include "InputActionValue.h"
#include "Kismet/BlueprintFunctionLibrary.h"
#include "GIPS_InputFunctionLibrary.generated.h"
/**
* Utility functions for the Generic Input System.
* 通用输入系统的实用功能。
*/
UCLASS()
class GENERICINPUTSYSTEM_API UGIPS_InputFunctionLibrary : public UBlueprintFunctionLibrary
{
GENERATED_BODY()
public:
/**
* Retrieves the input action value from the action instance.
* 从输入动作实例中获取输入动作值。
* @param ActionDataData The input action instance data. 输入动作实例数据。
* @return The input action value. 输入动作值。
*/
UFUNCTION(BlueprintCallable, BlueprintPure, Category="GIPS|Input", meta = (BlueprintAutocast))
static FInputActionValue GetInputActionValue(const FInputActionInstance& ActionDataData);
/**
* Gets the last tag name from a gameplay tag.
* 从游戏标签中获取最后一个标签名称。
* @param Tag The gameplay tag. 游戏标签。
* @return The last tag name. 最后一个标签名称。
*/
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "GIPS|Utilities", meta=(DisplayName="Get Last Tag Name(GIPS)"))
static FName GetLastTagName(FGameplayTag Tag);
/**
* Converts a gameplay tag container to a simple string.
* 将游戏标签容器转换为简单字符串。
* @param Tags The gameplay tag container. 游戏标签容器。
* @return The string representation of the tags. 标签的字符串表示。
*/
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "GIPS|Utilities", meta=(DisplayName="Get Simple String Of Tags(GIPS)"))
static FString GetSimpleStringOfTags(FGameplayTagContainer Tags);
/**
* Gets an array of last tag names from a tag container.
* 从标签容器中获取最后一个标签名称的数组。
* @param Tags The gameplay tag container. 游戏标签容器。
* @return Array of last tag names. 最后一个标签名称的数组。
*/
static TArray<FName> GetLastTagNameArray(FGameplayTagContainer Tags);
/**
* Gets a string of last tag names from a tag container.
* 从标签容器中获取最后一个标签名称的字符串。
* @param Tags The gameplay tag container. 游戏标签容器。
* @return String of last tag names. 最后一个标签名称的字符串。
*/
static FString GetLastTagNameString(FGameplayTagContainer Tags);
/**
* Gets a description of a gameplay tag query.
* 获取游戏标签查询的描述。
* @param TagQuery The gameplay tag query. 游戏标签查询。
* @return The description of the tag query. 标签查询的描述。
*/
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "GIPS|Utilities", meta=(DisplayName="Get TagQuery Description(GIPS)"))
static FString GetTagQueryDescription(const FGameplayTagQuery& TagQuery);
/**
* Converts a trigger event to a string.
* 将触发事件转换为字符串。
* @param TriggerEvent The trigger event. 触发事件。
* @return The string representation of the trigger event. 触发事件的字符串表示。
*/
static FString GetTriggerEventString(ETriggerEvent TriggerEvent);
};

View File

@@ -0,0 +1,193 @@
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "GameplayTagContainer.h"
#include "InputAction.h"
#include "InputActionValue.h"
#include "GIPS_InputProcessor.generated.h"
class UGIPS_InputChecker;
struct FInputActionInstance;
enum class ETriggerEvent : uint8;
class UGIPS_InputSystemComponent;
/**
* Base class for processing input actions.
* 处理输入动作的基类。
*/
UCLASS(EditInlineNew, DefaultToInstanced, CollapseCategories, Blueprintable, Const, HideDropdown)
class GENERICINPUTSYSTEM_API UGIPS_InputProcessor : public UObject
{
GENERATED_BODY()
public:
UGIPS_InputProcessor();
/**
* Indicates if the processor supports networking.
* 指示处理器是否支持网络。
* @return True if networking is supported, false otherwise. 如果支持网络则返回true否则返回false。
*/
virtual bool IsSupportedForNetworking() const override { return true; }
/**
* Checks if the processor can handle the given input.
* 检查处理器是否可以处理给定的输入。
* @param IC The input system component. 输入系统组件。
* @param ActionData The input action data. 输入动作数据。
* @param InputTag The gameplay tag for the input. 输入的游戏标签。
* @param TriggerEvent The trigger event type. 触发事件类型。
* @return True if the input can be handled, false otherwise. 如果可以处理输入则返回true否则返回false。
*/
UFUNCTION(BlueprintCallable, Category="GIPS|Input", meta=(AutoCreateRefTerm="ActionData"))
bool CanHandleInput(UGIPS_InputSystemComponent* IC, const FInputActionInstance& ActionData, UPARAM(meta = (Categories="InputTag,GIPS.InputTag"))
FGameplayTag InputTag,
ETriggerEvent TriggerEvent) const;
/**
* Handles the input action.
* 处理输入动作。
* @param IC The input system component. 输入系统组件。
* @param ActionData The input action data. 输入动作数据。
* @param InputTag The gameplay tag for the input. 输入的游戏标签。
* @param TriggerEvent The trigger event type. 触发事件类型。
*/
UFUNCTION(BlueprintCallable, Category="GIPS|Input", meta=(AutoCreateRefTerm="ActionData"))
void HandleInput(UGIPS_InputSystemComponent* IC, const FInputActionInstance& ActionData, UPARAM(meta = (Categories="InputTag,GIPS.InputTag"))
FGameplayTag InputTag,
ETriggerEvent TriggerEvent) const;
/**
* Tags that this processor responds to (if empty, responds to all inputs).
* 处理器响应的输入标签(如果为空,则响应所有输入)。
*/
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="InputProcessor", meta=(Categories="InputTag,GIPS.InputTag", DisplayPriority=0))
FGameplayTagContainer InputTags;
/**
* Trigger events that this processor responds to.
* 处理器响应的触发事件。
*/
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="InputProcessor", meta=(DisplayPriority=1))
TArray<ETriggerEvent> TriggerEvents{ETriggerEvent::Started};
protected:
/**
* Gets the input action value (deprecated).
* 获取输入动作值(已弃用)。
* @param ActionData The input action data. 输入动作数据。
* @return The input action value. 输入动作值。
*/
UFUNCTION(BlueprintPure, Category="InputProcessor", meta=(DeprecatedFunction, DeprecationMessage="Use GetInputActionValueOfInputTag"))
FInputActionValue GetInputActionValue(const FInputActionInstance& ActionData) const;
/**
* Blueprint-implementable check for input handling.
* 可通过蓝图实现的输入处理检查。
* @param IC The input system component. 输入系统组件。
* @param ActionData The input action data. 输入动作数据。
* @param InputTag The gameplay tag for the input. 输入的游戏标签。
* @param TriggerEvent The trigger event type. 触发事件类型。
* @return True if the input can be handled, false otherwise. 如果可以处理输入则返回true否则返回false。
*/
UFUNCTION(BlueprintNativeEvent, Category="InputProcessor")
bool CheckCanHandleInput(UGIPS_InputSystemComponent* IC, const FInputActionInstance& ActionData, FGameplayTag InputTag, ETriggerEvent TriggerEvent) const;
/**
* Native implementation of input handling check.
* 输入处理检查的原生实现。
*/
virtual bool CheckCanHandleInput_Implementation(UGIPS_InputSystemComponent* IC, const FInputActionInstance& ActionData, FGameplayTag InputTag, ETriggerEvent TriggerEvent) const;
/**
* Handles the triggered input event.
* 处理触发的输入事件。
* @param IC The input system component. 输入系统组件。
* @param ActionData The input action data. 输入动作数据。
* @param InputTag The gameplay tag for the input. 输入的游戏标签。
*/
UFUNCTION(BlueprintNativeEvent, Category="InputProcessor")
void HandleInputTriggered(UGIPS_InputSystemComponent* IC, const FInputActionInstance& ActionData, FGameplayTag InputTag) const;
/**
* Handles the started input event.
* 处理开始的输入事件。
* @param IC The input system component. 输入系统组件。
* @param ActionData The input action data. 输入动作数据。
* @param InputTag The gameplay tag for the input. 输入的游戏标签。
*/
UFUNCTION(BlueprintNativeEvent, Category="InputProcessor")
void HandleInputStarted(UGIPS_InputSystemComponent* IC, const FInputActionInstance& ActionData, FGameplayTag InputTag) const;
/**
* Handles the ongoing input event.
* 处理持续的输入事件。
* @param IC The input system component. 输入系统组件。
* @param ActionData The input action data. 输入动作数据。
* @param InputTag The gameplay tag for the input. 输入的游戏标签。
*/
UFUNCTION(BlueprintNativeEvent, Category="InputProcessor")
void HandleInputOngoing(UGIPS_InputSystemComponent* IC, const FInputActionInstance& ActionData, FGameplayTag InputTag) const;
/**
* Handles the canceled input event.
* 处理取消的输入事件。
* @param IC The input system component. 输入系统组件。
* @param ActionData The input action data. 输入动作数据。
* @param InputTag The gameplay tag for the input. 输入的游戏标签。
*/
UFUNCTION(BlueprintNativeEvent, Category="InputProcessor")
void HandleInputCanceled(UGIPS_InputSystemComponent* IC, const FInputActionInstance& ActionData, FGameplayTag InputTag) const;
/**
* Handles the completed input event.
* 处理完成的输入事件。
* @param IC The input system component. 输入系统组件。
* @param ActionData The input action data. 输入动作数据。
* @param InputTag The gameplay tag for the input. 输入的游戏标签。
*/
UFUNCTION(BlueprintNativeEvent, Category="InputProcessor")
void HandleInputCompleted(UGIPS_InputSystemComponent* IC, const FInputActionInstance& ActionData, FGameplayTag InputTag) const;
/**
* Gets a friendly name for the editor.
* 获取编辑器友好的名称。
* @return The editor-friendly name. 编辑器友好的名称。
*/
UFUNCTION(BlueprintNativeEvent, Category="InputProcessor")
FString GetEditorFriendlyName() const;
#if WITH_EDITORONLY_DATA
/**
* Friendly name for displaying in the editor.
* 在编辑器中显示的友好名称。
*/
UPROPERTY(VisibleAnywhere, Category=AlwaysHidden, Meta=(EditCondition=False, EditConditionHides))
FString EditorFriendlyName;
/**
* Description for developers in the editor.
* 编辑器中用于开发者的描述。
*/
UPROPERTY(EditAnywhere, Category = "Editor")
FString DevDescription;
#endif
#if WITH_EDITOR
/**
* Native implementation to get editor-friendly name.
* 获取编辑器友好名称的原生实现。
* @return The editor-friendly name. 编辑器友好的名称。
*/
FString NativeGetEditorFriendlyName() const;
/**
* Called before saving the object.
* 在保存对象之前调用。
* @param SaveContext The save context. 保存上下文。
*/
virtual void PreSave(FObjectPreSaveContext SaveContext) override;
#endif
};

View File

@@ -0,0 +1,550 @@
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "EnhancedInputComponent.h"
#include "GameplayTagContainer.h"
#include "GIPS_InputProcessor.h"
#include "GIPS_InputTypes.h"
#include "GIPS_InputSystemComponent.generated.h"
class UGIPS_InputControlSetup;
class UInputMappingContext;
class UGIPS_InputConfig;
class UEnhancedInputLocalPlayerSubsystem;
/**
* Core component for handling input in the Generic Input System.
* 通用输入系统中处理输入的核心组件。
* @attention Must be attached to a Pawn or PlayerController. 必须挂载到Pawn或PlayerController上。
*/
UCLASS(ClassGroup=GIPS, Blueprintable, meta=(BlueprintSpawnableComponent), AutoExpandCategories=("GIPS"))
class GENERICINPUTSYSTEM_API UGIPS_InputSystemComponent : public UActorComponent
{
GENERATED_BODY()
friend UGIPS_InputControlSetup;
public:
enum class EGIPS_OwnerType:uint8
{
Pawn,
PC,
};
public:
UGIPS_InputSystemComponent(const FObjectInitializer& ObjectInitializer);
//~ Begin UActorComponent interface
/**
* Called when the component is registered.
* 组件注册时调用。
*/
virtual void OnRegister() override;
/**
* Called when the component is unregistered.
* 组件取消注册时调用。
*/
virtual void OnUnregister() override;
//~ End UActorComponent interface
/**
* Gets the Pawn associated with this component.
* 获取与此组件关联的Pawn。
* @return The associated Pawn, or the Pawn controlled by the PlayerController if the owner is a PlayerController. 如果拥有者是Pawn则返回Pawn如果是PlayerController则返回其控制的Pawn。
*/
UFUNCTION(BlueprintPure, Category="GIPS|Input")
APawn* GetControlledPawn() const;
/**
* Gets the input system component from an actor.
* 从演员获取输入系统组件。
* @param Actor The actor to query. 要查询的演员。
* @return The input system component, or nullptr if not found. 输入系统组件如果未找到则返回nullptr。
*/
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "GIPS|Input", Meta = (DefaultToSelf="Actor"))
static UGIPS_InputSystemComponent* GetInputSystemComponent(const AActor* Actor);
/**
* Finds the input system component on an actor.
* 在演员上查找输入系统组件。
* @param Actor The actor to query. 要查询的演员。
* @param Component The found component (output). 找到的组件(输出)。
* @return True if the component was found, false otherwise. 如果找到组件则返回true否则返回false。
*/
UFUNCTION(BlueprintCallable, Category = "GIPS|Input", Meta = (DefaultToSelf="Actor", ExpandBoolAsExecs="ReturnValue"))
static bool FindInputSystemComponent(const AActor* Actor, UGIPS_InputSystemComponent*& Component);
/**
* Event triggered when setting up the input component.
* 设置输入组件时触发的事件。
*/
UPROPERTY(BlueprintAssignable, Category="GIPS|Input")
FGIPS_InputComponentSignature SetupInputComponentEvent;
/**
* Event triggered when cleaning up the input component.
* 清理输入组件时触发的事件。
*/
UPROPERTY(BlueprintAssignable, Category="GIPS|Input")
FGIPS_InputComponentSignature CleanupInputComponentEvent;
/**
* Event triggered when the input buffer window state changes.
* 输入缓冲窗口状态改变时触发的事件。
*/
UPROPERTY(BlueprintAssignable, Category="GIPS|Input")
FGIPS_InputBufferWindowStateChangedSignature InputBufferWindowStateChangedEvent;
protected:
/**
* Sets up the player input component (Blueprint-implementable).
* 设置玩家输入组件(可通过蓝图实现)。
* @param NewInputComponent The new input component. 新输入组件。
*/
UFUNCTION(BlueprintNativeEvent, Category="GIPS|Input")
void OnSetupPlayerInputComponent(UEnhancedInputComponent* NewInputComponent);
virtual void OnSetupPlayerInputComponent_Implementation(UEnhancedInputComponent* NewInputComponent);
/**
* Cleans up the player input component (Blueprint-implementable).
* 清理玩家输入组件(可通过蓝图实现)。
* @param PrevInputComponent The previous input component. 前一个输入组件。
*/
UFUNCTION(BlueprintNativeEvent, Category="GIPS|Input")
void OnCleanupPlayerInputComponent(UEnhancedInputComponent* PrevInputComponent);
virtual void OnCleanupPlayerInputComponent_Implementation(UEnhancedInputComponent* PrevInputComponent);
/**
* Called when the pawn restarts.
* 当Pawn重启时调用。
* @param Pawn The pawn that restarted. 重启的Pawn。
*/
UFUNCTION()
virtual void OnPawnRestarted(APawn* Pawn);
/**
* Called when the controller changes.
* 当控制器更改时调用。
* @param Pawn The associated pawn. 关联的Pawn。
* @param OldController The previous controller. 前一个控制器。
* @param NewController The new controller. 新控制器。
*/
UFUNCTION()
virtual void OnControllerChanged(APawn* Pawn, AController* OldController, AController* NewController);
/**
* Cleans up input action value bindings.
* 清理输入动作值绑定。
*/
void CleanInputActionValueBindings();
/**
* Sets up input action value bindings.
* 设置输入动作值绑定。
*/
void SetupInputActionValueBindings();
/**
* Sets up the input component.
* 设置输入组件。
* @param NewInputComponent The new input component. 新输入组件。
*/
virtual void SetupInputComponent(UInputComponent* NewInputComponent);
/**
* Cleans up the input component.
* 清理输入组件。
* @param OldController The previous controller (optional). 前一个控制器(可选)。
*/
virtual void CleanupInputComponent(AController* OldController = nullptr);
/**
* Gets the enhanced input subsystem.
* 获取增强输入子系统。
* @param OldController The previous controller (optional). 前一个控制器(可选)。
* @return The enhanced input subsystem. 增强输入子系统。
*/
UEnhancedInputLocalPlayerSubsystem* GetEnhancedInputSubsystem(AController* OldController = nullptr) const;
/**
* The bound input component.
* 绑定的输入组件。
*/
UPROPERTY(transient)
TObjectPtr<UEnhancedInputComponent> InputComponent;
/**
* The type of owner (Pawn or PlayerController).
* 拥有者类型Pawn或PlayerController
*/
EGIPS_OwnerType OwnerType = EGIPS_OwnerType::PC;
protected:
/**
* Binds input actions to the component.
* 将输入动作绑定到组件。
*/
void BindInputActions();
/**
* Input mapping context to be added to the enhanced input subsystem.
* 将添加到增强输入子系统的输入映射上下文。
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GIPS|Input")
TObjectPtr<UInputMappingContext> InputMappingContext;
/**
* Priority for binding the input mapping context.
* 绑定输入映射上下文的优先级。
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GIPS|Input")
int32 InputPriority = 0;
/**
* Main configuration for the input system.
* 输入系统的主配置。
*/
UPROPERTY(EditDefaultsOnly, Category="GIPS|Input")
TObjectPtr<UGIPS_InputConfig> InputConfig;
/**
* Maximum number of input entries to keep.
* 保留的最大输入记录数。
*/
UPROPERTY(EditDefaultsOnly, Category="GIPS|Input", meta=(ClampMin=0, ClampMax=10))
int32 MaxInputEntriesNum{5};
/**
* If true, inputs are processed externally via OnReceivedInput event.
* 如果为true输入通过OnReceivedInput事件在外部处理。
*/
UPROPERTY(EditDefaultsOnly, Category="GIPS|Input")
bool bProcessingInputExternally{false};
/**
* List of input control setups, with the last one being the current setup.
* 输入控制设置列表,最后一个为当前设置。
*/
UPROPERTY(EditAnywhere, Category="GIPS|Input")
TArray<TObjectPtr<UGIPS_InputControlSetup>> InputControlSetups;
public:
#pragma region InputControl
/**
* Gets the current input control setup.
* 获取当前输入控制设置。
* @return The last input control setup in the list. 列表中的最后一个输入控制设置。
*/
UFUNCTION(BlueprintPure, Category="GIPS|Input")
UGIPS_InputControlSetup* GetCurrentInputSetup() const;
/**
* Gets the input configuration.
* 获取输入配置。
* @return The input configuration. 输入配置。
*/
UFUNCTION(BlueprintPure, Category="GIPS|Input")
UGIPS_InputConfig* GetInputConfig() const;
/**
* Sets a new input control setup as the current one.
* 将新的输入控制设置设为当前设置。
* @param NewSetup The new input control setup. 新输入控制设置。
*/
UFUNCTION(BlueprintCallable, Category="GIPS|Input")
virtual void PushInputSetup(UGIPS_InputControlSetup* NewSetup);
/**
* Removes the current input setup, making the last one in the list the new current setup.
* 删除当前输入设置,使列表中的最后一个成为新的当前设置。
* @attention Does nothing if only one setup exists. 如果只有一个设置,则无操作。
*/
UFUNCTION(BlueprintCallable, Category="GIPS|Input")
virtual void PopInputSetup();
#pragma endregion
/**
* Checks if an input is allowed.
* 检查输入是否被允许。
* @param InputTag The gameplay tag for the input. 输入的游戏标签。
* @param TriggerEvent The trigger event type. 触发事件类型。
* @return True if the input is allowed, false otherwise. 如果输入被允许则返回true否则返回false。
*/
UFUNCTION(BlueprintCallable, Category="GIPS|Input", meta=(ExpandBoolAsExecs="ReturnValue"))
virtual bool CheckInputAllowed(UPARAM(meta = (Categories="InputTag,GIPS.InputTag"))
FGameplayTag InputTag, ETriggerEvent TriggerEvent);
/**
* Checks if an input is allowed with action data.
* 使用动作数据检查输入是否被允许。
* @param ActionData The input action data. 输入动作数据。
* @param InputTag The gameplay tag for the input. 输入的游戏标签。
* @param TriggerEvent The trigger event type. 触发事件类型。
* @return True if the input is allowed, false otherwise. 如果输入被允许则返回true否则返回false。
*/
// UFUNCTION(BlueprintCallable, Category="GIPS|Input", meta=(ExpandBoolAsExecs="ReturnValue", AutoCreateRefTerm="ActionData"))
virtual bool CheckInputAllowed(const FInputActionInstance& ActionData, UPARAM(meta = (Categories="InputTag,GIPS.InputTag"))
FGameplayTag InputTag, ETriggerEvent TriggerEvent);
/**
* Processes an input action.
* 处理输入动作。
* @param ActionData The input action data. 输入动作数据。
* @param InputTag The gameplay tag for the input. 输入的游戏标签。
* @param TriggerEvent The trigger event type. 触发事件类型。
*/
// UFUNCTION(BlueprintCallable, Category="GIPS|Input")
virtual void ProcessInput(const FInputActionInstance& ActionData, const FGameplayTag& InputTag, ETriggerEvent TriggerEvent);
/**
* Gets the input action associated with a tag.
* 获取与标签关联的输入动作。
* @param InputTag The gameplay tag for the input. 输入的游戏标签。
* @return The associated input action. 关联的输入动作。
*/
UFUNCTION(BlueprintCallable, Category="GIPS|Input")
UInputAction* GetInputActionOfInputTag(UPARAM(meta = (Categories="InputTag,GIPS.InputTag"))
FGameplayTag InputTag) const;
/**
* Gets the current input action value for a tag.
* 获取标签的当前输入动作值。
* @param InputTag The gameplay tag for the input. 输入的游戏标签。
* @return The current input action value. 当前输入动作值。
*/
UFUNCTION(BlueprintCallable, Category="GIPS|Input")
FInputActionValue GetInputActionValueOfInputTag(UPARAM(meta = (Categories="InputTag,GIPS.InputTag"))
FGameplayTag InputTag) const;
/**
* Gets the last input action value for a tag.
* 获取标签的最后输入动作值。
* @param InputTag The gameplay tag for the input. 输入的游戏标签。
* @return The last input action value. 最后输入动作值。
*/
UFUNCTION(BlueprintCallable, Category="GIPS|Input")
FInputActionValue GetLastInputActionValueOfInputTag(UPARAM(meta = (Categories="InputTag,GIPS.InputTag"))
FGameplayTag InputTag) const;
/**
* Gets the list of passed input entries.
* 获取通过的输入记录列表。
* @return Array of passed input entries. 通过的输入记录数组。
*/
UFUNCTION(BlueprintCallable, Category="GIPS|Input")
TArray<FGIPS_BufferedInput> GetPassedInputEntries() const { return PassedInputEntries; };
/**
* Registers a passed input entry.
* 注册通过的输入记录。
* @param InputEntry The input entry to register. 要注册的输入记录。
*/
virtual void RegisterPassedInputEntry(const FGIPS_BufferedInput& InputEntry);
/**
* Gets the list of blocked input entries.
* 获取阻止的输入记录列表。
* @return Array of blocked input entries. 阻止的输入记录数组。
*/
UFUNCTION(BlueprintCallable, Category="GIPS|Input")
TArray<FGIPS_BufferedInput> GetBlockedInputEntries() const { return BlockedInputEntries; };
/**
* Registers a blocked input entry.
* 注册阻止的输入记录。
* @param InputEntry The input entry to register. 要注册的输入记录。
*/
virtual void RegisterBlockedInputEntry(const FGIPS_BufferedInput& InputEntry);
/**
* Gets the list of buffered input entries.
* 获取缓冲的输入记录列表。
* @return Array of buffered input entries. 缓冲的输入记录数组。
*/
UFUNCTION(BlueprintCallable, Category="GIPS|Input")
TArray<FGIPS_BufferedInput> GetBufferedInputEntries() const { return BufferedInputEntries; };
/**
* Registers a buffered input entry.
* 注册缓冲的输入记录。
* @param InputEntry The input entry to register. 要注册的输入记录。
*/
virtual void RegisterBufferedInputEntry(const FGIPS_BufferedInput& InputEntry);
/**
* Event triggered when an input action generates an event.
* 输入动作生成事件时触发的事件。
*/
UPROPERTY(BlueprintAssignable)
FGIPS_ReceivedInputSignature OnReceivedInput;
protected:
/**
* Callback for input action events.
* 输入动作事件的回调。
* @param ActionData The input action data. 输入动作数据。
* @param InputTag The gameplay tag for the input. 输入的游戏标签。
* @param TriggerEvent The trigger event type. 触发事件类型。
*/
UFUNCTION()
void InputActionCallback(const FInputActionInstance& ActionData, FGameplayTag InputTag, ETriggerEvent TriggerEvent);
/**
* Mapping of input tags to action value bindings.
* 输入标签到动作值绑定的映射。
*/
UPROPERTY(VisibleInstanceOnly, Category="GIPS|Input", meta=(ForceInlineRow))
TMap<FGameplayTag, int32> InputActionValueBindings;
/**
* Tracks the last action value for each input action.
* 跟踪每个输入动作的最后动作值。
*/
UPROPERTY(VisibleInstanceOnly, Category="GIPS|Input", meta=(ForceInlineRow))
TMap<FGameplayTag, FInputActionValue> LastInputActionValues;
/**
* List of passed input entries.
* 通过的输入记录列表。
*/
UPROPERTY(VisibleAnywhere, Category="GIPS|Input")
TArray<FGIPS_BufferedInput> PassedInputEntries;
/**
* List of blocked input entries.
* 阻止的输入记录列表。
*/
UPROPERTY(VisibleAnywhere, Category="GIPS|Input")
TArray<FGIPS_BufferedInput> BlockedInputEntries;
/**
* List of buffered input entries.
* 缓冲的输入记录列表。
*/
UPROPERTY(VisibleAnywhere, Category="GIPS|Input")
TArray<FGIPS_BufferedInput> BufferedInputEntries;
#pragma region InputBuffer
public:
/**
* Attempts to save an input to the buffer.
* 尝试将输入保存到缓冲区。
* @param ActionData The input action data. 输入动作数据。
* @param InputTag The gameplay tag for the input. 输入的游戏标签。
* @param TriggerEvent The trigger event type. 触发事件类型。
* @return True if the input was saved, false otherwise. 如果输入被保存则返回true否则返回false。
*/
bool TrySaveInput(const FInputActionInstance& ActionData, const FGameplayTag& InputTag, ETriggerEvent TriggerEvent);
/**
* Fires buffered input actions.
* 触发缓冲的输入动作。
*/
void FireBufferedInput();
/**
* Event triggered when buffered input is fired.
* 缓冲输入触发时的事件。
*/
UPROPERTY(BlueprintAssignable)
FGIPS_ReceivedInputSignature OnFireBufferedInput;
/**
* Opens an input buffer window to allow input saving.
* 开启输入缓冲窗口以允许保存输入。
* @param BufferWindowName The name of the buffer window to open. 要开启的缓冲窗口名称。
*/
UFUNCTION(BlueprintCallable, Category="GIPS|InputBuffer")
virtual void OpenInputBufferWindow(UPARAM(meta=(Categories="GIPS.InputBuffer"))
FGameplayTag BufferWindowName);
/**
* Closes an input buffer window.
* 关闭输入缓冲窗口。
* @param BufferWindowName The name of the buffer window to close. 要关闭的缓冲窗口名称。
*/
UFUNCTION(BlueprintCallable, Category="GIPS|InputBuffer")
virtual void CloseInputBufferWindow(UPARAM(meta=(Categories="GIPS.InputBuffer"))
FGameplayTag BufferWindowName);
/**
* Closes all active input buffer windows.
* 关闭所有激活的输入缓冲窗口。
*/
UFUNCTION(BlueprintCallable, Category="GIPS|InputBuffer")
virtual void CloseActiveInputBufferWindows();
/**
* Gets the last buffered input.
* 获取最后缓冲的输入。
* @return The last buffered input. 最后缓冲的输入。
*/
UFUNCTION(BlueprintCallable, BlueprintPure, Category="GIPS|InputBuffer")
FGIPS_BufferedInput GetLastBufferedInput() const;
/**
* Gets all currently active buffer windows.
* 获取当前所有激活的缓冲窗口。
* @return Map of active buffer windows. 激活的缓冲窗口映射。
*/
UFUNCTION(BlueprintCallable, BlueprintPure, Category="GIPS|InputBuffer")
TMap<FGameplayTag, FGIPS_BufferedInput> GetActiveBufferWindows() const;
protected:
/**
* Resets the buffered input.
* 重置缓冲输入。
*/
void ResetBufferedInput();
/**
* Attempts to save an input as a buffered input.
* 尝试将输入保存为缓冲输入。
* @param BufferWindowName The buffer window name. 缓冲窗口名称。
* @param ActionData The input action data. 输入动作数据。
* @param InputTag The gameplay tag for the input. 输入的游戏标签。
* @param TriggerEvent The trigger event type. 触发事件类型。
* @return True if the input was saved, false otherwise. 如果输入被保存则返回true否则返回false。
*/
bool TrySaveAsBufferedInput(const FGameplayTag BufferWindowName, const FInputActionInstance& ActionData, const FGameplayTag& InputTag, ETriggerEvent TriggerEvent);
/**
* Map of currently active input buffer windows.
* 当前激活的输入缓冲窗口映射。
*/
UPROPERTY(VisibleInstanceOnly, BlueprintReadOnly, Category="GIPS|InputBuffer", meta=(ForceInlineRow))
TMap<FGameplayTag, FGIPS_BufferedInput> ActiveBufferWindows;
/**
* The last fired buffered input.
* 最后触发的缓冲输入。
*/
UPROPERTY(VisibleInstanceOnly, BlueprintReadOnly, Category="GIPS|InputBuffer")
FGIPS_BufferedInput LastBufferedInput;
/**
* The current buffered input.
* 当前缓冲输入。
*/
UPROPERTY()
FGIPS_BufferedInput CurrentBufferedInput;
#pragma endregion
#pragma region DataValidation
#if WITH_EDITOR
/**
* Validates the component data in the editor.
* 在编辑器中验证组件数据。
* @param Context The data validation context. 数据验证上下文。
* @return The validation result. 验证结果。
*/
virtual EDataValidationResult IsDataValid(FDataValidationContext& Context) const override;
#endif
#pragma endregion
};

View File

@@ -0,0 +1,372 @@
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "GameplayTagContainer.h"
#include "InputAction.h"
#include "InputTriggers.h"
#include "Templates/TypeHash.h"
#include "UObject/Object.h"
#include "GIPS_InputTypes.generated.h"
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FGIPS_InputComponentSignature, UEnhancedInputComponent*, InputComponent);
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FGIPS_InputBufferWindowStateChangedSignature, FGameplayTag, BufferWindowName, bool, State);
DECLARE_DYNAMIC_MULTICAST_DELEGATE_ThreeParams(FGIPS_ReceivedInputSignature, const FInputActionInstance&, ActionData, const FGameplayTag&, InputTag, ETriggerEvent, TriggerEvent);
enum class ETriggerEvent : uint8;
class UGIPS_InputProcessor;
/**
* Defines the type of input buffering behavior.
* 定义输入缓冲行为的类型。
*/
UENUM()
enum class EGIPS_InputBufferType : uint8
{
/**
* Only the last input is executed when the buffer window closes.
* 仅在缓冲窗口关闭时执行最后按下的输入。
*/
LastInput,
/**
* Inputs are executed immediately during the buffer window.
* 在缓冲窗口期间立即执行输入。
*/
Instant,
/**
* Inputs with higher priority (earlier in the list) are executed first.
* 优先级较高的输入(列表中较早的)优先执行。
*/
HighestPriority
};
/**
* Defines how input processors are executed for a single input event.
* 定义单个输入事件的处理器执行方式。
*/
UENUM()
enum class EGIPS_InputProcessorExecutionType : uint8
{
/**
* All valid processors are executed sequentially in top-to-bottom order.
* 所有有效处理器按从上到下的顺序依次执行。
*/
MatchAll,
/**
* Only the first valid input processor is executed.
* 仅执行第一个有效的输入处理器。
*/
FirstOnly
};
/**
* Struct for defining allowed inputs and their trigger events.
* 定义允许的输入及其触发事件的结构体。
*/
USTRUCT()
struct GENERICINPUTSYSTEM_API FGIPS_AllowedInput
{
GENERATED_BODY()
/**
* The allowed input tag.
* 允许的输入标签。
*/
UPROPERTY(EditAnywhere, Category="GIPS|Input", meta=(Categories="InputTag,GIPS.InputTag"))
FGameplayTag InputTag;
/**
* Allowed trigger event types (all allowed if empty).
* 允许的触发事件类型(如果为空则允许所有)。
*/
UPROPERTY(EditAnywhere, Category="GIPS|Input")
TArray<ETriggerEvent> TriggerEvents{ETriggerEvent::Started};
/**
* Equality operator for allowed inputs.
* 允许输入的相等比较运算符。
*/
friend bool operator==(const FGIPS_AllowedInput& Lhs, const FGIPS_AllowedInput& RHS)
{
return Lhs.InputTag == RHS.InputTag;
}
/**
* Inequality operator for allowed inputs.
* 允许输入的不相等比较运算符。
*/
friend bool operator!=(const FGIPS_AllowedInput& Lhs, const FGIPS_AllowedInput& RHS)
{
return !(Lhs == RHS);
}
#if WITH_EDITORONLY_DATA
/**
* Description for developers in the editor.
* 编辑器中用于开发者的描述。
*/
UPROPERTY(EditAnywhere, Category = "Editor")
FString DevDescription;
#endif
};
/**
* Struct for defining an input buffer window.
* 定义输入缓冲窗口的结构体。
*/
USTRUCT()
struct GENERICINPUTSYSTEM_API FGIPS_InputBufferWindow
{
GENERATED_BODY()
public:
/**
* The tag for the buffer window.
* 缓冲窗口的标签。
*/
UPROPERTY(EditAnywhere, Category="GIPS|Input", meta=(Categories="GIPS.InputBuffer"))
FGameplayTag Tag = FGameplayTag::EmptyTag;
/**
* The type of input buffering behavior.
* 输入缓冲行为的类型。
*/
UPROPERTY(EditAnywhere, Category="GIPS|Input")
EGIPS_InputBufferType BufferType = EGIPS_InputBufferType::LastInput;
/**
* List of allowed inputs for the buffer window.
* 缓冲窗口允许的输入列表。
*/
UPROPERTY(EditAnywhere, Category="GIPS|Input", meta=(TitleProperty="InputTag"))
TArray<FGIPS_AllowedInput> AllowedInputs;
/**
* Finds the index of an allowed input.
* 查找允许输入的索引。
* @param InputTag The input tag to find. 要查找的输入标签。
* @param TriggerEvent The trigger event type. 触发事件类型。
* @return The index of the allowed input, or INDEX_NONE if not found. 允许输入的索引如果未找到则返回INDEX_NONE。
*/
int32 IndexOfAllowedInput(const FGameplayTag& InputTag, const ETriggerEvent& TriggerEvent) const
{
for (int32 i = 0; i < AllowedInputs.Num(); i++)
{
if (AllowedInputs[i].InputTag == InputTag && (AllowedInputs[i].TriggerEvents.IsEmpty() || AllowedInputs[i].TriggerEvents.Contains(TriggerEvent)))
{
return i;
}
}
return INDEX_NONE;
}
/**
* Equality operator for buffer window tags.
* 缓冲窗口标签的相等比较运算符。
*/
bool operator==(const FGameplayTag& OtherTag) const;
/**
* Inequality operator for buffer window tags.
* 缓冲窗口标签的不相等比较运算符。
*/
bool operator!=(const FGameplayTag& OtherTag) const;
};
/**
* Struct for storing a single input entry.
* 存储单个输入记录的结构体。
*/
USTRUCT(BlueprintType, meta=(DisplayName="GIPS Input Entry"))
struct GENERICINPUTSYSTEM_API FGIPS_BufferedInput
{
GENERATED_BODY()
/**
* The input tag for the entry.
* 输入记录的输入标签。
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GIPS|Input", meta=(Categories="InputTag,GIPS.InputTag"))
FGameplayTag InputTag{FGameplayTag::EmptyTag};
/**
* The input action data.
* 输入动作数据。
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GIPS|Input")
FInputActionInstance ActionData;
/**
* The trigger event type.
* 触发事件类型。
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GIPS|Input")
ETriggerEvent TriggerEvent{ETriggerEvent::None};
/**
* Equality operator for buffered inputs.
* 缓冲输入的相等比较运算符。
*/
friend bool operator==(const FGIPS_BufferedInput& Lhs, const FGIPS_BufferedInput& RHS)
{
return Lhs.InputTag == RHS.InputTag
&& Lhs.TriggerEvent == RHS.TriggerEvent;
}
/**
* Inequality operator for buffered inputs.
* 缓冲输入的不相等比较运算符。
*/
friend bool operator!=(const FGIPS_BufferedInput& Lhs, const FGIPS_BufferedInput& RHS)
{
return !(Lhs == RHS);
}
FGIPS_BufferedInput() = default;
/**
* Constructor for buffered input.
* 缓冲输入的构造函数。
*/
FGIPS_BufferedInput(const FGameplayTag& InputTag, const FInputActionInstance& ActionData, const ETriggerEvent TriggerEvent)
: InputTag(InputTag),
ActionData(ActionData),
TriggerEvent(TriggerEvent)
{
}
/**
* Copy constructor for buffered input.
* 缓冲输入的复制构造函数。
*/
FGIPS_BufferedInput(const FGIPS_BufferedInput& Other)
: InputTag(Other.InputTag),
ActionData(Other.ActionData),
TriggerEvent(Other.TriggerEvent)
{
}
/**
* Assignment operator for buffered input.
* 缓冲输入的赋值运算符。
*/
FGIPS_BufferedInput& operator=(const FGIPS_BufferedInput& Other)
{
if (this == &Other)
return *this;
InputTag = Other.InputTag;
ActionData = Other.ActionData;
TriggerEvent = Other.TriggerEvent;
return *this;
}
/**
* Converts the buffered input to a string.
* 将缓冲输入转换为字符串。
* @return The string representation of the buffered input. 缓冲输入的字符串表示。
*/
FString ToString() const;
};
/**
* Struct for defining input action settings.
* 定义输入动作设置的结构体。
*/
USTRUCT()
struct GENERICINPUTSYSTEM_API FGIPS_InputActionSetting
{
GENERATED_BODY()
/**
* The input action object.
* 输入动作对象。
*/
UPROPERTY(EditAnywhere, Category="GIPS|Input")
TObjectPtr<UInputAction> InputAction;
/**
* Enables value binding for the input action.
* 为输入动作启用值绑定。
*/
UPROPERTY(EditAnywhere, Category="GIPS|Input")
bool bValueBinding{true};
};
/**
* Struct for defining relationships between actor states and input permissions.
* 定义演员状态与输入权限关系的结构体。
*/
USTRUCT()
struct GENERICINPUTSYSTEM_API FGIPS_InputTagRelationship
{
GENERATED_BODY()
/**
* Query to check if the actor's tags allow the input.
* 检查演员标签是否允许输入的查询。
*/
UPROPERTY(EditAnywhere, Category="GIPS|Input")
FGameplayTagQuery ActorTagQuery;
/**
* List of allowed inputs if the tag query is satisfied.
* 如果标签查询满足,则允许的输入列表。
* @note Empty means no check on if input is allowed. 留空则不检查是否允许。
*/
UPROPERTY(EditAnywhere, Category="GIPS|Input", meta=(TitleProperty="InputTag"))
TArray<FGIPS_AllowedInput> AllowedInputs;
/**
* List of blocked inputs if the tag query is satisfied.
* 如果标签查询满足,则禁止的输入列表。
* @note Empty means no check on if input is blocked. 留空则不检查是否阻挡。
*/
UPROPERTY(EditAnywhere, Category="GIPS|Input", meta=(TitleProperty="InputTag"))
TArray<FGIPS_AllowedInput> BlockedInputs;
/**
* Finds the index of an allowed input.
* 查找允许输入的索引。
* @param InputTag The input tag to find. 要查找的输入标签。
* @param TriggerEvent The trigger event type. 触发事件类型。
* @return The index of the allowed input, or INDEX_NONE if not found. 允许输入的索引如果未找到则返回INDEX_NONE。
*/
int32 IndexOfAllowedInput(const FGameplayTag& InputTag, const ETriggerEvent& TriggerEvent) const
{
for (int32 i = 0; i < AllowedInputs.Num(); i++)
{
if (AllowedInputs[i].InputTag == InputTag && (AllowedInputs[i].TriggerEvents.IsEmpty() || AllowedInputs[i].TriggerEvents.Contains(TriggerEvent)))
{
return i;
}
}
return INDEX_NONE;
}
int32 IndexOfBlockedInput(const FGameplayTag& InputTag, const ETriggerEvent& TriggerEvent) const
{
for (int32 i = 0; i < BlockedInputs.Num(); i++)
{
if (BlockedInputs[i].InputTag == InputTag && (BlockedInputs[i].TriggerEvents.IsEmpty() || BlockedInputs[i].TriggerEvents.Contains(TriggerEvent)))
{
return i;
}
}
return INDEX_NONE;
}
#if WITH_EDITORONLY_DATA
/**
* Friendly name for displaying in the editor.
* 在编辑器中显示的友好名称。
*/
UPROPERTY(VisibleAnywhere, Category=AlwaysHidden, Meta=(EditCondition=False, EditConditionHides))
FString EditorFriendlyName;
#endif
};

View File

@@ -0,0 +1,26 @@
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
#pragma once
#include "Logging/LogMacros.h"
GENERICINPUTSYSTEM_API FString GetGIPSLogContextString(const UObject* ContextObject = nullptr);
GENERICINPUTSYSTEM_API DECLARE_LOG_CATEGORY_EXTERN(LogGIPS, Log, All);
#define GIPS_LOG(Verbosity, Format, ...) \
{ \
UE_LOG(LogGIPS, Verbosity, TEXT("%S: %s"),__FUNCTION__, *FString::Printf(TEXT(Format), ##__VA_ARGS__)) \
}
#define GIPS_CLOG(Verbosity, Format, ...) \
{ \
UE_LOG(LogGIPS, Verbosity, TEXT("%S: ctx(%s) %s"),__FUNCTION__, *GetGIPSLogContextString(this), *FString::Printf(TEXT(Format), ##__VA_ARGS__)) \
}
#define GIPS_OWNED_CLOG(LogOwner,Verbosity, Format, ...) \
{ \
UE_LOG(LogGIPS, Verbosity, TEXT("%S: ctx(%s) %s"),__FUNCTION__, *GetGIPSLogContextString(LogOwner), *FString::Printf(TEXT(Format), ##__VA_ARGS__)) \
}

View File

@@ -0,0 +1,14 @@
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
#pragma once
#include "Modules/ModuleManager.h"
class FGenericInputSystemModule : public IModuleInterface
{
public:
/** IModuleInterface implementation */
virtual void StartupModule() override;
virtual void ShutdownModule() override;
};