ยินดีด้วยครับ! คุณมาถึง Day 7
Weekly Review Quiz & Graduation Exam
คุณได้ผ่านการฝึกฝนเข้มข้นมาครบ 6 วัน ทั้งเรื่อง Composition vs Inheritance, Type Safety, Node Lifecycles, Scene Unique Nodes (%), Dynamic Spawning และ Pure Component Design วันนี้คือบทสรุปเพื่อประเมินความรู้และลงมือสร้างมินิเกมวัดผลจริง "Modular Cannon Defense" เพื่อรับวุฒิบัตร Architect Grade!
1. Architectural Quiz
ทบทวน 8 ข้อสำคัญ
2. Exam Project
สร้าง Cannon Defense
3. Level 2 Preview
Physics & CharacterBody2D
คีย์เวิร์ดแห่งชัยชนะ Level 1
"เขียนสถาปัตยกรรมที่ยืดหยุ่น: ตัดขาดการผูกติด ส่งสัญญาณขึ้น เรียกคำสั่งลง พิมพ์ Type ชัดเจนทุกบรรทัด"
1. Weekly Review Quiz (แบบทดสอบสถาปัตยกรรมและการแก้บัค 8 ข้อ)
ลองทดสอบตนเองกับ 8 สถานการณ์จำลองที่โปรแกรมเมอร์มักเจอใน Godot 4 ตอบคำถามในใจก่อนกดดูเฉลยเพื่อทดสอบความแม่นยำทางสถาปัตยกรรมครับ:
@onready var sprite = $Sprite2D แต่เรียกใช้ sprite.visible = false ภายในฟังก์ชัน _init() แล้วเกมค้าง เกิดจากสาเหตุใด?
กดดูเฉลยข้อ 1
เพราะในขั้นตอน _init() วัตถุเพิ่งถูกจัดสรรในหน่วยความจำ ยังไม่ได้ถูกนำเข้าสู่ SceneTree จึงยังไม่มี Node ลูก ($Sprite2D) อยู่จริง ต้องเรียกใช้หลัง _ready() หรือในฟังก์ชันหลังจากเข้า Tree แล้วเท่านั้น
ParentNode -> ChildNode ฟังก์ชัน _ready() ของ Node ใดจะทำงานเสร็จสิ้นก่อนกัน?
กดดูเฉลยข้อ 2
เพราะ Godot ใช้ลำดับการทำ _ready() แบบ Bottom-Up (จากลูกขึ้นไปหาแม่) เพื่อให้มั่นใจว่าเมื่อ Node แม่ทำ _ready() Node ลูกทั้งหมดจะเตรียมพร้อมและมีข้อมูลสมบูรณ์แล้ว
%ScoreLabel ใน HUD.tscn แล้วนำ HUD.tscn ไป Instance ใส่ใน MainGame.tscn สคริปต์ของ MainGame.gd จะสามารถเรียก %ScoreLabel ตรงๆ ได้หรือไม่?
กดดูเฉลยข้อ 3
เพราะ Scene Unique Node (%) มีขอบเขตการใช้งาน (Scope) เฉพาะภายใน Scene เดียวกันเท่านั้น เพื่อรักษาหลัก Encapsulation สคริปต์ภายนอกต้องเข้าถึงผ่านฟังก์ชันของตัวแม่ เช่น $HUD.update_score() หรือส่งข้อมูลผ่าน Signal
load("res://Bullet.tscn") ด้านในฟังก์ชันยิงกระสุน จะเกิดผลเสียอย่างไร?
กดดูเฉลยข้อ 4
เนื่องจากเอนจินต้องอ่านและ Parse ไฟล์จากฮาร์ดดิสก์ใหม่ทุกครั้งที่ยิง ควรใช้ preload("res://Bullet.tscn") ที่คอมไพล์ไว้ล่วงหน้า หรือใช้ @export var bullet_scene: PackedScene
queue_free() แทนการใช้ free() กับ Node ที่อยู่ใน SceneTree?
กดดูเฉลยข้อ 5
queue_free() จะรอให้รอบการประมวลผลของ Frame ปัจจุบันทำงานเสร็จสิ้นก่อนจึงค่อยลบ Node ออกจาก RAM อย่างปลอดภัย ป้องกันปัญหาที่สคริปต์อื่นหรือระบบฟิสิกส์กำลังพยายามอ้างอิง Node นั้นอยู่ในเฟรมเดียวกัน
$UI/MarginContainer/VBoxContainer/Panel/Label.text = "HP" มีจุดอ่อนอย่างไรในเชิง Game Architecture?
กดดูเฉลยข้อ 6
หากฝ่าย UI ปรับเปลี่ยนโครงสร้างเพียงชั้นเดียว (เช่น แทรก HBoxContainer หรือเปลี่ยน Panel) พาทจะหลุดและเกมจะแครชทันที ควรแก้ไขโดยใช้ Scene Unique Node (%Label) หรือสร้างฟังก์ชันอัปเดตจากตัวแม่ลงมา
position.x += speed * delta ใน _process() ข้อดีของการคูณ delta คืออะไร?
กดดูเฉลยข้อ 7
ทำให้ความเร็วในการเคลื่อนที่ของวัตถุเท่ากันในทุกเครื่องคอมพิวเตอร์อย่างคงที่ ไม่ว่าเครื่องนั้นจะรันเกมที่ 30 FPS, 60 FPS หรือจอ 144 FPS ก็ตาม
get_tree().change_scene_to_file() เพื่อเปลี่ยนเป็นหน้า Game Over โดยตรงหรือไม่?
กดดูเฉลยข้อ 8
เพราะจะทำให้ HealthComponent ไม่สามารถนำไปใช้กับวัตถุอื่นๆ (เช่น ศัตรู หรือ กล่องไม้พังได้) ได้อีก ควรปล่อยสัญญาณ died ออกไปแทน แล้วให้ Node แม่หรือ Level Manager เป็นผู้จัดการตัดสินใจเปลี่ยนฉากตามบริบทของตนเอง
2. Level 1 Graduation Exam: "Modular Cannon Defense"
โจทย์โปรเจกต์มินิเกม (Mini-Game Specification):
สร้างเกมป้องกันป้อมปืน 2D ที่ประกอบด้วยป้อมปืนยิงเป้าหมาย โดยต้องประยุกต์ใช้ความรู้ทั้งหมดจาก Day 1 ถึง Day 6 อย่างเคร่งครัด เน้นการแบ่งโมดูลแบบ Pure Composition และ Type Safety 100%
ป้อมปืนหมุนตามตำแหน่งเมาส์ (look_at) และยิงกระสุนออกจากปากกระบอก (Marker2D) เมื่อคลิกเมาส์ซ้าย มี Cooldown Timer
กระสุนถูกเสกจาก PackedScene บินไปข้างหน้าตาม transform.x และลบตนเองอัตโนมัติด้วย queue_free() เมื่อครบ 3 วินาที
เป้าหมายใช้ HealthComponent เมื่อถูกกระสุนชนจะลด HP และลบตนเองออกจากเกมพร้อมส่ง Signal แจ้งเตือนเมื่อ HP เหลือ 0
แสดงคะแนนปัจจุบัน โดยใช้อ้างอิงแบบ Scene Unique Node (%ScoreLabel) อัปเดตผ่าน Signal เมื่อเป้าหมายถูกทำลาย
├── Turret (Node2D)
│ └── Muzzle (Marker2D) [แนบ TurretMuzzle.gd พร้อม export bullet_scene]
├── TargetDummy (Node2D)
│ ├── Sprite2D [รูปเป้าหมาย]
│ ├── Area2D (Hurtbox) [CollisionShape2D ตรวจจับกระสุนชน]
│ └── HealthComponent (Node) [แนบ HealthComponent.gd ที่ทำใน Day 6]
└── CanvasLayer (UI)
└── HUD (Control)
└── MarginContainer
└── Label (%ScoreLabel) [เปิด Access as Unique Name]
จำกฎ "Call Down, Signal Up" ให้แม่นยำครับ: เมื่อกระสุนชนเป้าหมาย ให้ Area2D ของเป้าหมายเรียก health_component.take_damage(25) ลงไป และเมื่อ HP หมด HealthComponent จะส่ง Signal died ขึ้นมาบอก TargetDummy ให้ทำลายตัวเองและบอก MainGame ให้เพิ่มคะแนนบน %ScoreLabel โดยไม่มีการเรียกข้าม Scene Tree ให้ผิดหลักการแม้แต่จุดเดียว!
3. เกณฑ์การประเมินผลระดับ Architect (Evaluation Rubric)
ใช้เกณฑ์การประเมินนี้ในการตรวจเช็คโค้ดของตนเอง เพื่อตรวจสอบว่าคุณภาพงานอยู่ในระดับมืออาชีพแล้วหรือยัง:
| หัวข้อประเมิน (Criteria) | 1 คะแนน (ต้องปรับปรุง) | 3 คะแนน (ผ่านเกณฑ์) | 5 คะแนน (ระดับ Architecture Grade) 🏆 |
|---|---|---|---|
|
Node Composition
|
เขียนโค้ดทุกอย่างรวมไว้ในสคริปต์เดียวที่ Root Node (Monolithic script) |
แยก Scene แต่ยังมี NodePath ผูกติดกันแน่น เช่น get_parent().get_node(...)
|
ใช้ Pure Component (HealthComponent) แยกอิสระ 100% ไร้เงาของ Parent นำไป Reuse กับวัตถุใดก็ได้ทันที |
|
GDScript Type Safety
|
ไม่ระบุ Static Type เลย หรือใช้ var ลอยๆ ทั้งหมด ไม่มี Type Hinting
|
ระบุ Type เฉพาะตัวแประดับ Class แต่ฟังก์ชันและ Parameter ไม่มี Type และไม่มี Return Type |
ใช้ Strict Static Typing 100% (: Type, -> void), มี @export_range, และ @onready ครบถ้วน
|
|
Signal & Event Flow
|
เรียกฟังก์ชันข้าม Scene ตรงๆ โดยไม่ใช้ Signal ทำให้เกิดการผูกมัดแน่น (Tight Coupling) | ใช้ Signal แต่ยังมีการอ้างอิง Hardcoded Path ข้าม Scene เพื่อ Connect Signal | ปฏิบัติตามหลัก "Call Down, Signal Up" 100% และใช้ Scene Unique Nodes (%) จัดการ UI อย่างแข็งแกร่ง |
|
Lifecycle & Memory
|
เรียก Node ใน _init() หรือใช้ free() ตรงๆ จนเกมแครช
|
รันผ่านแต่เรียก load() ในลูปยิง หรือค้นหา Node ซ้ำๆ ใน _process()
|
แคช Node ใน _ready(), ใช้ queue_free() ป้องกัน Memory Leak และใช้ PackedScene ถูกต้อง
|
4. สรุปความสำเร็จ และก้าวต่อไปสู่ LEVEL 2!
ขอแสดงความยินดีด้วยครับ! คุณจบหลักสูตร 7-Day Foundation แล้ว 🎉
ตลอด 7 วันที่ผ่านมา คุณไม่ได้เพียงแค่เรียนรู้การเขียนโค้ด Godot 4 แต่คุณได้ปลูกฝัง "สถาปัตยกรรมระดับซีเนียร์ (Architect Mindset)" ตั้งแต่วันแรก ทั้งการออกแบบระบบแบบ Component-Based, การจัดการ Scene Tree, การเขียนโค้ดที่ทนทานต่อการ Refactor ด้วย Unique Nodes และการจัดการหน่วยความจำที่ปลอดภัย
เจาะลึกความต่างของ StaticBody2D, RigidBody2D, Area2D และ CharacterBody2D
สร้างระบบควบคุมตัวละครระดับ Production พร้อม Coyote Time, Jump Buffering และ Inertia
ระบบต่อสู้และการรับความเสียหายแยก Collision Layers vs. Masks อย่างเป็นระบบ
เมื่อสร้างโปรเจกต์ Modular Cannon Defense เรียบร้อยแล้ว สามารถส่งโค้ดมาพูดคุยหรือรีวิวได้ตลอดเวลาครับ!