Blame
|
1 | <!-- Generated from applied-computing-au vic/unit3-4/sat/C06-2026/C06-Reference-Godot — do not hand-edit; re-run the port. --> |
||||||
| 2 | # OOP Concepts — GDScript |
|||||||
| 3 | ||||||||
| 4 | ||||||||
| 5 | **Skill codes on this page** |
|||||||
| 6 | ||||||||
| 7 | | Code | Level | Skill | |
|||||||
| 8 | |---|---|---| |
|||||||
| 9 | | C643 | 7–8 | access modifiers | |
|||||||
| 10 | | C651 | 9–10 | classes | |
|||||||
| 11 | | C652 | 9–10 | objects | |
|||||||
| 12 | | C653 | 9–10 | abstraction | |
|||||||
| 13 | | C654 | 9–10 | encapsulation | |
|||||||
| 14 | | C655 | 9–10 | generalisation | |
|||||||
| 15 | | C656 | 9–10 | inheritance | |
|||||||
| 16 | ||||||||
| 17 | **Definition.** *Object-oriented programming (OOP):* programming based on objects that contain data in the form of fields or attributes, and code in the form of methods. |
|||||||
| 18 | ||||||||
| 19 | Level 9–10 asks for classes and objects **plus all relevant OOP principles** — one class on its own does not reach the top band. |
|||||||
| 20 | ||||||||
| 21 | ## C651 — Classes and C652 — Objects |
|||||||
| 22 | ||||||||
| 23 | **Definitions.** *Class:* a program code template for creating objects. *Object:* any instantiated class that a program can inspect and/or change. *Instantiation:* the process by which an object is created from a class. |
|||||||
| 24 | ||||||||
| 25 | ```gdscript |
|||||||
| 26 | # MykiCard.gd |
|||||||
| 27 | class_name MykiCard # C651 — class: the template for every card |
|||||||
| 28 | extends RefCounted |
|||||||
| 29 | ||||||||
| 30 | var _balance: float = 20.00 |
|||||||
| 31 | ||||||||
| 32 | func top_up(amount: float) -> void: |
|||||||
| 33 | _balance += amount |
|||||||
| 34 | ``` |
|||||||
| 35 | ||||||||
| 36 | ```gdscript |
|||||||
| 37 | # C652 — objects: two cards from one template, separate balances |
|||||||
| 38 | var my_card: MykiCard = MykiCard.new() |
|||||||
| 39 | var your_card: MykiCard = MykiCard.new() |
|||||||
| 40 | my_card.top_up(10.0) |
|||||||
| 41 | ``` |
|||||||
| 42 | ||||||||
| 43 | **Earns the tick:** C651 — a class *you wrote* (`class_name`, fields, methods). C652 — `.new()` creating an object your program then uses. Two objects holding different state is the cleanest demo. |
|||||||
| 44 | ||||||||
| 45 | **In tile-matching-game:** `card.gd` opens with `class_name Card` — fields (`pair_id`, `is_face_up`) plus methods (`flip_up()`, `mark_matched()`) in one template (C651). The game then holds two of them: |
|||||||
| 46 | ||||||||
| 47 | ```gdscript |
|||||||
| 48 | # tile-matching-game — game.gd |
|||||||
| 49 | var first_card: Card = null # C652 — two objects from one class, |
|||||||
| 50 | var second_card: Card = null # each with its own state |
|||||||
| 51 | ``` |
|||||||
| 52 | ||||||||
| 53 | (The board creates them with `card_scene.instantiate()` — instancing the card *scene* constructs a `Card` object, because the scene's root node runs `card.gd`.) |
|||||||
| 54 | ||||||||
| 55 | **In club-ladder** — the direct `.new()` case: |
|||||||
| 56 | ||||||||
| 57 | ```gdscript |
|||||||
| 58 | # club-ladder — app.gd |
|||||||
| 59 | season.teams.append(Team.new(team_name)) # C652 — object created and used |
|||||||
| 60 | ``` |
|||||||
| 61 | ||||||||
| 62 | `season.gd` builds them four arguments at a time from the XML — `Team.new(team_name, wins, draws, losses)` — and `Team._init` gives every parameter a default, so both calls work. |
|||||||
| 63 | ||||||||
| 64 | ## C653 — Abstraction |
|||||||
| 65 | ||||||||
| 66 | **Definition.** An OOP principle that allows programmers to manage complexity by hiding implementation details and exposing only the essential features of an object. |
|||||||
| 67 | ||||||||
| 68 | **How it works in GDScript:** |
|||||||
| 69 | ||||||||
| 70 | - Base class defines structure meant for inheritance |
|||||||
| 71 | - Methods can have default implementations or error messages |
|||||||
| 72 | - Convention-based — not meant to be used directly |
|||||||
| 73 | - `assert(false)` is one way to indicate override needed |
|||||||
| 74 | - Focus on inheritance structure rather than enforcement |
|||||||
| 75 | ||||||||
| 76 | ```gdscript |
|||||||
| 77 | # C653 — abstraction: base class with common structure |
|||||||
| 78 | class_name Animal |
|||||||
| 79 | extends RefCounted |
|||||||
| 80 | ||||||||
| 81 | var name: String |
|||||||
| 82 | ||||||||
| 83 | func _init(p_name: String): |
|||||||
| 84 | name = p_name |
|||||||
| 85 | ||||||||
| 86 | func make_sound(): |
|||||||
| 87 | print("Some animal sound") |
|||||||
| 88 | ||||||||
| 89 | func move(): |
|||||||
| 90 | print(name + " is moving") |
|||||||
| 91 | ``` |
|||||||
| 92 | ||||||||
| 93 | Myki version: |
|||||||
| 94 | ||||||||
| 95 | ```gdscript |
|||||||
| 96 | # C653 — abstraction: callers use get_cost() and never see the fare rules |
|||||||
| 97 | class_name Journey |
|||||||
| 98 | extends RefCounted |
|||||||
| 99 | ||||||||
| 100 | var fare: float |
|||||||
| 101 | ||||||||
| 102 | func get_cost() -> float: |
|||||||
| 103 | return fare |
|||||||
| 104 | ``` |
|||||||
| 105 | ||||||||
| 106 | **Earns the tick:** a base class used through its simple interface while the details stay inside. "The HUD calls `get_cost()` and doesn't care how the cost is worked out" is the sentence to say. |
|||||||
| 107 | ||||||||
| 108 | **In tile-matching-game:** `card.gd`'s own header comment makes the abstraction argument — a card "does NOT decide whether two cards match — that is the Game's job. The card only reports being clicked." The game calls `flip_up()` and `mark_matched()`; how a card draws itself stays hidden inside the card. |
|||||||
| 109 | ||||||||
| 110 | ## C654 — Encapsulation |
|||||||
| 111 | ||||||||
| 112 | **Definition.** An OOP principle that involves bundling the data, and the methods that operate on that data, into a single unit or class. |
|||||||
| 113 | ||||||||
| 114 | **How it works in GDScript:** |
|||||||
| 115 | ||||||||
| 116 | - Single underscore `_` indicates private by convention only |
|||||||
| 117 | - No enforcement mechanism — purely documentation |
|||||||
| 118 | - All class members are technically public |
|||||||
| 119 | - Relies entirely on developer discipline |
|||||||
| 120 | - IDE may highlight convention violations |
|||||||
| 121 | ||||||||
| 122 | ```gdscript |
|||||||
| 123 | class_name BankAccount |
|||||||
| 124 | extends RefCounted |
|||||||
| 125 | ||||||||
| 126 | var _balance: float = 0.0 # C654 — private by convention |
|||||||
| 127 | ||||||||
| 128 | func get_balance() -> float: |
|||||||
| 129 | return _balance |
|||||||
| 130 | ||||||||
| 131 | func deposit(amount: float): |
|||||||
| 132 | if amount > 0: |
|||||||
| 133 | _balance += amount |
|||||||
| 134 | print("Deposited $" + str(amount)) |
|||||||
| 135 | else: |
|||||||
| 136 | print("Invalid amount") |
|||||||
| 137 | ``` |
|||||||
| 138 | ||||||||
| 139 | Myki version: |
|||||||
| 140 | ||||||||
| 141 | ```gdscript |
|||||||
| 142 | class_name MykiCard |
|||||||
| 143 | extends RefCounted |
|||||||
| 144 | ||||||||
| 145 | var _balance: float = 20.00 # C654 — private, cannot be changed directly |
|||||||
| 146 | ||||||||
| 147 | func _is_valid(amount: float) -> bool: # C654 — private helper, used only inside this class |
|||||||
| 148 | return amount > 0 |
|||||||
| 149 | ||||||||
| 150 | func top_up(amount: float) -> void: # C654 — the only door to _balance |
|||||||
| 151 | if _is_valid(amount): |
|||||||
| 152 | _balance += amount |
|||||||
| 153 | ``` |
|||||||
| 154 | ||||||||
| 155 | **Earns the tick:** data behind `_`, changed only through methods that validate. In validation: show `top_up(-50)` being rejected. |
|||||||
| 156 | ||||||||
| 157 | **In tile-matching-game:** `Card` bundles its state (`pair_id`, `is_face_up`, `is_matched`) with the methods that change it — outside code never *sets* those fields, it calls `flip_up()`, `flip_down()`, `mark_matched()`. (The game reads them directly; underscore-prefixing the fields and adding getters would push this to full-marks encapsulation.) |
|||||||
| 158 | ||||||||
| 159 | ### C643 — Access modifiers |
|||||||
| 160 | ||||||||
| 161 | GDScript has no `private` / `public` keywords — the `_` prefix **is** the access modifier, applied by convention: |
|||||||
| 162 | ||||||||
| 163 | ```gdscript |
|||||||
| 164 | var _balance: float = 20.00 # C643 — "private": underscore convention |
|||||||
| 165 | func get_balance() -> float: # C643 — public getter is the only way in |
|||||||
| 166 | return _balance |
|||||||
| 167 | ``` |
|||||||
| 168 | ||||||||
| 169 | **Earns the tick:** the convention applied consistently *and named* — "the underscore marks it private by convention; other code must go through the getter." |
|||||||
| 170 | ||||||||
| 171 | **In connect-go-dots:** `_open()` and `_on_back_pressed()` carry the underscore as internal helpers no other script should call. (`_ready()` also starts with `_`, but that is Godot's callback naming, not privacy.) |
|||||||
| 172 | ||||||||
| 173 | **In tile-matching-game:** `Card` shows the split in one class — public `flip_up()` / `mark_matched()` for other scripts, private `_on_pressed()` for itself. |
|||||||
| 174 | ||||||||
| 175 | ## C655 — Generalisation |
|||||||
| 176 | ||||||||
| 177 | **Definition.** The process of defining a general class (superclass) that encapsulates common attributes and behaviours of more specific classes (subclasses). |
|||||||
| 178 | ||||||||
| 179 | **How it works in GDScript:** |
|||||||
| 180 | ||||||||
| 181 | - `class_name` makes class globally accessible |
|||||||
| 182 | - `extends RefCounted` for automatic memory management |
|||||||
| 183 | - Constructor `_init` sets up shared data |
|||||||
| 184 | - Base class defines common interface for all subclasses |
|||||||
| 185 | ||||||||
| 186 | ```gdscript |
|||||||
| 187 | # C655 — generalisation: common functionality in base class |
|||||||
| 188 | class_name Vehicle |
|||||||
| 189 | extends RefCounted |
|||||||
| 190 | ||||||||
| 191 | var brand: String |
|||||||
| 192 | var model: String |
|||||||
| 193 | var is_running: bool = false |
|||||||
| 194 | ||||||||
| 195 | func _init(p_brand: String, p_model: String): |
|||||||
| 196 | brand = p_brand |
|||||||
| 197 | model = p_model |
|||||||
| 198 | ||||||||
| 199 | func start_engine(): |
|||||||
| 200 | is_running = true |
|||||||
| 201 | print(brand + " " + model + " started") |
|||||||
| 202 | ||||||||
| 203 | func stop_engine(): |
|||||||
| 204 | is_running = false |
|||||||
| 205 | print(brand + " stopped") |
|||||||
| 206 | ``` |
|||||||
| 207 | ||||||||
| 208 | Myki version: |
|||||||
| 209 | ||||||||
| 210 | ```gdscript |
|||||||
| 211 | # C655 — generalisation: what every journey shares lives in the parent |
|||||||
| 212 | class_name Journey |
|||||||
| 213 | extends RefCounted |
|||||||
| 214 | ||||||||
| 215 | var zone: int |
|||||||
| 216 | var fare: float |
|||||||
| 217 | ||||||||
| 218 | func _init(p_zone: int, p_fare: float): |
|||||||
| 219 | zone = p_zone |
|||||||
| 220 | fare = p_fare |
|||||||
| 221 | ||||||||
| 222 | func get_cost() -> float: |
|||||||
| 223 | return fare |
|||||||
| 224 | ``` |
|||||||
| 225 | ||||||||
| 226 | **Earns the tick:** shared fields and methods pulled *up* into one parent instead of repeated in each child — point at the duplication you avoided. |
|||||||
| 227 | ||||||||
| 228 | ## C656 — Inheritance |
|||||||
| 229 | ||||||||
| 230 | **Definition.** A method of basing an object or class on another object or class, taking on its attributes and methods and potentially extending upon them. |
|||||||
| 231 | ||||||||
| 232 | **How it works in GDScript:** |
|||||||
| 233 | ||||||||
| 234 | - `extends` establishes parent-child relationship |
|||||||
| 235 | - `super()` calls parent constructor with parameters |
|||||||
| 236 | - Override by redefining method with same name |
|||||||
| 237 | - Single inheritance model (one parent) |
|||||||
| 238 | - Parent methods available unless explicitly overridden |
|||||||
| 239 | ||||||||
| 240 | ```gdscript |
|||||||
| 241 | # C656 — inheritance: Car is a Vehicle with extras |
|||||||
| 242 | class_name Car |
|||||||
| 243 | extends Vehicle |
|||||||
| 244 | ||||||||
| 245 | var doors: int |
|||||||
| 246 | ||||||||
| 247 | func _init(p_brand: String, p_model: String, p_doors: int): |
|||||||
| 248 | super(p_brand, p_model) |
|||||||
| 249 | doors = p_doors |
|||||||
| 250 | ||||||||
| 251 | func honk(): |
|||||||
| 252 | print("Beep beep!") |
|||||||
| 253 | ||||||||
| 254 | # C656 — override parent method |
|||||||
| 255 | func start_engine(): |
|||||||
| 256 | super.start_engine() |
|||||||
| 257 | print("Car is ready to drive") |
|||||||
| 258 | ``` |
|||||||
| 259 | ||||||||
| 260 | Myki version: |
|||||||
| 261 | ||||||||
| 262 | ```gdscript |
|||||||
| 263 | # C656 — inheritance: a ConcessionJourney is a Journey with one changed rule |
|||||||
| 264 | class_name ConcessionJourney |
|||||||
| 265 | extends Journey |
|||||||
| 266 | ||||||||
| 267 | func get_cost() -> float: # override |
|||||||
| 268 | return fare * 0.5 |
|||||||
| 269 | ``` |
|||||||
| 270 | ||||||||
| 271 | **Earns the tick:** `extends` *your own* class, `super` used, at least one method overridden — and both parent and child actually used by the running program. |
|||||||
| 272 | ||||||||
| 273 | **Not enough:** every script in connect-go-dots has `extends Control` or `extends Node` — inheriting from an *engine* class. Necessary in Godot, but C656 wants `extends` on a class of your own. |
|||||||
| 274 | ||||||||
| 275 | **Worth seeing anyway:** in tile-matching-game, `Card extends Button` shows what inheriting *buys* — the card never defines `pressed` or `disabled`, yet uses both, because they come from `Button`. That is inheritance working; it just is not *your* parent class, so on its own it does not earn C656. |
|||||||
| 276 | ||||||||
| 277 | ## Check Your Understanding |
|||||||
| 278 | ||||||||
| 279 | 1. Why does `extends Control` not earn C656? |
|||||||
| 280 | ||||||||
| 281 | >| ### Answer |
|||||||
| 282 | >| It inherits from an *engine* class — the tick needs `extends` on a class you wrote. |
|||||||
| 283 | ||||||||
| 284 | 2. What is GDScript's access modifier, and which code is it? |
|||||||
| 285 | ||||||||
| 286 | >| ### Answer |
|||||||
| 287 | >| The `_` underscore prefix, applied by convention — C643. |
|||||||
| 288 | ||||||||
| 289 | 3. List the minimum ingredients of full C656 evidence. |
|||||||
| 290 | ||||||||
| 291 | >| ### Answer |
|||||||
| 292 | >| Your own parent class, `extends` on the child, `super` used, at least one override — and both classes used by the running program. |
|||||||
