• Listen to a special audio message from Bill Roper to the Hive Workshop community (Bill is a former Vice President of Blizzard Entertainment, Producer, Designer, Musician, Voice Actor) 🔗Click here to hear his message!
  • Read Evilhog's interview with Gregory Alper, the original composer of the music for WarCraft: Orcs & Humans 🔗Click here to read the full interview.
  • The Hive's 22nd Icon Contest: Creep Abilities is now concluded, time to vote for your favourite set of icons! Click here to vote!
  • ✅ The POLL for Hive's Texturing Contest #34 is OPEN! Vote for the TOP 3 SKINS! 🔗Click here to cast your vote!
  • ✅ The POLL for Hive's Techtree Contest #20 is OPEN! Vote for the TOP 3 FACTIONS! 🔗Click here to cast your vote!

[General] Cinematics without Waits + multiplayer skip system – overkill or valid approach

Level 16
Joined
Feb 22, 2025
Messages
315
So I ran into a problem while working on a co-op cinematic setup.

I needed:
  • reliable timing (no Wait issues)
  • multiplayer-safe behavior
  • and a proper skip system (ideally with voting, not just one player skipping)

From what I found on Hive, most cinematic setups still rely on Waits, and I couldn’t really find something that cleanly handles skipping in multiplayer scenarios.


So I ended up trying something different:


JASS:
//===========================================================================
// ESC key press detection and voting start
//===========================================================================
function Cinematic_OnEscPress takes nothing returns boolean
    local player p = GetTriggerPlayer()
    local integer pid = GetPlayerId(p)
    // Only act if cinematic is running and voting not already active
    if udg_Cinematic_Running and not udg_Skip_VotingActive then
        // Show dialog for this player
        if pid == 0 then
            call DialogDisplay(p, udg_Skip_Dialog1, true)
        elseif pid == 1 then
            call DialogDisplay(p, udg_Skip_Dialog2, true)
        endif
        set udg_Skip_VotingActive = true
        // Start a timeout timer (e.g., 10 seconds) to auto‑reset votes
        call TimerStart(udg_Skip_VoteTimer, 10.0, false, null)
    endif
    set p = null
    return false
