P2.3 — Thinking in Objects¶
What you'll learn
- What an object is: data and behavior bundled together as one thing.
- That a class is a blueprint, and an instance is one thing built from it.
- The two parts of a class: properties (its data) and methods (its behavior).
- How to make an instance and use its properties and methods.
How it applies
- Real things have data and behavior. An enemy has health (data) and can take damage (behavior). Keeping the two together in one object — rather than a loose variable here and a function there — is how programs model the world without the pieces drifting apart.
- One blueprint, many things. A single
Enemyclass produces a hundred enemies, each with its own health. Without this, a hundred enemies means a hundred hand-made copies that cannot share a design or be fixed in one place. - Behavior lives with its data. Calling
goblin.take_damage(3)puts the action right on the thing it acts on, so the enemy controls how its own health changes. Scattering that logic away from the data is how rules get applied inconsistently. - This is the leap into the books above. Foundations and the game books are built almost entirely from objects (nodes, resources). Grasping class-versus-instance here is what makes those books readable rather than mysterious.
Concepts¶
Objects: data and behavior together¶
So far, data (variables) and behavior (functions) have been separate. An object bundles them: it is one thing that holds some data and can perform some behavior on that data. An enemy object holds its health and knows how to take damage. A player object holds its name and score and knows how to level up. Bundling the two means the data and the actions that belong to it travel together as a unit.
Class and instance: blueprint and thing¶
You do not build objects one by one from scratch — you write a class, a blueprint describing what data and behavior its objects have, and then make instances from it. The classic comparison: a class is an architect's blueprint; an instance is an actual house built from it. One blueprint, many houses — each its own building, all sharing the design.
In GDScript, each script is a class (you have been writing classes all along — every _ready
script was one). Giving it a name with class_name lets other scripts use it as a blueprint:
# in a script saved as enemy.gd
class_name Enemy
extends Node
var health := 10 # a PROPERTY — data each Enemy has
func take_damage(amount): # a METHOD — behavior each Enemy has
health = health - amount
- A property is a variable that belongs to the object — data each instance carries (
health). - A method is a function that belongs to the object — behavior each instance can perform
(
take_damage). A method is just a function defined inside a class.
(extends Node and class_name are the same frame words from earlier; the next book explains them
fully. Here, class_name Enemy simply means "this blueprint is called Enemy.")
Making and using an instance¶
To build an instance from the blueprint, call .new() on the class name. Then reach its properties and
methods with a dot:
func _ready():
var goblin := Enemy.new() # build one Enemy instance
print(goblin.health) # 10 — read its property
goblin.take_damage(3) # run its method; its health becomes 7
print(goblin.health) # 7
Enemy.new() makes one fresh enemy with its own health of 10. goblin.health reads that
instance's data; goblin.take_damage(3) runs that instance's behavior on its own health. The dot
means "belonging to this object."
Example
One blueprint, two independent instances:
func _ready():
var goblin := Enemy.new()
var orc := Enemy.new()
goblin.take_damage(4) # only the goblin is hurt
print(goblin.health) # 6
print(orc.health) # 10 — the orc is untouched
Both enemies came from the same Enemy class, but each instance has its own health: damaging
the goblin does nothing to the orc, because goblin and orc are separate objects. This is the
point of class-versus-instance — the blueprint defines what an enemy is, and each instance is a
distinct enemy with its own data. A game with fifty enemies makes fifty instances of one class, each
tracking its own state, all sharing one design you can edit in a single place.
Walkthrough¶
You will make two scripts this time.
- Create a new script file (right-click in the FileSystem panel → New Script), name it
enemy.gd, and write theEnemyclass from above:class_name Enemy,extends Node, ahealthproperty, and atake_damage(amount)method. Save it. - In your usual run-script's
_ready, writevar goblin := Enemy.new(). Printgoblin.health(10). - Call
goblin.take_damage(3), then printgoblin.healthagain (7). You have read a property and run a method on an instance. - Make a second instance
var orc := Enemy.new(). Damage only the goblin, then print both healths; confirm the orc is unaffected — two independent objects from one blueprint.
Optional sanity check
Add a second property to Enemy, like var name_tag := \"goblin\", and a method
func describe(): print(name_tag + \" has \" + str(health) + \" hp\"). Make an instance and call
describe(). Notice the method uses the object's own properties (name_tag, health) without
being passed them — because they belong to the same object the method does. That togetherness of
data and behavior is what "object" means.
If you see a 'leaked instance' message when the window closes
The enemies you build with Enemy.new() are practice objects that nothing cleans up, so when you
close the program Godot may print a notice about a "leaked" or orphaned object. That is expected
here and is not a mistake in your code — an object made from a Node blueprint is not removed
automatically the way an ordinary variable is. Managing an object's lifetime — creating it, freeing
it, or attaching it to the running scene — is a Godot specific the next book, Foundations,
covers; for now the message is harmless and you can ignore it.
Self-check quiz¶
Q1 — What is the relationship between a class and an instance?
A. They are the same thing. B. A class is a blueprint describing data and behavior; an instance is one actual object built from that blueprint. C. An instance is the blueprint; a class is the object. D. A class can only ever make one instance.
Reveal answer
B. A class is the design (like a blueprint); an instance is a concrete object made from it (like a house), and you can make many. A conflates the two. C reverses them. D is false — one class makes as many instances as you want, each with its own data.
Q2 — In the Enemy class, what is health and what is take_damage?
A. Both are properties.
B. health is a property (data the object holds); take_damage is a method (behavior the object performs).
C. Both are methods.
D. health is a method; take_damage is a property.
Reveal answer
B. A property is a variable belonging to the object (health); a method is a function
belonging to the object (take_damage). A and C lump them together; D swaps them. Data =
property, behavior = method.
Q3 — What does Enemy.new() do?
A. Renames the Enemy class.
B. Creates one new instance (object) of the Enemy class, with its own properties.
C. Deletes an enemy.
D. Calls the take_damage method.
Reveal answer
B. .new() builds a fresh instance from the class blueprint — a new object carrying its own
copy of the properties. A, C, and D describe unrelated actions; .new() is specifically how you
make an instance.
Integration question¶
Q4 — open
A game needs three potions, each with its own remaining-uses count, all behaving the same way (each
use decreases its count by one). A teammate suggests three separate variables — potion1_uses,
potion2_uses, potion3_uses — and a function use_potion that takes which one to change.
Explain how a Potion class with a uses property and a use() method models this better, what
.new() gives you, and why each potion's count stays independent — connecting to how the books
above are built from objects.
Reveal expected answer
Define one class Potion with a property uses (its data) and a method use() that does
uses = uses - 1 (its behavior). Each potion is then an instance: var p1 := Potion.new(),
p2, p3 — .new() builds a fresh object each time, each carrying its own uses. Calling
p1.use() decreases only p1's count, because the method acts on the instance it was called on;
p2 and p3 are untouched, exactly as damaging the goblin left the orc alone. This beats three
separate variables plus a routing function because the data and the behavior that changes it live
together in one blueprint: there is one place that defines what a potion is and how it is
used, and you make as many independent potions as you need without inventing a new variable name
and a new branch for each. That is precisely how the books above are built — Godot's nodes and
resources are classes, and a game is full of instances of them, each with its own state, all
sharing a design you edit once. Recognizing "this is a class; these are its instances" is the
mental model the next book assumes throughout.