Initial push to repo

This commit is contained in:
2026-07-03 19:56:31 +02:00
commit 4cf4176d57
1305 changed files with 43455 additions and 0 deletions

Binary file not shown.

View File

@@ -0,0 +1,24 @@
{
"FileVersion": 3,
"Version": 1,
"VersionName": "1.0",
"FriendlyName": "InteractionSystem",
"Description": "",
"Category": "Gameplay",
"CreatedBy": "",
"CreatedByURL": "",
"DocsURL": "",
"MarketplaceURL": "",
"SupportURL": "",
"CanContainContent": true,
"IsBetaVersion": false,
"IsExperimentalVersion": false,
"Installed": false,
"Modules": [
{
"Name": "InteractionSystem",
"Type": "Runtime",
"LoadingPhase": "Default"
}
]
}

Binary file not shown.

View File

@@ -0,0 +1,53 @@
// Copyright Epic Games, Inc. All Rights Reserved.
using UnrealBuildTool;
public class InteractionSystem : ModuleRules
{
public InteractionSystem(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", "UMG",
// ... add other public dependencies that you statically link with here ...
}
);
PrivateDependencyModuleNames.AddRange(
new string[]
{
"CoreUObject",
"Engine",
"Slate",
"SlateCore",
"DeveloperSettings"
}
);
DynamicallyLoadedModuleNames.AddRange(
new string[]
{
// ... add any modules that your module loads dynamically here ...
}
);
}
}

View File

@@ -0,0 +1,53 @@
// Fill out your copyright notice in the Description page of Project Settings.
#include "Components/InteractionWidgetComponent.h"
#include "InteractionSettings.h"
#include "Interface/InteractableActorInterface.h"
#include "Widgets/BaseInteractionWidget.h"
UInteractionWidgetComponent::UInteractionWidgetComponent()
{
PrimaryComponentTick.bCanEverTick = true;
Space = EWidgetSpace::Screen;
bManuallyRedraw = true;
RedrawTime = 1.f;
bDrawAtDesiredSize = true;
Pivot = FVector2D(0.5f, 0.5f);
TickMode = ETickMode::Disabled;
if (const UInteractionSettings* InteractionSettings = GetDefault<UInteractionSettings>())
{
WidgetClass = InteractionSettings->DefaultInteractionWidgetClass;
}
}
void UInteractionWidgetComponent::ToggleWidget(bool bActive)
{
if (bActive)
{
if (UBaseInteractionWidget* InteractionWidget = Cast<UBaseInteractionWidget>(GetWidget()))
{
InteractionWidget->UpdateInfo(IInteractableActorInterface::Execute_GetInteractionText(GetOwner()));
}
}
SetVisibility(bActive);
RequestRenderUpdate();
}
void UInteractionWidgetComponent::BeginPlay()
{
Super::BeginPlay();
if (UBaseInteractionWidget* InteractionWidget = Cast<UBaseInteractionWidget>(GetWidget()))
{
InteractionWidget->UpdateInfo(IInteractableActorInterface::Execute_GetInteractionText(GetOwner()));
}
SetPivot(GetPivot() + WidgetOffset);
ToggleWidget(false);
}

View File

@@ -0,0 +1,20 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#include "InteractionSystem.h"
#define LOCTEXT_NAMESPACE "FInteractionSystemModule"
void FInteractionSystemModule::StartupModule()
{
// This code will execute after your module is loaded into memory; the exact timing is specified in the .uplugin file per-module
}
void FInteractionSystemModule::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.
}
#undef LOCTEXT_NAMESPACE
IMPLEMENT_MODULE(FInteractionSystemModule, InteractionSystem)

View File