endfunction

  • Cinematic Skip Register
    • Events
      • Map initialization
    • Conditions
    • Actions
      • Custom script: local trigger t
      • Custom script: local integer i
      • Custom script: set i = 0
      • Custom script: loop
      • Custom script: exitwhen i >= bj_MAX_PLAYER_SLOTS
      • Custom script: if GetPlayerSlotState(Player(i)) == PLAYER_SLOT_STATE_PLAYING and GetPlayerController(Player(i)) == MAP_CONTROL_USER then
      • Custom script: set t = CreateTrigger()
      • Custom script: call BlzTriggerRegisterPlayerKeyEvent(t, Player(i), OSKEY_ESCAPE, 0, true)
      • Custom script: call TriggerAddCondition(t, Condition(function Cinematic_OnEscPress))
      • Custom script: endif
      • Custom script: set i = i + 1
      • Custom script: endloop
      • Custom script: set t = null
  • Cinematic Create Dialogs
    • Events
      • Map initialization
    • Conditions
    • Actions
      • // Player 1
      • Dialog - Create a dialog titled "Skip Cinematic?"
      • Set Skip_Dialog1 = (Last created dialog)
      • Dialog - Create a button for Skip_Dialog1 labelled "Yes"
      • Set Skip_ButtonYes1 = (Last created dialog button)
      • Dialog - Create a button for Skip_Dialog1 labelled "No"
      • Set Skip_ButtonNo1 = (Last created dialog button)
      • // Player 2
      • Dialog - Create a dialog titled "Skip Cinematic?"
      • Set Skip_Dialog2 = (Last created dialog)
      • Dialog - Create a button for Skip_Dialog2 labelled "Yes"
      • Set Skip_ButtonYes2 = (Last created dialog button)
      • Dialog - Create a button for Skip_Dialog2 labelled "No"
      • Set Skip_ButtonNo2 = (Last created dialog button)
  • Skip Dialog Click
    • Events
      • Dialog - A dialog button is clicked for Skip_Dialog1
      • Dialog - A dialog button is clicked for Skip_Dialog2
    • Conditions
    • Actions
      • // Determine which player and which button
      • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
        • If - Conditions
          • (Clicked dialog) Equal to Skip_Dialog1
        • Then - Actions
          • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
            • If - Conditions
              • (Clicked dialog button) Equal to Skip_ButtonYes1
            • Then - Actions
              • Set Skip_VoteYes1 = True
            • Else
              • Set Skip_VoteYes1 = False // No – cancel vote
          • Else - Actions
        • Else - Actions
          • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
            • If - Conditions
              • (Clicked dialog button) Equal to Skip_ButtonYes2
            • Then - Actions
              • Set Skip_VoteYes2 = True
            • Else
              • Set Skip_VoteYes2 = False
          • Else - Actions
      • // After processing, check if both voted Yes
      • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
        • If - Conditions
          • Skip_VoteYes1 Equal to True
          • Skip_VoteYes2 Equal to True
        • Then - Actions
          • // Both want to skip
          • Set Cinematic_Skipped = True
          • Trigger - Run Cinematic Intro End <gen> (ignoring conditions)
          • Trigger - Turn off Cinematic Intro Loop <gen>
          • Set Cinematic_Running = False
          • // Close dialogs
          • Dialog - Hide Skip_Dialog1 for Player 1 (Red)
          • Dialog - Hide Skip_Dialog2 for Player 2 (Blue)
          • // Reset votes
          • Set Skip_VoteYes1 = False
          • Set Skip_VoteYes2 = False
          • Set Skip_VotingActive = False
          • Countdown Timer - Pause Skip_VoteTimer
        • Else
          • // If any player voted No, cancel the whole vote
          • If (Skip_VoteYes1 Equal to False) then
            • Set Skip_VoteYes1 = False
            • Set Skip_VoteYes2 = False
            • Dialog - Hide Skip_Dialog1 for Player 1 (Red)
            • Dialog - Hide Skip_Dialog2 for Player 2 (Blue)
            • Set Skip_VotingActive = False
            • Countdown Timer - Pause Skip_VoteTimer
          • ElseIf (Skip_VoteYes2 Equal to False) then
            • Set Skip_VoteYes1 = False
            • Set Skip_VoteYes2 = False
            • Dialog - Hide Skip_Dialog1 for Player 1 (Red)
            • Dialog - Hide Skip_Dialog2 for Player 2 (Blue)
            • Set Skip_VotingActive = False
            • Countdown Timer - Pause Skip_VoteTimer
          • Else
            • // Both still waiting – do nothing
          • EndIf
        • EndIf
  • Skip Vote Timeout
    • Events
      • Time - Skip_VoteTimer expires
    • Conditions
    • Actions
      • Set Skip_VoteYes1 = False
      • Set Skip_VoteYes2 = False
      • Set Skip_VotingActive = False
      • Dialog - Hide Skip_Dialog1 for Player 1 (Red)
      • Dialog - Hide Skip_Dialog2 for Player 2 (Blue)
  • Cinematic Intro Start
    • Events
      • Map initialization
    • Conditions
    • Actions
      • // ----- Initial setup
      • Sound - Use the Lordaeron Fall daytime ambient theme
      • Sound - Clear the music list
      • Sound - Stop the currently playing music theme
      • Special Effect - Create a special effect at (Target of current camera view) using Daemonic Arrow.mdx
      • Special Effect - Destroy (Last created special effect)
      • Unit - Order Royal Forsaken Captain Mort 0012 <gen> to Hold Position.
      • Unit - Order Warsong Grunt 0016 <gen> to Hold Position.
      • Unit - Order Warsong Grunt 0024 <gen> to Hold Position.
      • Unit - Order Dark Ranger 0006 <gen> to Hold Position.
      • Unit Group - Pick every unit in (Units in Rect 014 <gen>) and do (Actions)
        • Loop - Actions
          • Animation - Play (Picked unit)'s stand ready animation
      • Animation - Change Kodo Beast 0036 <gen>'s animation speed to 150.00% of its original speed
      • Unit - Hide Cannon Fire dummy 0035 <gen>
      • Unit - Hide Cannon Fire dummy 0034 <gen>
      • // Apply camera for all players
      • Player Group - Pick every player in Cinematic_PlayerGroup and do (Actions)
        • Loop - Actions
          • Camera - Apply Camera 005 <gen> for (Picked player) over 0.00 seconds
      • Environment - Set sky to Lordaeron Fall Sky
      • Environment - Set fog to style Linear, z-start 2000.00, z-end 3000.00, density 0.00 and color (30.00%, 30.00%, 5.00%)
      • Cinematic - Turn cinematic mode On for (All players)
      • Cinematic - Fade out over 0.00 seconds using texture Black Mask and color (0.00%, 0.00%, 0.00%) with 0.00% transparency
      • // ----- Start step-based cinematic -----
      • Set Cinematic_Running = True
      • Set Cinematic_Skipped = False
      • Set Cinematic_Phase = 0
      • Set Cinematic_Step = 1
      • Set Cinematic_StepDelay = 1.00
      • Countdown Timer - Start Cinematic_Timer as a One-shot timer that will expire in 1.00 seconds
      • Trigger - Turn on Cinematic Intro Loop <gen>
  • Cinematic Intro Loop
    • Events
      • Time - Cinematic_Timer expires
    • Conditions
      • Cinematic_Running Equal to True
    • Actions
      • If (Cinematic_Skipped Equal to True) then
        • Skip remaining actions
        • Trigger - Run Cinematic Intro End <gen> (ignoring conditions)
        • Trigger - Turn off (This trigger)
        • Set Cinematic_Running = False
      • Else
        • // ----- Phase 0: Intro cinematic -----
        • If (Cinematic_Phase == 0) then
          • If (Cinematic_Step == 1) then
            • // Step 1: after initial 1 sec – play UC_intro, camera 008, fade in, cannon sound
            • Sound - Play UC_intro <gen>
            • Player Group - Pick every player in Cinematic_PlayerGroup and do (Actions)
              • Loop - Actions
                • Camera - Apply Camera 008 <gen> for (Picked player) over 0.00 seconds
            • Cinematic - Fade in over 6.00 seconds using texture White Mask and color (0.00%, 0.00%, 0.00%) with 0.00% transparency
            • Sound - Set Cannon_2 <gen> distances to 1000.00 minimum and 2000.00 maximum
            • Set Cinematic_TempPoint = (Center of Rect 003 <gen>)
            • Sound - Play Cannon_2 <gen> at 30.00% volume, located at Cinematic_TempPoint with Z offset 0.00
            • Custom script: call RemoveLocation(udg_Cinematic_TempPoint)
            • Set Cinematic_StepDelay = 2.00
          • ElseIf (Cinematic_Step == 2) then
            • // Wait 2 sec – nothing
            • Set Cinematic_StepDelay = 0.00
          • ElseIf (Cinematic_Step == 3) then
            • // Move units from rect 007 to 003 and rect 006 to 008
            • Set Cinematic_TempPoint = (Center of Rect 003 <gen>)
            • Unit Group - Pick every unit in (Units in Rect 007 <gen>) and do (Actions)
              • Loop - Actions
                • Unit - Order (Picked unit) to Move To Cinematic_TempPoint
            • Custom script: call RemoveLocation(udg_Cinematic_TempPoint)
            • Set Cinematic_TempPoint = (Center of Rect 008 <gen>)
            • Unit Group - Pick every unit in (Units in Rect 006 <gen>) and do (Actions)
              • Loop - Actions
                • Unit - Order (Picked unit) to Move To Cinematic_TempPoint
            • Custom script: call RemoveLocation(udg_Cinematic_TempPoint)
            • Set Cinematic_StepDelay = 5.00
          • ElseIf (Cinematic_Step == 4) then
            • // Wait 5 sec – nothing
            • Set Cinematic_StepDelay = 0.00
          • ElseIf (Cinematic_Step == 5) then
            • Set Cinematic_TempPoint = (Center of Rect 008 <gen>)
            • Sound - Play Cannon_4 <gen> at 30.00% volume, located at Cinematic_TempPoint with Z offset 0.00
            • Custom script: call RemoveLocation(udg_Cinematic_TempPoint)
            • Cinematic - Fade out over 2.00 seconds using texture Black Mask and color (0.00%, 0.00%, 0.00%) with 0.00% transparency
            • Set Cinematic_StepDelay = 3.00
          • ElseIf (Cinematic_Step == 6) then
            • // Wait 3 sec – nothing
            • Set Cinematic_StepDelay = 0.00
          • ElseIf (Cinematic_Step == 7) then
            • // Fog, camera 001, fade in, camera 010, Swish_1
            • Environment - Set fog to style Linear, z-start 3400.00, z-end 4100.00, density 0.00 and color (30.00%, 30.00%, 5.00%)
            • Player Group - Pick every player in Cinematic_PlayerGroup and do (Actions)
              • Loop - Actions
                • Camera - Apply Camera 001 <gen> for (Picked player) over 0.00 seconds
            • Cinematic - Fade in over 1.00 seconds using texture White Mask and color (0.00%, 0.00%, 0.00%) with 0.00% transparency
            • Player Group - Pick every player in Cinematic_PlayerGroup and do (Actions)
              • Loop - Actions
                • Camera - Apply Camera 010 <gen> for (Picked player) over 25.00 seconds
            • Set Cinematic_TempPoint = (Center of Rect 002 <gen>)
            • Sound - Play Swish_1 <gen> at 100.00% volume, located at Cinematic_TempPoint with Z offset 0.00
            • Custom script: call RemoveLocation(udg_Cinematic_TempPoint)
            • Set Cinematic_StepDelay = 3.50
          • ElseIf (Cinematic_Step == 8) then
            • // Wait 3.5 sec – nothing
            • Set Cinematic_StepDelay = 0.00
          • ElseIf (Cinematic_Step == 9) then
            • // Explosion at rect 002
            • Set Cinematic_TempPoint = (Center of Rect 002 <gen>)
            • Unit - Create 1 Explosion Dummy for Player 1 (Red) at Cinematic_TempPoint facing Default building facing degrees
            • Unit - Kill (Last created unit)
            • Sound - Play Explosion_2 <gen> at 100.00% volume, located at Cinematic_TempPoint with Z offset 0.00
            • Set Cinematic_TempGroup = (Units within 800.00 of Cinematic_TempPoint)
            • Unit Group - Pick every unit in Cinematic_TempGroup and do (Actions)
              • Loop - Actions
                • Unit - Kill (Picked unit)
            • Custom script: call DestroyGroup(udg_Cinematic_TempGroup)
            • Custom script: call RemoveLocation(udg_Cinematic_TempPoint)
            • // Play Swish_2 at rect 009
            • Set Cinematic_TempPoint = (Center of Rect 009 <gen>)
            • Sound - Play Swish_2 <gen> at 100.00% volume, located at Cinematic_TempPoint with Z offset 0.00
            • Custom script: call RemoveLocation(udg_Cinematic_TempPoint)
            • Set Cinematic_StepDelay = 2.50
          • // ----- Continue all remaining steps from your original trigger -----
          • // Each step corresponds to a wait; uses the original wait durations as step delays.
          • // When you reach the end of the intro, run the end trigger.
          • ElseIf (Cinematic_Step == [last_step]) then
            • Trigger - Run Cinematic Intro End <gen> (ignoring conditions)
            • Trigger - Turn off (This trigger)
            • Set Cinematic_Running = False
          • EndIf
          • // After each step, schedule the next
          • Set Cinematic_Step = Cinematic_Step + 1
          • If Cinematic_Step <= [total_steps] then
            • Countdown Timer - Start Cinematic_Timer as a One-shot timer that will expire in Cinematic_StepDelay seconds
          • EndIf
        • EndIf
      • EndIf
  • Cinematic Intro End
    • Events
    • Conditions
    • Actions
      • // ----- Reset camera and cinematic mode -----
      • Player Group - Pick every player in Cinematic_PlayerGroup and do (Actions)
        • Loop - Actions
          • Camera - Reset camera for (Picked player) to standard game-view over 0.00 seconds
      • Cinematic - Fade in over 0.00 seconds using texture White Mask and color (0.00%, 0.00%, 0.00%) with 0.00% transparency
      • Cinematic - Turn cinematic mode Off for (All players)
      • // ----- Final fog and environment (from original end) -----
      • Environment - Set fog to style Linear, z-start 3600.00, z-end 4300.00, density 0.00 and color (30.00%, 30.00%, 5.00%)
      • // ----- Order heroes to move to entrance positions -----
      • Unit - Order Thrall (Unmounted) 0013 <gen> to Attack-Move To (Center of Rect 028 <gen>)
      • Unit - Order Sylvanas 0004 <gen> to Attack-Move To (Center of Rect 028 <gen>)
      • Unit - Order Vol'jin 0037 <gen> to Attack-Move To (Center of Rect 028 <gen>)
      • // ----- Create the 7 ghouls (same as original end) -----
      • Set FallenGhoul_AttackG1 = (Create new unit group)
      • Unit - Create 7 Fallen Ghoul for Player 11 (Dark Green) at (Center of Plague Enterance <gen>) facing Default building facing degrees
      • Wait 0.10 seconds
      • Unit Group - Pick every unit in (Units in Plague Enterance <gen> matching ((Unit-type of (Matching unit)) Equal to Fallen Ghoul)) and do (Actions)
        • Loop - Actions
          • Unit Group - Add (Picked unit) to FallenGhoul_AttackG1
          • Unit - Order (Picked unit) to Attack-Move To (Center of Rect 028 <gen>)
      • // ----- Ensure entrance trigger is enabled (it should be) -----
      • Trigger - Turn on Enterance <gen>
      • // ----- Enable AI and selection -----
      • Set AI_Enabled = True
      • Selection - Enable Selection: True, Enable Selection Circle: True


