Special Effects use Sub-Animations which don't really work here. A Dummy unit would work nicely though.
That being said, I made this library to solve the Special Effect problem:
vJASS:
library RingProgressHelper
globals
private hashtable RingHash = InitHashtable()
endglobals
private function StopPlaying takes nothing returns nothing
local timer t = GetExpiredTimer()
local integer id = GetHandleId(t)
local effect sfx = LoadEffectHandle(RingHash, id, 0)
call BlzSetSpecialEffectTimeScale(sfx, 0.0)
call FlushChildHashtable(RingHash, id)
call DestroyTimer(t)
set t = null
endfunction
// This resets the Birth animation and jumps to the given "tick" or index.
// This is less smooth than RingProgressPlay but fixes any potential issues.
// I haven't tested RingProgressPlay enough to be sure if this is even necessary.
function RingProgressSet takes effect sfx, integer tick returns nothing
local timer t = CreateTimer()
local real desiredDuration = 0.625 * tick
local real actualDuration = 0.05 // Anything less than 0.05 won't work properly
local real timeScale = desiredDuration / actualDuration
call BlzPlaySpecialEffect(sfx, ANIM_TYPE_BIRTH)
call BlzSetSpecialEffectTimeScale(sfx, timeScale)
call TimerStart(t, actualDuration, false, function StopPlaying)
call SaveEffectHandle(RingHash, GetHandleId(t), 0, sfx)
set t = null
endfunction
// This assumes the Birth animation is already playing and
// jumps forward by the number of given "ticks" or indexes.
// This is smoother than RingProgressSet but may produce off results if you
// call it on the same Special Effect multiple times in under 0.05 seconds.
// I haven't tested it enough to be sure, so use with caution.
function RingProgressPlay takes effect sfx, integer ticks returns nothing
local timer t = CreateTimer()
local real desiredDuration = 0.625 * ticks
local real actualDuration = 0.05 // Anything less than 0.05 won't work properly
local real timeScale = desiredDuration / actualDuration
call BlzSetSpecialEffectTimeScale(sfx, timeScale)
call TimerStart(t, actualDuration, false, function StopPlaying)
call SaveEffectHandle(RingHash, GetHandleId(t), 0, sfx)
set t = null
endfunction
endlibrary
This code takes advantage of the Birth animation of the
Ring Progress Indicator model, which plays through all
16 animations over 10 seconds.
What I did was divide the animation duration by
16 to get the time between each animation index: 10/16 =
0.625 seconds. With that value known, I am able to determine the amount of time that has to pass to reach a given
animation index (or tick). To mimic the instant nature of SetUnitAnimationByIndex() I rely on a really fast Timescale to run that much animation time over
0.05 seconds. I tested other values and anything less than a 0.05 second timer didn't work.
Basically each time you call
RingProgressPlay() you pass in your Special Effect and how many ticks you want it to progress, and it goes into super fast forward mode to get you to that next animation. This appears to be instant for the user.
Note that
RingProgressPlay() may produce off results if you call it on the same Special Effect while it's already running, I'm not actually 100% sure. But that's a short time frame of 0.05 seconds so it may never happen, it all depends on your use case. Luckily, you can use
RingProgressSet() instead which resets the Animation each time to ensure consistency. Again, I can't say for certain if it's an actual problem, I didn't test it enough to find out.
Also, this is how you could create the Special Effect and ensure that it plays nicely with the Ring functions:
-
Actions
-
Special Effect - Create a special effect at (Center of (Playable map area)) using CaptureProgress.mdx
-
Set VariableSet sfx = (Last created special effect)
-
Special Effect - Play Special Effect: sfx, Animation: Birth
-
Special Effect - Set Time Scale of sfx to 0.00