Getting started with Python in UE4

Learn how to get started writing and running Python scripts for UE4 as an alternative or addition to editor scripting with blueprints.

Python was first introduced in Unreal back in version 4.19, and has been given a lot of much needed love with every major engine release since, especially for 4.24. For this tutorial I'll try to be as version agnostic as possible, but I'll note a required version if applicable.


This will be a long post, but I hope for it to cover all the basics you need to get started with Python in Unreal and to act as a sort of bible for beginners. It is the result of watching live streams, reading blogs and scouring the forums for the topics people have the most questions about.


Note that python is only meant for editor scripting, and running python code in play-mode is not possible.



Overview

  1. Enabling the plugin
  2. Python in the editor
  3. Adding your own scripts
  4. Autocomplete in VS Code
  5. Importing assets
  6. Managing blueprints



1 - Enabling the plugin

The python plugin comes with the engine, but is not enabled by default. Go to your plugin settings and under the Scripting category you'll find Python Editor Script Plugin and Editor Scripting Utilities. Enable them both. The latter is not required for Python to work, but will open up more possibilities as to what we can do. Restart the editor.


There is also the Sequencer Scripting plugin specifically for interfacing with sequencer, but I won't be using that in this tutorial.






2 - Python in the editor

If you now open up the output log through Window -> Developer Tools -> Output Log. You'll see that you can now click the dropdown where it says Cmd and switch it for Python, and now you can write and execute python code using the built in Python 2.7 interpreter.


The one python module UE4 introduces is the unreal module, and it is through that we will interface with the editor. It is an in-memory module generated when the editor starts and so it doesn't actually exists on disk. This is so that you'll have access to your own classes defined in C++, just like with blueprints.


To get started interacting with some assets, make a new material and name it whatever you like. You can now right click it and Copy Reference. Back in the output log, with Python selected we can now use unreal.load_asset(path) to grab this material, pasting the reference into the function as the path to the asset. Note that the reference starts with the object type, so delete this bit, leaving only the game path. Also note that these reference paths start with /Game/ and not /Content/ as one might expect.


# loads an asset based on a project path
mat = unreal.load_asset('/Game/NewMaterial.NewMaterial')
# print this object
print(mat)


This will print:

<Object '/Game/NewMaterial.NewMaterial' (0x00000234721AD0C0) Class 'Material'>

but with a different address in your case.


This shows that the load_asset() function returns an object and that it is of class Material. The easiest way to see more of what can be done is to list the object attributes using the python built in function dir(). Let's do that, wrapping it up in a little for loop for better formatting.

# loop over all attributes for the object and print it
for attr in dir(mat):
  print(attr)


Note that usually one would use Pythons prettyprint for this sort of thing, but it doesn't play nice with the editor so the for loop will have to do.


An alternative to this is to use Pythons help() function which print a lot of good documentation for the object based on its C++ base.

help(mat)


This same documentation can also be found here.



Making changes to assets

The most common way of interfacing with assets is using the get_editor_property and set_editor_property. You can find all these properties using help() or in the documentation. For example, we can now change the material to be two sided, and or read back if it is currently two sided with the following code:


# use get_editor_property to read back a property
print('Is the material two sided?')
print(mat.get_editor_property('two_sided'))

# make a change to the editor property of the material
mat.set_editor_property('two_sided', True)

print('Is the material two sided now?')
print(mat.get_editor_property('two_sided'))


As one can imagine, this can be very powerful for batch processing of assets or sanity checkers. You'll notice this will make changes but not save the asset, just marking it "dirty". To also save the asset we can use:

unreal.EditorAssetLibrary.save_asset(path)

Because this function expects a path and not an object we will need to use get_path_name() on our material as we pass it in.


# Saves the object using the path name
unreal.EditorAssetLibrary.save_asset(mat.get_path_name())

# There is also the save_directory function. For example this will save all dirty assets in the project
urneal.EditorAssetLibrary.save_directory ('/Game', recursive=True)




3 - Adding your own scripts

Now you probably want to put together some more complex scripts and run them directly. There are a couple of folders UE4 will look for scripts, most notably it will look for a Python folder inside all engine, project and plugin content folders. If you put a python file, I.e. a python module, in one of these folders you will be able to import it in the editor, or in other scripts.


Do note that the editor will only look for files if the Python folder exists, and will only register the folder if it existed at editor launch, so if it is the first time you add scripts, you'll need to restart the editor.


Add a Python folder inside your project Content folder and in there create a file called my_module.py and restart the editor. You'll now be able to edit this file in your IDE of choice, mine being Visual Studio Code. I'm gonna add a function definition and output some log strings.


import unreal

def my_function():
    unreal.log('This is an Unreal log')
    unreal.log_error('This is an Unreal error')


