init
This commit is contained in:
@@ -0,0 +1,27 @@
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
|
||||
#include "Variant_Shooter/AI/EnvQueryContext_Target.h"
|
||||
#include "EnvironmentQuery/Items/EnvQueryItemType_Actor.h"
|
||||
#include "EnvironmentQuery/EnvQueryTypes.h"
|
||||
#include "ShooterAIController.h"
|
||||
|
||||
void UEnvQueryContext_Target::ProvideContext(FEnvQueryInstance& QueryInstance, FEnvQueryContextData& ContextData) const
|
||||
{
|
||||
// get the controller from the query instance
|
||||
if (AShooterAIController* Controller = Cast<AShooterAIController>(QueryInstance.Owner))
|
||||
{
|
||||
// ensure the target is valid
|
||||
if (IsValid(Controller->GetCurrentTarget()))
|
||||
{
|
||||
// add the controller's target actor to the context
|
||||
UEnvQueryItemType_Actor::SetContextHelper(ContextData, Controller->GetCurrentTarget());
|
||||
|
||||
} else {
|
||||
|
||||
// if for any reason there's no target, default to the controller
|
||||
UEnvQueryItemType_Actor::SetContextHelper(ContextData, Controller);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "EnvironmentQuery/EnvQueryContext.h"
|
||||
#include "EnvQueryContext_Target.generated.h"
|
||||
|
||||
/**
|
||||
* Custom EnvQuery Context that returns the actor currently targeted by an NPC
|
||||
*/
|
||||
UCLASS()
|
||||
class AUDIOVIDEORECORD_API UEnvQueryContext_Target : public UEnvQueryContext
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
|
||||
/** Provides the context locations or actors for this EnvQuery */
|
||||
virtual void ProvideContext(FEnvQueryInstance& QueryInstance, FEnvQueryContextData& ContextData) const override;
|
||||
|
||||
};
|
||||
@@ -0,0 +1,74 @@
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
|
||||
#include "Variant_Shooter/AI/ShooterAIController.h"
|
||||
#include "ShooterNPC.h"
|
||||
#include "Components/StateTreeAIComponent.h"
|
||||
#include "Perception/AIPerceptionComponent.h"
|
||||
#include "Navigation/PathFollowingComponent.h"
|
||||
#include "AI/Navigation/PathFollowingAgentInterface.h"
|
||||
|
||||
AShooterAIController::AShooterAIController()
|
||||
{
|
||||
// create the StateTree component
|
||||
StateTreeAI = CreateDefaultSubobject<UStateTreeAIComponent>(TEXT("StateTreeAI"));
|
||||
|
||||
// create the AI perception component. It will be configured in BP
|
||||
AIPerception = CreateDefaultSubobject<UAIPerceptionComponent>(TEXT("AIPerception"));
|
||||
|
||||
// subscribe to the AI perception delegates
|
||||
AIPerception->OnTargetPerceptionUpdated.AddDynamic(this, &AShooterAIController::OnPerceptionUpdated);
|
||||
AIPerception->OnTargetPerceptionForgotten.AddDynamic(this, &AShooterAIController::OnPerceptionForgotten);
|
||||
}
|
||||
|
||||
void AShooterAIController::OnPossess(APawn* InPawn)
|
||||
{
|
||||
Super::OnPossess(InPawn);
|
||||
|
||||
// ensure we're possessing an NPC
|
||||
if (AShooterNPC* NPC = Cast<AShooterNPC>(InPawn))
|
||||
{
|
||||
// add the team tag to the pawn
|
||||
NPC->Tags.Add(TeamTag);
|
||||
|
||||
// subscribe to the pawn's OnDeath delegate
|
||||
NPC->OnPawnDeath.AddDynamic(this, &AShooterAIController::OnPawnDeath);
|
||||
}
|
||||
}
|
||||
|
||||
void AShooterAIController::OnPawnDeath()
|
||||
{
|
||||
// stop movement
|
||||
GetPathFollowingComponent()->AbortMove(*this, FPathFollowingResultFlags::UserAbort);
|
||||
|
||||
// stop StateTree logic
|
||||
StateTreeAI->StopLogic(FString(""));
|
||||
|
||||
// unpossess the pawn
|
||||
UnPossess();
|
||||
|
||||
// destroy this controller
|
||||
Destroy();
|
||||
}
|
||||
|
||||
void AShooterAIController::SetCurrentTarget(AActor* Target)
|
||||
{
|
||||
TargetEnemy = Target;
|
||||
}
|
||||
|
||||
void AShooterAIController::ClearCurrentTarget()
|
||||
{
|
||||
TargetEnemy = nullptr;
|
||||
}
|
||||
|
||||
void AShooterAIController::OnPerceptionUpdated(AActor* Actor, FAIStimulus Stimulus)
|
||||
{
|
||||
// pass the data to the StateTree delegate hook
|
||||
OnShooterPerceptionUpdated.ExecuteIfBound(Actor, Stimulus);
|
||||
}
|
||||
|
||||
void AShooterAIController::OnPerceptionForgotten(AActor* Actor)
|
||||
{
|
||||
// pass the data to the StateTree delegate hook
|
||||
OnShooterPerceptionForgotten.ExecuteIfBound(Actor);
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "AIController.h"
|
||||
#include "ShooterAIController.generated.h"
|
||||
|
||||
class UStateTreeAIComponent;
|
||||
class UAIPerceptionComponent;
|
||||
struct FAIStimulus;
|
||||
|
||||
DECLARE_DELEGATE_TwoParams(FShooterPerceptionUpdatedDelegate, AActor*, const FAIStimulus&);
|
||||
DECLARE_DELEGATE_OneParam(FShooterPerceptionForgottenDelegate, AActor*);
|
||||
|
||||
/**
|
||||
* Simple AI Controller for a first person shooter enemy
|
||||
*/
|
||||
UCLASS(abstract)
|
||||
class AUDIOVIDEORECORD_API AShooterAIController : public AAIController
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
/** Runs the behavior StateTree for this NPC */
|
||||
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="Components", meta = (AllowPrivateAccess = "true"))
|
||||
UStateTreeAIComponent* StateTreeAI;
|
||||
|
||||
/** Detects other actors through sight, hearing and other senses */
|
||||
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="Components", meta = (AllowPrivateAccess = "true"))
|
||||
UAIPerceptionComponent* AIPerception;
|
||||
|
||||
protected:
|
||||
|
||||
/** Team tag for pawn friend or foe identification */
|
||||
UPROPERTY(EditAnywhere, Category="Shooter")
|
||||
FName TeamTag = FName("Enemy");
|
||||
|
||||
/** Enemy currently being targeted */
|
||||
TObjectPtr<AActor> TargetEnemy;
|
||||
|
||||
public:
|
||||
|
||||
/** Called when an AI perception has been updated. StateTree task delegate hook */
|
||||
FShooterPerceptionUpdatedDelegate OnShooterPerceptionUpdated;
|
||||
|
||||
/** Called when an AI perception has been forgotten. StateTree task delegate hook */
|
||||
FShooterPerceptionForgottenDelegate OnShooterPerceptionForgotten;
|
||||
|
||||
public:
|
||||
|
||||
/** Constructor */
|
||||
AShooterAIController();
|
||||
|
||||
protected:
|
||||
|
||||
/** Pawn initialization */
|
||||
virtual void OnPossess(APawn* InPawn) override;
|
||||
|
||||
protected:
|
||||
|
||||
/** Called when the possessed pawn dies */
|
||||
UFUNCTION()
|
||||
void OnPawnDeath();
|
||||
|
||||
public:
|
||||
|
||||
/** Sets the targeted enemy */
|
||||
void SetCurrentTarget(AActor* Target);
|
||||
|
||||
/** Clears the targeted enemy */
|
||||
void ClearCurrentTarget();
|
||||
|
||||
/** Returns the targeted enemy */
|
||||
AActor* GetCurrentTarget() const { return TargetEnemy; };
|
||||
|
||||
protected:
|
||||
|
||||
/** Called when the AI perception component updates a perception on a given actor */
|
||||
UFUNCTION()
|
||||
void OnPerceptionUpdated(AActor* Actor, FAIStimulus Stimulus);
|
||||
|
||||
/** Called when the AI perception component forgets a given actor */
|
||||
UFUNCTION()
|
||||
void OnPerceptionForgotten(AActor* Actor);
|
||||
};
|
||||
208
Source/AudioVideoRecord/Variant_Shooter/AI/ShooterNPC.cpp
Normal file
208
Source/AudioVideoRecord/Variant_Shooter/AI/ShooterNPC.cpp
Normal file
@@ -0,0 +1,208 @@
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
|
||||
#include "Variant_Shooter/AI/ShooterNPC.h"
|
||||
#include "ShooterWeapon.h"
|
||||
#include "Components/SkeletalMeshComponent.h"
|
||||
#include "Camera/CameraComponent.h"
|
||||
#include "Kismet/KismetMathLibrary.h"
|
||||
#include "Engine/World.h"
|
||||
#include "ShooterGameMode.h"
|
||||
#include "Components/CapsuleComponent.h"
|
||||
#include "GameFramework/CharacterMovementComponent.h"
|
||||
#include "TimerManager.h"
|
||||
|
||||
void AShooterNPC::BeginPlay()
|
||||
{
|
||||
Super::BeginPlay();
|
||||
|
||||
// spawn the weapon
|
||||
FActorSpawnParameters SpawnParams;
|
||||
SpawnParams.Owner = this;
|
||||
SpawnParams.Instigator = this;
|
||||
SpawnParams.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn;
|
||||
|
||||
Weapon = GetWorld()->SpawnActor<AShooterWeapon>(WeaponClass, GetActorTransform(), SpawnParams);
|
||||
}
|
||||
|
||||
void AShooterNPC::EndPlay(const EEndPlayReason::Type EndPlayReason)
|
||||
{
|
||||
Super::EndPlay(EndPlayReason);
|
||||
|
||||
// clear the death timer
|
||||
GetWorld()->GetTimerManager().ClearTimer(DeathTimer);
|
||||
}
|
||||
|
||||
float AShooterNPC::TakeDamage(float Damage, struct FDamageEvent const& DamageEvent, AController* EventInstigator, AActor* DamageCauser)
|
||||
{
|
||||
// ignore if already dead
|
||||
if (bIsDead)
|
||||
{
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
// Reduce HP
|
||||
CurrentHP -= Damage;
|
||||
|
||||
// Have we depleted HP?
|
||||
if (CurrentHP <= 0.0f)
|
||||
{
|
||||
Die();
|
||||
}
|
||||
|
||||
return Damage;
|
||||
}
|
||||
|
||||
void AShooterNPC::AttachWeaponMeshes(AShooterWeapon* WeaponToAttach)
|
||||
{
|
||||
const FAttachmentTransformRules AttachmentRule(EAttachmentRule::SnapToTarget, false);
|
||||
|
||||
// attach the weapon actor
|
||||
WeaponToAttach->AttachToActor(this, AttachmentRule);
|
||||
|
||||
// attach the weapon meshes
|
||||
WeaponToAttach->GetFirstPersonMesh()->AttachToComponent(GetFirstPersonMesh(), AttachmentRule, FirstPersonWeaponSocket);
|
||||
WeaponToAttach->GetThirdPersonMesh()->AttachToComponent(GetMesh(), AttachmentRule, FirstPersonWeaponSocket);
|
||||
}
|
||||
|
||||
void AShooterNPC::PlayFiringMontage(UAnimMontage* Montage)
|
||||
{
|
||||
// unused
|
||||
}
|
||||
|
||||
void AShooterNPC::AddWeaponRecoil(float Recoil)
|
||||
{
|
||||
// unused
|
||||
}
|
||||
|
||||
void AShooterNPC::UpdateWeaponHUD(int32 CurrentAmmo, int32 MagazineSize)
|
||||
{
|
||||
// unused
|
||||
}
|
||||
|
||||
FVector AShooterNPC::GetWeaponTargetLocation()
|
||||
{
|
||||
// start aiming from the camera location
|
||||
const FVector AimSource = GetFirstPersonCameraComponent()->GetComponentLocation();
|
||||
|
||||
FVector AimDir, AimTarget = FVector::ZeroVector;
|
||||
|
||||
// do we have an aim target?
|
||||
if (CurrentAimTarget)
|
||||
{
|
||||
// target the actor location
|
||||
AimTarget = CurrentAimTarget->GetActorLocation();
|
||||
|
||||
// apply a vertical offset to target head/feet
|
||||
AimTarget.Z += FMath::RandRange(MinAimOffsetZ, MaxAimOffsetZ);
|
||||
|
||||
// get the aim direction and apply randomness in a cone
|
||||
AimDir = (AimTarget - AimSource).GetSafeNormal();
|
||||
AimDir = UKismetMathLibrary::RandomUnitVectorInConeInDegrees(AimDir, AimVarianceHalfAngle);
|
||||
|
||||
|
||||
} else {
|
||||
|
||||
// no aim target, so just use the camera facing
|
||||
AimDir = UKismetMathLibrary::RandomUnitVectorInConeInDegrees(GetFirstPersonCameraComponent()->GetForwardVector(), AimVarianceHalfAngle);
|
||||
|
||||
}
|
||||
|
||||
// calculate the unobstructed aim target location
|
||||
AimTarget = AimSource + (AimDir * AimRange);
|
||||
|
||||
// run a visibility trace to see if there's obstructions
|
||||
FHitResult OutHit;
|
||||
|
||||
FCollisionQueryParams QueryParams;
|
||||
QueryParams.AddIgnoredActor(this);
|
||||
|
||||
GetWorld()->LineTraceSingleByChannel(OutHit, AimSource, AimTarget, ECC_Visibility, QueryParams);
|
||||
|
||||
// return either the impact point or the trace end
|
||||
return OutHit.bBlockingHit ? OutHit.ImpactPoint : OutHit.TraceEnd;
|
||||
}
|
||||
|
||||
void AShooterNPC::AddWeaponClass(const TSubclassOf<AShooterWeapon>& InWeaponClass)
|
||||
{
|
||||
// unused
|
||||
}
|
||||
|
||||
void AShooterNPC::OnWeaponActivated(AShooterWeapon* InWeapon)
|
||||
{
|
||||
// unused
|
||||
}
|
||||
|
||||
void AShooterNPC::OnWeaponDeactivated(AShooterWeapon* InWeapon)
|
||||
{
|
||||
// unused
|
||||
}
|
||||
|
||||
void AShooterNPC::OnSemiWeaponRefire()
|
||||
{
|
||||
// are we still shooting?
|
||||
if (bIsShooting)
|
||||
{
|
||||
// fire the weapon
|
||||
Weapon->StartFiring();
|
||||
}
|
||||
}
|
||||
|
||||
void AShooterNPC::Die()
|
||||
{
|
||||
// ignore if already dead
|
||||
if (bIsDead)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// raise the dead flag
|
||||
bIsDead = true;
|
||||
|
||||
// increment the team score
|
||||
if (AShooterGameMode* GM = Cast<AShooterGameMode>(GetWorld()->GetAuthGameMode()))
|
||||
{
|
||||
GM->IncrementTeamScore(TeamByte);
|
||||
}
|
||||
|
||||
// disable capsule collision
|
||||
GetCapsuleComponent()->SetCollisionEnabled(ECollisionEnabled::NoCollision);
|
||||
|
||||
// stop movement
|
||||
GetCharacterMovement()->StopMovementImmediately();
|
||||
GetCharacterMovement()->StopActiveMovement();
|
||||
|
||||
// enable ragdoll physics on the third person mesh
|
||||
GetMesh()->SetCollisionProfileName(RagdollCollisionProfile);
|
||||
GetMesh()->SetSimulatePhysics(true);
|
||||
GetMesh()->SetPhysicsBlendWeight(1.0f);
|
||||
|
||||
// schedule actor destruction
|
||||
GetWorld()->GetTimerManager().SetTimer(DeathTimer, this, &AShooterNPC::DeferredDestruction, DeferredDestructionTime, false);
|
||||
}
|
||||
|
||||
void AShooterNPC::DeferredDestruction()
|
||||
{
|
||||
Destroy();
|
||||
}
|
||||
|
||||
void AShooterNPC::StartShooting(AActor* ActorToShoot)
|
||||
{
|
||||
// save the aim target
|
||||
CurrentAimTarget = ActorToShoot;
|
||||
|
||||
// raise the flag
|
||||
bIsShooting = true;
|
||||
|
||||
// signal the weapon
|
||||
Weapon->StartFiring();
|
||||
}
|
||||
|
||||
void AShooterNPC::StopShooting()
|
||||
{
|
||||
// lower the flag
|
||||
bIsShooting = false;
|
||||
|
||||
// signal the weapon
|
||||
Weapon->StopFiring();
|
||||
}
|
||||
153
Source/AudioVideoRecord/Variant_Shooter/AI/ShooterNPC.h
Normal file
153
Source/AudioVideoRecord/Variant_Shooter/AI/ShooterNPC.h
Normal file
@@ -0,0 +1,153 @@
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "AudioVideoRecordCharacter.h"
|
||||
#include "ShooterWeaponHolder.h"
|
||||
#include "ShooterNPC.generated.h"
|
||||
|
||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE(FPawnDeathDelegate);
|
||||
|
||||
class AShooterWeapon;
|
||||
|
||||
/**
|
||||
* A simple AI-controlled shooter game NPC
|
||||
* Executes its behavior through a StateTree managed by its AI Controller
|
||||
* Holds and manages a weapon
|
||||
*/
|
||||
UCLASS(abstract)
|
||||
class AUDIOVIDEORECORD_API AShooterNPC : public AAudioVideoRecordCharacter, public IShooterWeaponHolder
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
|
||||
/** Current HP for this character. It dies if it reaches zero through damage */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="Damage")
|
||||
float CurrentHP = 100.0f;
|
||||
|
||||
protected:
|
||||
|
||||
/** Name of the collision profile to use during ragdoll death */
|
||||
UPROPERTY(EditAnywhere, Category="Damage")
|
||||
FName RagdollCollisionProfile = FName("Ragdoll");
|
||||
|
||||
/** Time to wait after death before destroying this actor */
|
||||
UPROPERTY(EditAnywhere, Category="Damage")
|
||||
float DeferredDestructionTime = 5.0f;
|
||||
|
||||
/** Team byte for this character */
|
||||
UPROPERTY(EditAnywhere, Category="Team")
|
||||
uint8 TeamByte = 1;
|
||||
|
||||
/** Pointer to the equipped weapon */
|
||||
TObjectPtr<AShooterWeapon> Weapon;
|
||||
|
||||
/** Type of weapon to spawn for this character */
|
||||
UPROPERTY(EditAnywhere, Category="Weapon")
|
||||
TSubclassOf<AShooterWeapon> WeaponClass;
|
||||
|
||||
/** Name of the first person mesh weapon socket */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category ="Weapons")
|
||||
FName FirstPersonWeaponSocket = FName("HandGrip_R");
|
||||
|
||||
/** Name of the third person mesh weapon socket */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category ="Weapons")
|
||||
FName ThirdPersonWeaponSocket = FName("HandGrip_R");
|
||||
|
||||
/** Max range for aiming calculations */
|
||||
UPROPERTY(EditAnywhere, Category="Aim")
|
||||
float AimRange = 10000.0f;
|
||||
|
||||
/** Cone variance to apply while aiming */
|
||||
UPROPERTY(EditAnywhere, Category="Aim")
|
||||
float AimVarianceHalfAngle = 10.0f;
|
||||
|
||||
/** Minimum vertical offset from the target center to apply when aiming */
|
||||
UPROPERTY(EditAnywhere, Category="Aim")
|
||||
float MinAimOffsetZ = -35.0f;
|
||||
|
||||
/** Maximum vertical offset from the target center to apply when aiming */
|
||||
UPROPERTY(EditAnywhere, Category="Aim")
|
||||
float MaxAimOffsetZ = -60.0f;
|
||||
|
||||
/** Actor currently being targeted */
|
||||
TObjectPtr<AActor> CurrentAimTarget;
|
||||
|
||||
/** If true, this character is currently shooting its weapon */
|
||||
bool bIsShooting = false;
|
||||
|
||||
/** If true, this character has already died */
|
||||
bool bIsDead = false;
|
||||
|
||||
/** Deferred destruction on death timer */
|
||||
FTimerHandle DeathTimer;
|
||||
|
||||
public:
|
||||
|
||||
/** Delegate called when this NPC dies */
|
||||
FPawnDeathDelegate OnPawnDeath;
|
||||
|
||||
protected:
|
||||
|
||||
/** Gameplay initialization */
|
||||
virtual void BeginPlay() override;
|
||||
|
||||
/** Gameplay cleanup */
|
||||
virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override;
|
||||
|
||||
public:
|
||||
|
||||
/** Handle incoming damage */
|
||||
virtual float TakeDamage(float Damage, struct FDamageEvent const& DamageEvent, AController* EventInstigator, AActor* DamageCauser) override;
|
||||
|
||||
public:
|
||||
|
||||
//~Begin IShooterWeaponHolder interface
|
||||
|
||||
/** Attaches a weapon's meshes to the owner */
|
||||
virtual void AttachWeaponMeshes(AShooterWeapon* Weapon) override;
|
||||
|
||||
/** Plays the firing montage for the weapon */
|
||||
virtual void PlayFiringMontage(UAnimMontage* Montage) override;
|
||||
|
||||
/** Applies weapon recoil to the owner */
|
||||
virtual void AddWeaponRecoil(float Recoil) override;
|
||||
|
||||
/** Updates the weapon's HUD with the current ammo count */
|
||||
virtual void UpdateWeaponHUD(int32 CurrentAmmo, int32 MagazineSize) override;
|
||||
|
||||
/** Calculates and returns the aim location for the weapon */
|
||||
virtual FVector GetWeaponTargetLocation() override;
|
||||
|
||||
/** Gives a weapon of this class to the owner */
|
||||
virtual void AddWeaponClass(const TSubclassOf<AShooterWeapon>& WeaponClass) override;
|
||||
|
||||
/** Activates the passed weapon */
|
||||
virtual void OnWeaponActivated(AShooterWeapon* Weapon) override;
|
||||
|
||||
/** Deactivates the passed weapon */
|
||||
virtual void OnWeaponDeactivated(AShooterWeapon* Weapon) override;
|
||||
|
||||
/** Notifies the owner that the weapon cooldown has expired and it's ready to shoot again */
|
||||
virtual void OnSemiWeaponRefire() override;
|
||||
|
||||
//~End IShooterWeaponHolder interface
|
||||
|
||||
protected:
|
||||
|
||||
/** Called when HP is depleted and the character should die */
|
||||
void Die();
|
||||
|
||||
/** Called after death to destroy the actor */
|
||||
void DeferredDestruction();
|
||||
|
||||
public:
|
||||
|
||||
/** Signals this character to start shooting at the passed actor */
|
||||
void StartShooting(AActor* ActorToShoot);
|
||||
|
||||
/** Signals this character to stop shooting */
|
||||
void StopShooting();
|
||||
};
|
||||
@@ -0,0 +1,366 @@
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
|
||||
#include "Variant_Shooter/AI/ShooterStateTreeUtility.h"
|
||||
#include "StateTreeExecutionContext.h"
|
||||
#include "ShooterNPC.h"
|
||||
#include "Camera/CameraComponent.h"
|
||||
#include "AIController.h"
|
||||
#include "Perception/AIPerceptionComponent.h"
|
||||
#include "ShooterAIController.h"
|
||||
#include "StateTreeAsyncExecutionContext.h"
|
||||
|
||||
bool FStateTreeLineOfSightToTargetCondition::TestCondition(FStateTreeExecutionContext& Context) const
|
||||
{
|
||||
const FInstanceDataType& InstanceData = Context.GetInstanceData(*this);
|
||||
|
||||
// ensure the target is valid
|
||||
if (!IsValid(InstanceData.Target))
|
||||
{
|
||||
return !InstanceData.bMustHaveLineOfSight;
|
||||
}
|
||||
|
||||
// check if the character is facing towards the target
|
||||
const FVector TargetDir = (InstanceData.Target->GetActorLocation() - InstanceData.Character->GetActorLocation()).GetSafeNormal();
|
||||
|
||||
const float FacingDot = FVector::DotProduct(TargetDir, InstanceData.Character->GetActorForwardVector());
|
||||
const float MaxDot = FMath::Cos(FMath::DegreesToRadians(InstanceData.LineOfSightConeAngle));
|
||||
|
||||
// is the facing outside of our cone half angle?
|
||||
if (FacingDot <= MaxDot)
|
||||
{
|
||||
return !InstanceData.bMustHaveLineOfSight;
|
||||
}
|
||||
|
||||
// get the target's bounding box
|
||||
FVector CenterOfMass, Extent;
|
||||
InstanceData.Target->GetActorBounds(true, CenterOfMass, Extent, false);
|
||||
|
||||
// divide the vertical extent by the number of line of sight checks we'll do
|
||||
const float ExtentZOffset = Extent.Z * 2.0f / InstanceData.NumberOfVerticalLineOfSightChecks;
|
||||
|
||||
// get the character's camera location as the source for the line checks
|
||||
const FVector Start = InstanceData.Character->GetFirstPersonCameraComponent()->GetComponentLocation();
|
||||
|
||||
// ignore the character and target. We want to ensure there's an unobstructed trace not counting them
|
||||
FCollisionQueryParams QueryParams;
|
||||
QueryParams.AddIgnoredActor(InstanceData.Character);
|
||||
QueryParams.AddIgnoredActor(InstanceData.Target);
|
||||
|
||||
FHitResult OutHit;
|
||||
|
||||
// run a number of vertically offset line traces to the target location
|
||||
for (int32 i = 0; i < InstanceData.NumberOfVerticalLineOfSightChecks - 1; ++i)
|
||||
{
|
||||
// calculate the endpoint for the trace
|
||||
const FVector End = CenterOfMass + FVector(0.0f, 0.0f, Extent.Z - ExtentZOffset * i);
|
||||
|
||||
InstanceData.Character->GetWorld()->LineTraceSingleByChannel(OutHit, Start, End, ECC_Visibility, QueryParams);
|
||||
|
||||
// is the trace unobstructed?
|
||||
if (!OutHit.bBlockingHit)
|
||||
{
|
||||
// we only need one unobstructed trace, so terminate early
|
||||
return InstanceData.bMustHaveLineOfSight;
|
||||
}
|
||||
}
|
||||
|
||||
// no line of sight found
|
||||
return !InstanceData.bMustHaveLineOfSight;
|
||||
}
|
||||
|
||||
#if WITH_EDITOR
|
||||
FText FStateTreeLineOfSightToTargetCondition::GetDescription(const FGuid& ID, FStateTreeDataView InstanceDataView, const IStateTreeBindingLookup& BindingLookup, EStateTreeNodeFormatting Formatting /*= EStateTreeNodeFormatting::Text*/) const
|
||||
{
|
||||
return FText::FromString("<b>Has Line of Sight</b>");
|
||||
}
|
||||
#endif
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
|
||||
EStateTreeRunStatus FStateTreeFaceActorTask::EnterState(FStateTreeExecutionContext& Context, const FStateTreeTransitionResult& Transition) const
|
||||
{
|
||||
// have we transitioned from another state?
|
||||
if (Transition.ChangeType == EStateTreeStateChangeType::Changed)
|
||||
{
|
||||
// get the instance data
|
||||
FInstanceDataType& InstanceData = Context.GetInstanceData(*this);
|
||||
|
||||
// set the AI Controller's focus
|
||||
InstanceData.Controller->SetFocus(InstanceData.ActorToFaceTowards);
|
||||
}
|
||||
|
||||
return EStateTreeRunStatus::Running;
|
||||
}
|
||||
|
||||
void FStateTreeFaceActorTask::ExitState(FStateTreeExecutionContext& Context, const FStateTreeTransitionResult& Transition) const
|
||||
{
|
||||
// have we transitioned to another state?
|
||||
if (Transition.ChangeType == EStateTreeStateChangeType::Changed)
|
||||
{
|
||||
// get the instance data
|
||||
FInstanceDataType& InstanceData = Context.GetInstanceData(*this);
|
||||
|
||||
// clear the AI Controller's focus
|
||||
InstanceData.Controller->ClearFocus(EAIFocusPriority::Gameplay);
|
||||
}
|
||||
}
|
||||
|
||||
#if WITH_EDITOR
|
||||
FText FStateTreeFaceActorTask::GetDescription(const FGuid& ID, FStateTreeDataView InstanceDataView, const IStateTreeBindingLookup& BindingLookup, EStateTreeNodeFormatting Formatting /*= EStateTreeNodeFormatting::Text*/) const
|
||||
{
|
||||
return FText::FromString("<b>Face Towards Actor</b>");
|
||||
}
|
||||
#endif // WITH_EDITOR
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
|
||||
EStateTreeRunStatus FStateTreeFaceLocationTask::EnterState(FStateTreeExecutionContext& Context, const FStateTreeTransitionResult& Transition) const
|
||||
{
|
||||
// have we transitioned from another state?
|
||||
if (Transition.ChangeType == EStateTreeStateChangeType::Changed)
|
||||
{
|
||||
// get the instance data
|
||||
FInstanceDataType& InstanceData = Context.GetInstanceData(*this);
|
||||
|
||||
// set the AI Controller's focus
|
||||
InstanceData.Controller->SetFocalPoint(InstanceData.FaceLocation);
|
||||
}
|
||||
|
||||
return EStateTreeRunStatus::Running;
|
||||
}
|
||||
|
||||
void FStateTreeFaceLocationTask::ExitState(FStateTreeExecutionContext& Context, const FStateTreeTransitionResult& Transition) const
|
||||
{
|
||||
// have we transitioned to another state?
|
||||
if (Transition.ChangeType == EStateTreeStateChangeType::Changed)
|
||||
{
|
||||
// get the instance data
|
||||
FInstanceDataType& InstanceData = Context.GetInstanceData(*this);
|
||||
|
||||
// clear the AI Controller's focus
|
||||
InstanceData.Controller->ClearFocus(EAIFocusPriority::Gameplay);
|
||||
}
|
||||
}
|
||||
|
||||
#if WITH_EDITOR
|
||||
FText FStateTreeFaceLocationTask::GetDescription(const FGuid& ID, FStateTreeDataView InstanceDataView, const IStateTreeBindingLookup& BindingLookup, EStateTreeNodeFormatting Formatting /*= EStateTreeNodeFormatting::Text*/) const
|
||||
{
|
||||
return FText::FromString("<b>Face Towards Location</b>");
|
||||
}
|
||||
#endif // WITH_EDITOR
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
|
||||
EStateTreeRunStatus FStateTreeSetRandomFloatTask::EnterState(FStateTreeExecutionContext& Context, const FStateTreeTransitionResult& Transition) const
|
||||
{
|
||||
// have we transitioned to another state?
|
||||
if (Transition.ChangeType == EStateTreeStateChangeType::Changed)
|
||||
{
|
||||
// get the instance data
|
||||
FInstanceDataType& InstanceData = Context.GetInstanceData(*this);
|
||||
|
||||
// calculate the output value
|
||||
InstanceData.OutValue = FMath::RandRange(InstanceData.MinValue, InstanceData.MaxValue);
|
||||
}
|
||||
|
||||
return EStateTreeRunStatus::Running;
|
||||
}
|
||||
|
||||
#if WITH_EDITOR
|
||||
FText FStateTreeSetRandomFloatTask::GetDescription(const FGuid& ID, FStateTreeDataView InstanceDataView, const IStateTreeBindingLookup& BindingLookup, EStateTreeNodeFormatting Formatting /*= EStateTreeNodeFormatting::Text*/) const
|
||||
{
|
||||
return FText::FromString("<b>Set Random Float</b>");
|
||||
}
|
||||
#endif // WITH_EDITOR
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
|
||||
EStateTreeRunStatus FStateTreeShootAtTargetTask::EnterState(FStateTreeExecutionContext& Context, const FStateTreeTransitionResult& Transition) const
|
||||
{
|
||||
// have we transitioned from another state?
|
||||
if (Transition.ChangeType == EStateTreeStateChangeType::Changed)
|
||||
{
|
||||
// get the instance data
|
||||
FInstanceDataType& InstanceData = Context.GetInstanceData(*this);
|
||||
|
||||
// tell the character to shoot the target
|
||||
InstanceData.Character->StartShooting(InstanceData.Target);
|
||||
}
|
||||
|
||||
return EStateTreeRunStatus::Running;
|
||||
}
|
||||
|
||||
void FStateTreeShootAtTargetTask::ExitState(FStateTreeExecutionContext& Context, const FStateTreeTransitionResult& Transition) const
|
||||
{
|
||||
// have we transitioned to another state?
|
||||
if (Transition.ChangeType == EStateTreeStateChangeType::Changed)
|
||||
{
|
||||
// get the instance data
|
||||
FInstanceDataType& InstanceData = Context.GetInstanceData(*this);
|
||||
|
||||
// tell the character to stop shooting
|
||||
InstanceData.Character->StopShooting();
|
||||
}
|
||||
}
|
||||
|
||||
#if WITH_EDITOR
|
||||
FText FStateTreeShootAtTargetTask::GetDescription(const FGuid& ID, FStateTreeDataView InstanceDataView, const IStateTreeBindingLookup& BindingLookup, EStateTreeNodeFormatting Formatting /*= EStateTreeNodeFormatting::Text*/) const
|
||||
{
|
||||
return FText::FromString("<b>Shoot at Target</b>");
|
||||
}
|
||||
#endif // WITH_EDITOR
|
||||
|
||||
EStateTreeRunStatus FStateTreeSenseEnemiesTask::EnterState(FStateTreeExecutionContext& Context, const FStateTreeTransitionResult& Transition) const
|
||||
{
|
||||
// have we transitioned from another state?
|
||||
if (Transition.ChangeType == EStateTreeStateChangeType::Changed)
|
||||
{
|
||||
// get the instance data
|
||||
FInstanceDataType& InstanceData = Context.GetInstanceData(*this);
|
||||
|
||||
// bind the perception updated delegate on the controller
|
||||
InstanceData.Controller->OnShooterPerceptionUpdated.BindLambda(
|
||||
[WeakContext = Context.MakeWeakExecutionContext()](AActor* SensedActor, const FAIStimulus& Stimulus)
|
||||
{
|
||||
// get the instance data inside the lambda
|
||||
const FStateTreeStrongExecutionContext StrongContext = WeakContext.MakeStrongExecutionContext();
|
||||
|
||||
if (FInstanceDataType* LambdaInstanceData = StrongContext.GetInstanceDataPtr<FInstanceDataType>())
|
||||
{
|
||||
if (SensedActor->ActorHasTag(LambdaInstanceData->SenseTag))
|
||||
{
|
||||
bool bDirectLOS = false;
|
||||
|
||||
// calculate the direction of the stimulus
|
||||
const FVector StimulusDir = (Stimulus.StimulusLocation - LambdaInstanceData->Character->GetActorLocation()).GetSafeNormal();
|
||||
|
||||
// infer the angle from the dot product between the character facing and the stimulus direction
|
||||
const float DirDot = FVector::DotProduct(StimulusDir, LambdaInstanceData->Character->GetActorForwardVector());
|
||||
const float MaxDot = FMath::Cos(FMath::DegreesToRadians(LambdaInstanceData->DirectLineOfSightCone));
|
||||
|
||||
// is the direction within our perception cone?
|
||||
if (DirDot >= MaxDot)
|
||||
{
|
||||
// run a line trace between the character and the sensed actor
|
||||
FCollisionQueryParams QueryParams;
|
||||
QueryParams.AddIgnoredActor(LambdaInstanceData->Character);
|
||||
QueryParams.AddIgnoredActor(SensedActor);
|
||||
|
||||
FHitResult OutHit;
|
||||
|
||||
// we have direct line of sight if this trace is unobstructed
|
||||
bDirectLOS = !LambdaInstanceData->Character->GetWorld()->LineTraceSingleByChannel(OutHit, LambdaInstanceData->Character->GetActorLocation(), SensedActor->GetActorLocation(), ECC_Visibility, QueryParams);
|
||||
|
||||
}
|
||||
|
||||
// check if we have a direct line of sight to the stimulus
|
||||
if (bDirectLOS)
|
||||
{
|
||||
// set the controller's target
|
||||
LambdaInstanceData->Controller->SetCurrentTarget(SensedActor);
|
||||
|
||||
// set the task output
|
||||
LambdaInstanceData->TargetActor = SensedActor;
|
||||
|
||||
// set the flags
|
||||
LambdaInstanceData->bHasTarget = true;
|
||||
LambdaInstanceData->bHasInvestigateLocation = false;
|
||||
|
||||
// no direct line of sight to target
|
||||
} else {
|
||||
|
||||
// if we already have a target, ignore the partial sense and keep on them
|
||||
if (!IsValid(LambdaInstanceData->TargetActor))
|
||||
{
|
||||
// is this stimulus stronger than the last one we had?
|
||||
if (Stimulus.Strength > LambdaInstanceData->LastStimulusStrength)
|
||||
{
|
||||
// update the stimulus strength
|
||||
LambdaInstanceData->LastStimulusStrength = Stimulus.Strength;
|
||||
|
||||
// set the investigate location
|
||||
LambdaInstanceData->InvestigateLocation = Stimulus.StimulusLocation;
|
||||
|
||||
// set the investigate flag
|
||||
LambdaInstanceData->bHasInvestigateLocation = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// bind the perception forgotten delegate on the controller
|
||||
InstanceData.Controller->OnShooterPerceptionForgotten.BindLambda(
|
||||
[WeakContext = Context.MakeWeakExecutionContext()](AActor* SensedActor)
|
||||
{
|
||||
// get the instance data inside the lambda
|
||||
FInstanceDataType* LambdaInstanceData = WeakContext.MakeStrongExecutionContext().GetInstanceDataPtr<FInstanceDataType>();
|
||||
|
||||
if (!LambdaInstanceData)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
bool bForget = false;
|
||||
|
||||
// are we forgetting the current target?
|
||||
if (SensedActor == LambdaInstanceData->TargetActor)
|
||||
{
|
||||
bForget = true;
|
||||
|
||||
} else {
|
||||
|
||||
// are we forgetting about a partial sense?
|
||||
if (!IsValid(LambdaInstanceData->TargetActor))
|
||||
{
|
||||
bForget = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (bForget)
|
||||
{
|
||||
// clear the target
|
||||
LambdaInstanceData->TargetActor = nullptr;
|
||||
|
||||
// clear the flags
|
||||
LambdaInstanceData->bHasInvestigateLocation = false;
|
||||
LambdaInstanceData->bHasTarget = false;
|
||||
|
||||
// reset the stimulus strength
|
||||
LambdaInstanceData->LastStimulusStrength = 0.0f;
|
||||
|
||||
// clear the target on the controller
|
||||
LambdaInstanceData->Controller->ClearCurrentTarget();
|
||||
LambdaInstanceData->Controller->ClearFocus(EAIFocusPriority::Gameplay);
|
||||
}
|
||||
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
return EStateTreeRunStatus::Running;
|
||||
}
|
||||
|
||||
void FStateTreeSenseEnemiesTask::ExitState(FStateTreeExecutionContext& Context, const FStateTreeTransitionResult& Transition) const
|
||||
{
|
||||
// have we transitioned to another state?
|
||||
if (Transition.ChangeType == EStateTreeStateChangeType::Changed)
|
||||
{
|
||||
// get the instance data
|
||||
FInstanceDataType& InstanceData = Context.GetInstanceData(*this);
|
||||
|
||||
// unbind the perception delegates
|
||||
InstanceData.Controller->OnShooterPerceptionUpdated.Unbind();
|
||||
InstanceData.Controller->OnShooterPerceptionForgotten.Unbind();
|
||||
}
|
||||
}
|
||||
|
||||
#if WITH_EDITOR
|
||||
FText FStateTreeSenseEnemiesTask::GetDescription(const FGuid& ID, FStateTreeDataView InstanceDataView, const IStateTreeBindingLookup& BindingLookup, EStateTreeNodeFormatting Formatting /*= EStateTreeNodeFormatting::Text*/) const
|
||||
{
|
||||
return FText::FromString("<b>Sense Enemies</b>");
|
||||
}
|
||||
#endif // WITH_EDITOR
|
||||
@@ -0,0 +1,309 @@
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "StateTreeTaskBase.h"
|
||||
#include "StateTreeConditionBase.h"
|
||||
|
||||
#include "ShooterStateTreeUtility.generated.h"
|
||||
|
||||
class AShooterNPC;
|
||||
class AAIController;
|
||||
class AShooterAIController;
|
||||
|
||||
/**
|
||||
* Instance data struct for the FStateTreeLineOfSightToTargetCondition condition
|
||||
*/
|
||||
USTRUCT()
|
||||
struct FStateTreeLineOfSightToTargetConditionInstanceData
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
/** Targeting character */
|
||||
UPROPERTY(EditAnywhere, Category = "Context")
|
||||
AShooterNPC* Character;
|
||||
|
||||
/** Target to check line of sight for */
|
||||
UPROPERTY(EditAnywhere, Category = "Condition")
|
||||
AActor* Target;
|
||||
|
||||
/** Max allowed line of sight cone angle, in degrees */
|
||||
UPROPERTY(EditAnywhere, Category = "Condition")
|
||||
float LineOfSightConeAngle = 35.0f;
|
||||
|
||||
/** Number of vertical line of sight checks to run to try and get around low obstacles */
|
||||
UPROPERTY(EditAnywhere, Category = "Condition")
|
||||
int32 NumberOfVerticalLineOfSightChecks = 5;
|
||||
|
||||
/** If true, the condition passes if the character has line of sight */
|
||||
UPROPERTY(EditAnywhere, Category = "Condition")
|
||||
bool bMustHaveLineOfSight = true;
|
||||
};
|
||||
STATETREE_POD_INSTANCEDATA(FStateTreeLineOfSightToTargetConditionInstanceData);
|
||||
|
||||
/**
|
||||
* StateTree condition to check if the character is grounded
|
||||
*/
|
||||
USTRUCT(DisplayName = "Has Line of Sight to Target", Category="Shooter")
|
||||
struct FStateTreeLineOfSightToTargetCondition : public FStateTreeConditionCommonBase
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
/** Set the instance data type */
|
||||
using FInstanceDataType = FStateTreeLineOfSightToTargetConditionInstanceData;
|
||||
virtual const UStruct* GetInstanceDataType() const override { return FInstanceDataType::StaticStruct(); }
|
||||
|
||||
/** Default constructor */
|
||||
FStateTreeLineOfSightToTargetCondition() = default;
|
||||
|
||||
/** Tests the StateTree condition */
|
||||
virtual bool TestCondition(FStateTreeExecutionContext& Context) const override;
|
||||
|
||||
#if WITH_EDITOR
|
||||
/** Provides the description string */
|
||||
virtual FText GetDescription(const FGuid& ID, FStateTreeDataView InstanceDataView, const IStateTreeBindingLookup& BindingLookup, EStateTreeNodeFormatting Formatting = EStateTreeNodeFormatting::Text) const override;
|
||||
#endif
|
||||
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
|
||||
/**
|
||||
* Instance data struct for the Face Towards Actor StateTree task
|
||||
*/
|
||||
USTRUCT()
|
||||
struct FStateTreeFaceActorInstanceData
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
/** AI Controller that will determine the focused actor */
|
||||
UPROPERTY(EditAnywhere, Category = Context)
|
||||
TObjectPtr<AAIController> Controller;
|
||||
|
||||
/** Actor that will be faced towards */
|
||||
UPROPERTY(EditAnywhere, Category = Input)
|
||||
TObjectPtr<AActor> ActorToFaceTowards;
|
||||
};
|
||||
|
||||
/**
|
||||
* StateTree task to face an AI-Controlled Pawn towards an Actor
|
||||
*/
|
||||
USTRUCT(meta=(DisplayName="Face Towards Actor", Category="Shooter"))
|
||||
struct FStateTreeFaceActorTask : public FStateTreeTaskCommonBase
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
/* Ensure we're using the correct instance data struct */
|
||||
using FInstanceDataType = FStateTreeFaceActorInstanceData;
|
||||
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;
|
||||
|
||||
/** Runs when the owning state is ended */
|
||||
virtual void ExitState(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
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
|
||||
/**
|
||||
* Instance data struct for the Face Towards Location StateTree task
|
||||
*/
|
||||
USTRUCT()
|
||||
struct FStateTreeFaceLocationInstanceData
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
/** AI Controller that will determine the focused location */
|
||||
UPROPERTY(EditAnywhere, Category = Context)
|
||||
TObjectPtr<AAIController> Controller;
|
||||
|
||||
/** Location that will be faced towards */
|
||||
UPROPERTY(EditAnywhere, Category = Parameter)
|
||||
FVector FaceLocation = FVector::ZeroVector;
|
||||
};
|
||||
|
||||
/**
|
||||
* StateTree task to face an AI-Controlled Pawn towards a world location
|
||||
*/
|
||||
USTRUCT(meta=(DisplayName="Face Towards Location", Category="Shooter"))
|
||||
struct FStateTreeFaceLocationTask : public FStateTreeTaskCommonBase
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
/* Ensure we're using the correct instance data struct */
|
||||
using FInstanceDataType = FStateTreeFaceLocationInstanceData;
|
||||
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;
|
||||
|
||||
/** Runs when the owning state is ended */
|
||||
virtual void ExitState(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
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
|
||||
/**
|
||||
* Instance data struct for the Set Random Float StateTree task
|
||||
*/
|
||||
USTRUCT()
|
||||
struct FStateTreeSetRandomFloatData
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
/** Minimum random value */
|
||||
UPROPERTY(EditAnywhere, Category = Parameter)
|
||||
float MinValue = 0.0f;
|
||||
|
||||
/** Maximum random value */
|
||||
UPROPERTY(EditAnywhere, Category = Parameter)
|
||||
float MaxValue = 0.0f;
|
||||
|
||||
/** Output calculated value */
|
||||
UPROPERTY(EditAnywhere, Category = Output)
|
||||
float OutValue = 0.0f;
|
||||
};
|
||||
|
||||
/**
|
||||
* StateTree task to calculate a random float value within the specified range
|
||||
*/
|
||||
USTRUCT(meta=(DisplayName="Set Random Float", Category="Shooter"))
|
||||
struct FStateTreeSetRandomFloatTask : public FStateTreeTaskCommonBase
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
/* Ensure we're using the correct instance data struct */
|
||||
using FInstanceDataType = FStateTreeSetRandomFloatData;
|
||||
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
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
|
||||
/**
|
||||
* Instance data struct for the Shoot At Target StateTree task
|
||||
*/
|
||||
USTRUCT()
|
||||
struct FStateTreeShootAtTargetInstanceData
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
/** NPC that will do the shooting */
|
||||
UPROPERTY(EditAnywhere, Category = Context)
|
||||
TObjectPtr<AShooterNPC> Character;
|
||||
|
||||
/** Target to shoot at */
|
||||
UPROPERTY(EditAnywhere, Category = Input)
|
||||
TObjectPtr<AActor> Target;
|
||||
};
|
||||
|
||||
/**
|
||||
* StateTree task to have an NPC shoot at an actor
|
||||
*/
|
||||
USTRUCT(meta=(DisplayName="Shoot at Target", Category="Shooter"))
|
||||
struct FStateTreeShootAtTargetTask : public FStateTreeTaskCommonBase
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
/* Ensure we're using the correct instance data struct */
|
||||
using FInstanceDataType = FStateTreeShootAtTargetInstanceData;
|
||||
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;
|
||||
|
||||
/** Runs when the owning state is ended */
|
||||
virtual void ExitState(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
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
|
||||
/**
|
||||
* Instance data struct for the Sense Enemies StateTree task
|
||||
*/
|
||||
USTRUCT()
|
||||
struct FStateTreeSenseEnemiesInstanceData
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
/** Sensing AI Controller */
|
||||
UPROPERTY(EditAnywhere, Category = Context)
|
||||
TObjectPtr<AShooterAIController> Controller;
|
||||
|
||||
/** Sensing NPC */
|
||||
UPROPERTY(EditAnywhere, Category = Context)
|
||||
TObjectPtr<AShooterNPC> Character;
|
||||
|
||||
/** Sensed actor to target */
|
||||
UPROPERTY(EditAnywhere, Category = Output)
|
||||
TObjectPtr<AActor> TargetActor;
|
||||
|
||||
/** Sensed location to investigate */
|
||||
UPROPERTY(EditAnywhere, Category = Output)
|
||||
FVector InvestigateLocation = FVector::ZeroVector;
|
||||
|
||||
/** True if a target was successfully sensed */
|
||||
UPROPERTY(EditAnywhere, Category = Output)
|
||||
bool bHasTarget = false;
|
||||
|
||||
/** True if an investigate location was successfully sensed */
|
||||
UPROPERTY(EditAnywhere, Category = Output)
|
||||
bool bHasInvestigateLocation = false;
|
||||
|
||||
/** Tag required on sensed actors */
|
||||
UPROPERTY(EditAnywhere, Category = Parameter)
|
||||
FName SenseTag = FName("Player");
|
||||
|
||||
/** Line of sight cone half angle to consider a full sense */
|
||||
UPROPERTY(EditAnywhere, Category = Parameter)
|
||||
float DirectLineOfSightCone = 85.0f;
|
||||
|
||||
/** Strength of the last processed stimulus */
|
||||
UPROPERTY(EditAnywhere)
|
||||
float LastStimulusStrength = 0.0f;
|
||||
};
|
||||
|
||||
/**
|
||||
* StateTree task to have an NPC process AI Perceptions and sense nearby enemies
|
||||
*/
|
||||
USTRUCT(meta=(DisplayName="Sense Enemies", Category="Shooter"))
|
||||
struct FStateTreeSenseEnemiesTask : public FStateTreeTaskCommonBase
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
/* Ensure we're using the correct instance data struct */
|
||||
using FInstanceDataType = FStateTreeSenseEnemiesInstanceData;
|
||||
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;
|
||||
|
||||
/** Runs when the owning state is ended */
|
||||
virtual void ExitState(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