Level 1 Deep Dive

7-Day Architecture & Practice Roadmap

20–30 Mins / Day
Nodes, Scenes & GDScript Basics

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"

Scene คือ Class และ Instance
Composition เหนือกว่า Inheritance
Strict Static Typing 100%
D1
Day 1 • Architecture Core

Object Composition & Scene Tree Architecture

เข้าใจระบบ SceneTree ใน Godot 4 และการสร้างฟังก์ชันผ่าน Component แทน Deep Inheritance

20–30 Mins Routine

Micro-Concept & Architecture Breakdown (5–10 Mins)

Scene as Class & Nodes Tree

ใน Godot 4 ทุก Scene คือไฟล์ Tree ของโหนดที่บันทึกไว้ หลีกเลี่ยงการสืบทอดเชิงลึก เช่น Player > CombatEntity > PhysicsEntity เพราะจะสร้างหนี้ทางเทคนิค ให้หันมาใช้ Composition ด้วยการแปะ Node ย่อยที่มีหน้าที่เฉพาะเจาะจงลงไปแทน

SceneTree Lifecycle Rule

ทุกโหนดที่มีผลในการคำนวณและเรนเดอร์จะต้องอยู่ภายใน SceneTree เท่านั้น โหนดที่ถูกสร้างขึ้นใน Memory แต่ยังไม่ได้ถูก add_child() เข้าสู่ทรี จะไม่ได้รับ Process Ticks หรืออัปเดตฟิสิกส์ใดๆ ทั้งสิ้น

Drill 1.1
RotatorComponent Node (Pure Modular Rotation)
Node Hierarchy:
Sprite2D (Planet)
└── 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
D2
Day 2 • Language Mastery

GDScript 2.0 Type Safety & Editor Annotations

การใช้ Static Typing, Type Inlining, @export_range และ @onready อย่างมีประสิทธิภาพ

20–30 Mins Routine

Micro-Concept & Architecture Breakdown (5–10 Mins)

Static Typing Optimization

GDScript 2.0 มีระบบ Strict Static Typing ที่เข้มงวด การกำหนด Type ชัดเจน (เช่น var health: int = 100) ช่วยให้ Compiler ใน Godot ทำ Direct Opcode Optimization และป้องกัน Null-Pointer Crash ได้ก่อนเริ่มรันเกม

Inspector Annotations & @onready

@export เปิดโอกาสให้ Game Designer ปรับค่าใน Inspector ได้ทันที และสามารถจำกัดขอบเขตด้วย @export_range() ส่วน @onready ช่วยผูกโหนดอย่างปลอดภัยเมื่อเข้าสู่ทรี

Drill 2.1
Typed CharacterStats with Clamped Bounds
Node Hierarchy:
Node2D (Character)
└── 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)
D3
Day 3 • Execution Lifecycle

Node Lifecycles & Execution Order

ลำดับการทำงานแบบ Bottom-up ของ _ready() และความแตกต่างระหว่าง _process กับ _physics_process

20–30 Mins Routine

Micro-Concept & Architecture Breakdown (5–10 Mins)

5 สเต็ปลำดับวงจรชีวิตที่ต้องจำให้ขึ้นใจ:
  1. _init(): อ็อบเจกต์สร้างใน RAM (โหนดลูกยังไม่มี และยังไม่อยู่ใน SceneTree)
  2. _enter_tree(): โหนดก้าวเข้าสู่ SceneTree แต่ลูกอาจยังโหลดไม่เสร็จ
  3. _ready(): ทำงานแบบ Bottom-Up (โหนดลูกต้อง _ready() ให้เสร็จก่อนเสมอ แม่ถึงจะ _ready())
  4. _process(delta): ทำงานทุก Render Frame (เวลาผันผวนตามการแสดงผล ใช้กับ UI / Visual)
  5. _physics_process(delta): ทำงานด้วยอัตราคงที่ Fixed 60 Hz (ใช้คำนวณการชนและการเคลื่อนที่)
Drill 3.1
LifecycleTracker Component (Console Verification)
Node Hierarchy:
Node2D (Main)
└── 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
D4
Day 4 • Clean Node Tree

Robust Node Access & Scene Unique Nodes (%)

บอกลาเส้นทางเปราะบาง $A/B/C/D ด้วยฟีเจอร์ Scene Unique Nodes (%) ของ Godot 4

20–30 Mins Routine

Micro-Concept & Architecture Breakdown (5–10 Mins)

ปัญหาของ NodePath แบบดั้งเดิม

การเข้าถึงโหนดผ่านพาทตายตัว เช่น $UI/Panel/VBoxContainer/Label มีความเปราะบางสูงมาก เมื่อ Game Designer ย้ายลำดับโหนดหรือใส่ Container เพิ่ม โค้ดจะพังและเกิด Null-Pointer Error ทันที

Scene Unique Nodes (%) คือคำตอบ

ใน Godot 4 เราสามารถคลิกขวาที่โหนดแล้วเปิด Access as Unique Name ซึ่งจะเติมสัญลักษณ์ % ทำให้เราอ้างอิงผ่าน %Label ได้โดยตรงจากทุกที่ภายในซีนเดียวกัน

Drill 4.1
HUD Manager with Scene Unique Nodes
Node Hierarchy:
Control (HUD) [Script attached]
└── 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()
D5
Day 5 • Dynamic Spawning

Dynamic Scene Instantiation & Memory Management

การเสกซีนผ่าน PackedScene, การเลือกใช้ preload() เทียบกับ load() และการจัดการตำแหน่งพิกัด

