Step 1: the Salty project, two modules, tests and the gym
Salty.uproject (UE 5.8) from the Third Person template with class redirects, the SaltyCore and Salty modules, the three build targets, USaltyAssetManager calling InitGlobalData, Config/Tags with the 23 root namespaces, Scripts/run-tests.sh, build.sh and Authoring/create_gym.py, L_Gym, Git LFS attributes and the placeholder test. Template variants kept as reference (D-41). The four Fab packs stay out of the repository for now (~10 GB). The editor serves the engine MCP plugin on 127.0.0.1:8000 through DefaultEditorPerProjectUserSettings.ini. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5.1
parent
0e61a77346
commit
4f2c55cd2a
@@ -0,0 +1,19 @@
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
|
||||
#include "SideScrollingAIController.h"
|
||||
#include "GameplayStateTreeModule/Public/Components/StateTreeAIComponent.h"
|
||||
|
||||
ASideScrollingAIController::ASideScrollingAIController()
|
||||
{
|
||||
// create the StateTree AI Component
|
||||
StateTreeAI = CreateDefaultSubobject<UStateTreeAIComponent>(TEXT("StateTreeAI"));
|
||||
check(StateTreeAI);
|
||||
|
||||
// ensure we start the StateTree when we possess the pawn
|
||||
bStartAILogicOnPossess = true;
|
||||
|
||||
// ensure we're attached to the possessed character.
|
||||
// this is necessary for EnvQueries to work correctly
|
||||
bAttachToPawn = true;
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "AIController.h"
|
||||
#include "SideScrollingAIController.generated.h"
|
||||
|
||||
class UStateTreeAIComponent;
|
||||
|
||||
/**
|
||||
* A basic AI Controller capable of running StateTree
|
||||
*/
|
||||
UCLASS(abstract)
|
||||
class ASideScrollingAIController : public AAIController
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
/** StateTree Component */
|
||||
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "AI", meta = (AllowPrivateAccess = "true"))
|
||||
UStateTreeAIComponent* StateTreeAI;
|
||||
|
||||
public:
|
||||
|
||||
/** Constructor */
|
||||
ASideScrollingAIController();
|
||||
};
|
||||
@@ -0,0 +1,53 @@
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
|
||||
#include "SideScrollingNPC.h"
|
||||
#include "Engine/World.h"
|
||||
#include "GameFramework/CharacterMovementComponent.h"
|
||||
#include "TimerManager.h"
|
||||
|
||||
ASideScrollingNPC::ASideScrollingNPC()
|
||||
{
|
||||
PrimaryActorTick.bCanEverTick = true;
|
||||
|
||||
GetCharacterMovement()->MaxWalkSpeed = 150.0f;
|
||||
}
|
||||
|
||||
void ASideScrollingNPC::EndPlay(EEndPlayReason::Type EndPlayReason)
|
||||
{
|
||||
Super::EndPlay(EndPlayReason);
|
||||
|
||||
// clear the deactivation timer
|
||||
GetWorld()->GetTimerManager().ClearTimer(DeactivationTimer);
|
||||
}
|
||||
|
||||
void ASideScrollingNPC::Interaction(AActor* Interactor)
|
||||
{
|
||||
// ignore if this NPC has already been deactivated
|
||||
if (bDeactivated)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// reset the deactivation flag
|
||||
bDeactivated = true;
|
||||
|
||||
// stop character movement immediately
|
||||
GetCharacterMovement()->StopMovementImmediately();
|
||||
|
||||
// launch the NPC away from the interactor
|
||||
FVector LaunchVector = Interactor->GetActorForwardVector() * LaunchImpulse;
|
||||
LaunchVector.Y = 0.0f;
|
||||
LaunchVector.Z = LaunchVerticalImpulse;
|
||||
|
||||
LaunchCharacter(LaunchVector, true, true);
|
||||
|
||||
// set up a timer to schedule reactivation
|
||||
GetWorld()->GetTimerManager().SetTimer(DeactivationTimer, this, &ASideScrollingNPC::ResetDeactivation, DeactivationTime, false);
|
||||
}
|
||||
|
||||
void ASideScrollingNPC::ResetDeactivation()
|
||||
{
|
||||
// reset the deactivation flag
|
||||
bDeactivated = false;
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "GameFramework/Character.h"
|
||||
#include "SideScrollingInteractable.h"
|
||||
#include "SideScrollingNPC.generated.h"
|
||||
|
||||
/**
|
||||
* Simple platforming NPC
|
||||
* Its behaviors will be dictated by a possessing AI Controller
|
||||
* It can be temporarily deactivated through Actor interactions
|
||||
*/
|
||||
UCLASS(abstract)
|
||||
class ASideScrollingNPC : public ACharacter, public ISideScrollingInteractable
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
protected:
|
||||
|
||||
/** Horizontal impulse to apply to the NPC when it's interacted with */
|
||||
UPROPERTY(EditAnywhere, Category="NPC", meta = (ClampMin = 0, ClampMax = 10000, Units="cm/s"))
|
||||
float LaunchImpulse = 500.0f;
|
||||
|
||||
/** Vertical impulse to apply to the NPC when it's interacted with */
|
||||
UPROPERTY(EditAnywhere, Category="NPC", meta = (ClampMin = 0, ClampMax = 10000, Units="cm/s"))
|
||||
float LaunchVerticalImpulse = 500.0f;
|
||||
|
||||
/** Time that the NPC remains deactivated after being interacted with */
|
||||
UPROPERTY(EditAnywhere, Category="NPC", meta = (ClampMin = 0, ClampMax = 10, Units="s"))
|
||||
float DeactivationTime = 3.0f;
|
||||
|
||||
public:
|
||||
|
||||
/** If true, this NPC is deactivated and will not be interacted with */
|
||||
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="NPC")
|
||||
bool bDeactivated = false;
|
||||
|
||||
/** Timer to reactivate the NPC */
|
||||
FTimerHandle DeactivationTimer;
|
||||
|
||||
public:
|
||||
|
||||
/** Constructor */
|
||||
ASideScrollingNPC();
|
||||
|
||||
public:
|
||||
|
||||
/** Cleanup */
|
||||
virtual void EndPlay(EEndPlayReason::Type EndPlayReason) override;
|
||||
|
||||
public:
|
||||
|
||||
// ~begin IInteractable interface
|
||||
|
||||
/** Performs an interaction triggered by another actor */
|
||||
virtual void Interaction(AActor* Interactor) override;
|
||||
|
||||
// ~end IInteractable interface
|
||||
|
||||
/** Reactivates the NPC */
|
||||
void ResetDeactivation();
|
||||
};
|
||||
@@ -0,0 +1,59 @@
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
|
||||
#include "SideScrollingStateTreeUtility.h"
|
||||
#include "StateTreeExecutionContext.h"
|
||||
#include "StateTreeExecutionTypes.h"
|
||||
#include "AIController.h"
|
||||
#include "Kismet/GameplayStatics.h"
|
||||
|
||||
EStateTreeRunStatus FStateTreeGetPlayerTask::EnterState(FStateTreeExecutionContext& Context, const FStateTreeTransitionResult& Transition) const
|
||||
{
|
||||
// get the instance data
|
||||
FInstanceDataType& InstanceData = Context.GetInstanceData(*this);
|
||||
|
||||
// reset the selected target
|
||||
APawn* SelectedTarget = nullptr;
|
||||
float ClosestDistance = 1000000000000000000.0f;
|
||||
|
||||
// iterate through each local player
|
||||
const int32 NumPlayers = UGameplayStatics::GetNumLocalPlayerControllers(InstanceData.Controller.Get());
|
||||
|
||||
for (int32 i = 0; i < NumPlayers; ++i)
|
||||
{
|
||||
if (APawn* Current = UGameplayStatics::GetPlayerPawn(InstanceData.Controller.Get(), i))
|
||||
{
|
||||
// compute the distance to the target
|
||||
const float TargetDist = (Current->GetActorLocation() - InstanceData.NPC->GetActorLocation()).Size();
|
||||
|
||||
// if we haven't selected a target, or this one is closer to the current one, choose this pawn as target
|
||||
if (!SelectedTarget || TargetDist < ClosestDistance)
|
||||
{
|
||||
SelectedTarget = Current;
|
||||
ClosestDistance = TargetDist;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// set the selected target, assume out of range by default
|
||||
InstanceData.TargetPlayer = SelectedTarget;
|
||||
InstanceData.bValidTarget = false;
|
||||
|
||||
if (SelectedTarget)
|
||||
{
|
||||
// consider this a valid target if it's within range
|
||||
const float TargetDist = (SelectedTarget->GetActorLocation() - InstanceData.NPC->GetActorLocation()).Size();
|
||||
|
||||
InstanceData.bValidTarget = TargetDist < InstanceData.RangeMax;
|
||||
}
|
||||
|
||||
// succeed or fail depending on target validity
|
||||
return InstanceData.bValidTarget ? EStateTreeRunStatus::Succeeded : EStateTreeRunStatus::Failed;
|
||||
}
|
||||
|
||||
#if WITH_EDITOR
|
||||
FText FStateTreeGetPlayerTask::GetDescription(const FGuid& ID, FStateTreeDataView InstanceDataView, const IStateTreeBindingLookup& BindingLookup, EStateTreeNodeFormatting Formatting /*= EStateTreeNodeFormatting::Text*/) const
|
||||
{
|
||||
return FText::FromString("<b>Get Player</b>");
|
||||
}
|
||||
#endif // WITH_EDITOR
|
||||
@@ -0,0 +1,68 @@
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "StateTreeTaskBase.h"
|
||||
|
||||
#include "SideScrollingStateTreeUtility.generated.h"
|
||||
|
||||
class AAIController;
|
||||
|
||||
/**
|
||||
* Instance data for the FStateTreeGetPlayerTask task
|
||||
*/
|
||||
USTRUCT()
|
||||
struct FStateTreeGetPlayerInstanceData
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
/** NPC owning this task */
|
||||
UPROPERTY(VisibleAnywhere, Category="Context")
|
||||
TObjectPtr<APawn> NPC;
|
||||
|
||||
/** Holds the found player pawn */
|
||||
UPROPERTY(VisibleAnywhere, Category="Context")
|
||||
TObjectPtr<AAIController> Controller;
|
||||
|
||||
/** Holds the found player pawn */
|
||||
UPROPERTY(VisibleAnywhere, Category="Output")
|
||||
TObjectPtr<APawn> TargetPlayer;
|
||||
|
||||
/** Is the pawn close enough to be considered a valid target? */
|
||||
UPROPERTY(VisibleAnywhere, Category="Output")
|
||||
bool bValidTarget = false;
|
||||
|
||||
/** Max distance to be considered a valid target */
|
||||
UPROPERTY(EditAnywhere, Category="Parameter", meta = (ClampMin = 0, ClampMax = 10000, Units = "cm"))
|
||||
float RangeMax = 1000.0f;
|
||||
};
|
||||
|
||||
/**
|
||||
* StateTree task to get the player-controlled character
|
||||
*/
|
||||
USTRUCT(meta=(DisplayName="Get Player", Category="Side Scrolling"))
|
||||
struct FStateTreeGetPlayerTask : public FStateTreeTaskCommonBase
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
FStateTreeGetPlayerTask()
|
||||
{
|
||||
// disable tick
|
||||
bShouldCallTick = false;
|
||||
|
||||
// skip state change events if this is sustained
|
||||
bShouldStateChangeOnReselect = false;
|
||||
}
|
||||
|
||||
/* Ensure we're using the correct instance data struct */
|
||||
using FInstanceDataType = FStateTreeGetPlayerInstanceData;
|
||||
virtual const UStruct* GetInstanceDataType() const override { return FInstanceDataType::StaticStruct(); }
|
||||
|
||||
/** Runs when the owning state is entered */
|
||||
virtual EStateTreeRunStatus EnterState(FStateTreeExecutionContext& Context, const FStateTreeTransitionResult& Transition) const override;
|
||||
|
||||
#if WITH_EDITOR
|
||||
virtual FText GetDescription(const FGuid& ID, FStateTreeDataView InstanceDataView, const IStateTreeBindingLookup& BindingLookup, EStateTreeNodeFormatting Formatting = EStateTreeNodeFormatting::Text) const override;
|
||||
#endif // WITH_EDITOR
|
||||
};
|
||||
Reference in New Issue
Block a user