The whole concept of scopes confused me for a very very long time, I had no idea what it meant and I ran into so many problems which could have easily been solved if I simply understood the scope of my variables… right so you’re probably scared already, but as a beginner maxscript you might have already run into this issue before, classic example of when/why you need to understand scopes… You test out some code in the maxscript listener, you make a nice interface, put your code into it, you’ve got a working tool, it works, you’re happy, you go have a beer in the pub, come in the next day, start up max, load up your new amazing script to show your boss and suddenly there’s an error message that wasn’t there before! When I was first learning maxscript this happened a lot and I simply couldn’t understand why it was happening. There are loads of thread on CgTalk which are titled “Script doesn’t run on first run”, or “Script doesn’t work after I restart max”.
So lets try recreate these mysterious conditions….in a really simple example.
Type into your maxscript listener
b = 10
Now copy and paste the script below into a new script window and run it….
Rollout RL_Test "Test" ( button btn1 "press me" on btn1 pressed do ( Messagebox ("B = " + b as string) ) ) Createdialog RL_Test
When you run it you’ll get this, a messagebox saying “B = 10”, now restart max and run this above example without putting “b = 10” into the maxscript listener.
You’ll get an error saying b is undefined, now this makes a fair bit of sense as we’re referencing b in a script in which b has not been defined… so fairly easy to understand why there is an issue. Less so easy to understand is when b is defined in the script but in a different scope, for example…. Run this code in your listener…
Rollout RL_Test "Test" ( button btn1 "Press me first" button btn2 "press me" on btn1 pressed do ( b = 5 ) on btn2 pressed do ( Messagebox ("B = " + b as string) ) ) Createdialog RL_Test
Press the first button then the second, we’ve defined b as equally 5 but then when ask what b is, it’s undefined? Huh? Now just to confuse you even more…. Type this into the maxscript listener….
b = 10
Now run the script above again, press btn2 first, and see that b now equals 10, and then press the btn1 and then btn2, and b now equals 5. So it appears as if our script is working… huzzah… no, not beer time yet. Restart max and run the script again and it won’t work, b will be undefined. We need to understand, the different levels of scope, Global and local.
Everything we type into the maxscript listener is in Global scope, that means that it’s available to any bit of maxscript to reference, which is useful if you want to share values and variables between maxscripts, by default it’s not what we call ‘Persistent’, it doesn’t store with the max file, so when you restart max, that value is now lost from memory.
In the above example when we press btn1 we are defining b to equal 5, but this is in the scope of the button press event, that value is only stored in memory for that event, which means when we try and call it from another event, btn2 doesn’t know what b is. The easy (sloppy/lazy/numpty/dangerous) thing is to declare it as global….
on btn1 pressed do ( global b = 5 )
Now this will put the b value into the global scope so that btn2’s pressed event will now know what b is and will tell us it’s 5. This is what happened when we type b = 10 into the maxscript listener, it defined it in global scope and both events could access it. Btn1 now realised that b is already defined in the global so it changed the value of the global b value without the need for declaring it as global.
So why are global’s sloppy/lazy/crap…? Well you might run into issues where someone else who’s written a script has used the same global variable name and then your scripts will conflict, if someone else’s script changed the value of b to “Cheeseburger” our script which expecting an integer number will get very confused!
So to deal with situation we need to predeclare b as a value in our script, there are a couple of ways to do it.
We can declare it after we have defined our rollout items, however we have to declare it as either Local or Global, the following won’t work.
Rollout RL_Test "Test" ( button btn1 "Press me first" button btn2 "press me" b = 10 -- this is wrong on btn1 pressed do ( b = 5 ) on btn2 pressed do ( Messagebox ("B = " + b as string) ) ) Createdialog RL_Test
However we can explicitly declare it as a local variable to this script, we don’t need to say what value b has, which can be useful. The other thing about declaring it as a local variable like this is that if there was another global value called b then this script wouldn’t use it, it would use it’s own variable called b through out the script, which is useful for avoiding conflicts with other scripts.
Rollout RL_Test "Test" ( button btn1 "Press me first" button btn2 "press me" local b on btn1 pressed do ( b = 5 ) on btn2 pressed do ( Messagebox ("B = " + b as string) ) ) Createdialog RL_Test
To test this theory, put the above code into a new script window, then type into your maxscript listener
b = 10
Then run the script, if you press btn2 first b will be undefined, if you press btn1 then btn2 b will equal 5, if you then type into the maxscript listener, b and press enter, it will return 10, which means we haven’t touched this global value.
The final example is how you can locally declare a variable outside the main rollout, which is useful if you want to share a value between multiple rollouts but not have it available to other scripts. Notice the brackets at the start and end of the script. This contains the whole script with our variable b in it’s own scope, if you try it without these brackets you would be trying to declare a local variable in global space and you’ll get an error.
( local b = 10 Rollout RL_Test "Test" ( button btn1 "Press me first" button btn2 "press me" on btn1 pressed do ( b = 5 ) on btn2 pressed do ( Messagebox ("B = " + b as string) ) ) Createdialog RL_Test )
So just to explain that last bit again if we were to try this…..
b = 10 -- this is now global Rollout RL_Test "Test" ( button btn1 "Press me first" button btn2 "press me" on btn1 pressed do ( b = 5 ) on btn2 pressed do ( Messagebox ("B = " + b as string) ) ) Createdialog RL_Test
…then we would we be declaring b in global scope as it’s not contained in any brackets… so this throws us back into the issues of conflicting with other scripts and having our value available for anyone to mess with. It’s good practice if you’re not making an interface for your script to put the whole thing in brackets, this stops it being in global space and will mean that only the final result gets outputted to the maxscript listener which is faster and tidier.
Just to complete the topic of scopes it’s good to be aware how you can use local scopes within scopes…
( local b = 10 Rollout RL_Test "Test" ( button btn1 "Press me first" button btn2 "press me" on btn1 pressed do ( b = 5 ( local b = 10 ) MessageBox ("B = " + b as string) ) on btn2 pressed do ( Messagebox ("B = " + b as string) ) ) Createdialog RL_Test )
Press btn1 you might expect the messagebox to say b is now 10 but as we’ve explicitly declare b as local in the brackets it is effectively a new variable which is only in memory for the lines contained in those brackets. This can be useful to know, I tend to try avoid re-using variable names like that as it becomes difficult to understand.