20–30 Mins Routine

Micro-Concept & Architecture Breakdown (5–10 Mins)

PackedScene & .instantiate()

การเสกวัตถุระหว่างเล่น (กระสุน, มอนสเตอร์, ไอเทม) ให้ประกาศตัวแปรเป็น PackedScene แล้วเรียก .instantiate() กำหนดพิกัด global_position ก่อนเพิ่มเข้าสู่ SceneTree ด้วย add_child()

preload() vs. load()

preload("res://...") จะถูก Parse และโหลดไว้ล่วงหน้าตั้งแต่ตอน Compile Time ป้องกันอาการกระตุก (Stutter) ระหว่างเกม ส่วน load("res://...") จะโหลดจากดิสก์เมื่อถูกเรียก เหมาะกับระบบโหลดไดนามิกขนาดใหญ่

Drill 5.1
Dynamic Bullet Spawner with Marker2D
Node Hierarchy:
Node2D (Turret)
└── 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)
D6
Day 6 • Architectural Decoupling

Pure Component Design (HealthComponent)

ออกแบบ Leaf Component บริสุทธิ์ที่ไม่ผูกติดกับประเภท Entity ใดๆ พร้อมระบบ Signals

20–30 Mins Routine

Micro-Concept & Architecture Breakdown (5–10 Mins)

นิยามของ Pure Leaf Component:

คอมโพเนนต์ที่ดีต้องเป็นอิสระโดยสมบูรณ์ (Decoupled) ไม่ต้องสนใจว่าโหนดแม่คือ Player, Enemy, หรือกล่องไม้พังได้ (Breakable Crate) มีหน้าที่แค่จัดเก็บค่าพลังชีวิต (Health), จัดการการลดทอน และส่งสัญญาณ (Signals) แจ้งเตือนเมื่อเลือดเปลี่ยนหรือตาย เพื่อให้แม่หรือระบบ UI รับไปจัดการต่อตามหลัก "Call Down, Signal Up"

Drill 6.1
Re-usable Pure HealthComponent
Node Hierarchy:
Node2D (BreakableCrate)
├── 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()
D7
Day 7 • Review & Graduation

Weekly Review Quiz & Graduation Exam

สรุปข้อผิดพลาดมือใหม่ที่พบบ่อย พร้อมบททดสอบสร้างมินิเกมวัดผลจริง

15 Mins Quiz + Project

Micro-Concept: 3 ข้อผิดพลาดมือใหม่ที่ต้องระวัง (5–10 Mins)

1. เรียก Node ใน _init()

การเข้าถึงพาทหรือลูกใน $Sprite ตอน _init() จะพังเพราะลูกยังไม่อยู่ในหน่วยความจำของ SceneTree

2. เรียก get_node() ใน _process()

การค้นหาพาททุกเฟรมทำให้เปลือง CPU มหาศาล ให้แคชเป็นตัวแปร @onready ตั้งแต่เริ่มเสมอ

3. เรียก load() ซ้ำใน Loop ยิง

การสั่ง load ไฟล์จาก Disk ทุกครั้งที่กดเสกกระสุนจะทำให้เกมแลคกระตุก ให้ใช้ preload() หรือ PackedScene

Weekly Review Quiz (3 ข้อหลัก)

Q1: ทำไม $Sprite2D.visible = false ถึงแครชเมื่อเรียกใน _init()? คำตอบ: การอ้างอิงโหนดลูกยังไม่มีอยู่ใน SceneTree ระหว่างจองหน่วยความจำ _init()
Q2: ข้อดีที่เหนือกว่าของ preload() เทียบกับ load() คืออะไร? คำตอบ: preload() จะ Compile และโหลดข้อมูลไว้ล่วงหน้า ทำให้ไม่มีอาการกระตุก (Lag Spike) เมื่อเสกอินสแตนซ์ระหว่างเล่นเกม
Q3: Scene Unique Nodes (%) ช่วยให้การจัดระเบียบโหนดยั่งยืนได้อย่างไร? คำตอบ: ช่วยให้อ้างอิงโหนดได้โดยตรงแบบ Depth-Independent แม้มีการย้ายโครงสร้าง Parent/Child พาทก็จะไม่หลุด

Level 1 Graduation Exam: "Modular Cannon Defense"

โจทย์โปรเจกต์มินิเกม: สร้างซีน 2D ป้อมปืนใหญ่ที่หมุนกระบอกตามเมาส์ และเสกอินสแตนซ์ bullet_scene ยิงใส่เป้าหมายที่กำลังขยายขนาด โดยเป้าหมายต้องใช้ HealthComponent ที่สร้างขึ้นใน Day 6

เกณฑ์ประเมินระดับ Architect Grade (5 ดาว):
  • แยกโมดูลขาด 100% ไม่ใช้ Monolithic script
  • Static Typing เต็มระบบ พร้อม Return Types (: Type, -> void)
  • แคชพาทใน _ready() และใช้ _physics_process() จัดการฟิสิกส์
Next Step Suggestion

พร้อมก้าวต่อไปสู่การทดสอบความรู้หรือไม่?

คุณต้องการให้สร้าง Interactive Flashcards หรือชุดคำถาม Level 1 Interactive Quiz เพื่อทดสอบความรู้เรื่อง Node Lifecycles และ GDScript 2.0 ก่อนเริ่มลงมือทำ Day 1 หรือไม่?

คัดลอก GDScript ไปยังคลิปบอร์ดแล้ว!