Out of the gate, it’s important to understand that the Python exposure wraps BluePrint, not the C++ SDK, if something isn’t exposed to blueprint, you cannot access it with Python.
A Note on Naming
There are notable differences that should be pointed out regarding the python exposure and the UE4 programming documentation. UE4 class names have dropped the prefix, an FRotator would be unreal.Rotator, FVector, unreal.Vector, etc. Functions on those classes have been converted to PEP8 style names that differ from their C++ counterparts, for example Actor.GetActorBounds is Actor.get_actor_bounds, just FYI.
Detective Work
If you have ever used an initial alpha or beta embedded Python implementation in an application, like Maya or MotionBuilder, (if you’re old like me!) –you know the drill. There’s no documentation yet, but you can use existing blueprint/C++ documentation and native python built-in functions like dir, help, and type to try and navigate.
DIR()
Python’s dir command returns a ‘directory’ of all attributes of an object. This is extremely useful. Let’s query the attributes of a static mesh actor:
This returns a massive list, if you print each item, you’ll see something like this:
...
get_remote_role
get_squared_distance_to
get_tickable_when_paused
get_typed_outer
get_velocity
get_vertical_distance_to
get_world
has_authority
hidden
initial_life_span
instigator
is_actor_being_destroyed
... |
...
get_remote_role
get_squared_distance_to
get_tickable_when_paused
get_typed_outer
get_velocity
get_vertical_distance_to
get_world
has_authority
hidden
initial_life_span
instigator
is_actor_being_destroyed
...
HELP()
Jamie Dale has put a lot of work into making help work as expected, through dir we saw the actor has a get_velocity property, let’s ask help abut it:
Help returns not only info about get_velocity, but tells us what a function takes and returns:
Help on built-in function get_velocity:
get_velocity(...)
x.get_velocity() -> Vector -- Returns velocity (in cm/s (Unreal Units/second) of the rootcomponent if it is either using physics or has an associated MovementComponent
param: return_value (Vector) |
Help on built-in function get_velocity:
get_velocity(...)
x.get_velocity() -> Vector -- Returns velocity (in cm/s (Unreal Units/second) of the rootcomponent if it is either using physics or has an associated MovementComponent
param: return_value (Vector)
TYPE()
Type just returns the type of object, but it’s useful when you’re not sure exactly what something is. Let’s ask what type our actor is:
print type(actor)
#<type 'StaticMeshActor'> |
print type(actor)
#<type 'StaticMeshActor'>
Now let’s ask it about the get_velocity above, we know it’s a function because I just used it, but as an example:
print type(actor.get_velocity)
#<type 'builtin_function_or_method_with_closure'> |
print type(actor.get_velocity)
#<type 'builtin_function_or_method_with_closure'>
With these core concepts, you can really begin stumbling around and making useful scripts and tools!
In Practice
Let’s query all StaticMeshes in a level (the default UE4 map). First thing is to load the level:
import unreal
level_path = '/Game/StarterContent/Maps/Minimal_Default'
level = unreal.find_asset(None, name=level_path) |
import unreal
level_path = '/Game/StarterContent/Maps/Minimal_Default'
level = unreal.find_asset(None, name=level_path)
Next we use get_all_actors_of_class to query all the static meshes:
static_meshes = unreal.GameplayStatics.get_all_actors_of_class(level, unreal.StaticMeshActor)
print static_meshes
#returns: [StaticMeshActor'"/Game/StarterContent/Maps/Minimal_Default.Minimal_Default:PersistentLevel.Table"', StaticMeshActor'"/Game/StarterContent/Maps/Minimal_Default.Minimal_Default:PersistentLevel.Statue"', StaticMeshActor'"/Game/StarterContent/Maps/Minimal_Default.Minimal_Default:PersistentLevel.Floor_14"', StaticMeshActor'"/Game/StarterContent/Maps/Minimal_Default.Minimal_Default:PersistentLevel.Floor"', StaticMeshActor'"/Game/StarterContent/Maps/Minimal_Default.Minimal_Default:PersistentLevel.Chair_15"', StaticMeshActor'"/Game/StarterContent/Maps/Minimal_Default.Minimal_Default:PersistentLevel.Chair"'] |
static_meshes = unreal.GameplayStatics.get_all_actors_of_class(level, unreal.StaticMeshActor)
print static_meshes
#returns: [StaticMeshActor'"/Game/StarterContent/Maps/Minimal_Default.Minimal_Default:PersistentLevel.Table"', StaticMeshActor'"/Game/StarterContent/Maps/Minimal_Default.Minimal_Default:PersistentLevel.Statue"', StaticMeshActor'"/Game/StarterContent/Maps/Minimal_Default.Minimal_Default:PersistentLevel.Floor_14"', StaticMeshActor'"/Game/StarterContent/Maps/Minimal_Default.Minimal_Default:PersistentLevel.Floor"', StaticMeshActor'"/Game/StarterContent/Maps/Minimal_Default.Minimal_Default:PersistentLevel.Chair_15"', StaticMeshActor'"/Game/StarterContent/Maps/Minimal_Default.Minimal_Default:PersistentLevel.Chair"']
That’s returned a long list of class objects, let’s make that more readable by calling the get_name function of each class with a simple list comprehensions:
print [mesh.get_name() for mesh in static_meshes]
#returns: ['Table', 'Statue', 'Floor_14', 'Floor', 'Chair_15', 'Chair'] |
print [mesh.get_name() for mesh in static_meshes]
#returns: ['Table', 'Statue', 'Floor_14', 'Floor', 'Chair_15', 'Chair']
How did I know there was a get_name functions? I saw it when I ran directory on the static mesh actor: dir(actor).
Let’s move the table:
if mesh.get_name() == 'Table':
xform = actor.get_actor_transform()
location = actor.get_actor_location()
print xform
print location
#returns:
#<Struct 'Transform' (0x0000000095E5D3C0) {rotation: {x: 0.000000, y: 0.000000, z: 0.000000, w: 1.000000}, translation: {x: -180.000000, y: 0.000000, z: 32.000000}, scale3d: {x: 1.000000, y: 1.000000, z: 1.000000}}>
#<Struct 'Vector' (0x00000000B8ACE5C0) {x: -180.000000, y: 0.000000, z: 32.000000}> |
if mesh.get_name() == 'Table':
xform = actor.get_actor_transform()
location = actor.get_actor_location()
print xform
print location
#returns:
#<Struct 'Transform' (0x0000000095E5D3C0) {rotation: {x: 0.000000, y: 0.000000, z: 0.000000, w: 1.000000}, translation: {x: -180.000000, y: 0.000000, z: 32.000000}, scale3d: {x: 1.000000, y: 1.000000, z: 1.000000}}>
#<Struct 'Vector' (0x00000000B8ACE5C0) {x: -180.000000, y: 0.000000, z: 32.000000}>
Let’s change the location and use set_actor_location to set a new location:
location.z += 28 #location = 60
actor.set_actor_location(location)
#returns:
#Traceback (most recent call last):
#TypeError: Required argument 'sweep' (pos 2) not found |
location.z += 28 #location = 60
actor.set_actor_location(location)
#returns:
#Traceback (most recent call last):
#TypeError: Required argument 'sweep' (pos 2) not found
Wow, so that fails, we need to find out why, let’s ask help:
print help(actor.set_actor_location) |
print help(actor.set_actor_location)
Help on built-in function set_actor_location:
set_actor_location(...)
x.set_actor_location(new_location, sweep, teleport) -> HitResult or None -- Move the Actor to the specified location.
param: new_location (Vector) -- The new location to move the Actor to.
param: sweep (bool) -- Whether we sweep to the destination location, triggering overlaps along the way and stopping short of the target if blocked by something. Only the root component is swept and checked for blocking collision, child components move without sweeping. If collision is off, this has no effect.
param: teleport (bool) -- Whether we teleport the physics state (if physics collision is enabled for this object). If true, physics velocity for this object is unchanged (so ragdoll parts are not affected by change in location). If false, physics velocity is updated based on the change in position (affecting ragdoll parts). If CCD is on and not teleporting, this will affect objects along the entire swept volume.
param: sweep_hit_result (HitResult) -- The hit result from the move if swept.
param: return_value (bool)
return: Whether the location was successfully set (if not swept), or whether movement occurred at all (if swept). |
Help on built-in function set_actor_location:
set_actor_location(...)
x.set_actor_location(new_location, sweep, teleport) -> HitResult or None -- Move the Actor to the specified location.
param: new_location (Vector) -- The new location to move the Actor to.
param: sweep (bool) -- Whether we sweep to the destination location, triggering overlaps along the way and stopping short of the target if blocked by something. Only the root component is swept and checked for blocking collision, child components move without sweeping. If collision is off, this has no effect.
param: teleport (bool) -- Whether we teleport the physics state (if physics collision is enabled for this object). If true, physics velocity for this object is unchanged (so ragdoll parts are not affected by change in location). If false, physics velocity is updated based on the change in position (affecting ragdoll parts). If CCD is on and not teleporting, this will affect objects along the entire swept volume.
param: sweep_hit_result (HitResult) -- The hit result from the move if swept.
param: return_value (bool)
return: Whether the location was successfully set (if not swept), or whether movement occurred at all (if swept).
Ok, awesome, so I’ll tell it I don’t want it to sweep, and I do want it to teleport:
location.z += 28 #location = 60
actor.set_actor_location(location. False, True) |
location.z += 28 #location = 60
actor.set_actor_location(location. False, True)
You should see the table move!
NOTE: Careful, in order for your Python changes to be added to the undo stack, you need to use unreal.ScopedEditorTransaction.
I hope this was helpful, I can post some other examples later.