Agent skill

Level Actors

by kevinpbuckley in kevinpbuckley/VibeUE

List, find, place, move, rotate, and modify actors in the current level (ActorService + editor subsystems).

MITAuto-check passedGame Development

Install Level Actors

skills CLI
$ npx skills add kevinpbuckley/VibeUE --skill level-actors -a claude-code

Project install by default; add -g for ~/.claude/skills/.

GitHub CLI
$ gh skill install kevinpbuckley/VibeUE level-actors --agent claude-code

Project scope by default; add --scope user for a personal install. Needs GitHub CLI 2.90.0 or later (public preview).

Manual copy
$ git clone --depth 1 https://github.com/kevinpbuckley/VibeUE.git skills-src && mkdir -p .claude/skills && cp -r skills-src/Content/Skills/level-actors .claude/skills/level-actors && rm -rf skills-src

Use ~/.claude/skills/ instead of .claude/skills for a personal install. The folder must contain SKILL.md.

Claude Code skills documentation · loads skills from .claude/skills/

Facts

Skill name
level-actors
GitHub stars
730
Token cost
~6.4k tokens
SKILL.md length
1,510 words
Files
2 (incl. scripts)
Skills in repo
39
Repo updated
First seen
Licence
MIT

At a glance

List, find, place, move, rotate, and modify actors in the current level (ActorService + editor subsystems).

  • Works in 3 steps: HitResult fields cannot be read as… → set_actor_location(loc, sweep=True, ...)… → The definitive checks: compare the…
  • The user asks to find/list actors in the level
  • SKILL.md covers Critical Rules, Workflows, Re-run a placed actor's… and Sample scripts (run via…
  • Rotate an actor

What it does

Level Actors is an agent skill from kevinpbuckley/VibeUE. List, find, place, move, rotate, and modify actors in the current level (ActorService + editor subsystems). Use when the user asks to find/list actors in the level, move or rotate an actor, set an actor's properties/folder/transform, or spawn/delete level actors.

Its SKILL.md is about 6.4k tokens, which your agent loads only when the skill is triggered. The skill folder holds 2 other files, including scripts.

It sits in Game Development, covering Game development. It works with Python. The repository describes itself as: Unreal Engine Vibe Coding tool. The licence is MIT.

When your agent uses it

  • The user asks to find/list actors in the level
  • Rotate an actor
  • Set an actors properties/folder/transform
  • Spawn/delete level actors

Example prompts

  • “/level-actors”

Requirements

  • Python 3

Workflow steps

3 steps, taken from the first numbered list in SKILL.md.

  1. HitResult fields cannot be read as attributes — hit.blocking_hit / hit.bBlockingHit raise "property is protected". Use hit.to_tuple()[0]…
  2. set_actor_location(loc, sweep=True, ...) does NOT detect collisions in the editor world — it reports no hit even when moving straight…
  3. The definitive checks: compare the actor's final position to the target, and/or trace the path explicitly with…

What it can do on your machine

Read from SKILL.md and the folder at commit dc051be. It shows what the files ask for, not the result of running them.

  • Tool permissions

    Pre-approves nothing: there is no allowed-tools line, so your agent's usual permission prompts apply.

    From allowed-tools in the SKILL.md frontmatter.

  • Runs code

    Ships 1 file in scripts/, which the agent can run.

    From the folder's file list and the shell code blocks in SKILL.md.

  • Network

    No URLs in SKILL.md.

    From URLs in SKILL.md, links to its own repository left out.

  • Credentials

    Names no API keys, tokens, secrets or passwords.

    From names ending in _API_KEY, _TOKEN, _SECRET, _KEY or _PASSWORD in SKILL.md.

Context cost

Level Actors loads about 6.4k tokens when it runs. Until then it costs about 69 tokens; SKILL.md has 1,510 words of instructions outside code blocks.

Always · name and description, kept in context so the agent knows when to use it
~69
When it runs · the whole SKILL.md, loaded when a task matches
~6.4k

Estimates: characters ÷ 4, the usual rule of thumb; real counts depend on the model's tokenizer. Scripts and assets cost tokens only if the agent reads them.

Safety

Auto-check passed

The automated check found no risky patterns in SKILL.md.

Automated static check — not a guarantee. Review scripts before installing. It scans the text of SKILL.md for risky patterns (piping downloads into a shell, reading credential files, hidden Unicode, destructive commands); the scripts in this folder are not scanned.

SKILL.md

The full file from kevinpbuckley/VibeUE at commit dc051be, republished under its MIT licence (© kevinpbuckley). 1,510 words, ~6,386 tokens.