@@ -0,0 +1,180 @@
// Fill out your copyright notice in the Description page of Project Settings.
#include "Subsystem/InteractionSubsystem.h"
#include "EngineUtils.h"
#include "InteractionSettings.h"
#include "GameFramework/Character.h"
#include "Interface/InteractableActorInterface.h"
void UInteractionSubsystem::Initialize(FSubsystemCollectionBase& Collection)
{
Super::Initialize(Collection);
if (GetWorld())
{
GetWorld()->AddOnActorSpawnedHandler(FOnActorSpawned::FDelegate::CreateUObject(this, &UInteractionSubsystem::RegisterInteractableActor));
GetWorld()->AddOnActorDestroyedHandler(FOnActorDestroyed::FDelegate::CreateUObject(this, &UInteractionSubsystem::UnregisterInteractableActor));
}
}
void UInteractionSubsystem::Deinitialize()
{
InteractableActorList.Empty();
if (UInteractionSubsystem* IS = GetWorld()->GetSubsystem<UInteractionSubsystem>())
{
if (IS->CurrentlyInteractableObject.IsValid())
{
IInteractableActorInterface::Execute_ToggleInteractableUI(IS->CurrentlyInteractableObject.Get(), false);
}
IS->CurrentlyInteractableObject = nullptr;
}
Super::Deinitialize();
}
bool UInteractionSubsystem::ShouldCreateSubsystem(UObject* Outer) const
{
if (!Super::ShouldCreateSubsystem(Outer)) return false;
if (!Outer || !Outer->GetWorld()) return false;
UWorld* World = Outer->GetWorld();
return World->WorldType == EWorldType::Type::Game || World->WorldType == EWorldType::Type::PIE;
}
void UInteractionSubsystem::OnWorldBeginPlay(UWorld& InWorld)
{
Super::OnWorldBeginPlay(InWorld);
RescanWorld();
}
void UInteractionSubsystem::Tick(float DeltaTime)
{
GetClosestInteractable(this);
}
AActor* UInteractionSubsystem::GetClosestInteractable(const UObject* WorldContext)
{
AActor* Closest = nullptr;
if (!IsValid(WorldContext) || !IsValid(WorldContext->GetWorld()))
{
return Closest;
}
UInteractionSubsystem* IS = WorldContext->GetWorld()->GetSubsystem<UInteractionSubsystem>();
ensure(IS);
const APlayerController* PC = WorldContext->GetWorld()->GetFirstPlayerController();
if (!PC) return Closest;
const ACharacter* PlayerCharacter = Cast<ACharacter>(PC->GetPawn());
if (!PlayerCharacter) return Closest;
const UInteractionSettings* InteractionSettings = GetDefault<UInteractionSettings>();
ensure(InteractionSettings);
float ClosestDist = 9999999.f;
for (TWeakObjectPtr<AActor> Actor : IS->InteractableActorList)
{
if (!Actor.IsValid())
continue;
const float Dist = FVector::DistSquared(Actor->GetActorLocation(), PlayerCharacter->GetActorLocation());
const float MaxInteractionDistance = IInteractableActorInterface::Execute_GetInteractionDistance(Actor.Get());
if (FMath::Pow(MaxInteractionDistance, 2) < Dist || Dist > ClosestDist)
continue;
FVector2D ScreenLoc;
if (!PC->ProjectWorldLocationToScreen(Actor->GetActorLocation(), ScreenLoc))
{
continue;
}
if (!IInteractableActorInterface::Execute_IsInteractable(Actor.Get()))
{
continue;
}
ClosestDist = Dist;
Closest = Actor.Get();
}
if (IS->CurrentlyInteractableObject != Closest)
{
if (IS->CurrentlyInteractableObject.IsValid())
{
// UE_LOG(LogTemp, Warning, TEXT("Turning off last interactable: %s"), *IS->CurrentlyInteractableObject.Get()->GetActorNameOrLabel())
IInteractableActorInterface::Execute_ToggleInteractableUI(IS->CurrentlyInteractableObject.Get(), false);
}
if (Closest)
{
// UE_LOG(LogTemp, Warning, TEXT("Turning ON interactable: %s"), *Closest->GetActorNameOrLabel())
IInteractableActorInterface::Execute_ToggleInteractableUI(Closest, true);
}
IS->CurrentlyInteractableObject = Closest;
}
return Closest;
}
void UInteractionSubsystem::UpdateInteractableText(AActor* Interactable)
{
if (!Interactable) return;
UInteractionSubsystem* InteractionSubsystem = Interactable->GetWorld()->GetSubsystem<UInteractionSubsystem>();
if (!InteractionSubsystem) return;
if (InteractionSubsystem->CurrentlyInteractableObject != Interactable) return;
IInteractableActorInterface::Execute_ToggleInteractableUI(InteractionSubsystem->CurrentlyInteractableObject.Get(), true);
}
void UInteractionSubsystem::RescanWorld()
{
UWorld* World = GetWorld();
int32 Count = 0;
if (!World)
return;
// Prune stuff
InteractableActorList.RemoveAllSwap(
[](TWeakObjectPtr<AActor> Actor)
{
return !Actor.IsValid();
}
);
for (TActorIterator<AActor> It(World); It; ++It)
{
Count++;
AActor* Actor = *It;
if (IsValid(Actor) && Actor->Implements<UInteractableActorInterface>())
{
RegisterInteractableActor(Actor);
}
}
}
void UInteractionSubsystem::RegisterInteractableActor(AActor* InteractableActor)
{
if (IsValid(InteractableActor) && InteractableActor->Implements<UInteractableActorInterface>())
{
InteractableActorList.Add(InteractableActor);
}
}
void UInteractionSubsystem::UnregisterInteractableActor(AActor* InteractableActor)
{
InteractableActorList.RemoveAllSwap(
[InteractableActor](TWeakObjectPtr<AActor> Actor)
{
return !Actor.IsValid() || Actor.Get() == InteractableActor;
}
);
}

View File

@@ -0,0 +1,4 @@
// Fill out your copyright notice in the Description page of Project Settings.
#include "Widgets/BaseInteractionWidget.h"

View File

