Chapter 4.1: Organizing Code with Tables
As your scripts grow, you'll find yourself with many related variables and functions. Keeping them organized is key to writing clean, bug-free code. The best way to do this in Squirrel is to group them together inside a table, effectively creating your own custom "object".
This pattern is useful for managing the state and behaviors of a complex entity, like a turret, all in one place.
From Chaos to Order
// Disorganized script on a turret entity:
isActivated <- false
ammoCount <- 100
target <- null
function AcquireTarget() { /*...*/ }
function FireAtTarget() { /*...*/ }
// Organized approach using a table:
Turret <- {
isActivated = false,
ammoCount = 100,
target = null,
function AcquireTarget() { /*...*/ },
function FireAtTarget() { /*...*/ }
}
This is much cleaner! But it introduces a new, very important concept: scope isolation.
The Scope Problem: self vs. this
When you call a function that's inside a table (like Turret.AcquireTarget()), a special variable called this is created. Inside that function, this refers to the table the function lives in (the Turret table).
The problem? The function inside the table has no idea what self (the entity handle) is! It lives in a different scope.
// In the entity's main scope...
function InputEnable() {
// This will FAIL. The AcquireTarget function doesn't know what 'self' is.
Turret.AcquireTarget()
}
// Our organized table
Turret <- {
function AcquireTarget() {
// ERROR: The variable 'self' does not exist in this scope!
local myPos = self.GetOrigin()
}
}
The Solution: Passing self as a Parameter
The solution is simple: if a function needs access to the entity, you must pass self to it as a parameter.
// In the entity's main scope...
function InputEnable() {
// We pass 'self' (the entity handle) into the function.
Turret.AcquireTarget(self)
}
// Our organized table
Turret <- {
// The function now accepts the entity handle as a parameter.
function AcquireTarget(turretEntity) {
// Now it works! We use the passed-in handle.
local myPos = turretEntity.GetOrigin()
printl("Acquiring target from position: " + myPos)
}
}
This pattern of grouping logic into tables and passing self is fundamental to writing organized, large-scale VScripts.
What about Classes?
Squirrel has a class keyword which is a more formal way to create these kinds of objects. They behave very similarly to tables but have stricter rules and concepts like inheritance. For most VScripting needs, using tables as objects is simpler and more flexible... For someone :p
class Turret {
hp = 100
target = null
constructor(health) {
hp = health
}
function Fire() {
printl("Firing at " + target.GetName())
}
}
local myTurret = Turret(150)
⚠️ CRITICAL: Do NOT Use Class Inheritance in Portal 2!
Squirrel supports object-oriented programming with the class inheritance. However, Portal 2's save/load system does not support these features. If your script uses class inheritance, the game will crash when a player tries to save or load the game.
Safe Alternative: Use tables to organize your code instead. Tables work perfectly with save/load and offer all the functionality you need for most scripting tasks.