You'll now be able to go back to the editor and import the module and run this function with this


import my_module
my_module.my_function()


There is also a third type of Unreal log, and it's called log_warning. If we add that to the end we'll have this


import unreal

def my_function():
    unreal.log('This is an Unreal log')
    unreal.log_error('This is an Unreal error')
    unreal.log_warning('This is an unreal warning')


However, if you run the import bit above again, it won't display our warning, it'll run the same script as before. This is because it is already considered imported and Python won't import it again unless it's explicitly told to using the reload() function.


So if we run this, we'll see our warning added.


reload(my_module)
my_module.my_function()


Running scripts on editor startup

The editor will also look for a specific Python module named init_unreal.py. If this is present in any of the Python folders it will run that script during editor initialization. During init you also have more options when it comes to interfacing with and overriding other classes, which I'll get into more in later tutorials.




4 - Autocomplete in VS Code

One thing with the unreal module being transient which can be annoying is the lack of autocomplete in IDEs. There is however a workaround for this. In your Project Settings -> Plugins -> Python you can enable developer mode.


Unreal will now generate a python file named unreal.py, in the Project/Intermediate/PythonStub. This will represent the current state of the unreal module for the specific project, with only class and function declerations and no definitions.


In VS Code you will now be able to point to the folder of this file with a setting called python.autoComplete.extraPaths. Do this by opening the settings file in JSON format and add this, adjusting the path to your project.


"python.autoComplete.extraPaths": ["C:/path/to/PythonStub"]


I imagine a similar setting can be found in other IDEs.




5 - Importing assets

The way one imports assets might feel a bit convoluted at first, but it is a very strong system can give you access to all the same options as the GUI. It all revolves around creating tasks, which are just classes with import option properties on them. The two unreal classes of importance with importing meshes are unreal.AssetImportTask() and unreal.FbxImportUI(). That is, if you're importing an FBX.


You don't need all of these settings, but I've left them in there to show you some options.


import unreal


# Path on disk to the asset you want to import
mesh_path = 'C:/path/to/mesh.fbx'



# helper function for easy task creation, like a template
def build_mesh_import_task(filename, destination_path, destination_name=''):


    # the task, and the additional fbx options, since it is a mesh
    task = unreal.AssetImportTask()
    fbx_options = unreal.FbxImportUI()


    # basic task properties
    task.set_editor_property('automated', True)
    task.set_editor_property('filename', filename)
    task.set_editor_property('destination_path', destination_path)
    # if destination_name is '' it will use the on-disk filename
    task.set_editor_property('destination_name', destination_name)
    task.set_editor_property('replace_existing', True)
    task.set_editor_property('save', True)
    task.set_editor_property('options', fbx_options)


    # additional options specifically for meshes
    fbx_options.set_editor_property('import_mesh', True)
    fbx_options.set_editor_property('import_textures', False)
    fbx_options.set_editor_property('import_as_skeletal', False)
    fbx_options.set_editor_property('import_materials', False)


    # settings specifically for static meshes
    fbx_options.static_mesh_import_data.set_editor_property('combine_meshes', True)
    fbx_options.static_mesh_import_data.set_editor_property('generate_lightmap_u_vs', False)
    return task



def import_assets():
    # construct the task, passing the path on disk and the location to store it. 
    mesh_import_task = build_mesh_import_task(mesh_path, '/Game/Meshes')


    # using the very handy AssetToolsHelpers to do the work.
    # Note it takes a list, and so many imports can be batched
    unreal.AssetToolsHelpers.get_asset_tools().import_asset_tasks([mesh_import_task])


There are other ways of importing meshes like using unreal.AssetTools which have some more settings and functions, like being able to completely disable all popups/warnings, but it is also more complex to set up.


Note that 'simple' assets like textures and audio will import fine without the additional options.




6 - Managing blueprints

If you want to get or set variables on blueprints, you'll have to go through a couple more steps than with a regular asset, because if you load a blueprint object you will get the BlueprintGeneratedClass which will not actually affect the underlying blueprint, so to do that you must make changes to default object version.


Make a new blueprint, put some variable on it and copy its reference. When pasting it in unreal.load_object() note that we add "_C" to the end.


import unreal

# get the generated class, using a different load method than assets
blueprint_generated = unreal.load_object(None, "/Game/NewBlueprint.NewBlueprint_C")

# from that, get the class default object ( the actual template for the blueprint )
blueprint_class_default = unreal.get_default_object(blueprint_generated)

# set or get properties
blueprint_class_default.set_editor_property("MyFloatValue", 42)
blueprint_class_default.get_editor_property("MyInt")




That's it! Hopefully I'll do some more of these, especially on some more advanced topics.


Sources:

Livestream Python in Unreal Engine | Inside Unreal

Forum UE4 Editor Scripting forum section