@@ -0,0 +1,28 @@
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "Components/ActorComponent.h"
#include "Components/WidgetComponent.h"
#include "InteractionWidgetComponent.generated.h"
UCLASS(ClassGroup=(Custom), meta=(BlueprintSpawnableComponent))
class INTERACTIONSYSTEM_API UInteractionWidgetComponent : public UWidgetComponent
{
GENERATED_BODY()
public:
UInteractionWidgetComponent();
UFUNCTION(BlueprintCallable, Category = "Interaction Widget Component")
void ToggleWidget(bool bActive);
UPROPERTY(EditAnywhere, Category = "Interaction Widget Component")
FVector2D WidgetOffset;
protected:
virtual void BeginPlay() override;
};

View File

@@ -0,0 +1,31 @@
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "Engine/DeveloperSettings.h"
#include "InteractionSettings.generated.h"
class UBaseInteractionWidget;
/**
*
*/
UCLASS(Config=Game, DefaultConfig, meta=(DisplayName="Interaction System"))
class INTERACTIONSYSTEM_API UInteractionSettings : public UDeveloperSettings
{
GENERATED_BODY()
public:
// Max distance at which interactable will be considered active
UPROPERTY(Config, EditAnywhere)
float InteractableMaxDistance = 1000.f;
// Angle from the camera view towards object to consider "in view"
UPROPERTY(Config, EditAnywhere)
float InteractableCameraViewAngle = 60.f;
UPROPERTY(Config, EditAnywhere)
TSubclassOf<UBaseInteractionWidget> DefaultInteractionWidgetClass;
};

View File

@@ -0,0 +1,14 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "Modules/ModuleManager.h"
class FInteractionSystemModule : public IModuleInterface
{
public:
/** IModuleInterface implementation */
virtual void StartupModule() override;
virtual void ShutdownModule() override;
};

View File

@@ -0,0 +1,47 @@
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "UObject/Interface.h"
#include "InteractionSettings.h"
#include "InteractableActorInterface.generated.h"
UINTERFACE(MinimalAPI)
class UInteractableActorInterface : public UInterface
{
GENERATED_BODY()
};
/**
*
*/
class INTERACTIONSYSTEM_API IInteractableActorInterface
{
GENERATED_BODY()
public:
UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category = "Interactable")
FText GetInteractionText() const;
virtual FText GetInteractionText_Implementation() const { return FText::GetEmpty(); }
UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category = "Interactable")
bool IsInteractable() const;
virtual bool IsInteractable_Implementation() const { return false; }
UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category = "Interactable")
void Interact(AActor* InteractionInstigator);
virtual void Interact_Implementation(AActor* InteractionInstigator) {}
UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category = "Interactable")
void ToggleInteractableUI(bool bActive);
virtual void ToggleInteractableUI_Implementation(bool bActive) {}
UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category = "Interactable")
float GetInteractionDistance() const;
virtual float GetInteractionDistance_Implementation() const
{
return GetDefault<UInteractionSettings>()->InteractableMaxDistance;
}
};

View File

@@ -0,0 +1,48 @@
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "Subsystems/WorldSubsystem.h"
#include "Tickable.h"
#include "Stats/Stats.h"
#include "InteractionSubsystem.generated.h"
/**
*
*/
UCLASS()
class INTERACTIONSYSTEM_API UInteractionSubsystem : public UWorldSubsystem, public FTickableGameObject
{
GENERATED_BODY()
public:
virtual void Initialize(FSubsystemCollectionBase& Collection) override;
virtual void Deinitialize() override;
virtual bool ShouldCreateSubsystem(UObject* Outer) const override;
virtual void OnWorldBeginPlay(UWorld& InWorld) override;
virtual bool IsTickable() const override { return !IsTemplate(); }
virtual void Tick(float DeltaTime) override;
virtual TStatId GetStatId() const override { RETURN_QUICK_DECLARE_CYCLE_STAT(UInteractionSubsystem, STATGROUP_Tickables); }
static AActor* GetClosestInteractable(const UObject* WorldContext);
UFUNCTION(BlueprintCallable, Category = "Interactable")
static void UpdateInteractableText(AActor* Interactable);
protected:
UFUNCTION()
void RescanWorld();
void RegisterInteractableActor(AActor* InteractableActor);
void UnregisterInteractableActor(AActor* InteractableActor);
UPROPERTY()
TArray<TWeakObjectPtr<AActor>> InteractableActorList;
UPROPERTY()
TWeakObjectPtr<AActor> CurrentlyInteractableObject;
};

View File

@@ -0,0 +1,22 @@
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "Blueprint/UserWidget.h"
#include "BaseInteractionWidget.generated.h"
/**
*
*/
UCLASS()
class INTERACTIONSYSTEM_API UBaseInteractionWidget : public UUserWidget
{
GENERATED_BODY()
public:
UFUNCTION(BlueprintCallable, BlueprintImplementableEvent, Category = "Interaction System")
void UpdateInfo(const FText& InteractionText);
};