Unreal Engine: Accessing / Referencing Other Object's Elements (nested)

Hello,
I am +5 years of Unity developer and been using UE for 9 months. I was following blueprints but started using C++. I have a few questions to understand how to reach other elements in the game especially if they are in same hiearchy. Lets give an example:

We have a train & train lever c++ classes & derived blueprints. I have created a simple interaction system with interface. We will interact with train lever, and train will start moving. So I have two essential questions about that.

  1. How should train lever knows about train, so that it can call smth. like train.Move(); I mean in unity, we would simply create a field and reference train via editor. How do i access train inside the
    train lever especially if they are in hiearchy which is question 2.

  2. Since train & train lever are blueprints, how do i create hiearchy in blueprint way? So in unity, I would create a game object called “train”, then i would also create train lever and simply drag and drop that to the train object. So that hiearch would look like smth.

-Train
—Train Lever.

And that will help since lever is inside train, train lever moves with train. So how do we create this hierarchy in UE since they both are blueprints?

It doesn’t need to know.

Idealy we want a decoupled communication system rather than a direct reference:

  1. Create a base lever class with a dispatcher called MyTrigger (e.g.) with the interact interface.
  2. Create a child of this class and add the conditions, static meshes, animations, sounds, states, etc.
  3. On the train class add a Child Component and set the child lever class as child.
  4. On BeginPlay cast the child class to the base lever class and bind the corresponding method to the dispatcher. Cast to base class instead of the child. This will allow for the lever to be interchangable with any lever child class.

From the train class perspective it will still be Train->Lever, but now the same lever class can be used to trigger Move(), Stop(), SwitchLanes(), etc. The end goal should be a lever class that can be interchangable and used anywhere. The same can be done with wagons, windows, doors, track junctions, etc.

Let me know if this is enough or if you need a more detailed step by step.

@pezzott1
Great answer! So instead of lever knows about train, we will subscribe to the event of Lever.OnSwitch(); right?

What i dont know about your example is: Should i create BP_Lever blueprint? And can i add this to the BP_Train itself? What i didnt understand is as far is know components are classes that helps us to structure as component. Since Lever is an actor and it has a BP, How do i exactly add Lever to the BP_Train itself? Can I do that in BP side or should i do that in Constructor side in c++. Since i want to reposition it, it would be beautiful to set in BP.
Thats what i dont understand in BP hiearchy. I have Lever actor, and train actor. Like a super, and sub item. Lever should be inside the train, so train better knows about lever. I think the problem is i dont know how to add sub-items to the super items, like a room and it has items inside.
I can also basically reference the mesh and box inside the Train class itself and just no-creating of lever class but i will for sure then later on, need this kinda of stuff. Do you think Lever should be actor component class since you have said that “Add child component”

Edit: What i found is there is something called ChildActor which helps me add a lever to the train Blueprint. Did you mean this on the 3. step?

So to summarize questions:

  1. Do i add BP_Lever to the BP_Train as child actor?
  2. How can i access that Child Actor from Train.h c++ file? Are there any reference easily instead of getting all child actors, i simply drag and drop my bp_lever child to the Train’s lever variable?
  3. Is that a good way to create a hiearchy like that? Since my train will have a lot of interactions inside, like windows, items etc. and will be moved with train

Yes! This way the lever doesn’t need direct knowledge of the train. Instead, it simply announces its action (like being switched), and any interested object (like the train) that is listening can respond accordingly.

Yes. In the BP_Train add a ‘Child Actor Component’; this component is designed to hold an instance of another actor (BP_Lever in this case). Then Set the Child Actor Class to BP_Lever in the properties of the Child Actor Component. Now you can position the lever relative to the train directly in the Blueprint editor.

The line between c++ and bp is a bit blurry. I aim to get all base functionality in c++ and use bp to mostly inherit, set cosmetics, references, instance specifics, etc. For the levers specifically I would add the child components, binds and any other code needed in c++ then expose a TSubclassOf to BP to set the child class and its tansform in BP.

IMO the lever needs to be an actor because it requieres more components for it to work. Components commonly used to make code modular, the lever serves that purpose but it requieres more than what a AC can offer.


