Simulation of Blood Flow
This section explains the architecture of the simulation and the rationale behind creating different C++ classes for inheritance in Blueprints.
Why Simulation
Nursing students often struggle to visualize how the human body operates. This simulation aims to bring the 3D anatomy model to life, enhancing understanding through interactive learning. By incorporating animations, we will simulate blood behavior and the oxygen transition within the human body, providing a dynamic educational tool.
Event Driven Design
Given that many different components need to interact and transform data, the simplest approach might seem to be directly calling functions across various parts of the simulation. However, this is not feasible in Unreal Engine without obtaining the correct actors and subsequently calling their functions, which can be inefficient.
To overcome this issue, we decided to use delegates
, which are C++ mechanisms for dispatching events. This will enable us to signal different states of the simulation to various actors, allowing them to respond appropriately.
High level overview can be seen in this image:
The simulation is being triggered from the Simulation button class which is the green button on the top right corner of application. This action is going to drigger Simulation manager's FStartSimulation
delegate which will broadcast the event to all of its subscribers.
In order to listen to the event in any function we are need to access the SimulationManager
class.
Simulation Trigger
The simulation is triggered from the Simulation button class, represented by a green button located in the top right corner of the application. The CPP_StartSimulation
button holds all of the logic for the button the WB_SimulationButton
is ther to ensure smooth designing experience. When pressed, this button triggers the FStartSimulation
or FStopSimulation
delegate in the SimulationManager
class, which then broadcasts the event to all of its subscribers.
To listen to this event in any function, we need to access the SimulationManager
class.
Simulation manager and listening to the events
To access any class instance in Unreal Engine, it typically needs to be placed in the world, and then we must search the world to retrieve the instance we want. This approach, however, can become inefficient over time. To optimize access, the SimulationManager
is instantiated directly within the GameMode
class.
In the file ACPP_GameMode.h
, you’ll find a TObjectPtr
to the SimulationManager
class. Additionally, a helper function in CPP_AnatomyUtils.h
was created to retrieve this pointer conveniently.
Example Usage
#include "CPP_AnatomyUtils.h"
Class::EventBeginPlay()
{
Super::EventBeginPlay();
SimulationManger* simManager = AnatomyUtils::GetSimulationManager(GetWorld());
}
CAUTION: this function can only be called in the actors that have access to the World object and ONLY during the
EventBeginPlay
orNativeConstruct
; otherwise, the function will not find theGameMode
object, resulting in a segmentation fault and a crash of the editor.
Once the simulation manager is retrieved, either in the EventBeginPlay
or NativeConstruct
event, we can assign functions that will be executed once the simulation manager fires stop/start
events.
To do this, we have to assign functions to the delegates of the simulation manager class.
void Class::BeginPlay(){
SimulationManager->StartSimulationEventDelegate.AddUObject(this, &ACPP_ArteriesBloodFlowSimulation::OnSimulationStart);
SimulationManager->StopSimulationEventDelegate.AddUObject(this, &ACPP_ArteriesBloodFlowSimulation::OnSimulationEnd);
}
CAUTION: this can only be done inside the event begin play or native construct otherwise the
this
isnullptr
Once the functions are assigned every time the Simulation manger broadcasts the event the functions assigned to it are exectued.
Deletion of added functions
Due to the reseons unrelevant to explain here unreal engine will cause crash since functions added through AddUObject
methods were not deleted. Therefore we have to handle it. To do this you have to overwrite method that is responsible for deleting the acctor. Examples of such methods are void NativeDesctructor()
or void EventDestroy()
. To remove the function from the delegete you can call this method on the Simulation manger delegate.
void Class::NativeDestructor()
{
Super::NativeDestructor();
SimulationManager->StartSimulationEventDelegate.RemoveUObject(this, &ACPP_ArteriesBloodFlowSimulation::OnSimulationStart);
}
Starting the animations on simualtion button
Since each individual animation blueprint prefixed with BPA_
is inherited from its own C++ class, and each class is further inherited from CPP_BaseAnimation
, we can start and stop all animations simultaneously once the simulation manager dispatches the start simulation event.
If a specific human body animation requires modifications such as a delay or other adjustments these changes can be made in the base class of that animation blueprint or in the animation blueprint itself.
For more details see Animation classs specification