Creating an object generator in the Unreal Engine editor
Let's imagine a situation: We are creating in Unreal Engine 4 (or UE5) a cave scene in which cavemen live. The model is drawn, the main objects are placed, but the illusion that people really live here is missing: there is not enough chaos. To fix this, it is necessary to scatter pebbles, sticks, and leftovers from the meals of our Neanderthals throughout the cave. The first option is to place it with your hands. The option is good, then you can even note that everything is placed manually, each pebble is put in its “own” place. But I'm human lazy loves to automate everything, and I wanted to hang such processes on the shoulders of “robots”. The first thing that comes to mind is to connect the Houdini Engine to the project and use it to place the necessary objects, but what if there is no way to connect the Houdini Engine to the project (the answer to why is beyond the scope of this article, we’ll just take this as input). You can invent your own interface using Python, let’s say, create your own Houdini Engine on minimum wage.
To test the technology, let's simplify the task: we need to place shells in the created cave. We have three bags, one of the bags has seven material options.
Let's create a story: one of the people brought a handful of shells (mussels) to the fire and sat, picked and ate, and accordingly scattered the fragments near the place where he ate, since he liked it, he repeated the meal several more times.
This is what we get from this story: we need to randomly select the zones where he sat and ate, and scatter models of shells in these zones, with random rotation angles and scales.
The task is clear, let's get started.
To place objects on the stage, you can use a script written in Python; for this we must have the Python support plugin enabled.
To spawn objects, our team wrote the following script:
import unreal
#Сама функция
def place_static_mesh_with_material(static_mesh_path, material_path, location, rotation, scale):
static_mesh = unreal.EditorAssetLibrary.load_asset(static_mesh_path)
actor = unreal.EditorLevelLibrary.spawn_actor_from_class(unreal.StaticMeshActor, location, rotation)
mesh_component = actor.get_components_by_class(unreal.StaticMeshComponent)[0]
mesh_component.set_static_mesh(static_mesh)
material = unreal.EditorAssetLibrary.load_asset(material_path)
mesh_component.set_material(0, material)
mesh_component.set_relative_scale3d(scale)
#Описываем меши и материалы, которые будем использовать в скрипте
sm_shell1 = "/Game/Dvuglaz/Meshes/Props/Shells/SM_Shell.SM_Shell";
mat_shell1_1 = "/Game/Dvuglaz/Meshes/Props/Shells/Materials/MI_Shell.MI_Shell"
#Тут добавляем фунциии
place_static_mesh_with_material(sm_shell1, mat_shell1_1, unreal.Vector(-1083,-2219,297), unreal.Rotator(58,60,122), unreal.Vector(1.07212,1.07212,1.07212))
We save it to a file and execute the script.
Assuming we have three bags and several materials, we need to create links to these objects. Let's create three bags and materials, for the second bag we will define 7 materials.
sm_shell1 = "/Game/Dvuglaz/Meshes/Props/Shells/SM_Shell.SM_Shell";
sm_shell2 = "/Game/Dvuglaz/Meshes/Props/Shells/SM_Shell_Andontia_full.SM_Shell_Andontia_full";
sm_shell3 = "/Game/Dvuglaz/Meshes/Props/Shells/SM_Shell_Andontia_half.SM_Shell_Andontia_half";
mat_shell1_1 = "/Game/Dvuglaz/Meshes/Props/Shells/Materials/MI_Shell.MI_Shell"
mat_shell2_1 = "/Game/Dvuglaz/Meshes/Props/Shells/Materials/MI_Shell_Andontia2_1.MI_Shell_Andontia2_1"
mat_shell2_2 = "/Game/Dvuglaz/Meshes/Props/Shells/Materials/MI_Shell_Andontia2_2.MI_Shell_Andontia2_2"
mat_shell2_3 = "/Game/Dvuglaz/Meshes/Props/Shells/Materials/MI_Shell_Andontia2_3.MI_Shell_Andontia2_3"
mat_shell2_4 = "/Game/Dvuglaz/Meshes/Props/Shells/Materials/MI_Shell_Andontia2_4.MI_Shell_Andontia2_4"
mat_shell2_5 = "/Game/Dvuglaz/Meshes/Props/Shells/Materials/MI_Shell_Andontia2_5.MI_Shell_Andontia2_5"
mat_shell2_6 = "/Game/Dvuglaz/Meshes/Props/Shells/Materials/MI_Shell_Andontia2_6.MI_Shell_Andontia2_6"
mat_shell2_7 = "/Game/Dvuglaz/Meshes/Props/Shells/Materials/MI_Shell_Andontia2_7.MI_Shell_Andontia2_7"
mat_shell3_1 = "/Game/Dvuglaz/Meshes/Props/Shells/Materials/MI_Shell_Andontia2_Full.MI_Shell_Andontia2_Full"
All that remains is to create a list of the place_static_mesh_with_material functions, the parameters of which will indicate coordinates, rotation and scale. This is where we need a generator!
To create a generator, we will carry out preparatory work. First of all, we need to upload the entire scene to FBX.
The output will be a large file, in our case it is a 214 MB file. Unreal unloads everything, not only models but also collision files, lights and other elements.
Before arranging the shells, let's prepare the soil (in every sense of the word). It’s more convenient for me to do such processes in Maya, since a list of all elements is presented here.
First of all, you can safely delete all collisions, they have the prefix UCX_, UBX_, USP_ and UCP_), using the selection mechanism by name, we will select all collisions.
After all collisions have been selected, delete them.
After this, you can select the required zone in which we will work, and remove the remaining objects from the scene. Using the selection tool, we remove all unnecessary things (people, branches, bones), leaving only the relief. It is convenient to use inversion (we select what we want to leave using a lasso and invert the selection).
We export the resulting objects to a new FBX and open them in Houdini.
Let's open this file in Houdini and begin implementing the previously described logic for scattering shells.
First of all, let's turn all the objects into a relief surface, to do this we'll create a Box around the bag, then we'll delete everything except the top cover of this box, we'll create a mesh on the resulting plane and using the Ray node we'll project each point down, then we'll delete everything that was not involved. in projection.
The video shows all the node parameters.
The next step is to place on the resulting relief the zones where our man was sitting.
Well, with the last action we will create 30 points on the resulting zones.
When the points are complete, we will place parameters in them that can be used to transfer them back to Unreal Engine. To do this, add an Attribute Wrangle node. In this node we will write code that will generate object spawning functions.
In this code, we create three vectors Loc, Rot, Scl with random values (position, rotation and scale), we also select one of the three bags and if it is the second one (sm_shell2), then we assign a random material to it.
s@Loc = "unreal.Vector("+itoa(@P.x)+","+itoa(@P.z)+","+itoa(@P.y)+")";
vector rot;
rot.x = fit01((rand(@ptnum+87)),-180,180);
rot.y = fit01((rand(@ptnum+823)),-180,180);
rot.z = fit01((rand(@ptnum+586)),-180,180);
s@Rot = "unreal.Rotator("+itoa(rot.x)+","+itoa(rot.y)+","+itoa(rot.z)+")";
float scl = fit01((rand(@ptnum+652)),0.7,1.5);
s@Scl = "unreal.Vector("+sprintf("%g,%g,%g",scl,scl,scl)+")";
int type = rint(fit01(rand(@ptnum+467),0,3));
if (type==0){
s@Name = "sm_shell1";
s@Mat = "mat_shell1_1";
}
else{
if (type == 1){
s@Name = "sm_shell2";
int imat = rint(fit01(rand(@ptnum+357),1,7));
s@Mat = "mat_shell2_"+itoa(imat);
}
else{
s@Name = "sm_shell3";
s@Mat = "mat_shell3_1";
}
}
s@Code="place_static_mesh_with_material("+s@Name+", "+s@Mat+", "+s@Loc+", "+s@Rot+", "+s@Scl+") ";
The result of the script will be to create the Loc, Rot, Scl, Name and Mat parameters and also based on these parameters the Code parameter will be created.
Let's copy the values of the Code parameter and add them to the bottom of the script.
import unreal
#Сама функция
def place_static_mesh_with_material(static_mesh_path, material_path, location, rotation, scale):
static_mesh = unreal.EditorAssetLibrary.load_asset(static_mesh_path)
actor = unreal.EditorLevelLibrary.spawn_actor_from_class(unreal.StaticMeshActor, location, rotation)
mesh_component = actor.get_components_by_class(unreal.StaticMeshComponent)[0]
mesh_component.set_static_mesh(static_mesh)
material = unreal.EditorAssetLibrary.load_asset(material_path)
mesh_component.set_material(0, material)
mesh_component.set_relative_scale3d(scale)
# Описываем меши и материалы, которые будем использовать в скрипте
sm_shell1 = "/Game/Dvuglaz/Meshes/Props/Shells/SM_Shell.SM_Shell";
sm_shell2 = "/Game/Dvuglaz/Meshes/Props/Shells/SM_Shell_Andontia_full.SM_Shell_Andontia_full";
sm_shell3 = "/Game/Dvuglaz/Meshes/Props/Shells/SM_Shell_Andontia_half.SM_Shell_Andontia_half";
mat_shell1_1 = "/Game/Dvuglaz/Meshes/Props/Shells/Materials/MI_Shell.MI_Shell"
mat_shell2_1 = "/Game/Dvuglaz/Meshes/Props/Shells/Materials/MI_Shell_Andontia2_1.MI_Shell_Andontia2_1"
mat_shell2_2 = "/Game/Dvuglaz/Meshes/Props/Shells/Materials/MI_Shell_Andontia2_2.MI_Shell_Andontia2_2"
mat_shell2_3 = "/Game/Dvuglaz/Meshes/Props/Shells/Materials/MI_Shell_Andontia2_3.MI_Shell_Andontia2_3"
mat_shell2_4 = "/Game/Dvuglaz/Meshes/Props/Shells/Materials/MI_Shell_Andontia2_4.MI_Shell_Andontia2_4"
mat_shell2_5 = "/Game/Dvuglaz/Meshes/Props/Shells/Materials/MI_Shell_Andontia2_5.MI_Shell_Andontia2_5"
mat_shell2_6 = "/Game/Dvuglaz/Meshes/Props/Shells/Materials/MI_Shell_Andontia2_6.MI_Shell_Andontia2_6"
mat_shell2_7 = "/Game/Dvuglaz/Meshes/Props/Shells/Materials/MI_Shell_Andontia2_7.MI_Shell_Andontia2_7"
mat_shell3_1 = "/Game/Dvuglaz/Meshes/Props/Shells/Materials/MI_Shell_Andontia2_Full.MI_Shell_Andontia2_Full"
#Тут добавляем фунциии из генератора
place_static_mesh_with_material(sm_shell2, mat_shell2_3, unreal.Vector(-574,-2452,315), unreal.Rotator(-83,15,93), unreal.Vector(1.22731,1.22731,1.22731))
place_static_mesh_with_material(sm_shell3, mat_shell3_1, unreal.Vector(-190,-2225,303), unreal.Rotator(55,111,-147), unreal.Vector(0.709104,0.709104,0.709104))
place_static_mesh_with_material(sm_shell1, mat_shell1_1, unreal.Vector(-1083,-2219,297), unreal.Rotator(58,60,122), unreal.Vector(1.07212,1.07212,1.07212))
# ......
place_static_mesh_with_material(sm_shell3, mat_shell2_6, unreal.Vector(-617,-2511,319), unreal.Rotator(144,169,-116), unreal.Vector(0.929268,0.929268,0.929268))
place_static_mesh_with_material(sm_shell1, mat_shell1_1, unreal.Vector(-985,-1881,283), unreal.Rotator(-60,41,155), unreal.Vector(1.41147,1.41147,1.41147))
Now we have the code for spawning 30 elements, all that remains is to execute the script in the project.
Scattered shells will appear on our stage. If desired, you can correct some objects manually.
If you have not practiced such actions before, then all these steps may seem difficult to you, it seems that it is faster to place it manually, but if you have prepared the terrain once, written a script, now you can place any objects on the map (bones, stones, branches), everything What you need to do for this is to change the parameters of the Scatter nodes in Houdini. This method has a slight advantage even over the Houdini Engine – we build the surface only from objects that represent the terrain, removing unnecessary elements such as branches, large stones, skins and others.
Thank you for your attention.
Denis Berezhenko.