Hey guys, Im trying to use Smart Objects with Mass AI. I think I have it all setup correctly but in the State Tree it claims the smart object it finds (I think) but then when I try to use the ZG Find Smart Object Target it reports this in Visual Logger.
LogMassBehavior (Error) Entity [i: 1 sn: 1][MassZoneGraphFindSmartObjectTarget] Invalid claimed smart object ID.
The image has the tasks in the state tree. I did also try breaking out the claim into its own state and that didnt fix it.
Hey,
Did you ever figure out how to do this? We’re running into the exact same issue where the Mass Find Smart Object task never returns a valid claim handle.
So, after hours of debugging, I figured out some details:
- Find SmartObject and Claim SmartObject must be in separate states.
- Selection Behavior MUST be set to Try Enter, otherwise Claim will run before Find complete.
- On State Completed for Find does not work. Why is unclear. It only works by Tick with delay.
Nice work! With the setup you shared, I can at least see a MassAI entity try to move to a Mass SO slot in the world.
I haven’t been able to get the Mass Use Smart Object Task to work yet, but I’ll keep trying stuff.
And you won’t be able to make it work without custom C++ code. I spent a couple more hours digging through this mess and seem to have figured it out. In order for the NPC to start recognizing Smart Objects, it must have the Smart Object Mass Behavior Definition class in its Behavior Definitions. But this is an abstraction! If you need to execute some logic, you’ll have to write your own C++ code.
You can extract the CitySampleMassCrowd plugin from CitySample. It contains the implementation of Smart Object Mass Interaction Definition for launching animations via the Contextual Anim Asset, but there is nothing interesting that can be done with it.
Looks like I’ll spend some time getting this to work with Gameplay Behavior logic.
So, I sketched out a working solution. I took GameplayInteractionSmartObjectBehaviorDefinition and reworked it for the Mass context. However, I had to comment out some parts, and it’s unclear how critical they are.
GameplayInteractionSmartObjectMassBehaviorDefinition.h
#pragma once
#include "CoreMinimal.h"
#include "MassSmartObjectBehaviorDefinition.h"
#include "StateTreeExecutionContext.h"
#include "StateTreeReference.h"
#include "GameplayInteractionSmartObjectMassBehaviorDefinition.generated.h"
UCLASS(BlueprintType)
class AI_API UGameplayInteractionSmartObjectMassBehaviorDefinition : public USmartObjectMassBehaviorDefinition
{
GENERATED_BODY()
public:
UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category = "SmartObject", meta=(Schema="/Script/GameplayInteractionsModule.GameplayInteractionStateTreeSchema"))
FStateTreeReference StateTreeReference;
UFUNCTION(BlueprintCallable, Category = "StateTree")
void SetStateTree(UStateTree* NewStateTree)
{
StateTreeReference.SetStateTree(NewStateTree);
}
UFUNCTION(BlueprintPure, Category = "StateTree")
const UStateTree* GetStateTree() const
{
return StateTreeReference.GetStateTree();
}
virtual void Activate(FMassCommandBuffer& CommandBuffer, const FMassBehaviorEntityContext& EntityContext) const override;
bool ValidateSchema(const FStateTreeExecutionContext& StateTreeContext, AActor* ContextActor, AActor* SmartObjectActor) const;
bool SetContextRequirements(FStateTreeExecutionContext& StateTreeContext, FMassSmartObjectUserFragment& SOUser, AActor* ContextActor, AActor* SmartObjectActor) const;
#if WITH_EDITOR
//~ UObject
virtual void GetPreloadDependencies(TArray<UObject*>& OutDeps) override;
#endif //WITH_EDITOR
};
GameplayInteractionSmartObjectMassBehaviorDefinition.cpp
#include "GameplayInteractionSmartObjectMassBehaviorDefinition.h"
#include "GameplayInteractionStateTreeSchema.h"
#include "GameplayInteractionsTypes.h"
#include "MassActorSubsystem.h"
#include "MassSmartObjectFragments.h"
#include "SmartObjectComponent.h"
#include "SmartObjectSubsystem.h"
#include "StateTree.h"
#if WITH_EDITOR
void UGameplayInteractionSmartObjectMassBehaviorDefinition::GetPreloadDependencies(TArray<UObject*>& OutDeps)
{
Super::GetPreloadDependencies(OutDeps);
if (UStateTree* StateTree = StateTreeReference.GetMutableStateTree())
{
OutDeps.Add(StateTree);
}
}
#endif //WITH_EDITOR
void UGameplayInteractionSmartObjectMassBehaviorDefinition::Activate(FMassCommandBuffer& CommandBuffer,
const FMassBehaviorEntityContext& EntityContext) const
{
Super::Activate(CommandBuffer, EntityContext);
FMassSmartObjectUserFragment& SOUser = EntityContext.EntityView.GetFragmentData<FMassSmartObjectUserFragment>();
FMassActorFragment& ActorFragment = EntityContext.EntityView.GetFragmentData<FMassActorFragment>();
USmartObjectSubsystem& SmartObjectSubsystem = EntityContext.SmartObjectSubsystem;
USmartObjectComponent* SmartObjectComponent = SmartObjectSubsystem.GetSmartObjectComponent(SOUser.InteractionHandle);
AActor* SmartObjectActor = SmartObjectComponent ? SmartObjectComponent->GetOwner() : nullptr;
AActor* ContextActor = ActorFragment.GetMutable();
if (ContextActor == nullptr)
{
return;
}
const UStateTree* StateTree = StateTreeReference.GetStateTree();
if (StateTree == nullptr)
{
return;
}
FStateTreeInstanceData StateTreeInstanceData;
FStateTreeExecutionContext StateTreeContext(*ContextActor, *StateTree, StateTreeInstanceData);
if (!StateTreeContext.IsValid())
{
return;
}
if (!ValidateSchema(StateTreeContext, ContextActor, SmartObjectActor))
{
return;
}
if (!SetContextRequirements(StateTreeContext, SOUser, ContextActor, SmartObjectActor))
{
return;
}
SmartObjectSubsystem.MutateSlotData(SOUser.InteractionHandle.SlotHandle, [this, &SmartObjectSubsystem, ContextActor, SOUser](const FSmartObjectSlotView& SlotView)
{
if (FGameplayInteractionSlotUserData* UserData = SlotView.GetMutableStateDataPtr<FGameplayInteractionSlotUserData>())
{
UserData->UserActor = ContextActor;
}
else
{
SmartObjectSubsystem.AddSlotData(SOUser.InteractionHandle, FConstStructView::Make(FGameplayInteractionSlotUserData(ContextActor)));
}
});
// Start State Tree
StateTreeContext.Start(&StateTreeReference.GetParameters());
}
bool UGameplayInteractionSmartObjectMassBehaviorDefinition::ValidateSchema(const FStateTreeExecutionContext& StateTreeContext, AActor* ContextActor, AActor* SmartObjectActor) const
{
// Ensure that the actor and smart object match the schema.
const UGameplayInteractionStateTreeSchema* Schema = Cast<UGameplayInteractionStateTreeSchema>(StateTreeContext.GetStateTree()->GetSchema());
if (Schema == nullptr)
{
return false;
}
if (!ContextActor || !ContextActor->IsA(Schema->GetContextActorClass()))
{
return false;
}
if (!SmartObjectActor || !SmartObjectActor->IsA(Schema->GetSmartObjectActorClass()))
{
return false;
}
return true;
}
bool UGameplayInteractionSmartObjectMassBehaviorDefinition::SetContextRequirements(FStateTreeExecutionContext& StateTreeContext, FMassSmartObjectUserFragment& SOUser, AActor* ContextActor, AActor* SmartObjectActor) const
{
if (!StateTreeContext.IsValid())
{
return false;
}
StateTreeContext.SetContextDataByName(UE::GameplayInteraction::Names::ContextActor, FStateTreeDataView(ContextActor));
StateTreeContext.SetContextDataByName(UE::GameplayInteraction::Names::SmartObjectActor, FStateTreeDataView(SmartObjectActor));
StateTreeContext.SetContextDataByName(UE::GameplayInteraction::Names::SmartObjectClaimedHandle, FStateTreeDataView(FStructView::Make(SOUser.InteractionHandle)));
// StateTreeContext.SetContextDataByName(UE::GameplayInteraction::Names::SlotEntranceHandle, FStateTreeDataView(FStructView::Make(SOUser.InteractionHandle.SlotHandle)));
// StateTreeContext.SetContextDataByName(UE::GameplayInteraction::Names::AbortContext, FStateTreeDataView(FStructView::Make(AbortContext)));
checkf(ContextActor != nullptr, TEXT("Should never reach this point with an invalid ContextActor since it is required to get a valid StateTreeContext."));
const UWorld* World = ContextActor->GetWorld();
StateTreeContext.SetCollectExternalDataCallback(FOnCollectStateTreeExternalData::CreateLambda(
[World, ContextActor = ContextActor]
(const FStateTreeExecutionContext& Context, const UStateTree* StateTree, TArrayView<const FStateTreeExternalDataDesc> ExternalDescs, TArrayView<FStateTreeDataView> OutDataViews)
{
check(ExternalDescs.Num() == OutDataViews.Num());
for (int32 Index = 0; Index < ExternalDescs.Num(); Index++)
{
const FStateTreeExternalDataDesc& Desc = ExternalDescs[Index];
if (Desc.Struct != nullptr)
{
if (World != nullptr && Desc.Struct->IsChildOf(UWorldSubsystem::StaticClass()))
{
UWorldSubsystem* Subsystem = World->GetSubsystemBase(Cast<UClass>(const_cast<UStruct*>(ToRawPtr(Desc.Struct))));
OutDataViews[Index] = FStateTreeDataView(Subsystem);
}
else if (Desc.Struct->IsChildOf(AActor::StaticClass()))
{
OutDataViews[Index] = FStateTreeDataView(ContextActor);
}
}
}
return true;
})
);
// TODO: Idk why this is not working, but for me the context seems to be valid anyway
// return StateTreeContext.AreContextDataViewsValid();
return true;
}
After that you can add State Tree in SmartObject.
This is very useful! Thanks for sharing all of this so far.
Just a couple questions:
* How are you using the Mass Use SmartObject task in your state tree?
* Does the State Tree that you assign on the Smart Object Definition actually run once the Mass Use Smart Object task is started?
For me, the Mass AI doesn’t seem to actually execute the State Tree assigned on the SOD (simple state with a debug text and wait task).
With non-mass entities, you can get this working by calling UseSmartObjectWithGameplayInteraction on an AI Controller and using the claimed SO’s handle:
On entering this node, it will look for the SOD’s behaviour definition’s StateTree etc.