ลุย Day 3 กันต่อเลยครับ!
Node Lifecycles & Execution Order
วันนี้เราจะมาทำความเข้าใจ วงจรชีวิตและลำดับการทำงาน (Lifecycle) ของ Node ใน Godot 4 เพื่อไขข้อข้องใจว่าทำไมการอ้างอิงโหนดลูกใน _init() จึงพัง และเหตุใด _ready() ถึงทำงานแบบ Bottom-Up จากลูกขึ้นหาแม่เสมอ!
1. Micro-Concept
5 ลำดับขั้นของ Lifecycle
2. Tracker Drill
Parent & Child Code Verification
3. Console Test
สังเกต Log ลำดับการรันจริง
คีย์เวิร์ดสำคัญประจำวันนี้
"จำลำดับให้ขึ้นใจ: สร้างก่อน เข้าทรีทีหลัง ลูกเรดี้ก่อน แม่จึงพร้อม"
1. Micro-Concept: 5 ลำดับขั้นของวงจรชีวิต Node ใน Godot 4
_init()
ถูกเรียกตอนสร้างวัตถุใน RAM (Constructor) ยังไม่มีโหนดลูก และยังไม่อยู่ใน SceneTree
_enter_tree()
โหนดเริ่มถูกนำเข้าสู่ SceneTree แต่ลูกๆ อาจยังไม่พร้อม มักใช้ลงทะเบียน Singleton
_ready()
โหนดลูกรันเสร็จก่อนแม่เสมอ! เป็นจุดที่ปลอดภัย 100% ในการเข้าถึงโหนดลูก
_process(delta)
รันทุก Frame การแสดงผล (เวลาผันผวนตาม FPS) เหมาะกับ UI, กล้อง และ Visual Effects
_physics_process(delta)
รันตามรอบเวลาคงที่ (Fixed Tick Rate 60 Hz) เหมาะกับคำนวณการชนและการเคลื่อนที่
ลำดับการเข้าสู่ Tree (_enter_tree()) จะทำงานจาก แม่ลงไปหาลูก (Top-Down) แต่ลำดับความพร้อมสมบูรณ์ (_ready()) จะทำงานแบบ ลูกย้อนกลับขึ้นหาแม่ (Bottom-Up) เพื่อให้มั่นใจว่าเมื่อแม่เริ่มทำงาน โหนดลูกที่เป็นส่วนประกอบภายในจะถูกเตรียมพร้อมและมีข้อมูลครบถ้วนแล้วเสมอ
2. Hands-on Drill: พิสูจน์ลำดับจริงด้วย LifecycleTracker
เราจะสร้าง 2 สคริปต์เพื่อพิมพ์ลำดับการทำงานของ Node แม่ (Parent) และ Node ลูก (Child) ออกทาง Console Output เพื่อดูด้วยตาตนเองว่า _ready() ของลูกจะถูกพิมพ์ก่อนแม่จริงหรือไม่!
└── Node (ChildNode) [แนบสคริปต์ ChildTracker.gd]
class_name ParentTracker
extends Node2D
func _init() -> void:
print("1. [PARENT] _init(): สร้างวัตถุ Parent ในหน่วยความจำ")
func _enter_tree() -> void:
print("2. [PARENT] _enter_tree(): Parent เข้าสู่ SceneTree")
func _ready() -> void:
# สังเกตว่าบรรทัดนี้จะถูกเรียก *หลังจาก* Child _ready() ทำงานเสร็จแล้วเสมอ
print("4. [PARENT] _ready(): Parent และลูกๆ สมบูรณ์พร้อมใช้งาน")
func _process(delta: float) -> void:
print("5. [PARENT] _process(): ประมวลผล Frame (delta: %f)" % delta)
set_process(false) # ปิดการรัน _process เพื่อไม่ให้พิมพ์รัวจนล้น Console
func _physics_process(delta: float) -> void:
print("6. [PARENT] _physics_process(): ประมวลผล Physics Tick (delta: %f)" % delta)
set_physics_process(false) # ปิดการรัน _physics_process หลังทำงานครั้งแรก
class_name ChildTracker
extends Node
func _init() -> void:
print(" ↳ [CHILD] _init(): สร้างวัตถุ Child ในหน่วยความจำ")
func _enter_tree() -> void:
print(" ↳ [CHILD] _enter_tree(): Child เข้าสู่ SceneTree")
func _ready() -> void:
# ลูกจะพร้อมและรัน _ready() เสร็จสมบูรณ์ ก่อนโหนดแม่เสมอ!
print("3. [CHILD] _ready(): Child ทำงาน ready เสร็จสิ้นก่อน Parent!")
3. ขั้นตอนการทดสอบจริงใน Godot Engine 4
สเต็ปสร้าง Scene และสังเกต Output Console:
- สร้าง Scene ใหม่: เลือก Root Node เป็น
Node2Dแล้วตั้งชื่อว่าMainParent - เพิ่ม Child Node: คลิกขวาที่
MainParentเลือก Add Child Node > เลือกประเภทNodeแล้วตั้งชื่อว่าChildNode - แนบสคริปต์ทั้งสอง:
- แนบ
ParentTracker.gdให้กับโหนดMainParent - แนบ
ChildTracker.gdให้กับโหนดChildNode
- แนบ
- ทดสอบการรัน: กด Play Scene (F6) แล้วสังเกตลำดับข้อความในแท็บ Output ด้านล่างของ Godot Editor
[CHILD] _ready() ทำงานเสร็จในลำดับที่ 3 ก่อนที่ [PARENT] _ready() จะทำงานในลำดับที่ 4 เสมอ! นี่คือเหตุผลที่โหนดแม่สามารถเข้าถึงตัวแปรของโหนดลูกใน _ready() ได้อย่างปลอดภัย 100%
4. 🎯 Mini-Check สำหรับ Day 3
ลองทดสอบความเข้าใจ 2 ข้อสั้นๆ เกี่ยวกับสถานการณ์การใช้งานจริงครับ:
move_and_slide()) ควรเขียนใน _process หรือ _physics_process?
กดดูเฉลยข้อ 1
_physics_process เสมอ!
เพราะการคำนวณการเคลื่อนที่และการชนของฟิสิกส์ต้องการช่วงเวลาคงที่ (Fixed Tick Rate เช่น 60 Hz) หากไปเขียนใน _process ความเร็วและแรงตอบสนองจะขึ้นลงตาม Frame Rate ของหน้าจอ ทำให้ตัวละครเกิดอาการเดินทะลุกำแพงหรือการชนกระตุก (Physics Stutter) ได้ครับ
$ChildNode.position = Vector2.ZERO ด้านในฟังก์ชัน _init() จะเกิดผลลัพธ์อย่างไร?
กดดูเฉลยข้อ 2
เพราะในขณะที่เอนจินรัน _init() วัตถุเพิ่งจะเริ่มจอง RAM และยังไม่ได้ถูกนำเข้าสู่ SceneTree ทำให้ความสัมพันธ์ของ Parent และ Node ลูก ($ChildNode) ยังไม่มีอยู่จริง การอ้างอิงผ่าน $ จึงส่งค่า null กลับมาและแครชทันที ต้องย้ายไปทำใน _ready() หรือใช้ @onready เท่านั้นครับ
พร้อมต่อ Day 4 แล้วหรือยังครับ?
วันพรุ่งนี้เราจะเจาะลึก Day 4: Robust Node Access & Scene Unique Nodes (%) กำจัดปัญหาเส้นทางพังเปราะบาง $A/B/C/D ด้วยฟีเจอร์ % ของ Godot 4!