🧠 Brains complement: IF an
unreal-engine-skills-managertool (external MCP) exists in this session, call it with{action: "load", skill: "animation-system"}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.
Animation Montage Skill
Use AnimMontageService for animation montage operations.
Quick Reference - Python API
Discovery Methods
⚠️
list_montagesfilters are strings, notNone(issue #461) — pass""for "no filter", neverNone(an FString arg can't be null and raises a TypeError). Playback rate: there is noset_playback_rate_scalemethod; set the montage'sRateScaledirectly —unreal.load_asset(montage_path).set_editor_property("RateScale", 1.5)thenunreal.EditorAssetLibrary.save_asset(montage_path).
python# List all montages in a path (use "" for no skeleton filter, NOT None) montages = AnimMontageService.list_montages(search_path, skeleton_filter) -> Array[MontageInfo] # Get detailed montage info info = AnimMontageService.get_montage_info(montage_path) -> MontageInfo # Find montages for a skeleton montages = AnimMontageService.find_montages_for_skeleton(skeleton_path) -> Array[MontageInfo] # Find montages using a specific animation montages = AnimMontageService.find_montages_using_animation(anim_path) -> Array[MontageInfo]
Creation Methods
python# Create montage from an animation sequence path = AnimMontageService.create_montage_from_animation(anim_path, dest_path, name) -> str # Create empty montage for a skeleton path = AnimMontageService.create_empty_montage(skeleton_path, dest_path, name) -> str # Duplicate existing montage path = AnimMontageService.duplicate_montage(source_path, dest_path, new_name) -> str
Section Management (CRITICAL for gameplay!)
python# List all sections sections = AnimMontageService.list_sections(montage_path) -> Array[MontageSectionInfo] # Add new section AnimMontageService.add_section(montage_path, section_name, start_time) -> bool # Remove section AnimMontageService.remove_section(montage_path, section_name) -> bool # Rename section AnimMontageService.rename_section(montage_path, old_name, new_name) -> bool # Move section to new time AnimMontageService.set_section_start_time(montage_path, section_name, new_time) -> bool # Get section at time name = AnimMontageService.get_section_name_at_time(montage_path, time) -> str
Section Linking (Combo/Flow Control)
python# Link sections: Section1 -> Section2 AnimMontageService.set_next_section(montage_path, from_section, to_section) -> bool # Set section to loop AnimMontageService.set_section_loop(montage_path, section_name, loop=True) -> bool # Clear link (section ends montage) AnimMontageService.clear_section_link(montage_path, section_name) -> bool # Get all section links for flow visualization links = AnimMontageService.get_all_section_links(montage_path) -> Array[SectionLink]
Animation Segments (Multiple Animations in One Montage)
python# List segments in a slot track segments = AnimMontageService.list_anim_segments(montage_path, track_index) -> Array[AnimSegmentInfo] # Add animation at specific time index = AnimMontageService.add_anim_segment(montage_path, track_index, anim_path, start_time, play_rate) -> int # Remove segment AnimMontageService.remove_anim_segment(montage_path, track_index, segment_index) -> bool # Modify segment timing AnimMontageService.set_segment_start_time(montage_path, track_index, segment_index, time) -> bool AnimMontageService.set_segment_play_rate(montage_path, track_index, segment_index, rate) -> bool AnimMontageService.set_segment_loop_count(montage_path, track_index, segment_index, count) -> bool
Slot Tracks
python# List slot tracks tracks = AnimMontageService.list_slot_tracks(montage_path) -> Array[SlotTrackInfo] # Add new slot track index = AnimMontageService.add_slot_track(montage_path, slot_name) -> int # Change slot name AnimMontageService.set_slot_name(montage_path, track_index, new_slot_name) -> bool # Remove slot track AnimMontageService.remove_slot_track(montage_path, track_index) -> bool
Blend Settings
python# Set blend in (options: Linear, Cubic, HermiteCubic, Sinusoidal, etc.) AnimMontageService.set_blend_in(montage_path, time, option) -> bool # Set blend out AnimMontageService.set_blend_out(montage_path, time, option) -> bool # Set blend out trigger time (-1 = auto) AnimMontageService.set_blend_out_trigger_time(montage_path, time) -> bool # Get all blend settings settings = AnimMontageService.get_blend_settings(montage_path) -> MontageBlendSettings
Branching Points (Frame-Accurate Gameplay Events)
python# List all branching points points = AnimMontageService.list_branching_points(montage_path) -> Array[BranchingPointInfo] # Add branching point for combo input window index = AnimMontageService.add_branching_point(montage_path, name, trigger_time) -> int # Remove branching point AnimMontageService.remove_branching_point(montage_path, index) -> bool
Root Motion
python# Enable/disable root motion translation AnimMontageService.set_enable_root_motion_translation(montage_path, enable) -> bool # Enable/disable root motion rotation AnimMontageService.set_enable_root_motion_rotation(montage_path, enable) -> bool
Editor Methods
python# Open montage in Animation Editor AnimMontageService.open_montage_editor(montage_path) -> bool # Refresh editor UI: call this ONCE, AFTER all edits are done (see crash warning below) AnimMontageService.refresh_montage_editor(montage_path) -> bool # Jump to section in preview AnimMontageService.jump_to_section(montage_path, section_name) -> bool # Play preview AnimMontageService.play_preview(montage_path, start_section) -> bool
⚠️ CRASH WARNING — never structurally edit a montage while its editor is open. Adding/removing sections, slot tracks, segments, notifies, or branching points reallocates the montage's internal arrays. If the montage editor (Persona) is open, its live preview holds pointers into those arrays and the next tick will dereference freed memory → the editor hard-crashes. Rule: do ALL structural edits FIRST, then
refresh_montage_editor()/open_montage_editor()exactly once at the very end. Do NOT callrefresh_montage_editor()between edits. (The service now also auto-closes the editor before each structural edit as a safety net, but you should still batch edits and open last so the editor isn't opening and closing repeatedly.)
IMPORTANT: Asset Path Format
All montage_path parameters require the FULL asset path (package_name), NOT the folder path.
python# CORRECT: Full asset path montage_path = "/Game/Montages/AM_Attack" # WRONG: Folder path montage_path = "/Game/Montages" # This is a folder, not an asset!
IMPORTANT: Return Struct Field Names (read before printing results)
These methods return UStruct objects. Access fields with the exact snake_case names below —
guessing (skeleton_display_name, segment end_time, name, path) raises AttributeError.
Each struct field is read-only data already populated; just read it.
MontageInfo (from get_montage_info / list_montages / find_montages_for_skeleton):
montage_path, montage_name, skeleton_path, duration, section_count, slot_track_count,
notify_count, branching_point_count, blend_in_time, blend_out_time, blend_out_trigger_time,
enable_root_motion_translation, enable_root_motion_rotation, slot_names (Array[str]).
(No skeleton_display_name — use skeleton_path. No skeleton object — it's a path string.)
MontageSectionInfo (from list_sections):
section_name, section_index, start_time, end_time, duration, next_section_name,
loops, segment_count.
AnimSegmentInfo (from list_anim_segments / get_anim_segment_info):
segment_index, anim_sequence_path, anim_name, start_time, duration, play_rate,
anim_start_pos, anim_end_pos, loop_count, loops.
(Segments have NO end_time — that field exists only on sections. Compute segment end as
start_time + duration. Source-clip range is anim_start_pos → anim_end_pos.)
SlotTrackInfo (from list_slot_tracks): track_index, slot_name, segment_count, total_duration.
SectionLink (from get_all_section_links): from_section, to_section, is_loop.
BranchingPointInfo (from list_branching_points): index, notify_name, trigger_time, section_name.
Tip: when unsure, call
discover_python_class('unreal.MontageInfo, unreal.AnimSegmentInfo, unreal.MontageSectionInfo')— theEditor Propertiesblock lists every valid field name. Cheaper than a crash-and-retry loop.
Key Workflows
Create Multi-Animation Montage with Sections
pythonimport unreal # 1. Create empty montage path = unreal.AnimMontageService.create_empty_montage( "/Game/Characters/SK_Mannequin", "/Game/Montages", "AM_ComboAttack" ) # 2. Add animation segments sequentially (track 0) unreal.AnimMontageService.add_anim_segment(path, 0, "/Game/Animations/Attack1", 0.0) unreal.AnimMontageService.add_anim_segment(path, 0, "/Game/Animations/Attack2", 1.0) unreal.AnimMontageService.add_anim_segment(path, 0, "/Game/Animations/Attack3", 2.0) # 3. Add sections at animation boundaries unreal.AnimMontageService.add_section(path, "Attack1", 0.0) unreal.AnimMontageService.add_section(path, "Attack2", 1.0) unreal.AnimMontageService.add_section(path, "Attack3", 2.0) # 4. Link sections for combo flow unreal.AnimMontageService.set_next_section(path, "Attack1", "Attack2") unreal.AnimMontageService.set_next_section(path, "Attack2", "Attack3") # Attack3 has no next section - ends the montage # 5. Add branching points for input windows unreal.AnimMontageService.add_branching_point(path, "Combo1Window", 0.7) unreal.AnimMontageService.add_branching_point(path, "Combo2Window", 1.7) # 6. Configure blend settings unreal.AnimMontageService.set_blend_in(path, 0.15, "Cubic") unreal.AnimMontageService.set_blend_out(path, 0.2, "Linear") # 7. Save FIRST, while no editor is open unreal.EditorAssetLibrary.save_asset(path) # 8. THEN refresh/open the editor exactly once — never between the edits above # (refreshing mid-edit can crash the editor; see CRASH WARNING) unreal.AnimMontageService.refresh_montage_editor(path)
Create Layered Upper Body Montage
pythonimport unreal # 1. Create montage from animation path = unreal.AnimMontageService.create_montage_from_animation( "/Game/Animations/Reload", "/Game/Montages", "AM_Reload_UpperBody" ) # 2. Change slot to UpperBody for layered blending unreal.AnimMontageService.set_slot_name(path, 0, "UpperBody") # 3. Configure blend unreal.AnimMontageService.set_blend_in(path, 0.1, "Linear") unreal.AnimMontageService.set_blend_out(path, 0.15, "Linear") # 4. Disable root motion for upper body action unreal.AnimMontageService.set_enable_root_motion_translation(path, False) unreal.AnimMontageService.set_enable_root_motion_rotation(path, False) # 5. Save unreal.EditorAssetLibrary.save_asset(path)
Set Up Looping Idle Section
pythonimport unreal montage_path = "/Game/Montages/AM_Combat" # Add sections unreal.AnimMontageService.add_section(montage_path, "Idle", 0.0) unreal.AnimMontageService.add_section(montage_path, "Attack", 2.0) # Make Idle loop until gameplay interrupts unreal.AnimMontageService.set_section_loop(montage_path, "Idle", True) # Attack ends the montage (no next section) unreal.AnimMontageService.clear_section_link(montage_path, "Attack")
COMMON_MISTAKES
Wrong: Using folder path instead of asset path
python# WRONG montages = AnimMontageService.list_sections("/Game/Montages") # CORRECT sections = AnimMontageService.list_sections("/Game/Montages/AM_Attack")
Wrong: Refreshing the editor between structural edits (can CRASH the editor)
python# WRONG - opening/refreshing the editor mid-edit, then editing more, can hard-crash # the editor (live preview holds dangling pointers into reallocated montage arrays) AnimMontageService.add_section(path, "Attack", 0.5) AnimMontageService.refresh_montage_editor(path) # editor now open AnimMontageService.add_anim_segment(path, 0, anim, 1.0) # mutates arrays under live preview -> CRASH risk # CORRECT - do ALL structural edits first, save, THEN refresh/open exactly once at the end AnimMontageService.add_section(path, "Attack", 0.5) AnimMontageService.add_anim_segment(path, 0, anim, 1.0) unreal.EditorAssetLibrary.save_asset(path) AnimMontageService.refresh_montage_editor(path) # single refresh at the very end
Wrong: Adding sections beyond montage duration
python# WRONG - Section time must be within montage length # If montage is 3.0s, this fails: AnimMontageService.add_section(path, "Late", 5.0) # Beyond duration # CORRECT - Add segments first to extend duration, then add sections AnimMontageService.add_anim_segment(path, 0, anim_path, 3.0) # Extends montage AnimMontageService.add_section(path, "Late", 3.5) # Now within range
Wrong: Forgetting to save
python# WRONG - Changes lost on editor restart AnimMontageService.add_section(path, "Attack", 0.5) # CORRECT - Always save after changes AnimMontageService.add_section(path, "Attack", 0.5) unreal.EditorAssetLibrary.save_asset(path)
Sample scripts (run via execute_python_code)
scripts/create_montage.txt— create a montage from an animation and add named sections.