Download SKILL.mdSave it as .claude/skills/level-actors/SKILL.md (or your agent's skills folder). This skill also uses 1 other file; get the full folder from GitHub.
name
level-actors
description
List, find, place, move, rotate, and modify actors in the current level (ActorService + editor subsystems). Use when the user asks to find/list actors in the level, move or rotate an actor, set an actor's properties/folder/transform, or spawn/delete level actors.
display_name
Level Actors & Editor Subsystems
vibeue_classes
ActorService
unreal_classes
EditorActorSubsystem, LevelEditorSubsystem

🧠 Brains complement: IF an unreal-engine-skills-manager tool (external MCP) exists in this session, call it with {action: "load", skill: "actors-and-components"} for UE domain knowledge on this topic — correct APIs, architecture, best practices — and treat it as the rubric for any review / "best practices" question. If no such tool is available (e.g. running under Claude Code or Codex without that MCP), skip this line entirely and proceed with this skill alone — do NOT attempt the call.

Level Actors Skill

🔀 Engine owns the basics now. In the Unreal 5.8 consolidation, listing / finding / spawning / moving / transforming / selecting actors moved to Unreal's native toolsets — ActorTools, SceneTools, and EditorAppToolset (selection, camera, PIE, screenshots), reachable via call_tool. Equivalently you can drive unreal.EditorActorSubsystem / unreal.LevelEditorSubsystem directly from execute_python_code (shown throughout below). VibeUE's ActorService was trimmed to only the delta the engine doesn't provide: transform lock (set_actor_lock_location), absolute-transform flags, preserve-scale-ratio, viewport camera framing (get_actor_view_camera / calculate_actor_view / set_viewport_camera), and full property dumps (get_all_properties). ActorService.add_actor / set_property / get_property no longer exist — use the engine toolsets or EditorActorSubsystem for spawning and property edits.

Critical Rules

⚠️ unreal.Rotator(a, b, c) is (roll, pitch, yaw) — always pass by keyword

Python's FRotator constructor takes roll, pitch, yaw (X, Y, Z), not the C++ FRotator(Pitch, Yaw, Roll) order. Passing a yaw positionally lands it in pitch, and nothing warns you — the call succeeds and the actor is simply wrong:

python
# WRONG - pitches the actor onto its nose; yaw stays 0
actor.set_actor_rotation(unreal.Rotator(0.0, yaw, 0.0), False)

# CORRECT
actor.set_actor_rotation(unreal.Rotator(roll=0.0, pitch=0.0, yaw=yaw), False)

Observed failures from this exact mistake: a row of enemies rolled 180° and pitched instead of turned to face the player, a spawned sofa upside down, and a DirectionalLight aimed at the sky so every viewport capture came back black. It applies equally to spawn_actor_from_class(cls, location, rotation) and spawn_actor_from_object.

Verify, don't assume — read it back (r = actor.get_actor_rotation(); r.roll/r.pitch/r.yaw) or check get_actor_bounds: a mesh that should sit on the floor and reports a negative Z range got flipped. A "look at this point" camera or actor rotation is:

python
d = target - location
rot = unreal.Rotator(roll=0.0,
                     pitch=math.degrees(math.atan2(d.z, math.hypot(d.x, d.y))),
                     yaw=math.degrees(math.atan2(d.y, d.x)))
� Creating a "Basic" Level Requires new_level_from_template, NOT new_level

When the user asks to create a new level (especially "Basic", "Default", or with a sky/floor), always use new_level_from_template:

python
subsys = unreal.get_editor_subsystem(unreal.LevelEditorSubsystem)
# ❌ WRONG — creates a completely empty level with no content
subsys.new_level("/Game/Maps/MyLevel")

# ✅ CORRECT — creates level with floor, sky, lighting, player start
subsys.new_level_from_template("/Game/Maps/MyLevel", "/Engine/Maps/Templates/Template_Default")

Template names: Template_Default = Basic, OpenWorld, TimeOfDay_Default, VR-Basic


�🚫 DEPRECATED: unreal.EditorLevelLibrary

DO NOT use unreal.EditorLevelLibrary. The entire Editor Scripting Utilities Plugin is deprecated in UE 5.7+. Use unreal.EditorActorSubsystem via unreal.get_editor_subsystem() for all level actor operations.

python
# ❌ DEPRECATED - DO NOT USE
all_actors = unreal.EditorLevelLibrary.get_all_level_actors()
unreal.EditorLevelLibrary.spawn_actor_from_class(...)

# ✅ CORRECT - Use EditorActorSubsystem
actor_subsys = unreal.get_editor_subsystem(unreal.EditorActorSubsystem)
all_actors = actor_subsys.get_all_level_actors()
actor_subsys.spawn_actor_from_class(...)
🚫 get_all_level_actors_of_class DOES NOT EXIST

EditorActorSubsystem has NO get_all_level_actors_of_class() method. Always use get_all_level_actors() + isinstance() filtering:

python
# ❌ WRONG - This method does not exist, causes AttributeError
actor_subsys.get_all_level_actors_of_class(unreal.Landscape)

# ✅ CORRECT - Filter manually
landscapes = [a for a in actor_subsys.get_all_level_actors() if isinstance(a, unreal.Landscape)]
lights = [a for a in actor_subsys.get_all_level_actors() if isinstance(a, unreal.PointLight)]
🚨 Verified AttributeError traps (UE 5.7 Python) — don't burn iterations rediscovering these
You might write❌ Why it fails✅ Use instead
comp.get_static_mesh()StaticMeshComponent has no get_static_meshcomp.get_editor_property("static_mesh") (read) / comp.set_static_mesh(mesh) (write)
actor.get_components()Actors have no get_components()actor.get_components_by_class(unreal.StaticMeshComponent) → array
dir_light.directional_light_componentno such attributedir_light.light_component (ALL unreal.Light actors use light_component)
comp.set_editor_property("cast_shadow", …)property is pluralcomp.set_editor_property("cast_shadows", False)
unreal.HorizontalTextAligmentUE misspells itunreal.HorizTextAligment / unreal.VerticalTextAligment (for TextRenderActor)

When unsure of a property/method name, call discover_python_class(class_name="unreal.X") ONCE up front rather than guessing repeatedly — its doc_string lists the real editor-property names.

Migration guide:

Deprecated (EditorLevelLibrary)Replacement (EditorActorSubsystem)
get_all_level_actors()actor_subsys.get_all_level_actors()
get_all_level_actors_of_class(cls)[a for a in actor_subsys.get_all_level_actors() if isinstance(a, cls)]
spawn_actor_from_class()actor_subsys.spawn_actor_from_class()
destroy_actor()actor_subsys.destroy_actor()
get_selected_level_actors()actor_subsys.get_selected_level_actors()
set_actor_selection_state()actor_subsys.set_actor_selection_state()
🚨 Spawn Static Mesh Actors via spawn_actor_from_class + comp.set_static_mesh()

Do not spawn a bare actor and then poke StaticMesh through a generic property setter — that historically produced actors with zero-extent bounds (invisible in the viewport even though the mesh reads back as set). (The old ActorService.add_actor / set_property helpers that caused this were removed in 5.8.)

ALWAYS use spawn_actor_from_class + comp.set_static_mesh():

python
import unreal

actor_subsys = unreal.get_editor_subsystem(unreal.EditorActorSubsystem)
mesh = unreal.load_asset("/Engine/BasicShapes/Cube")
material = unreal.load_asset("/Game/Materials/M_MyMat")

actor = actor_subsys.spawn_actor_from_class(
    unreal.StaticMeshActor,
    unreal.Vector(0, 0, 100),
    unreal.Rotator(0, 0, 0)
)
actor.set_actor_label("MyMeshActor")
actor.set_actor_scale3d(unreal.Vector(2, 2, 2))

comp = actor.static_mesh_component
comp.set_static_mesh(mesh)
comp.set_material(0, material)   # Optional

# Verify it has real bounds (not 0,0,0)
bounds = actor.get_actor_bounds(False)
print(f"Bounds extent: {bounds[1].x:.0f}, {bounds[1].y:.0f}, {bounds[1].z:.0f}")

If bounds extent is 0, 0, 0 after spawning, the mesh was not applied correctly.

⚠️ Get Subsystem Instance First
python
import unreal

actor_subsys = unreal.get_editor_subsystem(unreal.EditorActorSubsystem)
level_subsys = unreal.get_editor_subsystem(unreal.LevelEditorSubsystem)
⚠️ Spawning Actors - Use Class Object, NOT Path String
python
# For built-in classes
actor = subsys.spawn_actor_from_class(unreal.StaticMeshActor, location, rotation)

# For Blueprint classes - load first
bp_class = unreal.EditorAssetLibrary.load_blueprint_class("/Game/BP_Enemy")
actor = subsys.spawn_actor_from_class(bp_class, location, rotation)
⚠️ FVector and FRotator Objects Required
python
# CORRECT
location = unreal.Vector(100, 200, 0)
rotation = unreal.Rotator(pitch=-45)  # ALWAYS use keyword args for Rotator (see rule below)

# WRONG - strings don't work
location = "(X=100,Y=200,Z=0)"
🚨 Constructor Argument Order Traps: Rotator is (Roll, Pitch, Yaw), Color is BGRA

The Python constructors do NOT use the order you expect. Always use keyword arguments — never positional — for unreal.Rotator and unreal.Color:

python
# ❌ WRONG — Rotator positional order is (Roll, Pitch, Yaw), NOT (Pitch, Yaw, Roll)
unreal.Rotator(-45, 0, 0)        # sets ROLL=-45, not pitch!

# ✅ CORRECT — keyword args, unambiguous
unreal.Rotator(pitch=-45)                    # angle down 45°
unreal.Rotator(roll=0, pitch=-90, yaw=45)    # straight down, spun 45°

# ❌ WRONG — Color positional order is (B, G, R, A), NOT (R, G, B, A)
unreal.Color(255, 180, 100)      # comes out BLUE, not warm orange!

# ✅ CORRECT — keyword args, or use LinearColor (which IS r, g, b, a positional)
unreal.Color(r=255, g=180, b=100, a=255)
unreal.LinearColor(1.0, 0.7, 0.4, 1.0)       # LinearColor positional is normal RGBA

Pitch sign convention: negative pitch = downward (pitch=-90 points straight down). For lights/cameras use negative pitch to aim at the ground.

⚠️ Refresh Viewport After Changes
python
level_subsys = unreal.get_editor_subsystem(unreal.LevelEditorSubsystem)
level_subsys.editor_invalidate_viewports()

Workflows

Create Level from Template

🚨 Critical: new_level() creates a blank/empty level with NO content (no floor, sky, lights, or player start). To get the standard "Basic" level with default content, ALWAYS use new_level_from_template() with /Engine/Maps/Templates/Template_Default.

Available UE 5.7 templates in /Engine/Maps/Templates/:

Template pathDescription
/Engine/Maps/Templates/Template_DefaultBasic — floor, sky sphere, directional light, player start
/Engine/Maps/Templates/OpenWorldOpen world with large terrain
/Engine/Maps/Templates/TimeOfDay_DefaultTime-of-day sky setup
/Engine/Maps/Templates/VR-BasicVR template

Pattern: Create a new level from the Basic template

python
import unreal

subsys = unreal.get_editor_subsystem(unreal.LevelEditorSubsystem)
save_path  = "/Game/Maps/MyLevel"
template   = "/Engine/Maps/Templates/Template_Default"

# new_level_from_template: closes current level, creates from template, saves + loads
result = subsys.new_level_from_template(save_path, template)
print(f"Created: {result}")  # True on success

⚠️ Cannot delete the currently-loaded level. If the target path already exists and is loaded, you must first switch away using new_level() to a temp path, then create from template:

python
import unreal

subsys = unreal.get_editor_subsystem(unreal.LevelEditorSubsystem)
target   = "/Game/Maps/MyLevel"
template = "/Engine/Maps/Templates/Template_Default"

# 1. Switch to temp level to unload target (so it can be overwritten)
subsys.new_level("/Game/Maps/__TempSwitch")

# 2. Create the actual level from template (overwrites any existing asset at target)
result = subsys.new_level_from_template(target, template)
print(f"Created from template: {result}")
# Note: __TempSwitch is automatically replaced/closed — no manual cleanup needed

Save the Current Level (Save / Save As)

Getting the editor world — it lives on UnrealEditorSubsystem, NOT LevelEditorSubsystem, NOT the deprecated EditorLevelLibrary:

python
import unreal

# ❌ WRONG — AttributeError: 'LevelEditorSubsystem' object has no attribute 'get_editor_world'
unreal.get_editor_subsystem(unreal.LevelEditorSubsystem).get_editor_world()

# ❌ DEPRECATED — Editor Scripting Utilities plugin
unreal.EditorLevelLibrary.get_editor_world()

# ✅ CORRECT
world = unreal.get_editor_subsystem(unreal.UnrealEditorSubsystem).get_editor_world()

Which save call to use:

  • Level already saved at a path → unreal.get_editor_subsystem(unreal.LevelEditorSubsystem).save_current_level()
  • Untitled/never-saved level, or "save as <new name>" → EditorLoadingAndSavingUtils.save_map(world, asset_path) (both args required, in that order):
python
import unreal

world = unreal.get_editor_subsystem(unreal.UnrealEditorSubsystem).get_editor_world()
ok = unreal.EditorLoadingAndSavingUtils.save_map(world, "/Game/Maps/MyLevel")
print(f"Saved: {ok}")

To also make the saved level the editor startup map, load the project-settings skill and set maps / EditorStartupMap with the full asset path (/Game/Maps/MyLevel.MyLevel).


Spawn Built-in Actor
python
import unreal

subsys = unreal.get_editor_subsystem(unreal.EditorActorSubsystem)
actor = subsys.spawn_actor_from_class(
    unreal.StaticMeshActor,
    unreal.Vector(0, 0, 100),
    unreal.Rotator(0, 0, 0)
)

level_subsys = unreal.get_editor_subsystem(unreal.LevelEditorSubsystem)
level_subsys.editor_invalidate_viewports()
Spawn Blueprint Actor
python
import unreal

subsys = unreal.get_editor_subsystem(unreal.EditorActorSubsystem)
bp_class = unreal.EditorAssetLibrary.load_blueprint_class("/Game/Blueprints/BP_Enemy")
if bp_class:
    actor = subsys.spawn_actor_from_class(bp_class, unreal.Vector(500, 0, 100), unreal.Rotator(0, 0, 0))
Get Level Actors

⚠️ unreal.EditorLevelLibrary is DEPRECATED. Always use EditorActorSubsystem instead.

python
import unreal

actor_subsys = unreal.get_editor_subsystem(unreal.EditorActorSubsystem)
all_actors = actor_subsys.get_all_level_actors()
lights = [a for a in all_actors if isinstance(a, unreal.PointLight)]

for actor in all_actors[:10]:
    loc = actor.get_actor_location()
    print(f"{actor.get_name()} at ({loc.x}, {loc.y}, {loc.z})")
Transform Operations
python
import unreal

# Get/Set location — args: (new_location, sweep, teleport)
loc = actor.get_actor_location()
actor.set_actor_location(unreal.Vector(100, 200, 300), False, False)

# Get/Set rotation — ALWAYS keyword args for Rotator (positional is Roll, Pitch, Yaw!)
rot = actor.get_actor_rotation()   # rot.pitch / rot.yaw / rot.roll
actor.set_actor_rotation(unreal.Rotator(pitch=-90, yaw=45), False)

# Relative rotation in LOCAL space (returns HitResult — see sweep section)
actor.add_actor_local_rotation(unreal.Rotator(pitch=30), False, False)
# World-space equivalent: actor.add_actor_world_rotation(...)

# Get/Set scale
scale = actor.get_actor_scale3d()
actor.set_actor_scale3d(unreal.Vector(2, 2, 2))

# Everything at once
actor.set_actor_transform(
    unreal.Transform(
        location=unreal.Vector(500, 500, 200),
        rotation=unreal.Rotator(pitch=45),
        scale=unreal.Vector(1.5, 1.5, 1.5)),
    False, False)
Sweep Moves & Collision Checks (HitResult)

Three verified gotchas (UE 5.7):

  1. HitResult fields cannot be read as attributes — hit.blocking_hit / hit.bBlockingHit raise "property is protected". Use hit.to_tuple()[0] (the blocking-hit bool) or hit.export_text() (full readable dump: Location, ImpactPoint, Normal, Distance, Time).
  2. set_actor_location(loc, sweep=True, ...) does NOT detect collisions in the editor world — it reports no hit even when moving straight through another actor. Don't rely on it for collision checks.
  3. The definitive checks: compare the actor's final position to the target, and/or trace the path explicitly with SystemLibrary.line_trace_single (this DOES hit editor actors):
python
import unreal

actor.set_actor_location(target, True, False)
# ⚠️ Vector.equals(b) takes NO tolerance arg — use distance() for tolerant compare
arrived = unreal.Vector.distance(actor.get_actor_location(), target) < 1.0

# Explicit path check that actually works in the editor:
world = unreal.get_editor_subsystem(unreal.UnrealEditorSubsystem).get_editor_world()
hit = unreal.SystemLibrary.line_trace_single(
    world, start_loc, target,
    unreal.TraceTypeQuery.ECC_VISIBILITY,      # Visibility channel (TRACE_TYPE_QUERY1 is the deprecated alias)
    False, [actor],                            # ignore the moving actor itself
    unreal.DrawDebugTrace.NONE, True)
if hit and hit.to_tuple()[0]:                  # [0] = blocking hit bool
    blocker = hit.to_tuple()[9]                # [9] = hit Actor, [10] = hit Component
    print(f"Blocked by {blocker.get_actor_label()}: {hit.export_text()[:200]}")
Show full SKILL.md (593 more words)Show less
Organize: Rename, Folders, Attach/Detach, Tags
python
import unreal

# Rename (the label shown in the outliner)
actor.set_actor_label("MainLight")

# Outliner folder (creates the folder if needed; "" = root, "A/B" nests)
actor.set_folder_path("TestLights")

# Attach to another actor, keeping current world position
actor.attach_to_actor(parent_actor, "",
    unreal.AttachmentRule.KEEP_WORLD,
    unreal.AttachmentRule.KEEP_WORLD,
    unreal.AttachmentRule.KEEP_WORLD, False)

# Detach, keeping world position
actor.detach_from_actor(
    unreal.DetachmentRule.KEEP_WORLD,
    unreal.DetachmentRule.KEEP_WORLD,
    unreal.DetachmentRule.KEEP_WORLD)

# Actor tags (read/write the whole list; query with `in`)
actor.tags = ["Test", "Lighting"]
tagged = [a for a in actor_subsys.get_all_level_actors() if "Lighting" in a.tags]
Light Actors (PointLight, SpotLight, DirectionalLight)

All unreal.Light actors expose their component as actor.light_component — no get_component_by_class needed:

python
import unreal

comp = light_actor.light_component        # PointLightComponent / SpotLightComponent / ...
comp.set_intensity(10000)
comp.set_light_color(unreal.LinearColor(1.0, 0.7, 0.4))   # warm; LinearColor IS positional RGBA
intensity = comp.get_editor_property("intensity")
# Spot cone angles (SpotLight only):
# comp.set_inner_cone_angle(20); comp.set_outer_cone_angle(35)
Selection
python
import unreal

subsys = unreal.get_editor_subsystem(unreal.EditorActorSubsystem)
subsys.select_nothing()  # Clear
subsys.set_actor_selection_state(actor, True)  # Select
selected = subsys.get_selected_level_actors()  # Get selected
Move Actor to Viewport Camera
python
import unreal

actor_subsys = unreal.get_editor_subsystem(unreal.EditorActorSubsystem)
editor_subsys = unreal.get_editor_subsystem(unreal.UnrealEditorSubsystem)

camera_info = editor_subsys.get_level_viewport_camera_info()
if camera_info:
    cam_loc, cam_rot = camera_info
    forward_vec = cam_rot.get_forward_vector()
    new_loc = cam_loc + (forward_vec * 200.0)
    actor.set_actor_location(new_loc, False, True)
Destroy/Duplicate Actors
python
import unreal

subsys = unreal.get_editor_subsystem(unreal.EditorActorSubsystem)

# Destroy
subsys.destroy_actor(actor)

# Duplicate
dup = subsys.duplicate_actor(actor)
loc = dup.get_actor_location()
dup.set_actor_location(unreal.Vector(loc.x + 200, loc.y, loc.z), False, False)
Camera View Methods (for screenshots and verification)

Use ActorService to position the viewport camera to frame actors from specific directions. (For a plain "focus on these actors" with no specific direction, the engine EditorAppToolset FocusOnActors action via call_tool also works — ActorService adds the directional/padding framing the engine toolset lacks.)

⚠️ NEVER guess camera coordinates with set_viewport_camera. Manual positions almost always point at sky or empty space. Always use get_actor_view_camera which auto-calculates position from the actor's bounding box.

python
import unreal

actor_service = unreal.ActorService

# Move camera to view an actor from above (top-down)
view = actor_service.get_actor_view_camera("MyLandscape", unreal.ViewDirection.TOP)

# Move camera to view from front with extra padding
view = actor_service.get_actor_view_camera("MyBuilding", unreal.ViewDirection.FRONT, 1.5)

# Calculate view without moving camera
view = actor_service.calculate_actor_view("MyActor", unreal.ViewDirection.RIGHT, 1.2)
# view.camera_location, view.camera_rotation, view.view_distance

Directions: TOP, BOTTOM, LEFT, RIGHT, FRONT, BACK Padding: 1.0=tight, 1.2=default, 2.0=far, 3.0=very far (use for large structures)

Choosing the Right View
GoalUse
Overview of a large structureTOP with padding 2.0–3.0
See the front faceFRONT with padding 1.5–2.5
Full castle/building viewFRONT padding 3.0, or TOP padding 2.5
Check layout from aboveTOP padding 1.5
Profile viewLEFT or RIGHT

⚠️ Do NOT switch to set_viewport_camera with manual coordinates to get a "better angle". Manual positions almost always miss the subject and point at sky or empty space. If the view isn't wide enough, increase the padding or switch to TOP. There is no need for a diagonal camera — TOP and FRONT with adequate padding cover every screenshot use case.


Transform Locking & Constraints
🔒 Location Locking (Per-Actor, Native UE5)

UE5 has a native bLockLocation property on all actors. When locked, the actor cannot be moved via viewport gizmos (but CAN still be moved via code).

python
import unreal

# Lock an actor's location
unreal.ActorService.set_actor_lock_location("MyCube", True)

# Check if locked
locked = unreal.ActorService.get_actor_lock_location("MyCube")
print(f"Locked: {locked}")

# Unlock
unreal.ActorService.set_actor_lock_location("MyCube", False)
🔒 Scale Ratio Lock (Uniform Scaling Padlock — Global Editor Setting)

The padlock icon next to Scale in the Details panel is the Preserve Scale Ratio setting. When enabled, scaling any axis scales ALL axes proportionally. This is a global editor preference, not per-actor.

python
import unreal

# Lock scale axes together (uniform scaling) — the padlock icon
unreal.ActorService.set_preserve_scale_ratio(True)

# Unlock for independent axis scaling
unreal.ActorService.set_preserve_scale_ratio(False)

# Check current state
locked = unreal.ActorService.get_preserve_scale_ratio()
print(f"Scale ratio locked: {locked}")
⚠️ There is NO Per-Actor Rotation or Scale Lock

UE5 does NOT have a per-actor lock for rotation or scale. There is no bLockRotation or bLockScale property on actors. Only location locking (set_actor_lock_location) is per-actor.

If user asks to "lock rotation" or "lock scale on this actor specifically":

  1. Explain this limitation clearly
  2. For uniform scaling, use set_preserve_scale_ratio(True) (global, affects all actors)
  3. For location locking, use set_actor_lock_location
  4. For world-space independence, use absolute transform flags
Absolute Transform Flags (Per-Component)

Make location/rotation/scale world-space instead of relative to parent. Useful when attaching actors but needing independent positioning.

python
import unreal

# Make rotation absolute (independent of parent), keep location/scale relative
unreal.ActorService.set_absolute_transform("MyCube", False, True, False)

# Check flags
loc, rot, scale = unreal.ActorService.get_absolute_transform("MyCube")
print(f"Absolute: loc={loc}, rot={rot}, scale={scale}")

Re-run a placed actor's construction script

After changing a Blueprint component template or a placed actor's exposed variable, the placed actor's construction script does not re-execute on its own, so SCS-driven state goes stale. Python previously had no way to force it (the old nudge-by-0.01 trick). ActorService.rerun_construction_scripts now does it directly. Editor world only; it refuses (returns False, logs) while PIE is running.

python
import unreal
# Re-run the construction script for a placed Blueprint actor by label or name
unreal.ActorService.rerun_construction_scripts("BP_Refrigerator_2")

To refresh the SCS viewport of an OPEN Blueprint editor (so it re-runs the construction script and redraws), use unreal.BlueprintService.refresh_blueprint_editor("/Game/Props/BP_Refrigerator") — it returns False when no editor is open for that Blueprint.

Sample scripts (run via execute_python_code)

  • scripts/manipulate_actors.txt — list level actors, find by class, move/rotate by name.

Additional gotchas

  • World Partition: save_current_level() does not save external actor packages; spawn_actor_from_object writes to disk immediately with no dirty flag, but a later set_actor_location does not; after load_level WP actors read as unloaded stubs (not data loss); deleting a WP actor means destroy_actor plus saving the emptied package, or an orphan resurrects it.
  • An actor whose constructor creates no components has no root and is pinned to (0,0,0) forever (RootComponent reads None).
  • Editor-scripted volumes work — spawn_actor_from_class(unreal.SomeVolume) gives a 200 uu brush — but encompasses_point is not exposed.

© kevinpbuckley, MIT. Rendered from Markdown: HTML in the file is shown as text, images as links, and headings moved down two levels. Raw file

Files

SKILL.md and 1 other file (scripts) in Content/Skills/level-actors of kevinpbuckley/VibeUE.

  • SKILL.md
  • scripts/manipulate_actors.txt

Open the folder on GitHubat commit dc051be

Compare with similar skills

Level Actors next to the 5 skills that share the most tags, products or categories with it. Stars are the repository's; “used in” counts other GitHub owners with a copy.

Level Actors compared with similar skills
SkillStarsUsed inTokensAuto-checkLicenceRepo updated
Level Actors this skillkevinpbuckley/VibeUE730—~6.4kAutomated safety check: PassMIT
2D Map and Scene Generator0x0funky/agent-sprite-forge4.4k—~2.9kAutomated safety check: PassMIT
Spine AnimationGenielabsOpenSource/spine-animation-ai521—~17kAutomated safety check: PassCC-BY-NC-4.0
Unreal BridgeTornLux/UnrealBridge306—~8.4kAutomated safety check: NotesMIT
Text To 3D AssetLaurentiuGabriel/unreal-game-assets-creation-skill148—~2.1kAutomated safety check: PassNone
Substance 3D Texturingfreshtechbro/claudedesignskills1k—~4kAutomated safety check: PassMIT

Similar skills

  • 2D Map and Scene Generator

    0x0funky/agent-sprite-forge

    Plans and builds 2D game maps and scenes, from tilemaps and parallax backgrounds to HD-2D plates, with collision checks, a playable HTML preview and Tiled, Godot or LDtk export.

    4.4k GitHub stars~2.9k tokensUpdated 3 days ago
    Game DevelopmentAuto-check passed
  • Spine Animation

    GenielabsOpenSource/spine-animation-ai

    Create Spine 2D skeletal animations from pre-existing character assets (separated body-part PNGs, atlas spritesheet, or a full character image).

    521 GitHub stars~17k tokensUpdated 1 mo ago
    Game DevelopmentAuto-check passed
  • Unreal Bridge

    TornLux/UnrealBridge

    Execute Python scripts inside a running Unreal Engine 5.3+ editor via TCP bridge.

    306 GitHub stars~8.4k tokensUpdated 9 days ago
    Game DevelopmentAuto-check: notes
  • Text To 3D Asset

    LaurentiuGabriel/unreal-game-assets-creation-skill

    Generate a game-ready 3D asset by running the local AI pipeline sequentially: Fooocus (SDXL text-to-image) - Hunyuan3D-2 (image-to-textured-GLB) - optional Blender FBX convert + Unreal import.

    148 GitHub stars~2.1k tokensUpdated 2 mo ago
    Game DevelopmentAuto-check passed
  • Substance 3D Texturing

    freshtechbro/claudedesignskills

    Comprehensive skill for Adobe Substance 3D Painter texturing and material creation workflow.

    1k GitHub stars~4k tokensUpdated 10 mo ago
    Game DevelopmentAuto-check passed
  • Capability-based multi-tool matrix for game development: engine, 3D/2D/audio assets, AI-generated assets, agent playtesting, packaging, store publishing, and telemetry.

    52k GitHub stars~2.3k tokensUpdated 18 days ago
    Game DevelopmentAuto-check passed

More from kevinpbuckley/VibeUE

All 39 skills in this repo
  • Unreal Animation Bone Editor

    kevinpbuckley/VibeUE

    Previews, validates and bakes bone-rotation edits on Unreal Engine AnimSequences, enforcing a coordinate space and a constraint-checked preview-validate-bake loop.

    730 GitHub stars~1.3k tokensUpdated 2 days ago
    Auto-check passed
  • Unreal Animation Montage

    kevinpbuckley/VibeUE

    Creates and edits Unreal Engine Animation Montages through AnimMontageService: sections, slots, segments, branching points, notifies and blend settings.

    730 GitHub stars~3.6k tokensUpdated 2 days ago
    Auto-check passed
  • Unreal AnimSequence Editor

    kevinpbuckley/VibeUE

    Creates and edits AnimSequence keyframes and bone tracks in Unreal Engine through AnimSequenceService, following an inspect, preview, validate and bake workflow with correct bone-space handling.

    730 GitHub stars~3.2k tokensUpdated 2 days ago
    Auto-check passed
  • Unreal Asset Import and Export

    kevinpbuckley/VibeUE

    Imports files into Unreal Engine, exports textures, reads the Content Browser selection and checks open assets via VibeUE, plus mesh reimport material fixes.

    730 GitHub stars~6.5k tokensUpdated 2 days ago
    Auto-check passed
  • Unreal Engine Settings Control

    kevinpbuckley/VibeUE

    Changes Unreal Engine console variables, scalability levels and raw INI values through VibeUE's Python API, now that registered settings moved to the engine's own toolset.

    730 GitHub stars~1.3k tokensUpdated 2 days ago
    Auto-check passed
  • Unreal Enhanced Input Setup

    kevinpbuckley/VibeUE

    Creates and wires Unreal Engine Enhanced Input assets through VibeUE's InputService: Input Actions, Mapping Contexts, key bindings, triggers and modifiers.

    730 GitHub stars~2.4k tokensUpdated 2 days ago
    Auto-check passed

Works with

Questions about Level Actors

What does Level Actors do?

List, find, place, move, rotate, and modify actors in the current level (ActorService + editor subsystems). Level Actors is an agent skill from kevinpbuckley/VibeUE. List, find, place, move, rotate, and modify actors in the current level (ActorService + editor subsystems).

When should I use Level Actors?

Level Actors fits situations like: the user asks to find/list actors in the level; rotate an actor; set an actors properties/folder/transform; spawn/delete level actors.

How do I install Level Actors in Claude Code?

Run `npx skills add kevinpbuckley/VibeUE --skill level-actors -a claude-code`. Or copy the skill folder (Content/Skills/level-actors in kevinpbuckley/VibeUE) into .claude/skills/level-actors in your project. Claude Code loads it when a task matches its description.

How do I install Level Actors in Codex?

Run `npx skills add kevinpbuckley/VibeUE --skill level-actors -a codex`. Or copy the skill folder (Content/Skills/level-actors in kevinpbuckley/VibeUE) into .agents/skills/level-actors in your project. Codex loads it when a task matches its description.

Can I use Level Actors in Cursor, Gemini CLI or GitHub Copilot?

Cursor, Gemini CLI, GitHub Copilot and OpenCode also load SKILL.md folders. With the skills CLI, run `npx skills add kevinpbuckley/VibeUE --skill level-actors -a cursor` (or -a gemini-cli, github-copilot or opencode for the others). To copy it by hand, put the folder in .cursor/skills/level-actors, .gemini/skills/level-actors, .github/skills/level-actors and .opencode/skills/level-actors in your project.

What does Level Actors need to run?

SKILL.md names no scripts, command-line tools or credentials: Level Actors is instructions for the agent only. Our summary lists: Python 3.

Does Level Actors access the network?

SKILL.md contains no URLs. Any network use would come from the scripts or tools the agent runs. This is read from the text; nothing was executed.

Is Level Actors safe to install?

Our automated static check of SKILL.md found no risky patterns, such as piping downloads into a shell, reading credential files or hidden Unicode. It is not a guarantee. The check reads SKILL.md only: the scripts in the folder are not scanned, so read them before running anything.

What licence does Level Actors use?

Level Actors is published under the MIT licence (the repository's licence). It allows redistribution, so the full SKILL.md is shown on this page.

How many tokens does Level Actors use?

About 6.4k tokens (SKILL.md is roughly 26k characters). Agents keep only the skill's name and description in context until a task matches; then they load SKILL.md in full.

What are the alternatives to Level Actors?

Skills that share tags, products or a category with Level Actors: 2D Map and Scene Generator (0x0funky/agent-sprite-forge, 4.4k stars), Spine Animation (GenielabsOpenSource/spine-animation-ai, 521 stars), Unreal Bridge (TornLux/UnrealBridge, 306 stars) and Text To 3D Asset (LaurentiuGabriel/unreal-game-assets-creation-skill, 148 stars). The comparison table on this page puts their stars, adoption, token cost, safety result and licence side by side.

Who maintains Level Actors?

kevinpbuckley (a GitHub user) maintains it in kevinpbuckley/VibeUE, which has 730 GitHub stars. The repository holds 39 skills in this directory. The repository was last updated on October 7, 2026.

Source: kevinpbuckley/VibeUE on GitHub. Facts on this page come from the repository at the commit we read; the author's words are quoted as theirs.