But at the same time… it feels kind of diabolical 💀

So I wanted to ask:
  • Is this a reasonable approach for longer / co-op cinematics?
  • Are there more standard or simpler ways to handle multiplayer skipping?
  • Am I solving a real problem here, or just overcomplicating things?
At this point I’m half expecting someone to tell me to stop before I fully commit to this system… so feel free to save me if needed, or is this worth doing for having a template for future cinematics.

Any feedback or alternative approaches would be really appreciated.

So this system is supposed to replace inaccurate waits and timing that I’m still struggling to achieve in the original trigger with waits as shown below:
 
Normally i wouldn't want to dabble with all that, simply for cinematics.
But, if this is a cinematic-heavy project of sorts, it seems worth it?!

Then again, trying is more important than studying - maybe you can get away with asking players before cinematics to skip?
 
Normally i wouldn't want to dabble with all that, simply for cinematics.
But, if this is a cinematic-heavy project of sorts, it seems worth it?!

Then again, trying is more important than studying - maybe you can get away with asking players before cinematics to skip?
Yeah, that’s exactly the point xd this project is cinematic-heavy, so I wanted to avoid the usual “wait spam” approach and try something more structured.

What I was aiming for here is more of a small “cinematic engine” rather than a one-off system. Basically:
  • step-based execution instead of hardcoded sequences
  • synchronized timing without waits
  • and a simple multiplayer-safe skip system