Yes.

MyTrain.h
// .h
	UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category="Components")
	TObjectPtr<UStaticMeshComponent> BodyComponent = nullptr;

	UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category="LeverComponents")
	TObjectPtr<UChildActorComponent> LeverA = nullptr;
	UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "LeverComponents")
	TObjectPtr<UChildActorComponent> LeverB = nullptr;

	UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category="Levers")
	TSubclassOf<class ALeverBase> LeverAClass;
	UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category="Levers")
	TSubclassOf<class ALeverBase> LeverBClass;

	// This will send it directly to blueprint.
	UFUNCTION(BlueprintImplementableEvent, meta = (DisplayName = "LeverATriggered"))
	void LeverATriggered();
	UFUNCTION(BlueprintImplementableEvent, meta = (DisplayName = "LeverBTriggered"))
	void LeverBTriggered();

private:
#if WITH_EDITOR
	virtual void PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) override;
#endif
MyTrain.cpp
AMyTrain::MyTrain()
{
	BodyComponent = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("StaticMeshComponent"));
	SetRootComponent(BodyComponent);
	LeverA = CreateDefaultSubobject<UChildActorComponent>("ChildActorComponent0");
	LeverB = CreateDefaultSubobject<UChildActorComponent>("ChildActorComponent1");

	LeverA->SetupAttachment(RootComponent);
	LeverB->SetupAttachment(RootComponent);
}

void AMyTrain::BeginPlay()
{
	Super::BeginPlay();
	
	if (ALeverBase* tmp = Cast<ALeverBase> (LeverA->GetChildActor()))
	{
		tmp->Triggered.AddDynamic(this, &AMyTrain::LeverATriggered);
	}
	if (ALeverBase*  tmp = Cast<ALeverBase> (LeverB->GetChildActor()))
	{
		tmp->Triggered.AddDynamic(this, &AMyTrain::LeverBTriggered);
	}
}

// ...
// PostEditChangeProperty() will catch any changes in this actor so we can have the update in editor.
#if WITH_EDITOR
void AMyTrain::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent)
{
	FName PropertyName = (PropertyChangedEvent.Property != nullptr)
		? PropertyChangedEvent.Property->GetFName() : NAME_None;

	if (PropertyName == GET_MEMBER_NAME_CHECKED(AMyTrain, LeverAClass))
	{
		LeverA->SetChildActorClass(LeverAClass.Get());
	}
	if (PropertyName == GET_MEMBER_NAME_CHECKED(AMyTrain, LeverBClass))
	{
		LeverB->SetChildActorClass(LeverBClass.Get());
	}

	Super::PostEditChangeProperty(PropertyChangedEvent);
}
#endif
LeverBase.h
DECLARE_DYNAMIC_MULTICAST_DELEGATE(FTriggerDelegate);

UCLASS(Abstract, Blueprintable)
class ALeverBase : public AActor
{
	UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category="Components")
	TObjectPtr<class USphereComponent> CollisionComponent = nullptr;
	UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Components")
	TObjectPtr<UStaticMeshComponent> StaticMeshComponentA = nullptr;
	
	// BlueprintCallable allows it to be called in BP. 
	UPROPERTY(BlueprintCallable, Category = "Test")
	FTriggerDelegate Triggered;
}
LeverBase.cpp
ALeverBase::ALeverBase()
{
	PrimaryActorTick.bCanEverTick = false;

	CollisionComponent = CreateDefaultSubobject<USphereComponent>(TEXT("SphereComponent0"));
	SetRootComponent(CollisionComponent);
	CollisionComponent->SetCollisionResponseToAllChannels(ECollisionResponse::ECR_Block);

	StaticMeshComponentA = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("StaticMeshComponent0"));
	StaticMeshComponentA->SetupAttachment(RootComponent);
}

Now in the lever child bp you can set up what ever interface or condition to call the delegate (or do it in c++) and set components defaults. Heres with a bp interface i already had in a test level:

In my “MyTrain” bp:

trainbp

2 Likes

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.