อาการที่เจอ: ฉากไหนที่ของเยอะ ๆ (เกิดศัตรู + ระเบิด + ดาว พร้อมกัน) บางเฟรม CM33 ส่งจดหมายไปวาดเร็วกว่าที่ CM55 จะรับไหว CM55 เลยตอบว่า "not ready" ถ้าเราปล่อยให้ error นี้เด้งขึ้นมา (raise) → เกมค้างตายกลางเฟรม
ความจริงของฮาร์ดแวร์: "not ready" ไม่ใช่ความผิดพลาด มันคือ backpressure — สัญญาณว่า "ช้าลงหน่อย เดี๋ยวฉันตามทัน" สภาวะแบบนี้เป็นเรื่องปกติและชั่วคราว รอไม่กี่มิลลิวินาทีมันก็หายเอง
error จริง ๆ (เช่นชื่อ widget ผิด) ยังต้อง
raiseเหมือนเดิม เรา retry เฉพาะ อาการ "not ready" ที่รู้ว่าเป็นของชั่วคราวเท่านั้น — ไม่ใช่กลืน error ทุกชนิด
_r() ใน bentogame.pyทุกคำสั่งที่ engine ส่งไป CM55 ถูกห่อด้วย helper ตัวเดียวชื่อ _r() (bentogame.py:117) — คำว่า _r ย่อจาก retry:
def _r(fn, *a, **k):
"""Retry a ui call while CM55 signals transient "not ready" backpressure
(the same tolerance start() already uses for ui.screen). A heavy scene can
briefly outrun the CM55 graphics core; retrying instead of raising keeps the
game from crashing mid-frame."""
for _ in range(40): # ลองใหม่ได้สูงสุด 40 ครั้ง = bounded loop
try:
return fn(*a, **k) # สำเร็จ → คืนค่าออกทันที
except Exception as _e:
if "not ready" in str(_e): # เฉพาะ backpressure ชั่วคราวเท่านั้น
time.sleep_ms(15) # ถอยหลัง 15ms ให้ CM55 ตามทัน
continue
raise # error อื่น ๆ = ของจริง ปล่อยให้เด้ง
return fn(*a, **k) # ครบ 40 ครั้งยังไม่ได้ = ยอมให้ error จริง
เวลาเราเรียก box.move_to(x, y) ข้างในมันคือ _r(self._panel.pos, ...) (bentogame.py:234) — เท่ากับว่า ทุกการขยับ widget ผ่านตาข่ายนิรภัยตัวนี้หมด โดยที่โค้ดเกม (30% ที่เราเขียน) ไม่ต้องรู้เรื่องเลย
ทำไมต้อง "bounded" (
range(40)) ไม่ใช่while True? เพราะถ้า CM55 ตายจริง ๆ เราต้องยอมให้ error หลุดออกมา ไม่ใช่ค้างวนไม่รู้จบ — retry ที่ดีต้องมีเพดานเสมอ
อาการที่เจอ: เกม Shooter เวอร์ชัน Python รู้สึก "หนืด" — ทั้งที่เกม C บนบอร์ดเดียวกันลื่นปรื๊ด ทำไม? มันคือ CPU เดียวกันนี่นา
สาเหตุที่แท้จริง: ไม่ใช่ Python คำนวณช้า — แต่เป็นจำนวนจดหมายข้าม core ต่อเฟรม ทุก move_to หนึ่งครั้ง = IPC หนึ่งฉบับ CM33→CM55 เกมมีศัตรู + กระสุน + ระเบิด ยิ่งของบนจอเยอะ จดหมายต่อเฟรมยิ่งเยอะ → คอขวดอยู่ที่ จำนวน IPC round-trip ไม่ใช่ที่ความเร็ว CPU
fix จริงอยู่ที่ shooter_full.py:30 — ลดจำนวนศัตรูกับกระสุนสูงสุดลง:
# ก่อนแก้: EMAX, BMAX = 8, 6 ← เท่าเกม C (แต่ Python ส่ง IPC ต่อ object ไม่ไหว → หนืด)
EMAX, BMAX, BOOMMAX, STARS = 5, 4, 3, 5 # จุดสมดุล: object น้อยพอให้ลื่น แต่ยังมี action
comment ในไฟล์ยังคำนวณ "งบ widget" ไว้ให้เห็น (shooter_full.py:25–29) — เกม C ถือได้ ศัตรู 8 + กระสุน 6 เพราะมันวาดเองบน CM55 ไม่ต้องส่ง IPC ต่อชิ้น แต่ Python ต้องส่ง เราจึงเลือก budget จำนวน object บนจอ ให้พอดีกับ IPC ที่ไหวใน 1 เฟรม
โยงกับคาบ 11 (Object Pool): pool ช่วยไม่ให้ สร้าง object ใหม่ทุกเฟรม (ประหยัด RAM) ส่วนบทเรียนนี้บอกให้ budget จำนวน object ที่ขยับพร้อมกัน (ประหยัด IPC) — สองเรื่องนี้ทำงานคู่กัน: จำนวน widget คงที่ และ ไม่มากเกินงบ IPC ต่อเฟรม
อาการที่เจอ: ท่อในเกม Flappy เคยถูก resize() ทุกเฟรม เพื่อ "ตัด" ความสูงให้พอดีขอบจอ ผลคือ CM55 วาดท่อขนาดใหม่ทับ แต่ ไม่ได้เคลียร์พิกเซลเดิมที่ท่อเคยกินไว้ → เกิด รอยเขียวลากเฉียง + เส้นดำขวางจอ (scanline)
เหตุใด resize จึงเปื้อน: resize() เรียก ui.size (bentogame.py:254) เปลี่ยนขนาด widget — พื้นที่ที่ widget "หด" ออกไป CM55 ไม่ได้ถือว่าเป็นหน้าที่ต้องทาสีพื้นหลังทับให้ พิกเซลเก่าจึงค้างเป็นรอย
fix ใน flappy_full.py เปลี่ยนวิธีคิดเรื่องท่อทั้งหมด — ท่อเป็นกล่อง "สูงเต็มสนาม" สร้างครั้งเดียว ไม่ resize อีกเลย (flappy_full.py:22, 66):
PIPE_W = 64 # ความกว้างท่อ (คงที่ — ท่อเป็นกล่องสูงเต็มจอ ไม่ resize เลย)
# ท่อบน/ล่างเป็นกล่อง "สูงเต็มสนาม" (PIPE_W x PLAY_H) สร้างครั้งเดียว ไม่ resize อีกเลย
parts = {"top": game.Box(-200, 0, PIPE_W, PLAY_H, game.GB_DARK),
"bot": game.Box(-200, 0, PIPE_W, PLAY_H, game.GB_DARK), ...}
ช่องว่างระหว่างท่อไม่ได้เกิดจากการ ย่อ ท่อ แต่เกิดจากการ เลื่อน ท่อยาว ๆ ให้โผล่จากขอบบน/ขอบล่าง แล้วส่วนที่ล้นออกนอกจอ ปล่อยให้ฮาร์ดแวร์ clip ทิ้ง — ทุกเฟรมทำแค่ move_to (flappy_full.py:113–128):
def render_pipe(pipe):
# ต่อเฟรม: "เลื่อนอย่างเดียว" ไม่ resize — ท่อสูงเต็มจอ ส่วนพ้นขอบให้ฮาร์ดแวร์ตัด
pipe["top"].move_to(x, pipe["gap_top"] - PLAY_H) # ขอบล่างของท่อบน = ขอบช่องบน
pipe["bot"].move_to(x, pipe["gap_bot"]) # ขอบบนของท่อล่าง = ขอบช่องล่าง
อาการที่เจอ: พอปิดเมนูเลือกโหมด ฉากเกมต้อง recomposite ใหม่ใช้เวลา ~3–4 วินาที ระหว่างนั้นถ้าเกมเริ่มนับฟิสิกส์ทันที นกจะร่วง + ตายก่อนที่ผู้เล่นจะทันเห็นจอด้วยซ้ำ — แพ้แบบงง ๆ ทั้งที่ยังไม่ได้เล่น
วิธีแก้ 2 ชั้น ใน flappy_full.py:
started flag, flappy_full.py:150, 180)flappy_full.py:186)started + gracestarted = False # ยังไม่เริ่มบิน — นกลอยรอจนกระพือครั้งแรก (:150)
start_ms = 0 # เวลาที่ออกตัว — ใช้คุมช่วง grace 4 วิแรก (นกตายไม่ได้)
def update():
...
if not started: # ยังไม่ออกตัว: นกลอยรอกลางจอ ไม่มีแรงโน้มถ่วง
if not (flap_a or flap_up):
return # ให้ผู้เล่นพร้อม (ฉากวาดครบตั้งแต่หน้าเมนูแล้ว)
started = True # กระพือครั้งแรก = ออกบิน
start_ms = now # เริ่มจับเวลา grace 4 วิ
grace = time.ticks_diff(now, start_ms) < 4000 # 4 วิแรก: นกตายไม่ได้ (ครอบช่วงจอกำลังวาด)
...
if bird_y > PLAY_H - BIRD_BOX / 2: # ร่วงถึงพื้น
if grace: # ช่วง grace: ไม่ตาย แค่หยุดที่พื้น
bird_y, velocity_y = PLAY_H - BIRD_BOX / 2, 0.0
else:
return False # หมด grace แล้วถึงตายจริง
...
if hit_pipe(pipe) and not grace: # ช่วง grace 4 วิแรก: ชนท่อไม่ตาย (:220)
return False
นี่คือดีไซน์ Flappy คลาสสิก "tap to start" — เกมไม่เริ่มเดินจนกว่าผู้เล่นจะพร้อม แล้วให้ช่วงผ่อนผันตอนต้นเกม เพื่อกลบ latency ของจอจริง
| อาการ (symptom) | สาเหตุจริง (root cause) | วิธีแก้ (fix) | บทเรียนที่ใช้ต่อได้ |
|---|---|---|---|
| เกมค้างตายกลางเฟรมตอนฉากหนัก | CM55 ส่ง backpressure "not ready" ชั่วคราว แล้วโค้ด raise |
_r() bounded retry 40 ครั้ง × 15ms เฉพาะ "not ready" (bentogame.py:117) |
สภาวะ "ไม่พร้อมชั่วคราว" ของ HW เป็นเรื่องปกติ → bounded retry ไม่ใช่ crash |
| Shooter Python หนืด แต่ C ลื่น | IPC ต่อ object เยอะเกินงบต่อเฟรม (ไม่ใช่ CPU ช้า) | ลด EMAX,BMAX 8,6 → 5,4 (shooter_full.py:30) |
คอขวดคือ จำนวน cross-core call ต่อเฟรม → budget จำนวน object |
| จอเปื้อนรอยเขียวเฉียง + เส้นดำ | resize() ทุกเฟรม → พิกเซลเก่าไม่ถูกเคลียร์ |
ท่อกล่องสูงเต็มจอ สร้างครั้งเดียว + move_to เท่านั้น (flappy_full.py:66,113) |
fixed-size + reposition สะอาดกว่า resize ทุกเฟรม ให้ HW clip |
| นกตกตายก่อนผู้เล่นเห็นจอ | จอ recomposite ~4s หลังปิดเมนู (latency) | started hover + grace 4s กันตาย (flappy_full.py:150,186) |
จอมี latency → ออกแบบวินาทีแรกให้ให้อภัย (tap to start) |
ครั้งหน้าที่น้อง ๆ เจอเกม (หรือระบบ embedded อะไรก็ตาม) ทำงานแปลก ๆ ลองถามตัวเอง 4 คำถามนี้: มีจังหวะ ไม่พร้อมชั่วคราว ไหม? · ส่งข้าม core กี่ครั้งต่อเฟรม? · วาดทับแล้ว เคลียร์ของเก่าหรือยัง? · เผื่อ latency ให้จอแล้วหรือยัง?
bentogame.py คือ "เฉลย" ที่เราใช้อยู่ทุกคาบทั้งคอร์สเราเขียนเกมด้วยของแค่หกอย่าง: start(), Box, Text, keys(), hit(), run() (bentogame.py:6-7) หกคำนี้ทำให้นิสิตปี 2 ที่ยังไม่แข็ง C ก็ทำเกมจริงเล่นบนบอร์ดได้ คำถามของคาบนี้คือ ใต้หกคำนั้นมีอะไรอยู่ และทำไมมันถึงไม่พังเวลาเจอฮาร์ดแวร์จริง
bentogame.py ไม่ใช่ 30% ที่ต้องเติม แต่เป็น engine ที่รองอยู่ใต้เกมของน้องทุกเกม "เฉลย" ที่นี่คือการอ่านให้เข้าใจว่ามันเลือกออกแบบแบบนี้ทำไมsolution_codes/README.md): อ่านให้เข้าใจ ปิดไฟล์ แล้วอธิบายด้วยคำพูดตัวเอง ว่าแต่ละท่อนแก้ปัญหาฮาร์ดแวร์ตัวไหน ไม่ใช่ท่องโค้ดเฉลยที่ดีไม่ได้ให้คำตอบ มันให้เหตุผล — วันนี้เราอ่าน engine ไม่ใช่เพื่อลอก แต่เพื่อวันหนึ่งจะเขียน engine ของเราเองได้
บอร์ดในคอร์สมีหลายรุ่น (AI Kit / Eva / Game Console / Dev Kit) ฮาร์ดแวร์ไม่เท่ากันเลย แต่เราอยากให้ เกมไฟล์เดียวรันได้ทุกบอร์ด (bentogame.py:12-16) เคล็ดอยู่ที่ตอนโหลดโมดูล ให้ถาม firmware ก่อนว่ามีความสามารถไหนบ้าง:
_HAS_SOUND = hasattr(ui, "sfx")
try:
import pots as _pots
_HAS_POTS = True
except ImportError:
_HAS_POTS = False
แล้วทุกฟังก์ชันที่แตะฮาร์ดแวร์จะเช็ก flag นั้นก่อน ถ้าไม่มีก็ เงียบ ๆ คืนออกไป ไม่ crash:
def sfx(name):
"""Play a built-in sound effect — by name ("eat","die","gameover",...) or number."""
if not _HAS_SOUND:
return
try:
ui.sfx(SFX.get(name, name) if isinstance(name, str) else int(name))
except Exception:
pass
hasattr(ui, "sfx") (:48) กับ try/import (:476-480) คือ capability probe — ถามว่า "ทำได้ไหม" แทนการเดาจากชื่อรุ่นบอร์ด ถ้า firmware เก่าไม่มี ui.sfx เกมก็ยังเล่น แค่ไม่มีเสียงsfx() (:73-80) เจอ "ไม่มีเสียง" ก็ return เฉย ๆ ไม่โยน error เพราะ เสียงหายไม่ควรทำให้ทั้งเกมล้ม นี่คือหลัก graceful degradation: ฟีเจอร์รองหายได้ ฟีเจอร์หลักต้องรอด_HAS_SOUND) ไม่เช็กทุกเฟรม? เพราะความสามารถของ firmware ไม่เปลี่ยนระหว่างรัน จ่ายค่าตรวจครั้งเดียว แล้วเฟรมต่อ ๆ ไปแค่ดู flag_r()): ทั้งคู่ห่อการเรียกฮาร์ดแวร์ด้วย try แล้วตัดสินใจว่า "อาการนี้ยอมได้ หรือต้องเด้ง" ต่างกันแค่ _r() ยอมกับ "not ready" ส่วนนี่ยอมกับ "ไม่มีฮาร์ดแวร์"โปรแกรมฝังตัวที่ดีไม่ถามว่า "บอร์ดรุ่นอะไร" แต่ถามว่า "ทำสิ่งนี้ได้ไหม" แล้วเตรียมทางลงไว้เสมอเผื่อคำตอบคือไม่ได้
game.Box คือสไปรต์ที่ง่ายที่สุด ข้างในมันคือ ui.Panel หนึ่งใบบน CM55 แต่โค้ดเกมไม่ต้องรู้เลย ทุกการขยับผ่านสองเมท็อดนี้ (bentogame.py:234-252):
def move_to(self, x, y):
"""Put the box at an exact position."""
self.x = x
self.y = y
_r(self._panel.pos, int(x), int(y))
def move(self, delta_x, delta_y):
"""Move the box by some amount, staying on screen."""
next_x = self.x + delta_x
next_y = self.y + delta_y
if next_x < 0:
next_x = 0
if next_y < 0:
next_y = 0
if next_x > WIDTH - self.w:
next_x = WIDTH - self.w
if next_y > HEIGHT - self.h:
next_y = HEIGHT - self.h
self.move_to(next_x, next_y)
และการชน — คณิตศาสตร์กล่องทับกล่องบรรทัดเดียว (bentogame.py:466-469):
def hit(a, b):
"""True if Box a overlaps Box b (axis-aligned bounding-box test)."""
return (a.x < b.x + b.w and a.x + a.w > b.x and
a.y < b.y + b.h and a.y + a.h > b.y)
move_to จบด้วย _r(self._panel.pos, ...) — จุดนี้เองที่ "จดหมายข้าม core" ถูกส่งจริง โยงตรงกับสไลด์ สองสมอง และ บทเรียน 1: ทุกการขยับ = IPC หนึ่งฉบับ ที่ผ่านตาข่าย _r() ให้แล้ว โค้ดเกมจึงเขียน box.move_to(x, y) เฉย ๆ ได้โดยไม่ต้องรู้จัก retry เลยmove() ตรึงกล่องไว้ในจออัตโนมัติ (clamp 0..WIDTH - self.w) ผู้เล่นสั่งเดินชนขอบก็ไม่หลุดจอ เก็บ เงื่อนไขขอบเขต ไว้ที่เดียว ไม่ให้เกมแต่ละเกมเขียนซ้ำkeys() คืน Keys ที่รวม D-pad กับ analog stick เป็น .left/.right/... (:305-319) ซ่อนว่า input มาจากปุ่มหรือแกนอนาล็อก เกมสนใจแค่ "ซ้ายหรือขวา"hit() เป็น AABB ล้วน ไม่แตะฮาร์ดแวร์ เร็ว ทำนายง่าย พอสำหรับเกมกล่อง นี่คือ "จ่ายเท่าที่จำเป็น" ไม่ยกเรขาคณิตทั้งวิชามาทั้งที่เกมไม่ได้ใช้ผิวที่เรียบง่ายไม่ได้แปลว่าข้างในง่าย — หกคำสั่งนี้เรียบเพราะมันแบกความยุ่งของ IPC เอาไว้ให้ ไม่ใช่เพราะไม่มีความยุ่ง
หัวใจสุดท้ายคือ run() — game loop ที่เรียก update() ของน้องทุกเฟรม แล้ว หลับเท่าที่เหลือจริงของคาบ เพื่อคุมให้เกมเดินตรง fps (bentogame.py:993-999; จับเวลาต้นเฟรมไว้ที่ t0 = time.ticks_ms(), :965):
if paused: # ระหว่างพัก: ไม่เรียก update() ปุ่มทิศทางไม่มีผล
time.sleep_ms(delay)
continue
if update() is False:
return True
# หลับเฉพาะเวลาที่เหลือของเฟรม (ticks_diff ปลอดภัยตอนตัวเลขนาฬิกาวน)
time.sleep_ms(max(0, delay - time.ticks_diff(time.ticks_ms(), t0)))
เฟรมไหนงานหนัก (IPC เยอะ) จะหลับสั้นลงเอง จังหวะเกมเลยนิ่งไม่ว่าจอจะยุ่งแค่ไหน โยงกับ บทเรียน 2: run() คุมได้แค่จังหวะ ส่วนจำนวน object ต่อเฟรมเป็นหน้าที่ pool() (:176-184) กับการ budget ที่เราคุยกันไปแล้ว
มองทั้งไฟล์เป็นชั้น ๆ — แต่ละชั้นซ่อนความจริงของฮาร์ดแวร์ไว้คนละอย่าง:
| ชั้น (layer) | ตัวอย่างใน bentogame.py |
ความจริงของ HW ที่มันซ่อนให้ | โยงบทเรียนในคาบนี้ |
|---|---|---|---|
| ผิวนอก 6 แนวคิด | Box keys() hit() run() (:6-7) |
ต้องรู้ ui.Panel / joystick / IPC |
เขียนเกมโดยไม่ต้องแตะ IPC เอง |
| ตาข่ายนิรภัย | _r() (:117), move_to→_r (:234) |
CM55 ตอบ "not ready" ชั่วคราว | บทเรียน 1 — Retry แทน Crash |
| งบต่อเฟรม | run() pacing (:999), pool() (:176) |
IPC round-trip จำกัดต่อเฟรม | บทเรียน 2 + คาบ 11 Object Pool |
| ล้มแบบไม่ตาย | _HAS_SOUND / _HAS_SPRITE (:48,:98) |
บอร์ดต่างรุ่นมี HW ไม่เท่ากัน | ไฟล์เดียวรันได้ทุกบอร์ด |
อ่านจากล่างขึ้นบน: ฮาร์ดแวร์มีจังหวะไม่พร้อม มีงบจำกัด มีของไม่ครบ — สามความจริงนี้ถูกห่อทีละชั้นจนบนสุดเหลือแค่หกคำที่นิสิตปี 2 เรียกใช้ได้สบาย นั่นแหละคืองานของ engine
ที่มา — ความคิด "ห่อฮาร์ดแวร์ที่เอาแน่เอานอนไม่ได้ไว้ใต้ผิวเรียบ ๆ" ไม่ได้เพิ่งเกิดในไฟล์นี้ มันเริ่มตั้งแต่สไลด์ "สองสมองที่คุยกันด้วยจดหมาย" ที่บอกว่า move_to หนึ่งครั้งคือจดหมายข้าม core หนึ่งฉบับ แล้ว บทเรียน 1 (_r()) ก็ตอบคำถามต่อว่า "ถ้าปลายทางยังไม่พร้อมล่ะ" — bentogame.py คือการเอาคำตอบเหล่านั้นมาประกอบเป็นกำแพงชั้นเดียว ให้โค้ดเกมข้างบนไม่ต้องเห็นความจริงที่ยุ่งของบอร์ดเลย
ที่ไป — ทักษะ "อ่าน engine ให้ออกว่ามันซ่อนอะไรไว้" คือก้าวแรกของการ เขียน engine เอง README ของเฉลยบอกไว้ว่าหกแนวคิดนี้ map 1:1 ไปที่ C game SDK ในคอร์สถัดไป วันที่น้องเขียนฝั่ง C น้องจะเป็นคน สร้าง _r() เอง สร้าง capability probe เอง ตัดสินใจเองว่าอะไรยอมได้อะไรต้องเด้ง นี่คือหน้าที่จริงของคนเขียน driver/HAL ในงานฝังตัว
ลองตอบในใจ นี่คือการเชื่อมจุดด้วยตัวเอง:
_r() ใน บทเรียน 1 ได้ไหม ถ้าให้น้องเขียนฟังก์ชันห่อการเรียก I2C ที่ "บางทีบัสไม่ว่าง" น้องจะออกแบบเพดาน retry กับเงื่อนไข "ยอมได้/ต้องเด้ง" ยังไง หน้าตาจะต่างจาก _r() มากไหมsfx() ออกว่ามันเลือก "เงียบดีกว่าล้ม" ได้ พรุ่งนี้พอเจอ sensor ที่บางบอร์ดไม่มี เราจะออกแบบให้มัน "คืนค่ากลาง" แทน crash เองได้ไหม_HAS_SOUND / _HAS_SPRITE มันคือเรื่องเดียวกับ BSP feature flag ของฝั่ง C ที่บอกว่าบอร์ดรุ่นนี้มีเซนเซอร์ตัวไหน ต่างกันแค่ Python ถามตอนรัน C กำหนดตอน buildการหยั่งรู้ที่อยากให้เกิด: engine ไม่ใช่กล่องดำ มันคือชุดการตัดสินใจของวิศวกรคนหนึ่ง — พออ่านออกว่าเขาตัดสินใจแบบนั้นทำไม น้องก็พร้อมเป็นคนตัดสินใจเองในเครื่องถัดไป
รูปแบบทั้งสี่ในไฟล์นี้ไม่ใช่ของเล่นในคอร์ส มันคือแกนของ firmware ระดับ production:
_HAS_SOUND / _HAS_SPRITE เป๊ะ — binary เดียวรันได้หลายเครื่องที่สเปกไม่เท่ากัน_r() ใน บทเรียน 1 — ไม่ retry คือเปราะ, retry ไม่มีเพดานคือค้างตายrun() (:999) ใน บทเรียน 2 — จังหวะนิ่งคือความต่างระหว่างเกมลื่นกับเกมกระตุก มอเตอร์นิ่งกับมอเตอร์สั่นbentogame.py รันได้ทั้ง AI Kit / Eva / Game Console / Dev Kitจำหลักไว้: layer ที่ดีเปลี่ยน "ของยากข้างใต้" ให้กลายเป็น "ของง่ายข้างบน" — ทั้งอุตสาหกรรมฝังตัวสร้างขึ้นบนความคิดนี้ ไม่ใช่แค่เกมของเรา
เอาโจทย์เหล่านี้ไปคิดต่อ ไม่มีคำตอบเดียว ทุกข้อเริ่มจากโค้ดที่น้องเพิ่งอ่านวันนี้:
_r() ยังไม่พอ — บางคำสั่งต้อง retry นานกว่า บางคำสั่งห้ามรอเลย จะออกแบบให้ _r() รับ "เพดาน/เงื่อนไข" ต่างกันต่อการเรียกยังไง โดยไม่ทำให้โค้ดเกมข้างบนยุ่งขึ้นrun() ออกมาโชว์เป็น HUD debug ได้ไหม โดยไม่กระทบจังหวะเกม (โยง บทเรียน 2: ก่อน optimize ต้องวัดให้เห็นก่อน)Box.move_to เวอร์ชัน C หน้าตายังไง ใครส่ง IPC ใคร retry ใครกัน object ให้อยู่ในจอ นี่คือสิ่งที่คอร์สถัดไป (ฝั่ง C game SDK) จะให้น้องลงมือทำจริง วันนี้อ่าน engine ให้ขาด พรุ่งนี้จะได้สร้างมันเองเลือกมาสักข้อ เขียนลงใบงานว่า "ถ้าเป็นเรา จะออกแบบยังไง" — คนที่เขียน engine เก่งไม่ได้เริ่มจากเขียนเป็น เริ่มจากอ่าน engine ของคนอื่นออกก่อน แล้วถามว่า "ถ้าเราทำ จะทำต่างจากนี้ตรงไหน"
fit-css