Godot 4 Master Curriculum

Architecture & Engineering Study Plan

20–30 Mins / Day
Intermediate to Professional Grade

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 ให้อยู่ในสัญชาตญาณ

Static Typing 100%
Call Down, Signal Up
Node Composition Over Inheritance
1
Level 1 Fundamentals

Nodes, Scenes & GDScript Basics

ทำความเข้าใจการจัดโครงสร้างเชิง Composition, Type Safety และวงจรชีวิตของ Node

Daily 20–30 Mins

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

Object-Oriented Composition

ใน Godot ทุก Scene คือคลาสและอินสแตนซ์ของโหนด หลีกเลี่ยงการสืบทอดเชิงลึก (Deep Inheritance) แต่ให้สร้างฟังก์ชันที่ซับซ้อนด้วยการประกอบโหนดที่มีหน้าที่เฉพาะเจาะจง (Single-purpose sibling/child nodes)

GDScript 2.0 Type Safety

การระบุ Type แบบคงที่ (เช่น var health: int = 100) ช่วยให้เอนจิน Optimize โค้ดได้ดีขึ้นและตรวจจับ Runtime Crash ตั้งแต่ Compile Time ใช้ @export เปิดค่าไปยัง Inspector และ @onready เชื่อมโยงโหนดอย่างปลอดภัย

Node Lifecycle Order
  1. _init(): สร้างอ็อบเจกต์ในหน่วยความจำ (ยังไม่อยู่ใน SceneTree)
  2. _enter_tree(): โหนดก้าวเข้าสู่ SceneTree
  3. _ready(): โหนดลูกสร้างเสร็จสมบูรณ์พร้อมทำงาน
  4. _process(delta): ทำงานทุกเฟรมที่เรนเดอร์ (Variable rate)
  5. _physics_process(delta): ทำงานตาม Physics Tick คงที่ (Fixed 60 Hz)

Daily Drills (15–20 Mins)

กดปุ่ม Copy เพื่อนำโค้ดไปทดลอง
Drill 1.1
Health Component Node (Pure Composition)
Node Hierarchy:
CharacterBody2D (Player)
├── 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()
Drill 1.2
Programmatic Spawner with Dynamic Scene Instantiation
Node Hierarchy:
Node2D (SpawnerWorld)
└── 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)
Drill 1.3
Node Access & Scene Unique Nodes (%)
Node Hierarchy:
Control (UIRoot)
└── 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

Q1: อ้างอิง @onready var sprite = $Sprite2D ใน _init() เกิดอะไรขึ้น? คำตอบ: Runtime Null-Pointer Error เพราะโหนดลูกยังไม่ถูกเชื่อมต่อเข้า SceneTree ในช่วง _init()
Q2: หลีกเลี่ยง NodePaths ยาวๆ อย่าง $Panel/VBox/HBox/Container/Label ได้อย่างไร? คำตอบ: ใช้ Scene Unique Nodes โดยเปิดไอคอน % เพื่ออ้างอิงผ่าน %Label ได้จากทุกที่ภายในซีนเดียวกัน
Q3: เมื่อใดควรใช้ _process() เทียบกับ _physics_process()? คำตอบ: _process() ทำงานทุกเฟรมสำหรับภาพและ UI ส่วน _physics_process() ทำงานแบบคงที่ (Fixed 60Hz) สำหรับงานฟิสิกส์และการชน
Q4: ทำไม @export var max_speed ดีกว่า var max_speed? คำตอบ: @export ช่วยเปิดตัวแปรสู่ Inspector ทำให้ Game Designer ปรับแต่งค่าได้โดยไม่ต้องเปิดแก้ไขโค้ด
Q5: ข้อผิดพลาดของ load("res://Bullet.tscn").instantiate() คืออะไร? คำตอบ: โหลดไฟล์ซ้ำจาก Disk ทุกครั้งที่เรียก ให้เปลี่ยนเป็น preload() หรือส่งผ่าน PackedScene

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()
2
Level 2 Mechanics

Physics, Signals & Player Controllers

สถาปัตยกรรม "Call Down, Signal Up", เลเยอร์การชน และตัวควบคุมตัวละครระดับ Production

Daily 20–30 Mins

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

Physics Body Selection
  • • StaticBody2D: กำแพง สิ่งแวดล้อมที่ไม่เคลื่อนที่
  • • RigidBody2D: คำนวณฟิสิกส์เต็มรูปแบบตามแรงและมวล
  • • Area2D: ตรวจจับการทับซ้อนและทริกเกอร์โดยไม่มีแรงผลัก
  • • CharacterBody2D: ควบคุมการเคลื่อนที่ผ่านสคริปต์ (move_and_slide())
Architecture Rule: "Call Down, Signal Up"

