第一次提交

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,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)