In Yesukai's
Looping Multimanager tutorial, Yesukai explained that a simple yet effective way to create a looping
multi_manager. The nice thing about his method is that once turned on, you can just leave the multi_manager to do it's job. With some clever use of triggers, you could even control the progress of a level. Forcing the player to move quickly by destroying the level around him.
This method isn't without its flaws, however. What if you need to
stop the multi_manager sequence? Ah, I hear you say, you just use a killtarget somewhere to do away with the multi_manager. Good suggestion, however, ultimately limited since that would totally obliterate any chance for you to re-trigger the multi_manager. What's needed is a relayed command with which you can check whether the multi_manager is allowed to continue or not. That and a keen sense of timing.
multi_manager Behavior
Before we get our hands dirty, I first want to explain a bit of theory about the multi_manager. You can totally disregard this section if you wish, but I firmly believe that one should at least know
why a certain object works in a certain way to use it to its full advantage.
When a multi_manager is
not multithreaded it will simply register itself as
on and busy, so any trigger events directed to that multi_manager will be ignored (Hence the inability to turn it off). However, when it
is mutlithreaded, it will create a clone of itself when triggered, and will do so for every subsequental trigger event.
Knowing this, it becomes quite clear
why looping multi_managers work the way they do -- when not multithreaded, a multi_manager won't be able to trigger itself.
Another point I'd like to bring up is the multi_managers timescale. I found in some occasions a value of 0 (zero, immediate) gave some unexpected results and made timing a complicated sequence quite unclear. On the other end, a value of 1 second in game time
is quite long. The minimum amount of time workable in such sequences was 0.001. That being the case one might as well get in the habit of using 0.001 as a minimum in the multi_managers timescale. This is, of course, personal preference.
Lastly it's good to observe that when adding a
key to the multi_manager it'll transform all uppercase to lowercase letters. Make sure you don't include capitals in your naming-convention.
In regards to the issue at hand, we have established that we:
- Cannot tigger/shut off the multi_manager when it's active and not multithreaded
- Can only create another instance of the multi_manager when multithreaded.
States
The first logical step would be to create a
trigger_relay. Let the multi_manager target the trigger_relay and vice-versa. But this only works for the looping, And once turned on it's impossible to shut it down without also removing one of the main entities.
What we need is a
State function (State as in
am I on? or
am I off?). For this we use a combination: the
env_global and the
multisource.
The env_global, which we call
mainloopglobal contains a variable which is either
off or
on. In this case we call it
mainloopactive. Now there's several modes to set the env_global, but since we want to "toggle" things we set the
Trigger Mode to
Toggle. Because we want the loop to be inactive when we start the level, we set the
Initial State to
Off, after which we check the flag
Set Initial State to activate the state as the level starts.
The multisource, which we call
mainloopstate, is nothing more than a
if-then gate. It checks wether its state master is on, in which case it will return
yes to any query made. This particular multisource has the env_global's name as its
Global State to Set, which of course is
mainloopactive.
Looping
Okay, so now we have a "state" function we can check... then what? We should make sure the multi_manager, from now on
mainloop, relays itself through a trigger entity. After some testing I found the
func_button the most suitable since it has 'master', reset and render properties. The render properties we use to hide the button from player view for it will only be used by the multi_manager. We connect this func_button, which we aptly name
mainloopgo, by it's "master" property with the multisource,
mainloopstate and set its renderproperties to Texture/0. Its target, ofcourse to the multi_manager
mainloop and its
Delay Before Reset very low, like 0.01, since we might want to access this button mutiple times over a short period. Now, only thing to do now is to let the multi_manager
mainloop target the new button as its last action and the looping is set.
On and On and On and On...
Ah, and now the moment where you have all been waiting for: Turning On And Off.
Of course, there's several ways to set the whole thing in motion. Most important is to FIRST set the env_global
mainloopglobal to "On" after which you'd trigger the func_button
mainloopgo. To turn it off again later just set the env_global
mainloopglobal to "Off", and when the multi_manager
mainloop tries to trigger the func_button
mainloopgo it will not succeed, effectively ending the loop.
What I would generally suggest is to have two more func_buttons (
loopon and loopoff) either target their own multi_manager (
mainloopon and mainloopoff) which in turn trigger the env_global
mainloopglobal after which only the multi_manager
mainloopon has to trigger the func_button
mainloopgo. This multi_manager relaying, especially that of the loopoff button, may seem a bit overdone, but I'd imagine you want to do a bit more with an on and off function than just loop something. (powerup/powerdown sequence, sounds, lights)
Wrapping it up
When you have implemented this into your map, you'll be able to toggle any series of sequences on and/or off. Pretty nifty, eh? Of course, there's some challenges left. Smart ones among you might have already noticed the obvious pitfall here: what if the player first uses the "Off" button? Why are there buttons? Shouldn't there be more? But I have to say... that's for another time. You've learned to switch a looping multi_manager off by sheer willpower using a creative entity setup. Now go forth and multi_manage.
Cray.
PS. Included for your pleasure is a simple
map which shows the setup in a test-situation. Also a
PDF with a flowchart of what is happening in said map.
The entity setup
1. buttonon. func_button.
target:(MM) mainloopon
2. buttonoff. func_button .
target:(MM) mainloopoff
3. mainloopon. multi_manager
not multithreaded
(EG)mainloopglobal - 0.01 (set to "On")
(FB)mainloopgo - 0.1
4. mainloopoff. multi_manager
not multithreaded
(EG)mainloopglobal - 0.01 (reset to "Off")
5. mainloopgo. func_button
target: (MM)mainloop
master: (MS)mainloopstate only trigger target when master = "On"
rendermode/fx: Texture/0 not an interface button, don't show
Delay before reset: 0.01
6. mainloop. multi_manager
not multithreaded
well, here's where you put your loop
end it with:
(FB)mainloopgo - N
7. mainloopglobal. env_global
Global State To Set: mainloopactive
Trigger Mode: Toggle
Initial state: Off
Set Initial State: flag=on
8. mainloopstate. multisource
Global State Master: mainloopactive
This article was originally published on the
Valve Editing Resource Collective (VERC).
TWHL only archives articles from defunct websites. For more information on TWHL's archiving efforts, please visit the
TWHL Archiving Project page.