I recently started putting together a large article about various good practices Clickteam Fusion 2.5 developers should be aware of. A 'Top 30 tips' type of thing, aimed primarily at beginner and intermediate users. One of those tips was: Learn how Fusion scopes.
But as soon as I waded into this topic, it quickly became apparent that it required its own blog post. Its own very long blog post. Well, here's that very long blog post. (Stay tuned for the article with the other 29 or so tips - that's probably coming in the next weeks).
If you're not familiar with the word "scope" in the context of Clickteam Fusion, don't worry - you're almost certainly familiar with the concept. One of the great things about Fusion is that it frequently scopes things automatically, in a way that is intuitive to the user....most of the time.
The basics of scoping in Fusion are very simple and easy to grasp. But as will soon become apparent, it becomes pretty byzantine the deeper you dig, with all sorts of unintuitive rules and weird exceptions that will sooner or later generate frustrating bugs in your projects, unless you familiarise yourself with their hidden idiosyncracies. But fear not. At the end of this post, I summarise it all into some simple guidelines.
Scoping in Clickteam Fusion 2.5 - The Basics
OK, let's dive in. In this first example, the simple action set angle of pear to 45 results in all pears being tilted. Naturally.
However, in the next example, the exact same action has only managed to rotate two pears. That's because the preceding condition (Y position of pear > 100) has scoped those two pears. In other words, Fusion has singled out those pears which have a Y value greater than 100px (ie. are visually lower down, and closer to the bottom of the screen, than y100). Thus, the subsequent action, because it takes place in the same event, now only concerns itself with those pears. I'm probably not telling you anything new here.
In the next example, notice that this scoping is cumulative. Both conditions have combined to scope just one pear (the only pear that happens to be both lower than 100px on the Y axis and further right than 100px on the X axis). And both actions have honoured that scope (only the scoped pear was tilted, and only the scoped pear was alpha-faded).
Scope on the left
So far so good, right? In most situations, scoping is as straight-forward as this. However, there are certain situations where Fusion's idiosyncrasies can make scoping less intuitive. One case is when two objects are compared. Look at the following two examples:
Mathematically, both above events appear to be identical. In each case we're talking about pears which are lower than apples. Yet only the pears in the first example were successfully scoped (the second example applied the tilting indiscriminately to all pears).
This is because when you compare two objects in a condition, Fusion generally only scopes the leftmost object. If this seems weird, then it may help to remember that a condition in Fusion is essentially an IF statement.
Let's replace the apple with the number 100 again, and look at these two events again:
The first statement, essentially, reads as follows:
If the Y position of a pear is greater than 100, then rotate the pear.
The second statement reads:
If 100 is less than the Y position of a pear, then rotate the pear.
While that second statement isn't exactly broken, it's kind of....odd, right? The focus of the sentence seems now to be the number 100, so it doesn't feel entirely right to assume that we're talking about any specific pears. It's a bit like, instead of saying.....
Fred went to the the third drawer, and got himself a spoon
.....I instead said.....
The drawer was the third from the top, and Fred got himself a spoon
There's something a bit off about the second statement, right? You can still infer that Fred's spoon came from the same drawer that was the third from the top, but it's not 100% clear. And since computers have a much lower tolerance for ambiguity than humans do, you can imagine that a computer would struggle to make that inference.
Hopefully this will help you get an intuitive sense of why Fusion only scopes objects from the left. Regardless, as long as you remain aware of this idiosyncrasy, it's not likely to cause you many problems.
Scoping and overlap
There are some exceptions to the 'scope on the left' rule. One such exception occurs, thankfully, when we test for overlap.
As the following example shows, when you test for overlapping between two objects, both objects are scoped, regardless whether they're on the left or right. Just as well - testing for overlaps would be near-pointless if it didn't result in both specific objects being scoped! The same is true when testing for collision, by the way.
Scoping and newly created objects
Creating new objects also affects scoping. When a new object is created, all subsequent actions in that event will be scoped to the newly created object(s).
Notice in the next example how only the 2 newly created pears are tilted and faded, while the original 5 pears are untouched.
Now notice below how the same event - but with the actions reordered - achieves a very different result. Because the fading and tilting actions appear at the beginning, before anything is scoped, they apply to all pears. So all 5 original pears are modified. The 2 new ones are not modified because they only get created after the fading and tilting actions have taken place:
So far so good. But now let's shuffle these actions around a little:
Hmmm. Shouldn't one of those new pears (the one that was created earlier, at x50 y100) now be faded? Perhaps the second newly created pear is stuffing things up somehow? Let's remove it:
Nope. We get essentially the same result. Both the fading and the tilting get applied to all previously existing pears, and not to the new pear (even though the fading is supposedly applied after it's created).
Perhaps newly created objects only affect scope when they are the topmost action? Let's try something:
Nope. In the above example (exactly the same as the preceding one, except that the tilting action is applied to apples instead of pears) the create new pear action does indeed affect scope. Only the newly created pear is faded. This all seems pretty inconsistent and weird. Who knows how exactly it works under the hood? But I can start to cobble together a hypothesis:
Perhaps, the first time an object is encountered in an action, its scoping is cemented for the rest of the event (including any scoping inherited from conditions, naturally).
The above hypothesis appears to agree with everything we've seen so far. It explains that when a pear is first encountered in Example newlyCreated #5 it cements the scope to that 1 pear (only that pear is faded).
And it explains that since the first pear action encountered in Example newlyCreated #4 applies to all 5 pre-existing pears, the scoping is cemented to those 5 pears (which is why fading is applied later to those 5, but not to the 6th that was created a moment after the scoping took place).
It also explains the next example. Because the first action applies to the 5 pre-existing pears, the third action does too. Thus, the newly created pear is neither destroyed nor faded. So far this hypothesis is looking good.
However, before I get too excited, I need to come clean and admit that, if you hadn't noticed already, the hypothesis doesn't actually fit with #1 newlycreated, where not 1 but 2 actions affect scope (both created pears are successfully faded and tilted).
My hypothesis also doesn't explain this:
The above example is identical to the previous one, but for the extra condition (Y position of pear >100). Yet while the newly created pear was spared in the previous example, it's destroyed in this one. Why? The first action here is clearly honouring the scope from the condition, since it destroys those pears which are lower than y=100 while leaving the top 3 intact.
So why does it also destroy the newly created pear? The newly created pear was neither part of the original scope (it didn't even yet exist when the scope was determined), nor does it qualify for that scope once it's created (since its Y position is less than 100).
Sadly, the best conclusion that I'm able to muster is that Clickteam Fusion is kind of like the English language: dazzling in its overall utilitarian simplicity, yet knotted with arcane rules below the surface that present seemingly even more exceptions than there are rules. People might think they know exactly how it works, but no fully does. Yet somehow we all get by anyway.
To try and summarise this section then: Creating objects at the beginning of an event automatically scopes those objects. This is highly useful. There may also be times when you want to create an object lower down in the action list - but do so at your peril, as it may produce unforseen consequences.
Scoping and fastloops
Scoping can also be unintuitive when fastloops are involved. In the next example, the insertion of the fastloop has broken the scoping of the pears, resulting in all the pears being rotated, rather than just the ones that are Y>100.
In a way, this makes sense, since the fastloop is wedged between the pear condition and the pear action. It shoots us out of event #1 and into event #2, thereby disrupting the hermetic nature of the event. We can perhaps forgive it for forgetting event #1's initial scoping once it eventually returns from the fastloop in #2.
So what happens when we move the Start loop "fade apple" 1 times to the end of the action list?
Well, nothing. It's no longer wedged in between the pear condition and the pear action, and there doesn't seem to be any intuitive reason why the fastloop should continue to break the pear's scope. Yet it does. It would seem that the mere presence of a fastloop call anywhere in event #1 breaks the scoping of the pears. They are all tilted, rather than just the Y>100 ones.
It gets weirder though.
This time, look what happens if we modify the fastloop to include a pear in it:
The fastloop successfully fades all the pears, but when it comes back to event #1, not only does it still not honour the Y>100 condition, but now it brings with it its own seemingly senseless scoping that no one asked for (the lower-right pear). In my view, this behaviour is no longer just a quirk but a definite flaw.
You might wonder what, if anything, was so special about that lower-right pear that gave it priveleged scoped status. The answer is that it happened to be the last pear that I created (in the frame editor). So at least it's a predictable flaw. But it does hint at some potentially useful functionality. Observe the next example:
As before, the fastloop causes Fusion to dishonour the Y>100 scoping. And as before, the fastloop imprints its own scoping on the event. But this time, that scoping has a somewhat satisfying logic to it: we specially created two pears in our fastloop, and upon returning to event #1, it is precisely those two pears that are scoped. I can imagine some good uses for that.
But don't get too excited...
This time, all we've done is add some extra actions to the fastloop, and suddenly our two newly created pears are no longer scoped. We are back to just the most recently created pear being scoped. I don't know about you, but this sort of behaviour seems far too flaky and unpredictable for me to ever rely on it. My advice is to exercise caution and avoid mixing fastloops and scoping. If in doubt, call the fastloop in a separate event.
Scoping and forEach loops
ForEach loops have their own scoping idiosyncrasies that are often different from those of fastloops. For one thing, a forEach loop's mere presence doesn't break scope (or at least, not in the same way that a fastloop's does).
Below is a recreation of Example fastloops #1 from above, but using a forEach loop instead of a fastloop. Notice how the apple forEach loop doesn't interfere with the successful pear scoping.
Furthermore, watch what happens when we call a forEach loop on pears instead of apples, below. The scoping from event #1 is actually remembered in event #2 (only the Y>100 pears are faded)! This gives you the ability to call a forEach loop on only a specific subset of objects, which is useful in various situations (eg. run a forEach loop that makes enemies shoot at the player...but only those which are close to the player).
Just to be clear, this is an ability that is unique to forEach loops. The exact same scenario using a fastloop causes scoping to be broken (all pears are faded, plus only the most recently created one is tilted):
So far, forEach loops seem like the good guys. Their mere presence doesn't seem to break scoping, and they obediently inherit scoping from their parent conditions. And of course, scoping is one of the main purposes of a forEach loop in the first place, as the very name "for each" implies: the forEach loop iterates through each instance of an object, one at a time, scoping each in turn as it progresses. Each time, all actions inside the forEach loop will be performed in the context of one specific, scoped object.
But alas, forEach loops also carry their own set of weird scoping problems.
In the next example, we create two new pears next to the apples. Then, we ask for the pears to be tilted. Intuitively, we might expect one of two scenarios: (a) that scoping will carry over from the forEach loop and only the 2 new pears will be tilted, or (b) that it won't, and all 7 pears will be tilted. We probably wouldn't expect the actual outcome, where all but the new pears were tilted. Strangely, event #1 behaves as if the order of its actions were reversed.
Incidentally, replacing the forEach loops with a fastloop, in an otherwise identical scenario, produces the exact opposite result. Now, only the new pears are tilted.
While a fastloop that returns to its parent event can bring with it new scoping, a forEach loop seems to arrive late for the party; it's as if any new objects it created don't quite make it back to the parent event. This can be seen explicitly in the next example:
In the above example, we start off with 5 pears (created in the Frame Editor). We then shoot off via a forEach loop to create 5 more. When that's done, we come back, and ask Fusion to tell us how many pears there are in total. Fusion declares confidently that there are 5, when we can see with our own eyes that there are 10.
We already know by now that creating new objects can do some wacky things to scoping. So let's see what happens if we try a similar scenario to the one above, but without the create new object bit:
Even now we have the same problem. The number 1 was added to Global Value A 5 times (since there are 5 pears, our forEach loop looped 5 times). Yet somehow, when we display this value afterwards, it is as if time has rewinded, and it's still 0.
Could it be that forEach loops are simply executed last, after all other actions? Even though the forEach loop appears to be the topmost action, is it secretly being bumped down in the queue? Upon further scrutiny, it appears that this may be what is happening. Have a look at this:
The fact that the pears in the example above end up tilted proves that the forEach loop did indeed run. Yet it was only allowed to run if at least one of the pears was ripe (ie. we changed its alterable value "ripe" from 0 to 1). But the pears only became ripe after the forEach loop had finished!
There are only two possible explanations for this that I can see. Either forEach loops have discovered the secret of time travel, or they are always executed last, no matter where they appear in the action list. The former explanation is the more compelling one. But we must keep an open mind and at least consider the possibility that Fusion routinely delays forEach loops until all other actions are completed.
Here's another example. Notice that we create a pear at the precise location of the apple. Yet when all is said and done, the pear ends up nowhere near the apple. That's because we moved the apple (but before the pear was supposed to have been created). Again, it seems as if Fusion has, without telling us, shuffled the forEach loop to the bottom of the action queue.
However, things are not this clear-cut (which by now probably shouldn't be surprising). This next example proves that although forEach loops often seem to be executed last, they aren't always executed last:
Had the forEach loop run in the above example, the application would have immediately ended. But as you can see, the application is alive and well. We can deduce from this that the order of events played out exactly as we would expect from looking at the events (for once).
First, Fusion ran a loop for each pear - but since there were no pears yet, it ran the forEach loop zero times, so it never had to see the end the application action. We can assume that only afterwards was a pear created (since the existence of a pear should have caused the forEach loop to execute). In this case at least, the forEach loop seems to have executed at the start of the action list, not at the end.
These last few examples weren't technically about scoping, but I think these idiosyncrasies are useful to know about, since they can, and do, affect scoping in certain circumstances (as we saw in Example forEach #4, for instance).
OK, so we've investigated the different ways in which Fusion automatically scopes objects (or doesn't). If you pay attention to the basic principles and steer away from the problem areas we identified, you'll find Fusion's automatic scoping to be both useful and easy to work with.
But there will still be times when Fusion won't automatically do the scoping that you need, and will require a helping hand from you. This will be the case when you need scoping to persist over multiple events (Fusion's autoscoping is predominantly limited to singular events).
Below, we have 4 pears. On top of each pear, we've also placed a worm:
Now, let's say that we want to rotate those pears whenever we press spacebar; we'll do it via a forEach loop. When we rotate the pears, the worms are left behind:
We can tell Fusion to rotate the worms the same amount as the pears, but that won't work properly. Each time we tell Fusion to rotate a pear, it does them one by one, because of the forEach loop. But when we tell it to rotate a worm, we haven't specified which worm we're talking about (ie. we haven't scoped the worms), so it rotates all of them. As a result, the worms are all rotated together, 4 times, and they end up facing the same way:
So we need a way to tell Fusion which worm we're talking about. In particular, we need some way to 'marry' each pear/worm pair. There are a number of ways to do this, though they all work on the same principle. Some people use spread values, for instance. My preferred method is to used fixed values, like so:
A fixed value is a unique number that Fusion automatically assigns to every object in the scene. It typically looks something like this: 374928. But the beauty of this method is that you don't need to actually know , or care, what the fixed value is. The point is that every object in your game already has its own unique little identification number, without you needing to do anything, which makes these fixed values perfect for manual scoping. OK, let's break down this example step by step:
EVENT 1: We launch a forEach loop for the pears. This will only run once (at start of frame).
EVENT 2: This is the forEach loop. In the first action, we create a worm at the pear. Fusion knows exactly which pear we're talking about, because the forEach loop is carefully going through each pear one by one, scoping an individual pear each time. At this point, we are in a unique position: Fusion has scoped a single instance of a pear (because of the forEach loop) and also a single instance of a worm (the one that was just created). But not just any pear and worm, of course: this pear and worm are located at the same physical X+Y location.
So while Fusion has both these objects scoped, we must take advantage of the situation and quickly find a way to slap some sort of permanent label on our duo, forever 'marrying' this particular worm with this particular pear. We do this in the second action, by assigning an alterable value (that I've chosen to call "ID") to the worm, and giving it the same value as the fixed value of the pear.
As discussed earlier, each object has a totally unique fixed value that can be used to tell it apart from all other objects. So by copying the pear's fixed value to the worm's "ID" (alterable value), we've effectively formed a link between them that we'll be able to use at any time in the future to locate the happy couple.
EVENT 4: Whenever spacebar is pressed, launch another forEach loop that will rotate the pears.
EVENT 5: The first condition (the forEach loop one) scopes the pear. Fusion now has one single pear in mind. The second condition scopes the worm, because it forces Fusion to find only the worm that has an "ID" (alterable value) that exactly matches the fixed value of a pear (which pear? the pear that was scoped in the preceding condition, of course!)
By the time we get to the actions, Fusion has successfully scoped one pear/worm couple. So when we ask Fusion to rotate the pear, it rotates just one. Then when we ask it to rotate a worm by the same amount, it knows to only rotate the worm that's 'married' to the pear.
This technique doesn't just apply to pairs (nor just to pears, har har). It can be used to combine and scope any number of items. Here, we add a cowboy hat to the mix:
Scoping - Rules of Thumb
Well, if you got through all of the above, then you know that Fusion's autoscoping can become one crazy rabbit hole. But that doesn't mean scoping needs to be intimidating or confusing. By simply remembering a few basic points, you'll be able to steer clear of 99% of Fusion autoscoping's weirdness, and reap the benefits of its intuitive, pleasant side. Here are the key points to be mindful of (as I see them, at least):
That's (not) all, folks!
This is the first such Clickteam Fusion article that I've posted, and I've no idea how many people will read it, and whether they'll find it useful. So please let me know what you thought. Tell me what you liked, and what you didn't like, either in the comments below, or at the Clickteam Fusion forums.
Stay tuned for the larger article that this post was originally meant to belong to. To be notified when new posts arrive, sign up to my newsletter.