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.
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.
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
. 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
. Because we want the loop to be inactive when we start the level, we set the Initial State
, 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
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.
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.
2. buttonoff. func_button .
3. mainloopon. multi_manager
(EG)mainloopglobal - 0.01 (set to "On")
(FB)mainloopgo - 0.1
4. mainloopoff. multi_manager
(EG)mainloopglobal - 0.01 (reset to "Off")
5. mainloopgo. func_button
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
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