The VScript Book

Chapter 2.2: Understanding Scope

Where your variables and functions "live" is called their scope. This is one of the most important concepts in programming, as it determines what parts of your code can see and interact with other parts.

Every Script is Its Own Sandbox

By default, each script runs in complete isolation from every other script. If you have two logic_script entities, they have no idea that the other exists. Each script gets its own unique table, or scope, where its variables are stored. Changing a variable in one script's scope will not affect a variable with the same name in another script's scope.

Practical Example: Scope Isolation

Imagine you have two logic_script entities in your map:

  1. logic_script_A has its "VScript File" property set to puzzle_a.nut.
  2. logic_script_B has its "VScript File" property set to puzzle_b.nut.

puzzle_a.nut:

// This creates a variable named 'puzzleCounter' inside script A's scope.
puzzleCounter <- 10
printl("[Puzzle A] My counter is: " + puzzleCounter) // Will print 10

// Let's change our counter.
puzzleCounter = 11
printl("[Puzzle A] I changed my counter to: " + puzzleCounter) // Will print 11

puzzle_b.nut:

// This creates a COMPLETELY SEPARATE variable, also named 'puzzleCounter',
// inside script B's scope.
puzzleCounter <- 99
printl("[Puzzle B] My counter is: " + puzzleCounter) // Will print 99

When you run the map, you will see this in the console:

[Puzzle A] My counter is: 10
[Puzzle A] I changed my counter to: 11
[Puzzle B] My counter is: 99

Notice how changing puzzleCounter in Script A had **zero effect** on the variable in Script B. They are in separate sandboxes.

The Solution: The Global Scope (Root Table)

So how do we make scripts talk to each other? We need a shared, public space. This space is called the global scope or the root table. Think of it as a public bulletin board where any script can post a message for any other script to see.

You can explicitly access this global "bulletin board" using the special :: operator.

[Root Table (Global Scope)]
[Scope for logic_script_A]
puzzle_A_counter <- 0
[Scope for logic_script_B]
puzzle_B_state <- "inactive"

logic_script_A cannot directly see puzzle_B_state. It would need to use the global scope to communicate.

Practical Example: Communication

Let's modify our scripts to communicate.

sender_script.nut:

// The :: operator tells Squirrel to put this variable on the global root table.
::sharedCode <- 481516
printl("[Sender] I have posted the shared code.")

receiver_script.nut:

// The :: operator tells Squirrel to LOOK for this variable on the global root table.
local theCode = ::sharedCode
printl("[Receiver] I have received the code: " + theCode) // Will print 481516

Now, the scripts can share data! The "Sender" posts the information to the global scope, and the "Receiver" reads it.

How :: Works: Syntactic Sugar

The :: operator is just a convenient shortcut, or "syntactic sugar". The long, formal way to access the root table is with a built-in function called getroottable(). The following two lines do the exact same thing:

// The short, easy way (syntactic sugar)
::myGlobalVar <- 100

// The long, explicit way
getroottable()["myGlobalVar"] <- 100

Best Practice: Global scope is powerful, but use it carefully. If too many scripts are changing global variables, it can become very confusing to track where data is coming from. Always prefer local variables and entity scripts. Only use the global scope (::) when you have a clear reason for different systems to communicate across the entire map.