- Parent เรียก Child: โหนดแม่สามารถเรียก Method ของโหนดลูกได้โดยตรง
- Child สื่อสารกลับ Parent: โหนดลูกต้องปล่อย Signal ขึ้นไปโดยไม่ต้องรู้ว่าใครเป็นแม่ เพื่อป้องกันการผูกโครงสร้างแบบถาวร (Loose Coupling)

Collision Layers vs. Masks

• Layer: "ฉันคือใคร?" (หมวดหมู่ของตนเอง)

• Mask: "ฉันมองหาและจะชนกับใคร?" (หมวดหมู่เป้าหมาย)

แยกเลเยอร์ชัดเจน ลดการตรวจจับชนที่ไม่จำเป็น

Daily Drills (15–20 Mins)

กดปุ่ม Copy เพื่อนำโค้ดไปทดลอง
Drill 2.1
Production-Grade 2D Character Controller (Coyote Time & Inertia)
Node Hierarchy:
CharacterBody2D (Player)
├── 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)
Drill 2.2
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)
Drill 2.3
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

Q1: เอา velocity * delta ก่อนเรียก move_and_slide() เกิดผลอย่างไร? คำตอบ: ตัวละครเคลื่อนที่ช้ามากจนแทบไม่ขยับ เพราะ move_and_slide() จัดการคูณ delta ภายในเองอยู่แล้ว
Q2: ทำไม get_parent().get_node("Enemy").health -= 10 ถึงผิดหลักสถาปัตยกรรม? คำตอบ: ละเมิดการครอบคลุม (Encapsulation) และทำลายกฎ "Call Down, Signal Up" ทำให้สคริปต์พังทันทีเมื่อโครงสร้างเปลี่ยน
Q3: ตั้งค่ากระสุนให้โดนศัตรู (Layer 3) แต่ทะลุผ่านไอเทม (Layer 4) อย่างไร? คำตอบ: ตั้งค่า collision_mask ของกระสุนให้เปิดเฉพาะบิต Layer 3 และปิด Layer 4
Q4: ซินแทกซ์เชื่อมต่อ Signal ใน GDScript 2.0 เขียนอย่างไร? คำตอบ: source_node.signal_name.connect(callable_target_function)
Q5: การแก้ position ของ RigidBody2D ใน _physics_process() ส่งผลเสียอย่างไร? คำตอบ: รบกวนการคำนวณของ Physics Engine ให้ใช้ _integrate_forces() หรือส่งแรง Impulse แทน

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 ซ้ำซ้อน
3
Level 3 Architecture

Game Architecture, UI & State Machines

Finite State Machine แบบ Node-based, Data-Driven Custom Resources และ Event Bus Autoload

Daily 20–30 Mins

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

Finite State Machines (FSM)

การจัดการท่าทางที่ซับซ้อน (Idle, Run, Jump, Attack) ด้วย match หรือ if/else ขนาดใหญ่จะดูแลยากในระยะยาว ให้แยก State ออกเป็นคลาสของ Node ย่อยภายใต้ตัวจัดการ StateMachine

Control Node Layout Principles

ออกแบบ UI ที่รองรับทุกขนาดหน้าจอโดยใช้ Anchors และ Container อัตโนมัติ (เช่น VBoxContainer, HBoxContainer, GridContainer, MarginContainer) แทนการวางพิกเซลแบบ Hardcoded

Custom Resources (.tres)

สืบทอด extends Resource เพื่อสร้างโครงสร้างข้อมูลแบบ Custom เช่น สถิติไอเทม หรือการตั้งค่าอาวุธ เซฟเป็นไฟล์ .tres เพื่อแยก Data ออกจาก Logic เกมอย่างสิ้นเชิง

Daily Drills (15–20 Mins)

กดปุ่ม Copy เพื่อนำโค้ดไปทดลอง
Drill 3.1
Node-Based Finite State Machine
Node Hierarchy:
CharacterBody2D (Player)
└── 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
Drill 3.2
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
Drill 3.3
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

Q1: แก้ไข ItemResource ในช่องหนึ่งแล้วกระทบทุกช่อง เพราะเหตุใด? คำตอบ: Resource ส่งผ่านแบบ Pass-by-reference และแชร์ Memory กัน ให้เรียก .duplicate() เมื่อต้องการแยกอิสระ
Q2: เมื่อขยายหน้าต่างแล้ว UI Label หลุดขอบ แสดงว่าลืมตั้งค่าอะไร? คำตอบ: ไม่ได้ตั้งค่า Layout Anchors หรือไม่ได้ครอบด้วย Containers (MarginContainer, VBoxContainer)
Q3: จุดเด่นหลักของ Event Bus เทียบกับการเชื่อม Signal แบบตรงคืออะไร? คำตอบ: ช่วย Decouple ซีนที่อยู่ไกลกันหรือถูกเสกแบบ Dynamic (เช่น Enemy กับ HUD) ผ่านฮับกลาง
Q4: ทำไมไม่ควรใส่โค้ดใน Autoload มากเกินไป? คำตอบ: ทำให้เกิด Global State ที่ซ่อน Dependency และทำให้เขียน Unit Test แยกโมดูลได้ยาก
Q5: การเช็ค Input เพื่อเปลี่ยน State ใน FSM ควรอยู่ที่ใด? คำตอบ: ภายในฟังก์ชัน update() ของ State โหนดที่ Active อยู่ แล้วปล่อยสัญญาณ Transition กลับไปยังแม่

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
4
Level 4 Production