I’m aware the current version isn’t ideal yet — especially the way step actions are handled (it doesn’t scale nicely and isn’t very reusable). I’m already looking into cleaner approaches for that part, like decoupling actions from the step logic.

The goal is to eventually have something I can reuse across multiple cinematics and even future maps, not just this one.

But yeah, I get your point, for smaller projects this would definitely be overkill i think...


I did change some stuff from the original idea + added Bribe's Table V6, now having this somewhat optimized, I'll try dispatching actions and see how it goes...:peasant-work-work:

If you're interested, I can share how I handled the skip system and step loop.
 
If you are using audio clips for entire cinematics I suggest splitting them into smaller clips. This helps with synchronization, even when timers are involved. Its more pertinent if audio clips are longer than 30 seconds.

If youre still using the step 1 -> 2 -> 3 method Ill offer an alternative method. It may not be optimal but allows for greater configurability. Using a periodic timer every f.e. 0.10 seconds and add counter that increments by +1. Instead of step 8 or 9 you can use if counter = 590. The drawback is you have to remember how long passes, the trigger runs more than necessary and there arnt clearly defined steps.
 
If you are using audio clips for entire cinematics I suggest splitting them into smaller clips. This helps with synchronization, even when timers are involved. Its more pertinent if audio clips are longer than 30 seconds.

