ยินดีต้อนรับสู่ Day 2 ครับ!
Type Safety & Annotations
วันนี้เราจะลงลึกเรื่อง GDScript 2.0 Static Typing และ Editor Annotations (@export, @onready) ซึ่งเป็นหัวใจสำคัญในการเขียนโค้ด Godot 4 ให้ทำงานได้รวดเร็ว ลดความผิดพลาดขณะรันเกม (Runtime Crash) และเปิดโอกาสให้ Game Designer ปรับแต่งค่าได้สะดวกผ่าน Inspector!
1. Micro-Concept
Static Types & Annotations
2. StatsController
สร้าง Component แบบ Type-Safe
3. Test & Validate
เชื่อมต่อ & รันจริงใน Output
คีย์เวิร์ดสำคัญประจำวันนี้
"ระบุชนิดตัวแปรให้ครบทุกตัวแปรและฟังก์ชัน เพื่อให้คอมไพเลอร์ช่วยดักจับบั๊กแทนผู้เล่นของคุณ"
1. Micro-Concept Breakdown: 3 เสาหลักของความปลอดภัยใน GDScript
1) Static Typing
การระบุชนิดตัวแปรอย่างชัดเจน เช่น var health: int = 100 หรือ func take_damage(...) -> void:
ช่วยให้ Godot ตรวจสอบข้อผิดพลาดตั้งแต่ตอนเขียนโค้ด (Compile Time) ป้องกันเกมแครชกลางคัน (Runtime Crash)
2) Editor Annotations
- •
@export: Expose ตัวแปรไปยัง Inspector ให้แก้ค่าได้โดยไม่ต้องเปิดโค้ด - •
@export_range(min, max, step): สร้างแถบ Slider จำกัดขอบเขตค่าตัวเลข - •
@export_group("..."): จัดหมวดหมู่ใน Inspector ให้เรียบร้อยสวยงาม
3) Safe Node Access (@onready)
@onready ชะลอการดึงค่าตัวแปรจนกว่า Node นั้นและลูกๆ ทั้งหมดจะก้าวเข้าสู่ SceneTree เสร็จสมบูรณ์ (หลังจากเหตุการณ์ _ready())
2. Hands-on Drill: สร้าง StatsController.gd
เราจะสร้าง Component สำหรับจัดการค่าพลังและสถานะตัวละคร ที่ระบุชนิดข้อมูลแบบ Static Typing พร้อมใช้ @export_group และ @export_range จัดหมวดหมู่ใน Inspector
└── StatsController (Node) [แนบสคริปต์ StatsController.gd]
class_name StatsController
extends Node
## Controller สำหรับจัดการค่าพลังตัวละครด้วย GDScript 2.0 Type Safety
# จัดกลุ่มตัวแปรพื้นฐานใน Inspector
@export_group("Base Stats")
@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"
# จัดกลุ่มการต่อสู้ใน Inspector
@export_group("Combat")
@export var attack_power: int = 15
# ตัวแประดับ Class ระบุ Type ชัดเจน (ยังไม่กำหนดค่าเริ่มต้น)
var current_health: int
func _ready() -> void:
current_health = max_health
# ใช้ print_rich เพื่อพิมพ์ข้อความแบบมีสีลงใน Console Output
print_rich("[color=green]%s initialized with %d HP.[/color]" % [character_name, current_health])
## ฟังก์ชันรับความเสียหาย (ระบุชนิด Parameter และ Return Type เป็น void)
func apply_damage(amount: int) -> void:
current_health = clampi(current_health - amount, 0, max_health)
print_rich("[color=yellow]%s took %d damage! Remaining HP: %d[/color]" % [character_name, amount, current_health])
3. ขั้นตอนการลงมือทำและทดสอบใน Godot Engine 4
สเต็ปสร้างและตรวจสอบใน Editor:
- เพิ่ม Component: คลิกขวาที่ Root Node ของคุณ (เช่น Node2D หรือ CharacterBody2D) เลือก Add Child Node > เลือกประเภท
Nodeตั้งชื่อว่าStatsController - แนบสคริปต์: คลิกขวาที่
StatsControllerเลือก Attach Script ตั้งชื่อว่าStatsController.gdแล้ววางโค้ดด้านบนลงไป - ตรวจสอบความเปลี่ยนแปลงใน Inspector: คลิกที่โหนด
StatsControllerแล้วสังเกตหน้าต่าง Inspector ด้านขวา:- จะเห็นแถบหมวดหมู่ Base Stats และ Combat ถูกแบ่งส่วนอย่างเป็นระเบียบ
- ตัวแปร
max_healthและmovement_speedจะกลายเป็น Slider Bar ให้ลากปรับค่าได้อย่างปลอดภัย
- สร้าง Script ทดสอบที่ Root Node: คลิกขวาที่ Root Node แล้วแนบสคริปต์ทดสอบเพื่อจำลองการรับความเสียหาย:
extends Node2D
# ใช้ @onready ผูกกับ StatsController อย่างปลอดภัย
@onready var stats_controller: StatsController = $StatsController
func _ready() -> void:
# ทดสอบเรียกใช้ฟังก์ชันรับความเสียหาย
stats_controller.apply_damage(25)
4. 🎯 Mini-Check สำหรับ Day 2
ลองทดสอบความเข้าใจสั้นๆ เพื่อปิดท้ายการฝึกของวันนี้ครับ:
@onready var sprite: Sprite2D = $Sprite2D ไว้ที่ส่วนหัวของ Script กับการเขียน var sprite: Sprite2D = $Sprite2D เฉยๆ (ไม่มี @onready) สองแบบนี้ให้ผลลัพธ์ต่างกันอย่างไร และแบบไหนจะเกิด Error?
กดดูเฉลยและคำอธิบายทางสถาปัตยกรรม
คำตอบ: แบบที่ไม่มี @onready จะทำให้เกิด Runtime Null-Pointer Error ทันที!
เหตุผลเชิงลึก: ตัวแปรระดับคลาสทั่วไปที่ไม่มี @onready จะถูกประมวลผลตั้งแต่ช่วงที่วัตถุกำลังจัดสรรพื้นที่ใน RAM (ช่วง _init()) ซึ่งในขณะนั้น Node ลูก ($Sprite2D) ยังไม่ได้ถูกสร้างขึ้นและยังไม่ได้เชื่อมต่อเข้ากับ SceneTree ตัวแปรจึงมีค่าเป็น null
การใส่ @onready เป็นการบอก Godot ให้ "รอจนกระทั่งทั้งตัวมันเองและ Node ลูกทั้งหมดก้าวเข้าสู่ SceneTree และรัน _ready() เสร็จสมบูรณ์ก่อน" จึงค่อยค้นหาพาทและกำหนดค่าให้ตัวแปร ทำให้ปลอดภัย 100%!
พร้อมต่อ Day 3 แล้วหรือยังครับ?
วันพรุ่งนี้เราจะเจาะลึก Day 3: Node Lifecycles & Execution Order (_init, _enter_tree, _ready, _process, _physics_process) ทำความเข้าใจลำดับการเกิดและตายของ Node อย่างลึกซึ้ง!