Godot 4 Architecture
Study Plan & Daily Routine
แผนการเรียนรู้เชิงสถาปัตยกรรมระดับซีเนียร์ (Architect-Level) คัดสรรสำหรับโปรแกรมเมอร์ที่มีพื้นฐานและต้องการยกระดับสู่มืออาชีพใน Godot Engine 4 ด้วยการลงทุนเวลาเพียงวันละ 20–30 นาที มุ่งเน้นการฝึกพิมพ์โค้ดด้วยตนเองเพื่อสร้าง Muscle Memory
5–10 นาทีแรก
สถาปัตยกรรม & คอนเซ็ปต์
15–20 นาทีหลัง
เจาะลึก Hands-on Drills
วันหยุดสุดสัปดาห์
Quiz & สอบวัดผล Mini-Game
กฎทองแห่งความสำเร็จ
"Type manually in editor — avoid copy-paste" เพื่อจดจำระบบ Type Safety และ Signal Flow ให้อยู่ในสัญชาตญาณ
Nodes, Scenes & GDScript Basics
ทำความเข้าใจการจัดโครงสร้างเชิง Composition, Type Safety และวงจรชีวิตของ Node
Micro-Concept & Architecture Breakdown (5–10 Mins)
ใน Godot ทุก Scene คือคลาสและอินสแตนซ์ของโหนด หลีกเลี่ยงการสืบทอดเชิงลึก (Deep Inheritance) แต่ให้สร้างฟังก์ชันที่ซับซ้อนด้วยการประกอบโหนดที่มีหน้าที่เฉพาะเจาะจง (Single-purpose sibling/child nodes)
การระบุ Type แบบคงที่ (เช่น var health: int = 100) ช่วยให้เอนจิน Optimize โค้ดได้ดีขึ้นและตรวจจับ Runtime Crash ตั้งแต่ Compile Time ใช้ @export เปิดค่าไปยัง Inspector และ @onready เชื่อมโยงโหนดอย่างปลอดภัย
- _init(): สร้างอ็อบเจกต์ในหน่วยความจำ (ยังไม่อยู่ใน SceneTree)
- _enter_tree(): โหนดก้าวเข้าสู่ SceneTree
- _ready(): โหนดลูกสร้างเสร็จสมบูรณ์พร้อมทำงาน
- _process(delta): ทำงานทุกเฟรมที่เรนเดอร์ (Variable rate)
- _physics_process(delta): ทำงานตาม Physics Tick คงที่ (Fixed 60 Hz)
Daily Drills (15–20 Mins)
กดปุ่ม Copy เพื่อนำโค้ดไปทดลองHealth Component Node (Pure Composition)
├── Sprite2D
├── CollisionShape2D
└── HealthComponent (Node) [Script attached]
class_name HealthComponent
extends Node
signal health_changed(current_health: int, max_health: int)
signal died
@export var max_health: int = 100
var current_health: int
func _ready() -> void:
current_health = max_health
health_changed.emit(current_health, max_health)
func damage(amount: int) -> void:
if current_health <= 0:
return
current_health = max(0, current_health - amount)
health_changed.emit(current_health, max_health)
if current_health == 0:
died.emit()
Programmatic Spawner with Dynamic Scene Instantiation
└── Marker2D (SpawnPoint) [Script attached]
extends Marker2D
@export var projectile_scene: PackedScene
@export var spawn_interval: float = 1.0
@onready var spawn_timer: Timer = Timer.new()
func _ready() -> void:
add_child(spawn_timer)
spawn_timer.wait_time = spawn_interval
spawn_timer.timeout.connect(_on_timer_timeout)
spawn_timer.start()
func _on_timer_timeout() -> void:
if not projectile_scene:
push_warning("Projectile scene not assigned!")
return
var instance: Node2D = projectile_scene.instantiate() as Node2D
instance.global_position = global_position
get_tree().current_scene.add_child(instance)
Node Access & Scene Unique Nodes (%)
└── PanelContainer
└── VBoxContainer
├── Label (%TitleLabel) [Scene Unique Node enabled]
└── Button (%StartButton) [Scene Unique Node enabled]
extends Control
# Accessing scene unique nodes directly regardless of hierarchy depth
@onready var title_label: Label = %TitleLabel
@onready var start_button: Button = %StartButton
func _ready() -> void:
title_label.text = "COMMAND CENTER"
start_button.pressed.connect(_on_start_pressed)
func _on_start_pressed() -> void:
title_label.text = "SYSTEM LAUNCHED"
Level 1 Weekly Review Quiz
Level 1 Graduation Exam: Modular Cannon Defense
โจทย์: สร้างซีนป้อมปืนหมุนตามเมาส์และยิงกระสุนใส่เป้าหมายที่กำลังขยายตัว โดยปืนต้องใช้คอมโพเนนต์ SpawnerComponent แยกจากตัวปืน และเป้าหมายต้องใช้ HealthComponent
| เกณฑ์ประเมิน | 3 คะแนน (Proficient) | 5 คะแนน (Architect Grade) |
|---|---|---|
| Node Composition | แยกซีนแต่ผูกกับ $Path แน่นหนา | คอมโพเนนต์แยกขาด 100% บริสุทธิ์ |
| Type Safety | ผสมผสาน dynamic กับ static | Strict Static Typing ครบทุกตัวแปรและฟังก์ชัน |
| Lifecycle Order | ทำงานได้แต่เรียก get_node() ใน process | แคชพาทใน _ready() และใช้ physics ใน _physics_process() |
Physics, Signals & Player Controllers
สถาปัตยกรรม "Call Down, Signal Up", เลเยอร์การชน และตัวควบคุมตัวละครระดับ Production
Micro-Concept & Architecture Breakdown (5–10 Mins)
- • StaticBody2D: กำแพง สิ่งแวดล้อมที่ไม่เคลื่อนที่
- • RigidBody2D: คำนวณฟิสิกส์เต็มรูปแบบตามแรงและมวล
- • Area2D: ตรวจจับการทับซ้อนและทริกเกอร์โดยไม่มีแรงผลัก
- • CharacterBody2D: ควบคุมการเคลื่อนที่ผ่านสคริปต์ (
move_and_slide())
- Parent เรียก Child: โหนดแม่สามารถเรียก Method ของโหนดลูกได้โดยตรง
- Child สื่อสารกลับ Parent: โหนดลูกต้องปล่อย Signal ขึ้นไปโดยไม่ต้องรู้ว่าใครเป็นแม่ เพื่อป้องกันการผูกโครงสร้างแบบถาวร (Loose Coupling)
• Layer: "ฉันคือใคร?" (หมวดหมู่ของตนเอง)
• Mask: "ฉันมองหาและจะชนกับใคร?" (หมวดหมู่เป้าหมาย)
แยกเลเยอร์ชัดเจน ลดการตรวจจับชนที่ไม่จำเป็นDaily Drills (15–20 Mins)
กดปุ่ม Copy เพื่อนำโค้ดไปทดลองProduction-Grade 2D Character Controller (Coyote Time & Inertia)
├── Sprite2D
├── CollisionShape2D
└── CoyoteTimer (Timer)
extends CharacterBody2D
@export var speed: float = 300.0
@export var jump_velocity: float = -400.0
@export var acceleration: float = 1200.0
@export var friction: float = 1000.0
var gravity: float = ProjectSettings.get_setting("physics/2d/default_gravity")
@onready var coyote_timer: Timer = $CoyoteTimer
func _physics_process(delta: float) -> void:
var was_on_floor: bool = is_on_floor()
# Apply gravity
if not is_on_floor():
velocity.y += gravity * delta
else:
coyote_timer.stop()
# Handle Jump with Coyote Time
if Input.is_action_just_pressed("jump"):
if is_on_floor() or not coyote_timer.is_stopped():
velocity.y = jump_velocity
coyote_timer.stop()
# Horizontal Movement & Interpolation
var direction: float = Input.get_axis("move_left", "move_right")
if direction != 0.0:
velocity.x = move_toward(velocity.x, direction * speed, acceleration * delta)
else:
velocity.x = move_toward(velocity.x, 0.0, friction * delta)
move_and_slide()
# Trigger Coyote Time on step-off
if was_on_floor and not is_on_floor() and velocity.y >= 0:
coyote_timer.start(0.15)
Area2D Hitbox / Hurtbox System (Layer-Separated)
# Hitbox2D.gd
class_name Hitbox2D
extends Area2D
@export var damage: int = 10
func _init() -> void:
collision_layer = 0
collision_mask = 2 # Target hurtbox layer
# Hurtbox2D.gd
class_name Hurtbox2D
extends Area2D
signal received_damage(amount: int)
func _init() -> void:
collision_layer = 2
collision_mask = 0
func _ready() -> void:
area_entered.connect(_on_area_entered)
func _on_area_entered(area: Area2D) -> void:
if area is Hitbox2D:
received_damage.emit(area.damage)
Decoupled Coin Collector System
# Coin.gd
extends Area2D
signal collected(value: int)
@export var coin_value: int = 1
func _ready() -> void:
body_entered.connect(_on_body_entered)
func _on_body_entered(body: Node2D) -> void:
if body is CharacterBody2D:
collected.emit(coin_value)
queue_free()
Level 2 Weekly Review Quiz
Level 2 Graduation Exam: Physics Arena Survival
โจทย์: สร้าง Top-down Character Controller พร้อมระบบ Dash, กับดักอันตราย (Area2D), และตัวเสกกระสุน โดยรับความเสียหายผ่าน Hurtbox2D และส่งสัญญาณ Signal ไปลดหลอดเลือด HUD
| เกณฑ์ประเมิน | 3 คะแนน (Proficient) | 5 คะแนน (Architect Grade) |
|---|---|---|
| Signal Architecture | ใช้สัญญาณแต่ระบุพาทแบบตายตัว | "Call Down, Signal Up" สมบูรณ์ ไร้ Cross-scene paths |
| Physics Engineering | เคลื่อนที่ได้แต่มีอาการกระตุกเมื่อชนขอบ | เคลื่อนที่นุ่มนวลด้วยการเกลี่ยความเร็วและ Coyote Buffer |
| Collision Matrix | ตั้งค่าผ่าน UI โดยไม่มีระบบชื่อเลเยอร์ | จัดระเบียบเลเยอร์ไม่มี Overlap ซ้ำซ้อน |
Game Architecture, UI & State Machines
Finite State Machine แบบ Node-based, Data-Driven Custom Resources และ Event Bus Autoload
Micro-Concept & Architecture Breakdown (5–10 Mins)
การจัดการท่าทางที่ซับซ้อน (Idle, Run, Jump, Attack) ด้วย match หรือ if/else ขนาดใหญ่จะดูแลยากในระยะยาว ให้แยก State ออกเป็นคลาสของ Node ย่อยภายใต้ตัวจัดการ StateMachine
ออกแบบ UI ที่รองรับทุกขนาดหน้าจอโดยใช้ Anchors และ Container อัตโนมัติ (เช่น VBoxContainer, HBoxContainer, GridContainer, MarginContainer) แทนการวางพิกเซลแบบ Hardcoded
สืบทอด extends Resource เพื่อสร้างโครงสร้างข้อมูลแบบ Custom เช่น สถิติไอเทม หรือการตั้งค่าอาวุธ เซฟเป็นไฟล์ .tres เพื่อแยก Data ออกจาก Logic เกมอย่างสิ้นเชิง
Daily Drills (15–20 Mins)
กดปุ่ม Copy เพื่อนำโค้ดไปทดลองNode-Based Finite State Machine
└── StateMachine (Node)
├── IdleState (Node)
└── MoveState (Node)
# State.gd
class_name State
extends Node
signal transitioned(state: State, new_state_name: String)
var actor: CharacterBody2D
func enter() -> void:
pass
func exit() -> void:
pass
func update(_delta: float) -> void:
pass
func physics_update(_delta: float) -> void:
pass
# StateMachine.gd
class_name StateMachine
extends Node
@export var initial_state: State
var current_state: State
var states: Dictionary = {}
func _ready() -> void:
for child in get_children():
if child is State:
states[child.name.to_lower()] = child
child.transitioned.connect(_on_child_transition)
child.actor = get_parent() as CharacterBody2D
if initial_state:
initial_state.enter()
current_state = initial_state
func _process(delta: float) -> void:
if current_state:
current_state.update(delta)
func _physics_process(delta: float) -> void:
if current_state:
current_state.physics_update(delta)
func _on_child_transition(state: State, new_state_name: String) -> void:
if state != current_state:
return
var new_state: State = states.get(new_state_name.to_lower())
if not new_state:
return
if current_state:
current_state.exit()
new_state.enter()
current_state = new_state
Data-Driven Weapon Resource
# WeaponData.gd
class_name WeaponData
extends Resource
@export var weapon_name: String = ""
@export var damage: int = 10
@export var fire_rate: float = 0.2
@export var icon: Texture2D
@export var projectile_scene: PackedScene
Event Bus Autoload Pattern
# EventBus.gd (Add to Project Settings -> Autoload)
extends Node
signal score_updated(new_score: int)
signal player_died
# ScoreUI.gd (Attached to Label)
extends Label
func _ready() -> void:
EventBus.score_updated.connect(_on_score_updated)
func _on_score_updated(new_score: int) -> void:
text = "SCORE: %d" % new_score
Level 3 Weekly Review Quiz
Level 3 Graduation Exam: ARPG Dungeon Crawler Prototype
โจทย์: สร้างผู้เล่น Top-down ขับเคลื่อนด้วย Finite State Machine (Idle, Move, Attack), อัปเดต UI HUD ผ่าน EventBus, และเสกไอเทมจากไฟล์ .tres ItemResource
| เกณฑ์ประเมิน | 3 คะแนน (Proficient) | 5 คะแนน (Architect Grade) |
|---|---|---|
| State Machine | แยกคลาส State แต่เข้าถึงพี่น้องโดยตรง | FSM บริสุทธิ์ สื่อสารผ่าน Transition Signals |
| Data Design | สร้าง Resource แต่ยังเขียน Parse มือ | Resource Asset (.tres) สมบูรณ์แบบ ลากวางใน Inspector ได้ทันที |
| UI Responsiveness | ใช้ Anchor พื้นฐานแต่สัดส่วนยังล้น | ใช้ Containers และ Themes ปรับขนาดรองรับทุก Aspect Ratio |
Polish, Optimization & Publishing
Tween Juice Effects, Generic Object Pooling, Asynchronous Threaded Scene Loading
Micro-Concept & Architecture Breakdown (5–10 Mins)
ใช้ create_tween() สำหรับแอนิเมชันน้ำหนักเบาและมีประสิทธิภาพสูง เช่น Squash & Stretch, UI Pop-ins, และ Camera Shakes โดยไม่ต้องพึ่งพา AnimationPlayer สำหรับการเปลี่ยนแปลงเล็กๆ
การเรียก instantiate() และ queue_free() บ่อยครั้งทำให้เกิด Memory Fragmentation และเกิด Lag Spike จาก Garbage Collection จึงต้องใช้ Object Pooling หมุนเวียนโหนดเดิมกลับมาใช้ใหม่
ใช้ ResourceLoader.load_threaded_request() เพื่อโหลดด่านขนาดใหญ่ใน Background Thread เพื่อป้องกันไม่ให้หน้าจอเกมค้าง (Freeze) ระหว่างเปลี่ยนฉาก
Daily Drills (15–20 Mins)
กดปุ่ม Copy เพื่อนำโค้ดไปทดลองCamera Juice & Tween Squash/Stretch
# JuiceEffects.gd
extends Node2D
@export var sprite: Sprite2D
@export var camera: Camera2D
func apply_squash_and_stretch() -> void:
var tween: Tween = create_tween().set_trans(Tween.TRANS_QUAD).set_ease(Tween.EASE_OUT)
sprite.scale = Vector2(1.4, 0.6)
tween.tween_property(sprite, "scale", Vector2(1.0, 1.0), 0.25)
func apply_camera_shake(intensity: float = 10.0, duration: float = 0.2) -> void:
var tween: Tween = create_tween()
var elapsed: float = 0.0
while elapsed < duration:
var offset: Vector2 = Vector2(
randf_range(-intensity, intensity),
randf_range(-intensity, intensity)
)
tween.tween_property(camera, "offset", offset, 0.05)
elapsed += 0.05
tween.tween_property(camera, "offset", Vector2.ZERO, 0.05)
High-Performance Generic Node Pooler
class_name NodePool
extends Node
@export var scene: PackedScene
@export var pool_size: int = 50
var _pool: Array[Node] = []
func _ready() -> void:
for i in range(pool_size):
var instance: Node = scene.instantiate()
instance.process_mode = PROCESS_MODE_DISABLED
if instance is CanvasItem:
instance.visible = false
add_child(instance)
_pool.append(instance)
func get_node_from_pool() -> Node:
for instance in _pool:
if instance.process_mode == PROCESS_MODE_DISABLED:
instance.process_mode = PROCESS_MODE_INHERIT
if instance is CanvasItem:
instance.visible = true
return instance
return null
func return_to_pool(instance: Node) -> void:
instance.process_mode = PROCESS_MODE_DISABLED
if instance is CanvasItem:
instance.visible = false
Threaded Level Loader
# AsyncLoader.gd
extends Node
signal loading_completed(loaded_scene: PackedScene)
signal progress_updated(percent: float)
var _target_path: String = ""
var _is_loading: bool = false
func load_level(path: String) -> void:
_target_path = path
var err: Error = ResourceLoader.load_threaded_request(path)
if err == OK:
_is_loading = true
func _process(_delta: float) -> void:
if not _is_loading:
return
var progress: Array = []
var status: ResourceLoader.ThreadLoadStatus = ResourceLoader.load_threaded_get_status(_target_path, progress)
if status == ResourceLoader.THREAD_LOAD_IN_PROGRESS:
progress_updated.emit(progress[0] * 100.0)
elif status == ResourceLoader.THREAD_LOAD_LOADED:
_is_loading = false
var scene: PackedScene = ResourceLoader.load_threaded_get(_target_path) as PackedScene
loading_completed.emit(scene)
Level 4 Weekly Review Quiz
Level 4 Graduation Exam: Commercial-Grade Shmup
โจทย์: สร้างเกมชูตติ้งยานอวกาศ 2D เต็มรูปแบบ ใช้ Object Pooler รองรับกระสุนจำนวนมหาศาล, Tween Juice, Asynchronous Transition ด่าน, เซฟ JSON ใน user:// และตั้งค่า Export Template
| เกณฑ์ประเมิน | 3 คะแนน (Proficient) | 5 คะแนน (Architect Grade) |
|---|---|---|
| Performance | ใช้พูลลิ่งแต่ขาดการจำกัดขนาดขอบเขต | รักษา 60 FPS นิ่งสนิทแม้กระสุนหลายร้อยนัด |
| Juice & Polish | พึ่งพาแต่ AnimationPlayer ใน UI เล็กๆ | ใช้ Procedural Tweens ทั้ง Squash, Shake และ Hitstop |
| Publish Readiness | เซฟลง user:// แต่ Render Thread ค้างตอนโหลด | Threaded Loading ไร้รอยต่อ + บันทึกสถานะพร้อมขายจริง |
Daily Study Matrix Summary
ภาพรวมการจัดตารางเวลาประจำสัปดาห์ เพื่อความเชี่ยวชาญแบบก้าวกระโดด
* สม่ำเสมอทุกวันจันทร์ - ศุกร์ สะสมความคุ้นเคยกับ GDScript 2.0
* เลือกวันเสาร์หรืออาทิตย์ 1 วัน เพื่อรวบยอดความรู้เป็นผลงานที่เล่นได้จริง
พร้อมเริ่มต้น Level 1, Day 1 แล้วหรือยัง?
คุณต้องการให้เริ่มเจาะลึก Level 1: Health Component พร้อมขั้นตอน Step-by-Step และการตั้งค่า Static Typing ใน Godot 4 Editor เลยหรือไม่?