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.

Work location

Work location

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.

Plugins

Plugins

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.

File → Execute Python Script...

File → Execute Python 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.

File → Export All

File → Export All

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.

Location file

Location file

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.

Outliner with all objects

Outliner with all objects

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.

Select by name

Select by name

All collisions are selected

All collisions are selected

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).

Inversion is a convenient tool

Inversion is a convenient tool

All that's left of the stage

All that's left of the stage

We export the resulting objects to a new FBX and open them in Houdini.

Export to FBX

Export to FBX

Let's open this file in Houdini and begin implementing the previously described logic for scattering shells.

Open the file with the File node

Open the file with the File node

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.

Model-based surface creation

Model-based surface creation

The video shows all the node parameters.

The next step is to place on the resulting relief the zones where our man was sitting.

Placing areas where items will be scattered

Placing areas where items will be scattered

Well, with the last action we will create 30 points on the resulting zones.

Scatter node parameters

Scatter node parameters

Points on zones

Points on 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.

Parameter table

Parameter table

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.

File → Execute Python Script...

File → Execute Python Script…

Scattered shells will appear on our stage. If desired, you can correct some objects manually.

Seashells on stage

Seashells on stage

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.

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *