level-actors
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.
pinned to #87ec7e6updated 2 weeks ago
Ask your AI client: “install skills/level-actors”.
Requires the metahub MCP server installed in your client. Set up MCP.
mh install skills/level-actorsmetahub onboarded this repo on the author's behalf.
If you own github.com/kevinpbuckley/VibeUE on GitHub, claim the listing to take over publishing. Your claim preserves the existing eval history and badges; only the curator label is replaced with verified-publisher on your next publish.
Stars
472
Last commit
2 weeks ago
Latest release
published
- #ai
- #ai-tools
- #automation
- #blueprint
- #claude
- #copilot
- #cursor
- #game-development
- #gamedev
- #landscape
- #mcp
- #mcp-server
- #model-context-protocol
- #niagara
- #open-source
- #python
- #ue5
- #unreal-engine
- #unreal-engine-plugin
- #vibe-coding
About this skill
Pulled from SKILL.md at publish time.
> 🧠 **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.
Evaluation report
WarningsAutomated checks the publisher passed at publish time — structure, docs, safety, and whether the artifact behaves as claimed.87ec7e6· 2 weeks ago
Kind-specific
31Skill: SKILL.md present
found at Content/Skills/level-actors/SKILL.md · frontmatter source: SKILL.md
Skill: body content present
2,449 words · 22,197 chars · 32 sections · 27 code blocks
Skill: triggers declaredwarn
No `trigger` phrases in SKILL.md frontmatter
Add `trigger:` lines so Claude knows when to activate this skill — e.g. `when building MCP servers` or `for diagram creation`.
Skill: allowed-tools scope
no allowed-tools restriction (Claude may use anything)
Release history
1- releasecurrent87ec7e6warn2 weeks ago
Contents
🧠 Brains complement: IF an
unreal-engine-skills-managertool (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, andEditorAppToolset(selection, camera, PIE, screenshots), reachable viacall_tool. Equivalently you can driveunreal.EditorActorSubsystem/unreal.LevelEditorSubsystemdirectly fromexecute_python_code(shown throughout below). VibeUE'sActorServicewas 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_propertyno longer exist — use the engine toolsets orEditorActorSubsystemfor spawning and property edits.
Critical Rules
� 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:
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.
# ❌ 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:
# ❌ 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_mesh | comp.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_component | no such attribute | dir_light.light_component (ALL unreal.Light actors use light_component) |
comp.set_editor_property("cast_shadow", …) | property is plural | comp.set_editor_property("cast_shadows", False) |
unreal.HorizontalTextAligment | UE misspells it | unreal.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():
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
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
# 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
# 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:
# ❌ 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
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 usenew_level_from_template()with/Engine/Maps/Templates/Template_Default.
Available UE 5.7 templates in /Engine/Maps/Templates/:
| Template path | Description |
|---|---|
/Engine/Maps/Templates/Template_Default | Basic — floor, sky sphere, directional light, player start |
/Engine/Maps/Templates/OpenWorld | Open world with large terrain |
/Engine/Maps/Templates/TimeOfDay_Default | Time-of-day sky setup |
/Engine/Maps/Templates/VR-Basic | VR template |
Pattern: Create a new level from the Basic template
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:
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:
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):
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
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
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.EditorLevelLibraryis DEPRECATED. Always useEditorActorSubsysteminstead.
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
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):
HitResultfields cannot be read as attributes —hit.blocking_hit/hit.bBlockingHitraise "property is protected". Usehit.to_tuple()[0](the blocking-hit bool) orhit.export_text()(full readable dump: Location, ImpactPoint, Normal, Distance, Time).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.- 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):
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]}")
Organize: Rename, Folders, Attach/Detach, Tags
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:
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
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
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
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 useget_actor_view_camerawhich auto-calculates position from the actor's bounding box.
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
| Goal | Use |
|---|---|
| Overview of a large structure | TOP with padding 2.0–3.0 |
| See the front face | FRONT with padding 1.5–2.5 |
| Full castle/building view | FRONT padding 3.0, or TOP padding 2.5 |
| Check layout from above | TOP padding 1.5 |
| Profile view | LEFT or RIGHT |
⚠️ Do NOT switch to
set_viewport_camerawith 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 toTOP. There is no need for a diagonal camera —TOPandFRONTwith 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).
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.
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":
- Explain this limitation clearly
- For uniform scaling, use
set_preserve_scale_ratio(True)(global, affects all actors) - For location locking, use
set_actor_lock_location - 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.
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}")
Sample scripts (run via execute_python_code)
scripts/manipulate_actors.txt— list level actors, find by class, move/rotate by name.
Reviews
No reviews yet. Be the first.
Related
Gpt Researcher
An autonomous agent that conducts deep research on any data using any LLM providers
Browser Use
🌐 Make websites accessible for AI agents. Automate tasks online with ease.
Frontend Slides
Create beautiful slides on the web using Claude's frontend skills
mh install skills/level-actors