LEVEL 1: 7-Day Study Plan
Daily Practice Roadmap
แผนการฝึกฝนเข้มข้น 7 วัน เพื่อวางรากฐานสถาปัตยกรรมระดับ Senior ใน Godot Engine 4 เน้นความเข้าใจวงจรชีวิต Node Lifecycle, การเลือกใช้ Pure Component แทนการสืบทอดเชิงลึก, และ Type Safety 100% โดยจัดเวลาฝึกเพียงวันละ 20–30 นาที
5–10 นาทีแรก
Micro-Concept เจาะทฤษฎี
15–20 นาทีหลัง
Daily Code Drills ใน Editor
วันที่ 7
Quiz & Mini-Game Exam
หลักคิดสำคัญประจำสัปดาห์
"อย่าก็อปปี้ ให้พิมพ์โค้ดเองทุกบรรทัดใน Godot 4 Editor เพื่อฝึกกล้ามเนื้อมือและสายตาให้คุ้นชินกับระบบ Type Hinting และ Signal Declaration"
Object Composition & Scene Tree Architecture
เข้าใจระบบ SceneTree ใน Godot 4 และการสร้างฟังก์ชันผ่าน Component แทน Deep Inheritance
Micro-Concept & Architecture Breakdown (5–10 Mins)
ใน Godot 4 ทุก Scene คือไฟล์ Tree ของโหนดที่บันทึกไว้ หลีกเลี่ยงการสืบทอดเชิงลึก เช่น Player > CombatEntity > PhysicsEntity เพราะจะสร้างหนี้ทางเทคนิค ให้หันมาใช้ Composition ด้วยการแปะ Node ย่อยที่มีหน้าที่เฉพาะเจาะจงลงไปแทน
ทุกโหนดที่มีผลในการคำนวณและเรนเดอร์จะต้องอยู่ภายใน SceneTree เท่านั้น โหนดที่ถูกสร้างขึ้นใน Memory แต่ยังไม่ได้ถูก add_child() เข้าสู่ทรี จะไม่ได้รับ Process Ticks หรืออัปเดตฟิสิกส์ใดๆ ทั้งสิ้น
RotatorComponent Node (Pure Modular Rotation)
└── RotatorComponent (Node) [Script attached]
class_name RotatorComponent
extends Node
## Rotation speed in radians per second
@export var rotation_speed: float = 2.0
func _process(delta: float) -> void:
var parent := get_parent() as Node2D
if parent:
parent.rotation += rotation_speed * delta
GDScript 2.0 Type Safety & Editor Annotations
การใช้ Static Typing, Type Inlining, @export_range และ @onready อย่างมีประสิทธิภาพ
Micro-Concept & Architecture Breakdown (5–10 Mins)
GDScript 2.0 มีระบบ Strict Static Typing ที่เข้มงวด การกำหนด Type ชัดเจน (เช่น var health: int = 100) ช่วยให้ Compiler ใน Godot ทำ Direct Opcode Optimization และป้องกัน Null-Pointer Crash ได้ก่อนเริ่มรันเกม
@export เปิดโอกาสให้ Game Designer ปรับค่าใน Inspector ได้ทันที และสามารถจำกัดขอบเขตด้วย @export_range() ส่วน @onready ช่วยผูกโหนดอย่างปลอดภัยเมื่อเข้าสู่ทรี
Typed CharacterStats with Clamped Bounds
└── StatsController (Node) [Script attached]
class_name StatsController
extends Node
@export_range(1, 1000, 1) var max_health: int = 100
@export_range(0.0, 500.0, 10.0) var movement_speed: float = 250.0
@export var character_name: String = "Hero"
var current_health: int
func _ready() -> void:
current_health = max_health
print_rich("[color=green]%s initialized with %d HP.[/color]" % [character_name, current_health])
func apply_damage(amount: int) -> void:
current_health = clampi(current_health - amount, 0, max_health)
Node Lifecycles & Execution Order
ลำดับการทำงานแบบ Bottom-up ของ _ready() และความแตกต่างระหว่าง _process กับ _physics_process
Micro-Concept & Architecture Breakdown (5–10 Mins)
- _init(): อ็อบเจกต์สร้างใน RAM (โหนดลูกยังไม่มี และยังไม่อยู่ใน SceneTree)
- _enter_tree(): โหนดก้าวเข้าสู่ SceneTree แต่ลูกอาจยังโหลดไม่เสร็จ
- _ready(): ทำงานแบบ Bottom-Up (โหนดลูกต้อง _ready() ให้เสร็จก่อนเสมอ แม่ถึงจะ _ready())
- _process(delta): ทำงานทุก Render Frame (เวลาผันผวนตามการแสดงผล ใช้กับ UI / Visual)
- _physics_process(delta): ทำงานด้วยอัตราคงที่ Fixed 60 Hz (ใช้คำนวณการชนและการเคลื่อนที่)
LifecycleTracker Component (Console Verification)
└── LifecycleTracker (Node) [Script attached]
class_name LifecycleTracker
extends Node
func _init() -> void:
print("1. _init(): Memory allocated.")
func _enter_tree() -> void:
print("2. _enter_tree(): Node entered SceneTree.")
func _ready() -> void:
print("3. _ready(): Children ready, node fully active.")
func _physics_process(delta: float) -> void:
print("4. First _physics_process tick (delta: %f)." % delta)
set_physics_process(false) # Disable after first tick
Robust Node Access & Scene Unique Nodes (%)
บอกลาเส้นทางเปราะบาง $A/B/C/D ด้วยฟีเจอร์ Scene Unique Nodes (%) ของ Godot 4
Micro-Concept & Architecture Breakdown (5–10 Mins)
การเข้าถึงโหนดผ่านพาทตายตัว เช่น $UI/Panel/VBoxContainer/Label มีความเปราะบางสูงมาก เมื่อ Game Designer ย้ายลำดับโหนดหรือใส่ Container เพิ่ม โค้ดจะพังและเกิด Null-Pointer Error ทันที
ใน Godot 4 เราสามารถคลิกขวาที่โหนดแล้วเปิด Access as Unique Name ซึ่งจะเติมสัญลักษณ์ % ทำให้เราอ้างอิงผ่าน %Label ได้โดยตรงจากทุกที่ภายในซีนเดียวกัน
HUD Manager with Scene Unique Nodes
└── PanelContainer
└── MarginContainer
└── VBoxContainer
├── Label (%ScoreLabel) [Scene Unique Flag Enabled]
└── Button (%ResetButton) [Scene Unique Flag Enabled]
extends Control
@onready var score_label: Label = %ScoreLabel
@onready var reset_button: Button = %ResetButton
var current_score: int = 0
func _ready() -> void:
update_score_display()
reset_button.pressed.connect(_on_reset_pressed)
func add_points(points: int) -> void:
current_score += points
update_score_display()
func update_score_display() -> void:
score_label.text = "SCORE: %d" % current_score
func _on_reset_pressed() -> void:
current_score = 0
update_score_display()
Dynamic Scene Instantiation & Memory Management
การเสกซีนผ่าน PackedScene, การเลือกใช้ preload() เทียบกับ load() และการจัดการตำแหน่งพิกัด
Micro-Concept & Architecture Breakdown (5–10 Mins)
การเสกวัตถุระหว่างเล่น (กระสุน, มอนสเตอร์, ไอเทม) ให้ประกาศตัวแปรเป็น PackedScene แล้วเรียก .instantiate() กำหนดพิกัด global_position ก่อนเพิ่มเข้าสู่ SceneTree ด้วย add_child()
preload("res://...") จะถูก Parse และโหลดไว้ล่วงหน้าตั้งแต่ตอน Compile Time ป้องกันอาการกระตุก (Stutter) ระหว่างเกม ส่วน load("res://...") จะโหลดจากดิสก์เมื่อถูกเรียก เหมาะกับระบบโหลดไดนามิกขนาดใหญ่
Dynamic Bullet Spawner with Marker2D
└── Marker2D (Muzzle) [Script attached]
extends Marker2D
@export var bullet_scene: PackedScene
@export var fire_rate: float = 0.5
@onready var cooldown_timer: Timer = Timer.new()
func _ready() -> void:
add_child(cooldown_timer)
cooldown_timer.wait_time = fire_rate
cooldown_timer.timeout.connect(_fire_bullet)
cooldown_timer.start()
func _fire_bullet() -> void:
if not bullet_scene:
push_error("Bullet scene missing!")
return
var bullet_instance := bullet_scene.instantiate() as Node2D
bullet_instance.global_position = global_position
get_tree().current_scene.add_child(bullet_instance)
Pure Component Design (HealthComponent)
ออกแบบ Leaf Component บริสุทธิ์ที่ไม่ผูกติดกับประเภท Entity ใดๆ พร้อมระบบ Signals
Micro-Concept & Architecture Breakdown (5–10 Mins)
คอมโพเนนต์ที่ดีต้องเป็นอิสระโดยสมบูรณ์ (Decoupled) ไม่ต้องสนใจว่าโหนดแม่คือ Player, Enemy, หรือกล่องไม้พังได้ (Breakable Crate) มีหน้าที่แค่จัดเก็บค่าพลังชีวิต (Health), จัดการการลดทอน และส่งสัญญาณ (Signals) แจ้งเตือนเมื่อเลือดเปลี่ยนหรือตาย เพื่อให้แม่หรือระบบ UI รับไปจัดการต่อตามหลัก "Call Down, Signal Up"
Re-usable Pure HealthComponent
├── Sprite2D
└── HealthComponent (Node) [Script attached]
class_name HealthComponent
extends Node
signal health_changed(current: int, max_hp: int)
signal died
@export var max_health: int = 100
var current_health: int
func _ready() -> void:
current_health = max_health
func take_damage(amount: int) -> void:
if current_health <= 0:
return
current_health = clampi(current_health - amount, 0, max_health)
health_changed.emit(current_health, max_health)
if current_health == 0:
died.emit()
Weekly Review Quiz & Graduation Exam
สรุปข้อผิดพลาดมือใหม่ที่พบบ่อย พร้อมบททดสอบสร้างมินิเกมวัดผลจริง
Micro-Concept: 3 ข้อผิดพลาดมือใหม่ที่ต้องระวัง (5–10 Mins)
การเข้าถึงพาทหรือลูกใน $Sprite ตอน _init() จะพังเพราะลูกยังไม่อยู่ในหน่วยความจำของ SceneTree
การค้นหาพาททุกเฟรมทำให้เปลือง CPU มหาศาล ให้แคชเป็นตัวแปร @onready ตั้งแต่เริ่มเสมอ
การสั่ง load ไฟล์จาก Disk ทุกครั้งที่กดเสกกระสุนจะทำให้เกมแลคกระตุก ให้ใช้ preload() หรือ PackedScene
Weekly Review Quiz (3 ข้อหลัก)
Level 1 Graduation Exam: "Modular Cannon Defense"
โจทย์โปรเจกต์มินิเกม: สร้างซีน 2D ป้อมปืนใหญ่ที่หมุนกระบอกตามเมาส์ และเสกอินสแตนซ์ bullet_scene ยิงใส่เป้าหมายที่กำลังขยายขนาด โดยเป้าหมายต้องใช้ HealthComponent ที่สร้างขึ้นใน Day 6
- แยกโมดูลขาด 100% ไม่ใช้ Monolithic script
- Static Typing เต็มระบบ พร้อม Return Types (: Type, -> void)
- แคชพาทใน _ready() และใช้ _physics_process() จัดการฟิสิกส์
พร้อมก้าวต่อไปสู่การทดสอบความรู้หรือไม่?
คุณต้องการให้สร้าง Interactive Flashcards หรือชุดคำถาม Level 1 Interactive Quiz เพื่อทดสอบความรู้เรื่อง Node Lifecycles และ GDScript 2.0 ก่อนเริ่มลงมือทำ Day 1 หรือไม่?