If youre still using the step 1 -> 2 -> 3 method Ill offer an alternative method. It may not be optimal but allows for greater configurability. Using a periodic timer every f.e. 0.10 seconds and add counter that increments by +1. Instead of step 8 or 9 you can use if counter = 590. The drawback is you have to remember how long passes, the trigger runs more than necessary and there arnt clearly defined steps.
Yeah, that makes sense; especially the part about splitting longer audio clips, I can definitely see how that would help with synchronization 👍


For the timing approach, I did consider a counter/timeline-style system like you described. It’s flexible, but I felt it gets harder to reason about once the cinematic grows (tracking things like “counter = 590” etc)

What I ended up going for instead is a small step-based loop where each step schedules the next one. Something like this at the core (simplified):

  • Cinematic Loop
    • Events
      • Time - Cinematic_Timer expires
    • Conditions
      • Cinematic_Running Equal to True
    • Actions
      • -------- // ----- ?? If skipped, end immediately ----- --------
      • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
        • If - Conditions
          • Cinematic_Skipped Equal to True
        • Then - Actions
          • Countdown Timer - Pause Cinematic_Timer
          • Set Cinematic_Running = False
          • Trigger - Run Cinematic End <gen> (ignoring conditions)
          • Trigger - Turn off (This trigger)
          • Skip remaining actions
        • Else - Actions
      • -------- // ----- Execute the action for the current step ----- --------
      • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
        • If - Conditions
          • Cinematic_StepAction[Cinematic_Step] Not equal to No Trigger
        • Then - Actions
          • Trigger - Run Cinematic_StepAction[Cinematic_Step] (ignoring conditions)
        • Else - Actions
      • -------- // ----- Move to next step ----- --------
      • Set Cinematic_Step = (Cinematic_Step + 1)
      • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
        • If - Conditions
          • Cinematic_Step Greater than Cinematic_TotalSteps
        • Then - Actions
          • -------- // Cinematic finished --------
          • Countdown Timer - Pause Cinematic_Timer
          • Set Cinematic_Running = False
          • Trigger - Run Cinematic End <gen> (ignoring conditions)
          • Trigger - Turn off (This trigger)
        • Else - Actions
          • -------- // Schedule next step (minimum 0.03 to avoid instant loops) !!!! --------
          • Set Cinematic_TempReal = Cinematic_StepDelay[Cinematic_Step]
          • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
            • If - Conditions
              • Cinematic_TempReal Less than 0.03
            • Then - Actions
              • Set Cinematic_TempReal = 0.03
            • Else - Actions
              • Countdown Timer - Start Cinematic_Timer as a One-shot timer that will expire in Cinematic_TempReal seconds
  • Cinematic Start
    • Events
    • Conditions
    • Actions
      • -------- // ----- Reset state ----- --------
      • Set Cinematic_Running = True
      • Set Cinematic_Skipped = False
      • Set Cinematic_Step = 1
      • -------- // replace with actual number --------
      • Set Cinematic_TotalSteps = 11
      • -------- // ----- Start the loop ----- --------
      • Countdown Timer - Start Cinematic_Timer as a One-shot timer that will expire in Cinematic_StepDelay[1] seconds
      • Trigger - Turn on Cinematic Loop <gen>
      • Trigger - Turn on Cinematic Skip <gen>
      • Trigger - Run Cinematic Player G Detection <gen> (ignoring conditions)
  • Cinematic Define Steps
    • Events
    • Conditions
    • Actions
      • -------- // ----- Example: define steps for intro cinematic ----- --------
      • Set Cinematic_StepAction[1] = C1S1 <gen>
      • Set Cinematic_StepDelay[1] = 2.00
      • -------- /////////////////////////////////////// --------
      • Set Cinematic_StepAction[2] = C1S2 <gen>
      • Set Cinematic_StepDelay[2] = 2.00
      • -------- ///////Continue for all scenes////////////////// ----