Polish, Optimization & Publishing

Tween Juice Effects, Generic Object Pooling, Asynchronous Threaded Scene Loading

Daily 20–30 Mins

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

Programmatic Polish (Juice)

ใช้ create_tween() สำหรับแอนิเมชันน้ำหนักเบาและมีประสิทธิภาพสูง เช่น Squash & Stretch, UI Pop-ins, และ Camera Shakes โดยไม่ต้องพึ่งพา AnimationPlayer สำหรับการเปลี่ยนแปลงเล็กๆ

Optimization & Pooling

การเรียก instantiate() และ queue_free() บ่อยครั้งทำให้เกิด Memory Fragmentation และเกิด Lag Spike จาก Garbage Collection จึงต้องใช้ Object Pooling หมุนเวียนโหนดเดิมกลับมาใช้ใหม่

Threaded Scene Loading

ใช้ ResourceLoader.load_threaded_request() เพื่อโหลดด่านขนาดใหญ่ใน Background Thread เพื่อป้องกันไม่ให้หน้าจอเกมค้าง (Freeze) ระหว่างเปลี่ยนฉาก

Daily Drills (15–20 Mins)

กดปุ่ม Copy เพื่อนำโค้ดไปทดลอง
Drill 4.1
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)
Drill 4.2
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
Drill 4.3
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

Q1: ยิงกระสุนนัดแรกแล้วเกมกระตุก เกิดจากอะไรและแก้ยังไง? คำตอบ: เกิดจาก Shader Compilation Stutter แก้ไขโดยทำ Pre-pass โหลด Shader ล่วงหน้าไว้ก่อนเริ่มเล่น
Q2: เหตุใดการสร้าง Tween ผ่าน create_tween() อาจเกิด Memory Leak? คำตอบ: เกิดจากการเก็บ Reference ข้ามการเปลี่ยนฉากหรือทำ Loop อนันต์โดยไม่เคลียร์ Tween เก่า
Q3: คำสั่งใดที่เคลียร์โหนดอย่างปลอดภัยเมื่อสิ้นสุดเฟรม? คำตอบ: queue_free() (หลีกเลี่ยง free() ตรงๆ ใน SceneTree เพราะทำให้ Process ตัวอื่น Crash)
Q4: ถ้าต้องการเซฟข้อมูลผู้เล่นลงดิสก์ ต้องใช้ Path Prefix ใด? คำตอบ: user:// (เพราะ res:// จะถูกล็อกเป็น Read-Only เมื่อ Export ตัวเกมจริง)
Q5: แท็บใดใน Debugger ที่ใช้วัดคอขวด Draw Calls เทียบกับ CPU Frame Time? คำตอบ: แท็บ Profiler และ Monitors ภายใน Debugger Panel

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 ไร้รอยต่อ + บันทึกสถานะพร้อมขายจริง
5
Final Blueprint

Daily Study Matrix Summary

ภาพรวมการจัดตารางเวลาประจำสัปดาห์ เพื่อความเชี่ยวชาญแบบก้าวกระโดด

WEEKDAY ROUTINE (25 MINS TOTAL)
5–10 Mins Architecture & Concept Review (ทำความเข้าใจโครงสร้างโหนดและหลักการ)
15–20 Mins Hands-on Code & Node Drills (ลงมือพิมพ์โค้ดจริงใน Godot 4 Editor)

* สม่ำเสมอทุกวันจันทร์ - ศุกร์ สะสมความคุ้นเคยกับ GDScript 2.0

WEEKEND REVIEW & GRADUATION
15 Mins Self-Graded Review Quiz (ตอบคำถามประเมินความเข้าใจเชิงสถาปัตยกรรม)
2–3 Hours Mini-Game Graduation Exam (ประกอบโปรเจกต์มินิเกมตามโจทย์ที่กำหนด)

* เลือกวันเสาร์หรืออาทิตย์ 1 วัน เพื่อรวบยอดความรู้เป็นผลงานที่เล่นได้จริง

Next Step Suggestion

พร้อมเริ่มต้น Level 1, Day 1 แล้วหรือยัง?

คุณต้องการให้เริ่มเจาะลึก Level 1: Health Component พร้อมขั้นตอน Step-by-Step และการตั้งค่า Static Typing ใน Godot 4 Editor เลยหรือไม่?

เปิด Day 1
คัดลอกโค้ดไปยังคลิปบอร์ดแล้ว!