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.
List, find, place, move, rotate, and modify actors in the current level (ActorService + editor subsystems).
$ npx skills add kevinpbuckley/VibeUE --skill level-actors -a claude-codeProject install by default; add -g for ~/.claude/skills/.
$ gh skill install kevinpbuckley/VibeUE level-actors --agent claude-codeProject scope by default; add --scope user for a personal install. Needs GitHub CLI 2.90.0 or later (public preview).
$ 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-srcUse ~/.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/
Install the "level-actors" agent skill from https://github.com/kevinpbuckley/VibeUE/tree/master/Content/Skills/level-actors into .claude/skills/level-actors/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "level-actors", then confirm the skill loads.Claude Code copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$skill-installer install https://github.com/kevinpbuckley/VibeUE/tree/master/Content/Skills/level-actorsType this inside Codex. $skill-installer <name> installs a curated skill from openai/skills. The installer writes to $CODEX_HOME/skills (default ~/.codex/skills). Restart Codex if the skill does not show up.
$ npx skills add kevinpbuckley/VibeUE --skill level-actors -a codexProject install goes to .agents/skills/; add -g for ~/.codex/skills/.
$ gh skill install kevinpbuckley/VibeUE level-actors --agent codexProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/kevinpbuckley/VibeUE.git skills-src && mkdir -p .agents/skills && cp -r skills-src/Content/Skills/level-actors .agents/skills/level-actors && rm -rf skills-srcUse ~/.agents/skills/ instead of .agents/skills for a personal install.
Codex skills documentation · loads skills from .agents/skills/
Install the "level-actors" agent skill from https://github.com/kevinpbuckley/VibeUE/tree/master/Content/Skills/level-actors into .agents/skills/level-actors/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "level-actors", then confirm the skill loads.Codex copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add kevinpbuckley/VibeUE --skill level-actors -a cursorProject install goes to .agents/skills/; add -g for ~/.cursor/skills/.
$ gh skill install kevinpbuckley/VibeUE level-actors --agent cursorProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/kevinpbuckley/VibeUE.git skills-src && mkdir -p .cursor/skills && cp -r skills-src/Content/Skills/level-actors .cursor/skills/level-actors && rm -rf skills-srcUse ~/.cursor/skills/ instead of .cursor/skills for a personal install.
Cursor skills documentation · loads skills from .cursor/skills/, .agents/skills/, .claude/skills/, .codex/skills/
Install the "level-actors" agent skill from https://github.com/kevinpbuckley/VibeUE/tree/master/Content/Skills/level-actors into .cursor/skills/level-actors/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "level-actors", then confirm the skill loads.Cursor copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ gemini skills install https://github.com/kevinpbuckley/VibeUE.git --path Content/Skills/level-actors--scope user (default) or --scope workspace; --path is the subfolder of the repo that holds the skill; --consent skips the security confirmation prompt.
$ npx skills add kevinpbuckley/VibeUE --skill level-actors -a gemini-cliProject install goes to .agents/skills/; add -g for ~/.gemini/skills/.
$ gh skill install kevinpbuckley/VibeUE level-actors --agent gemini-cliProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/kevinpbuckley/VibeUE.git skills-src && mkdir -p .gemini/skills && cp -r skills-src/Content/Skills/level-actors .gemini/skills/level-actors && rm -rf skills-srcUse ~/.gemini/skills/ instead of .gemini/skills for a personal install, then run /skills reload.
Gemini CLI skills documentation · loads skills from .gemini/skills/, .agents/skills/
Install the "level-actors" agent skill from https://github.com/kevinpbuckley/VibeUE/tree/master/Content/Skills/level-actors into .gemini/skills/level-actors/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "level-actors", then confirm the skill loads.Gemini CLI copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ gh skill install kevinpbuckley/VibeUE level-actorsInstalls for Copilot at project scope by default; add --scope user for a personal install. Preview a skill first with gh skill preview. Needs GitHub CLI 2.90.0 or later (public preview).
$ npx skills add kevinpbuckley/VibeUE --skill level-actors -a github-copilotProject install goes to .agents/skills/; add -g for ~/.copilot/skills/.
$ git clone --depth 1 https://github.com/kevinpbuckley/VibeUE.git skills-src && mkdir -p .github/skills && cp -r skills-src/Content/Skills/level-actors .github/skills/level-actors && rm -rf skills-srcUse ~/.copilot/skills/ instead of .github/skills for a personal install. Commit .github/skills so cloud agent and code review can use it.
GitHub Copilot skills documentation · loads skills from .github/skills/, .claude/skills/, .agents/skills/
Install the "level-actors" agent skill from https://github.com/kevinpbuckley/VibeUE/tree/master/Content/Skills/level-actors into .github/skills/level-actors/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "level-actors", then confirm the skill loads.GitHub Copilot copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add kevinpbuckley/VibeUE --skill level-actors -a opencodeOpenCode documents no install command of its own. Project install goes to .agents/skills/; add -g for ~/.config/opencode/skills/.
$ gh skill install kevinpbuckley/VibeUE level-actors --agent opencodeProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/kevinpbuckley/VibeUE.git skills-src && mkdir -p .opencode/skills && cp -r skills-src/Content/Skills/level-actors .opencode/skills/level-actors && rm -rf skills-srcUse ~/.config/opencode/skills/ instead of .opencode/skills for a personal install.
OpenCode skills documentation · loads skills from .opencode/skills/, .claude/skills/, .agents/skills/
Install the "level-actors" agent skill from https://github.com/kevinpbuckley/VibeUE/tree/master/Content/Skills/level-actors into .opencode/skills/level-actors/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "level-actors", then confirm the skill loads.OpenCode copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
level-actorsList, 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). 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.
3 steps, taken from the first numbered list in SKILL.md.
Read from SKILL.md and the folder at commit dc051be. It shows what the files ask for, not the result of running them.
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.
Ships 1 file in scripts/, which the agent can run.
From the folder's file list and the shell code blocks in SKILL.md.
No URLs in SKILL.md.
From URLs in SKILL.md, links to its own repository left out.
Names no API keys, tokens, secrets or passwords.
From names ending in _API_KEY, _TOKEN, _SECRET, _KEY or _PASSWORD in SKILL.md.
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.
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.
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.
The full file from kevinpbuckley/VibeUE at commit dc051be, republished under its MIT licence (© kevinpbuckley). 1,510 words, ~6,386 tokens.
.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.🧠 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.
🔀 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.
unreal.Rotator(a, b, c) is (roll, pitch, yaw) — always pass by keywordPython'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:
# 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:
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)))new_level_from_template, NOT new_levelWhen 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
unreal.EditorLevelLibraryDO 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 EXISTEditorActorSubsystem 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)]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_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.
import unreal
actor_subsys = unreal.get_editor_subsystem(unreal.EditorActorSubsystem)
level_subsys = unreal.get_editor_subsystem(unreal.LevelEditorSubsystem)# 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)# 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)"Rotator is (Roll, Pitch, Yaw), Color is BGRAThe 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 RGBAPitch sign convention: negative pitch = downward (pitch=-90 points straight down). For lights/cameras use negative pitch to aim at the ground.
level_subsys = unreal.get_editor_subsystem(unreal.LevelEditorSubsystem)
level_subsys.editor_invalidate_viewports()🚨 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 neededGetting 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:
unreal.get_editor_subsystem(unreal.LevelEditorSubsystem).save_current_level()<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).
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()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))⚠️
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})")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)Three verified gotchas (UE 5.7):
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).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.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]}")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]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)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 selectedimport 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)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)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_distanceDirections: TOP, BOTTOM, LEFT, RIGHT, FRONT, BACK Padding: 1.0=tight, 1.2=default, 2.0=far, 3.0=very far (use for large structures)
| 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.
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)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}")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":
set_preserve_scale_ratio(True) (global, affects all actors)set_actor_lock_locationMake 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}")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.
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.
execute_python_code)scripts/manipulate_actors.txt — list level actors, find by class, move/rotate by name.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.RootComponent reads None).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
SKILL.md and 1 other file (scripts) in Content/Skills/level-actors of kevinpbuckley/VibeUE.
Open the folder on GitHubat commit dc051be
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.
| Skill | Stars | Used in | Tokens | Auto-check | Licence | Repo updated |
|---|---|---|---|---|---|---|
| Level Actors this skillkevinpbuckley/VibeUE | 730 | — | ~6.4k | Automated safety check: Pass | MIT | |
| 2D Map and Scene Generator0x0funky/agent-sprite-forge | 4.4k | — | ~2.9k | Automated safety check: Pass | MIT | |
| Spine AnimationGenielabsOpenSource/spine-animation-ai | 521 | — | ~17k | Automated safety check: Pass | CC-BY-NC-4.0 | |
| Unreal BridgeTornLux/UnrealBridge | 306 | — | ~8.4k | Automated safety check: Notes | MIT | |
| Text To 3D AssetLaurentiuGabriel/unreal-game-assets-creation-skill | 148 | — | ~2.1k | Automated safety check: Pass | None | |
| Substance 3D Texturingfreshtechbro/claudedesignskills | 1k | — | ~4k | Automated safety check: Pass | MIT |
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.
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).
TornLux/UnrealBridge
Execute Python scripts inside a running Unreal Engine 5.3+ editor via TCP bridge.
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.
freshtechbro/claudedesignskills
Comprehensive skill for Adobe Substance 3D Painter texturing and material creation workflow.
HKUDS/CLI-Anything
Capability-based multi-tool matrix for game development: engine, 3D/2D/audio assets, AI-generated assets, agent playtesting, packaging, store publishing, and telemetry.
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.
kevinpbuckley/VibeUE
Creates and edits Unreal Engine Animation Montages through AnimMontageService: sections, slots, segments, branching points, notifies and blend settings.
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.
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.
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.
kevinpbuckley/VibeUE
Creates and wires Unreal Engine Enhanced Input assets through VibeUE's InputService: Input Actions, Mapping Contexts, key bindings, triggers and modifiers.
Works with
Categories
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).
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.
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.
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.
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.
SKILL.md names no scripts, command-line tools or credentials: Level Actors is instructions for the agent only. Our summary lists: Python 3.
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.
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.
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.
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.
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.
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.