So instead of tracking time globally, each step defines its own delay and schedules the next one.

It’s still a bit rough (especially how step actions are assigned + loads of triggers), but the idea is to keep it more readable and reusable across multiple cinematics.

That said, I can see the timeline approach working well for more free-form sequences.

I'm still implementing scenes from the original blueprint, I'll see how it goes.
 
Yeah, that makes sense; especially the part about splitting longer audio clips, I can definitely see how that would help with synchronization 👍


For the timing approach, I did consider a counter/timeline-style system like you described. It’s flexible, but I felt it gets harder to reason about once the cinematic grows (tracking things like “counter = 590” etc)

What I ended up going for instead is a small step-based loop where each step schedules the next one. Something like this at the core (simplified):

  • Cinematic Loop
    • Events
      • Time - Cinematic_Timer expires
    • Conditions
      • Cinematic_Running Equal to True
    • Actions
      • -------- // ----- ?? If skipped, end immediately ----- --------
      • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
        • If - Conditions
          • Cinematic_Skipped Equal to True
        • Then - Actions
          • Countdown Timer - Pause Cinematic_Timer
          • Set Cinematic_Running = False
          • Trigger - Run Cinematic End <gen> (ignoring conditions)
          • Trigger - Turn off (This trigger)
          • Skip remaining actions
        • Else - Actions
      • -------- // ----- Execute the action for the current step ----- --------
      • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
        • If - Conditions
          • Cinematic_StepAction[Cinematic_Step] Not equal to No Trigger
        • Then - Actions
          • Trigger - Run Cinematic_StepAction[Cinematic_Step] (ignoring conditions)
        • Else - Actions
      • -------- // ----- Move to next step ----- --------
      • Set Cinematic_Step = (Cinematic_Step + 1)
      • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
        • If - Conditions
          • Cinematic_Step Greater than Cinematic_TotalSteps
        • Then - Actions
          • -------- // Cinematic finished --------
          • Countdown Timer - Pause Cinematic_Timer
          • Set Cinematic_Running = False
          • Trigger - Run Cinematic End <gen> (ignoring conditions)
          • Trigger - Turn off (This trigger)
        • Else - Actions
          • -------- // Schedule next step (minimum 0.03 to avoid instant loops) !!!! --------
          • Set Cinematic_TempReal = Cinematic_StepDelay[Cinematic_Step]
          • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
            • If - Conditions
              • Cinematic_TempReal Less than 0.03
            • Then - Actions
              • Set Cinematic_TempReal = 0.03
            • Else - Actions
              • Countdown Timer - Start Cinematic_Timer as a One-shot timer that will expire in Cinematic_TempReal seconds
  • Cinematic Start
    • Events
    • Conditions
    • Actions
      • -------- // ----- Reset state ----- --------
      • Set Cinematic_Running = True
      • Set Cinematic_Skipped = False
      • Set Cinematic_Step = 1
      • -------- // replace with actual number --------
      • Set Cinematic_TotalSteps = 11
      • -------- // ----- Start the loop ----- --------
      • Countdown Timer - Start Cinematic_Timer as a One-shot timer that will expire in Cinematic_StepDelay[1] seconds
      • Trigger - Turn on Cinematic Loop <gen>
      • Trigger - Turn on Cinematic Skip <gen>
      • Trigger - Run Cinematic Player G Detection <gen> (ignoring conditions)
  • Cinematic Define Steps
    • Events
    • Conditions
    • Actions
      • -------- // ----- Example: define steps for intro cinematic ----- --------
      • Set Cinematic_StepAction[1] = C1S1 <gen>
      • Set Cinematic_StepDelay[1] = 2.00
      • -------- /////////////////////////////////////// --------
      • Set Cinematic_StepAction[2] = C1S2 <gen>
      • Set Cinematic_StepDelay[2] = 2.00
      • -------- ///////Continue for all scenes////////////////// ----


So instead of tracking time globally, each step defines its own delay and schedules the next one.

It’s still a bit rough (especially how step actions are assigned + loads of triggers), but the idea is to keep it more readable and reusable across multiple cinematics.

That said, I can see the timeline approach working well for more free-form sequences.

I'm still implementing scenes from the original blueprint, I'll see how it goes.
Thats pretty neat, Id suggest considering functions instead of triggers but thats pretty much small potatoes. I havnt done it myself but I think it works? At least this runs without issues.
  • Untitled Trigger 001
    • Events
    • Conditions
    • Actions
      • Custom script: endfunction
      • Custom script: function egg takes nothing returns nothing
      • Custom script: endfunction
      • Custom script: function egg1 takes nothing returns nothing
      • Custom script: endfunction
      • Custom script: function egg2 takes nothing returns nothing
Edit: Tho I realized I dont know if functions can be put into variables or similar so it may not work.

Id also suggest when you define the steps you can use the counter there instead and receive basically the same effect as a periodic timer. So f.e.:
  • -------- // ----- Example: define steps for intro cinematic ----- --------
  • Set Step_Counter = Step_Counter + 1
  • Set Cinematic_StepAction[Step_Counter] = C1S1 <gen>
  • Set Cinematic_StepDelay[Step_Counter] = 2.00
  • -------- /////////////////////////////////////// --------
  • Set Step_Counter = Step_Counter + 1
  • Set Cinematic_StepAction[Step_Counter] = C1S2 <gen>
  • Set Cinematic_StepDelay[Step_Counter] = 2.00
  • -------- ///////Continue for all scenes////////////////// ----
 
I did think about using functions instead of triggers, but if I'm not wrong you can’t really store functions in variables in GUI, so I went with triggers as a workaround.

The step counter approach you mentioned is actually pretty close to what I’m doing building the sequence incrementally and assigning actions + delays per step.

Main difference is I’m trying to keep execution separated from definition so I can reuse later


Edit: I just searched, you actually can lol ???


Actually I looked into that a bit more.... you can technically store function names as strings, but you can’t dynamically execute them, so it kind of falls apart unless you go back to if/then dispatching.

That’s mainly why I stuck with triggers; they behave more like callable references in this case.
 
Last edited:
Back
Top