คาบ 10 — Shooter #1

ยานที่ขับแล้ว "มีน้ำหนัก" + ป้าย HUD

เกมที่ 3 ของเรา ต่อจาก Catch (คาบ 4) และ Pong (คาบ 5–7)

วันนี้: วางยานลงจอ แล้วทำให้มัน เร่ง · ไถล · ชนขอบหยุด

เป้าหมายของคาบ

step 1 — วางยาน + ป้าย
เห็น ยานเขียว กลาง-ล่างของจอ + ป้าย Score: 0 Lives: 3 มุมซ้ายบน (ป้ายนี้เรียกว่า HUD)
step 2 — ขับได้ มีน้ำหนัก
กดจอยซ้าย/ขวา ยาน ค่อย ๆ เร่ง · ปล่อยแล้ว ไถลต่อ · ชนขอบจอแล้วหยุด ไม่หลุดออก

step 1 — วางยานลงสนาม + HUD

HUD คืออะไร? ย่อมาจาก Heads-Up Display = ป้ายสถานะที่ลอยทับจอเกมตลอดเวลา เช่น คะแนน ชีวิต เวลา ไม่ใช่ตัวละคร แค่บอกสถานะให้ผู้เล่นรู้ · คำนี้มาจากห้องนักบิน ที่ฉายข้อมูลขึ้นกระจกตรงหน้า นักบินจึงไม่ต้องก้มดูหน้าปัด · ป้าย Score ในเกม Catch ก็คือ HUD

เปิด practise_codes/shooter_step1.py↗ แล้วเติม 2 บรรทัด ตามพิกัดในภาพ

① ยาน ใช้ game.Box(...) ใส่ x, y, กว้าง, สูง, game.GREEN แล้วเก็บใน ship

② ป้าย HUD ใช้ game.Text(...) ใส่ข้อความ, x, y, game.WHITE แล้วเก็บใน hud

รันแล้วเป็น ภาพนิ่ง = ถูกต้อง เพราะ on_frame() ยังว่าง

เหมือนเข็นรถเข็นในห้าง

รถเข็น เกมเรา โค้ด
ผลักต่อเนื่อง รถเร็วขึ้นทีละนิด กดจอยค้าง ยานเร่ง ship_speed += ACCEL
ปล่อยมือ รถไถลต่อแล้วช้าลงเอง ปล่อยจอย ยานไถล ship_speed *= FRICTION
คนเข็นวิ่งได้เร็วสุดแค่ระดับหนึ่ง · เจอผนังก็หยุด เพดานความเร็ว 13 · ขอบจอ บีบค่าด้วย max() min()

จุดที่ไม่เหมือน: ① รถจริงหยุดสนิทได้ แต่เกมคูณ 0.80 ไปเรื่อย ๆ ความเร็วเล็กลงแต่ไม่เป็นศูนย์พอดี ② รถชนผนังความเร็วเป็นศูนย์ทันที แต่ใน step 2 ยานชนขอบแล้ว ship_speed ยังค้างที่ 13

ทายก่อน: ① เริ่มจากหยุดนิ่ง กดขวาค้าง (+1.4 ทุกเฟรม) กี่เฟรมจึงถึงเพดาน 13? ② ปล่อยปุ่มตอนวิ่งเต็มที่ ยานไถลได้ไกลสุดกี่พิกเซล? ก) 0 ข) ประมาณ 13 ค) ประมาณ 52 ง) ไกลไม่สิ้นสุด เพราะความเร็วไม่เคยเป็นศูนย์

ไล่ตัวเลข: กดขวาค้าง 12 เฟรม แล้วปล่อย

เริ่ม ship_x = 365.0, ship_speed = 0.0 · ทุกเฟรม: เปลี่ยนความเร็วก่อน แล้ว ตำแหน่ง + ความเร็ว

เฟรม0123…9101213141516…24
ship_speed01.42.84.2…12.613.013.010.48.36.75.3…0.9
ship_x365.0366.4369.2373.4…428.0441.0467.0477.4485.7492.4497.7…515.4
  • กดค้าง (ส้ม): บวก 1.4 เท่ากันทุกเฟรม · เฟรม 10: 12.6 + 1.4 = 14.0 เกินเพดาน ถูกบีบเหลือ 13.0 → ตอบข้อ ①: 10 เฟรม (1/3 วินาที)
  • ปล่อย (ฟ้า): ×0.80 = เก็บไว้ 80% ทิ้ง 20% · 13 → 10.4 → 8.3 → 6.7 ทิ้งไป 2.6, 2.1, 1.7 … ยิ่งช้ายิ่งทิ้งน้อย
  • ตอบข้อ ②: ระยะไถลเข้าใกล้ 52 px แต่ไม่เกิน (467 → เกือบ 519) = ค) สั้นกว่าตัวยาน 1 ลำ (62 px)
  • ตั้งแต่เฟรม 24 ความเร็วต่ำกว่า 1 พิกเซลต่อเฟรม · หลังเฟรม 30 ภาพบนจอไม่ขยับอีก ยานจึงดูหยุด

สูตร: พูดเป็นคำก่อน แล้วค่อยเขียนเป็นสัญลักษณ์

ปุ่มไม่ได้ขยับยานตรง ๆ: ปุ่ม → ความเร็ว → ตำแหน่ง → จอ (ไม้ตี Pong คาบ 6 ก็ใช้ท่านี้)

เป็นคำพูด สัญลักษณ์ แทนเลขจริง ในโค้ด
กดอยู่: ความเร็วใหม่ = ความเร็วเดิม + แรงเร่ง vใหม่=vเดิม+av_{\text{ใหม่}} = v_{\text{เดิม}} + a 2.8 = 1.4 + 1.4 ship_speed += ACCEL
ปล่อย: ความเร็วใหม่ = ความเร็วเดิม × ตัวไถล vใหม่=vเดิม×fv_{\text{ใหม่}} = v_{\text{เดิม}} \times f 10.4 = 13 × 0.80 ship_speed *= FRICTION
ทุกเฟรม: ตำแหน่งใหม่ = ตำแหน่งเดิม + ความเร็ว xใหม่=xเดิม+vx_{\text{ใหม่}} = x_{\text{เดิม}} + v 366.4 = 365.0 + 1.4 ship_x + ship_speed
  • xx = ตำแหน่ง ship_x · vv = ความเร็ว ship_speed (พิกเซลต่อเฟรม)
  • aa = แรงเร่ง ACCEL = 1.4 · ff = ตัวไถล FRICTION = 0.80 (เก็บไว้ 80%)
  • กดซ้ายใช้ ลบ แทนบวก: ship_speed -= ACCEL ความเร็วติดลบ = ไปทางซ้าย

clamp = บีบค่าให้อยู่ในช่วง ไม่ต่ำกว่าพื้น ไม่เกินเพดาน

อ่านจากในออกนอก: max(พื้น, min(เพดาน, ค่า)) → min ตัดส่วนที่เกินเพดาน · max ยกส่วนที่ต่ำกว่าพื้น

บีบอะไร พื้น … เพดาน ตัวอย่างเลขจริง
ความเร็ว −13 … 13 12.6 + 1.4 = 14.0 → min(13.0, 14.0) = 13.0
ตำแหน่ง ชนขวา 0 … 730 725.0 + 13 = 738.0 → min(730, 738.0) = 730
ตำแหน่ง ชนซ้าย 0 … 730 5.0 − 13 = −8.0 → max(0, -8.0) = 0

ทำไม ship_x เก็บเป็นทศนิยม (365.0)

เหมือน หยอดกระปุก: ได้เศษ 0.5 บาททุกวัน หยอดเก็บ 6 วันได้ 3 บาท · ตัดทิ้งทุกวันได้ 0 บาท
สมมติยานเหลือความเร็ว 0.5 พิกเซลต่อเฟรม (ช่วงใกล้หยุด):

เฟรม0123456
ship_x เก็บทศนิยม (ไปขวา +0.5)365.0365.5366.0366.5367.0367.5368.0
จอวาด (ตัดเศษตอนวาด)365365366366367367368
ตัดเศษเองทุกเฟรม ไปขวา365365365365365365365
ตัดเศษเองทุกเฟรม ไปซ้าย (−0.5)365364363362361360359
  • move_to() ตัดเศษให้เองตอนวาด (bentogame.py:238) ส่วน ship_x เก็บเศษไว้ครบ ซ้ายกับขวาจึงไถลเท่ากัน
  • ถ้า ตัดเศษเองทุกเฟรม (ship_x = int(ship_x + ship_speed)): ไปขวา ความเร็วต่ำกว่า 1 หายหมด ยานหยุดก่อนเวลา · ไปซ้าย กลับคืบทีละ 1 พิกเซลทุกเฟรม ไถลไกลเกินจริง
  • round() ก็ไม่รอด: ความเร็วต่ำกว่า 0.5 หายทั้งสองทิศ · Python คิด 365 + 1.4 ได้ 366.4 ให้อยู่แล้ว เราเขียน 365.0 ให้คนอ่านรู้ว่าตัวนี้เก็บทศนิยม

step 2 — งานของน้อง ๆ (30%): เติม 5 ขั้นใน on_frame()

เปิด practise_codes/shooter_step2.py↗ · ค่าฟิสิกส์ (บรรทัด 16) กับส่วนนี้ (25–27) ให้มาแล้ว:

def on_frame():
    global ship_x, ship_speed     # ให้ฟังก์ชันแก้ตัวแปรข้างนอกได้
    keys = game.keys()            # อ่านจอยของเฟรมนี้
ขั้น เขียนอะไร สูตรจากสไลด์ก่อน
① เร่ง กดซ้าย → ลบ ACCEL · กดขวา → บวก ACCEL v+av + a
② ไถล ไม่กดทั้งคู่ → คูณ FRICTION v×fv \times f
③ เพดาน บีบ ship_speed ไว้ใน -MAX_SPEED … MAX_SPEED clamp
④ ขยับ + ขอบจอ ship_x + ship_speed แล้วบีบไว้ใน 0 … game.WIDTH - ship.w x+vx + v แล้ว clamp
⑤ วาด ship.move_to(ship_x, 352) —

ติดตรงไหน เปิด pong_step2.py ของคาบ 6 — ท่าเดียวกัน: up/down → left/right · player_ → ship_ · HEIGHT - PADDLE_H → WIDTH - ship.w

รันบนบอร์ด · เช็คผ่าน · กับดัก

รัน: BENTO IDE → Program to Device (หรือ BENTO Emulator)
หน้าชื่อเกม Start = เริ่ม · ระหว่างเล่น Start = พัก · Back = ออก
เช็คผ่าน: ☐ ยาน + HUD ขึ้นจอ  ☐ กดค้างเร่ง · ปล่อยไถลต่อ
☐ ชนขอบซ้าย-ขวาไม่หลุด → ถ่ายรูป  ☐ ชี้บรรทัด ①–⑤ ได้
อาการ สาเหตุ แก้
error เรื่อง local variable ลบบรรทัด global ใส่คืนบนสุดของ on_frame()
ปล่อยปุ่มแล้วหยุดกึก ไม่ไถล เขียน ship_speed = 0 ใช้ ship_speed *= FRICTION
ยานหลุดขอบขวาทั้งลำ บีบที่ game.WIDTH ลืมลบ ship.w ใช้ game.WIDTH - ship.w
ไถลซ้าย-ขวาไม่เท่ากัน ตัดเศษเองด้วย int() ทุกเฟรม ให้ ship_x เก็บทศนิยม
ชนขอบขวาแล้วกดซ้าย ค้างราว 0.3 วินาที ความเร็วยัง +13 ต้องลบอีก 10 เฟรมจึงติดลบ ไม่ใช่บั๊กที่ทำให้เกมพัง แต่ปรับให้ดีขึ้นได้ (ภาคผนวก)
จอยกดไม่ติดหลังโหลดโค้ดใหม่ บอร์ดรีเซ็ต ถอด-เสียบสาย USB จอยใหม่

ส่งงาน: commit shooter_step2.py + รูปจอ · ข้อความ feat(shooter): step1-2 ship + accel/friction steering

สรุป + เอาไปใช้ที่อื่น

  • ปุ่ม → ความเร็ว → ตำแหน่ง → จอ: เก็บ ship_speed กับ ship_x ไว้ข้ามเฟรม แล้วอัปเดตทุกเฟรม
  • บวก 1.4 = ขั้นเท่ากัน · คูณ 0.80 = เก็บไว้ 80% · clamp = ไม่เกินเพดานและขอบ ครบชุดคือ "ยานมีน้ำหนัก"
  • เก็บตำแหน่งเป็นทศนิยม ตัดเศษเป็นพิกเซลแค่ตอนวาด
เอาไปใช้ที่ ตัวแปรที่จะตั้ง ถ้าน้องเขียนเอง (ตัวอย่าง)
ตัวละครในเกมของน้องเอง player_x, player_vx 5 ขั้นเดียวกับวันนี้ แค่เปลี่ยนชื่อ
ฟีดที่ปัดแล้วปล่อยนิ้วยังเลื่อนต่อ (หลักการคล้ายกัน) scroll_y, scroll_v ปล่อยนิ้ว: scroll_v *= FRICTION
พัดลม / มอเตอร์ค่อย ๆ เร่ง pwm, STEP pwm = min(100, pwm + STEP)
คาบหน้า (11) — กระสุน: กด A แล้วเลเซอร์พุ่ง · สร้างกระสุนไว้ 6 นัดครั้งเดียวแล้ววนใช้ซ้ำ เรียกว่า ชุดกระสุนหมุนเวียน (Object Pool)

ภาคผนวก

ไม่จำเป็นต่อแล็บ — เปิดเมื่อมีเวลา หรือให้น้อง ๆ อ่านเองที่บ้าน

  • ดูเกมเต็ม · ภาพ "ปุ่ม → ความเร็ว" เทียบ Catch · ตารางเต็มช่วงปล่อย · ภาพเคลื่อนไหวความเร็ว
  • ลองเปลี่ยนตัวเลข (ทายก่อนรัน) · เอาไปใช้ที่อื่น (ตารางเต็ม)
  • เฉลย step 1 และ step 2 · คณิตเพิ่มเติม: ระยะไถล 52 px มาจากไหน
  • ต่อยอด: ชนขอบแล้วหยุดความเร็ว · เชื่อมโยงรากฐาน · วิดีโอเสริม · ใช้จริงที่ไหน
  • ต่อยอด — คิดต่อเอง (โจทย์ Buddy Challenge) · ของเล่นในสไลด์: จูน 3 ค่า

ดูเกมเต็มบนบอร์ด BENTO (เปิดต้นคาบได้)

เดโมรันบนบอร์ด BENTO จริง — โดย อ.วิรุฬห์ ศรีบริรักษ์ (YouTube) · วันนี้ทำถึงยานกับ HUD ส่วนกระสุนและศัตรูมาคาบ 11–12

ความคิดหลัก: ปุ่มไม่ได้ขยับยาน ปุ่มแก้ "ความเร็ว"

  • ship_x = ยานอยู่ตรงไหน · ship_speed = เฟรมนี้ยานขยับกี่พิกเซล (+ ไปขวา · − ไปซ้าย)
  • ความเร็ว จำค่าข้ามเฟรม ยานจึงเร่งได้ และยังไถลต่อได้หลังปล่อยปุ่ม
  • ไม้ตี Pong คาบ 6 ทำแบบนี้มาแล้ว (player_speed) วันนี้แค่หมุนจากแนวตั้งเป็นแนวนอน
  • ไม่มีคำสั่งใหม่เลย: game.Box, game.keys, move_to คือ 70% ที่เอนจินให้ · งานของเรา (30%) คือ 5 ขั้นใน on_frame()

ตารางเต็มช่วงปล่อย: ×0.80 = เก็บไว้ 80% ทิ้ง 20%

เฟรม 13 ปล่อยปุ่ม ตั้งแต่นั้นทุกเฟรม ความเร็ว × 0.80 แล้ว ตำแหน่ง + ความเร็ว เหมือนเดิม

เฟรม121314151617181920…30
ship_speed13.010.48.36.75.34.33.42.72.2…0.2
หายไปเฟรมนี้–2.62.11.71.31.10.90.70.5…~0
ship_x467.0477.4485.7492.4497.7502.0505.4508.1510.3…518.1
  • เร็วมาก หายเยอะ (2.6) · ช้าแล้ว หายน้อย (0.5) → ความเร็ว ลดเป็นเส้นโค้ง ไม่ใช่เส้นตรง
  • ระยะไถลรวมเข้าใกล้ 52 px แต่ไม่เกิน (467 → เกือบ 519) สั้นกว่าตัวยาน 1 ลำ (62 px)
  • ตั้งแต่เฟรม 24 ความเร็วต่ำกว่า 1 พิกเซลต่อเฟรม (0.89) · หลังเฟรม 30 ภาพบนจอหยุดที่พิกเซล 518 ยานจึง ดูหยุด

ดูเป็นภาพ: บวก = ขั้นบันได · คูณ = เส้นโค้ง

  • แท่งส้ม = กดค้าง สูงขึ้นทีละ 1.4 เท่ากัน จนชนเส้นเพดาน 13 แล้วแบน
  • แท่งฟ้า = ปล่อย แต่ละแท่งสูง 80% ของแท่งก่อนหน้า ยิ่งเตี้ยยิ่งหดช้า
  • จุดบนแถบบน = ship_x แต่ละเฟรม จุดห่าง = เร็ว · จุดชิด = ช้า
  • ภาพช้าลงราว 5 เท่า (0.15 วินาทีต่อเฟรม) บนบอร์ดจริงเร็วกว่านี้

จำง่าย ๆ: บวกทุกเฟรม = ขั้นเท่ากัน · คูณทุกเฟรม = ลดทีละสัดส่วน

ลองเปลี่ยนตัวเลข: ทายก่อน แล้วค่อยรัน

แก้บรรทัด ACCEL, MAX_SPEED, FRICTION = ... ทีละค่า ทายเป็นตัวเลขก่อน แล้วรันเทียบ · จดลงใบงานข้อ 5

ค่าเดิม: ถึงเพดานใน 10 เฟรม · ไถลต่อเกือบ 1 ลำยาน (52 px) · ขับข้ามจอ ≈ 2 วินาที

เปลี่ยนเป็น ทายด้วยเลข จะรู้สึกว่า
FRICTION = 0.95 ไถลต่อ ≈ 4 ลำยาน (247 px) ลื่นเหมือนพื้นเปียกหรือน้ำแข็ง
FRICTION = 0.50 ไถลต่อ ≈ 1/5 ลำยาน (13 px) หยุดเกือบทันที
ACCEL = 3.0 ถึงเพดานใน 5 เฟรม ออกตัวแรง
MAX_SPEED = 5.0 ขับข้ามจอ ≈ 5 วินาที อืดลงมาก

ที่มาของเลขระยะไถลอยู่ในสไลด์ "คณิตเพิ่มเติม" · วัดบนบอร์ดโดยนับเป็นความยาวยาน (1 ลำ = 62 px)

เอาไปใช้ที่อื่น (ตารางเต็ม): "เร่ง · ไถล · บีบ" อยู่ทุกที่

ที่ไหน ตัวแปรที่จะตั้ง ถ้าน้องเขียนเอง (ตัวอย่าง)
ตัวละครในเกมของน้องเอง player_x, player_vx 5 ขั้นเดียวกับวันนี้ แค่เปลี่ยนชื่อ
ฟีดที่ปัดแล้วปล่อยนิ้วยังเลื่อนต่อ scroll_y, scroll_v ปล่อยนิ้ว: scroll_v *= FRICTION ทุกเฟรม
กล้องตามตัวละครแบบนุ่ม cam_x cam_x += (player_x - cam_x) * 0.1
เคอร์เซอร์ / ค่าเซนเซอร์ให้นิ่ง shown, raw shown += (raw - shown) * 0.2
พัดลม / มอเตอร์ค่อย ๆ เร่ง pwm, STEP pwm = min(100, pwm + STEP)
  • ฟีดมือถือ: หลักการคล้ายกัน ปล่อยนิ้วแล้วความเร็วค่อย ๆ ลดจนหยุด (แต่ละแอปตั้งค่าเอง)
  • กล้องกับเคอร์เซอร์คูณ ระยะห่าง แทนความเร็ว ระยะที่เหลือหดเป็นเส้นโค้งแบบเดียวกัน
  • มอเตอร์ใช้ "บวกทีละขั้น + เพดาน" = เร่ง + clamp กันกระแสกระชากตอนออกตัว

เฉลย step 1 — อ่านให้เข้าใจ ปิดไฟล์ แล้วพิมพ์เอง

import bentogame as game

game.title("SHOOTER")                          # หน้าชื่อเกม: Start = เล่น · Back = ออก

ship = game.Box(365, 352, 62, 24, game.GREEN)  # ① ยาน: x, y, กว้าง, สูง, สี
ship_x = 365.0                                 # ตำแหน่ง x เก็บทศนิยม ให้ step 2 ใช้
score, lives = 0, 3
hud = game.Text("Score: 0   Lives: 3", 10, 8, game.WHITE)  # ② ป้าย HUD

def on_frame():
    pass                                       # ยังไม่มีอะไรขยับ = ภาพนิ่ง

game.run(on_frame, fps=30)                     # เรียก on_frame 30 ครั้งต่อวินาที

ตรงกับ solution_codes/shooter_step1.py:11-29 (ตัดบรรทัดคอมเมนต์ออก)

  • 365 = (792 − 62) ÷ 2 จัดยานกลางจอ · 352 ใกล้ขอบล่าง
  • เก็บ ship กับ hud ใส่ตัวแปร เพราะต้องสั่งมันซ้ำทีหลัง (ship.move_to ใน step 2, hud.set ในคาบ 12)
  • on_frame() ว่างแต่ต้องมี เพราะ game.run() ต้องมีฟังก์ชันไว้เรียกทุกเฟรม

เฉลย step 2 — 5 ขั้นของยานที่มีน้ำหนัก

def on_frame():
    global ship_x, ship_speed
    keys = game.keys()

    if keys.left:    ship_speed -= ACCEL      # ① เร่งไปทางซ้าย
    elif keys.right: ship_speed += ACCEL      # ① เร่งไปทางขวา
    else:            ship_speed *= FRICTION   # ② ไถล: เหลือ 80% ทุกเฟรม
    ship_speed = max(-MAX_SPEED, min(MAX_SPEED, ship_speed))   # ③ เพดานความเร็ว
    ship_x = max(0, min(game.WIDTH - ship.w, ship_x + ship_speed))  # ④ ขยับ + ขอบจอ
    ship.move_to(ship_x, 352)                                  # ⑤ วาด

ตรงกับ solution_codes/shooter_step2.py:22-33 (ตัดบรรทัดคอมเมนต์ออก)

  • ใช้ if / elif / else เพราะเฟรมหนึ่งทำได้อย่างเดียว: เร่งซ้าย หรือ เร่งขวา หรือ ไถล
  • ③ ต้องมาก่อน ④ เสมอ: บีบความเร็วให้เรียบร้อยก่อน แล้วค่อยเอาไปบวกตำแหน่ง
  • ⑤ ส่ง 352 คือ y เดิม ยานเราวิ่งแนวนอนแกนเดียว

คณิตเพิ่มเติม: ระยะไถล 52 px มาจากไหน

คูณ 0.80 ซ้ำทุกเฟรม เขียนรวบเป็นสูตรเดียวได้ (ไม่ต้องใช้ในโค้ด แค่ใช้ทาย):

vหลังปล่อย n เฟรม=13×0.80 nn=1: 10.4n=3: 6.7n=12: 0.9v_{\text{หลังปล่อย } n \text{ เฟรม}} = 13 \times 0.80^{\,n} \qquad n=1:\ 10.4 \quad n=3:\ 6.7 \quad n=12:\ 0.9

  • ครึ่งหนึ่งหายไปในราว 3 เฟรม (0.80³ ≈ 0.51) และต่ำกว่า 1 พิกเซลต่อเฟรมหลัง 12 เฟรม (0.4 วินาที)
  • ระยะไถลรวม = ผลบวกความเร็วทุกเฟรมหลังปล่อย:

10.4+8.32+6.66+⋯=13×0.801−0.80=13×4=52 px10.4 + 8.32 + 6.66 + \dots = 13 \times \frac{0.80}{1 - 0.80} = 13 \times 4 = 52 \text{ px}

  • สูตรทั่วไป: ระยะไถล =v×f1−f= v \times \dfrac{f}{1-f} → f=0.95f = 0.95: 13 × 19 = 247 px · f=0.50f = 0.50: 13 × 1 = 13 px
  • ตัวเลขทั้งหมดคิด ต่อเฟรม ถ้าเปลี่ยน fps ความรู้สึกก็เปลี่ยนตาม (เกมใหญ่แก้ด้วยการคูณเวลาต่อเฟรม)

ต่อยอด: ชนขอบแล้วหยุดความเร็ว

ทายก่อน: ขับชนขอบขวาด้วยความเร็วเต็ม 13 แล้วกดซ้ายทันที ยานจะค้างที่ขอบกี่เฟรม?

เฟรมหลังกดซ้าย0123…8910
ship_speed13.011.610.28.8…1.80.4−1.0
ship_x730730730730…730730729.0

ความเร็วยังเป็นบวก 9 เฟรม ขอบบีบ ship_x ไว้ที่ 730 → ค้างราว 0.3 วินาที เกมเต็มแก้โดย ตั้งความเร็วเป็น 0 ตอนชนขอบ (full_games/shooter_full.py:103-105):

        if x > game.WIDTH - SHIP_W:
            x = float(game.WIDTH - SHIP_W)
            st["svx"] = 0.0

ไม่ใช่บั๊กที่ทำให้เกมพัง แต่ปรับให้ดีขึ้นได้: ลองทำในไฟล์ของตัวเอง ถ้า ship_x ชนขอบซ้ายหรือขวา ให้ ship_speed = 0.0

เชื่อมโยงรากฐาน

  • Asteroids (Atari, 1979) บังคับยานด้วยแรงเฉื่อยแบบเดียวกัน: เก็บความเร็วไว้ แล้วบวกเข้าตำแหน่งทุกเฟรม
  • Euler integration — x += v และ v += a คือการประมาณการเคลื่อนที่ทีละเฟรม ฟิสิกส์เอนจินจริงเริ่มจากตรงนี้
  • คูณค่าคงที่ซ้ำ ๆ = การลดแบบเรขาคณิต ท่าเดียวกับตัวกรองสัญญาณ (low-pass filter) ที่ทำให้ค่าเซนเซอร์นิ่ง
  • game loop = super-loop ของเฟิร์มแวร์ — game.run() วน on_frame() 30 ครั้งต่อวินาที เหมือนลูปหลักที่อ่านเซนเซอร์แล้วสั่งงานเป็นรอบ ๆ
  • ช่วงค่าของจอ — บีบ x ไว้ใน 0 … WIDTH − ship.w คือการทำงานกับระบบพิกัดของจอโดยตรง

ที่มาภาพ: "Asteroids cabinet" — Joho345, CC BY 4.0, Wikimedia Commons

วิดีโอเสริม: accel + friction ใน 5 นาที

ACCELERATION and FRICTION in Under 5 Minutes — John Ivess · คนละเอนจิน แต่แนวคิดเร่ง + ไถลตรงกับ 5 ขั้นของเรา

ใช้จริงที่ไหน — ยานนิ่ง + ป้าย HUD ของ step 1

ระบบจริง เหมือน step 1 ตรงไหน
แดชบอร์ดรถ / จอเครื่องมือแพทย์ ค่าจริงอยู่ในตัวแปร (score, lives) ป้ายแค่อ่านมาแสดง (hud)
ขึ้นจอใหม่ในงาน embedded วาง widget ให้ครบและนิ่งก่อน (on_frame() ว่าง) แล้วค่อยต่อเซนเซอร์
แอปทั่วไป: แยกข้อมูลออกจากการแสดงผล แก้ตัวแปรที่เดียว แล้วสั่ง hud.set(...) ให้ป้ายตาม
เกมเอนจินจริง: อัปเดตเป็นจังหวะคงที่ game.run(on_frame, fps=30) เรียกทีละเฟรมเท่า ๆ กัน

ยานนิ่งกับป้าย HUD ที่ดูเป็นของเล่นวันนี้ คือโครงเดียวกับหน้าจอในเครื่องจริงที่คนใช้ทุกวัน

ต่อยอด — คิดต่อเอง

เลือก 1 ข้อ เขียนลงใบงานข้อ 8 ว่า "ถ้าเป็นเรา จะออกแบบยังไง" ก่อน แก้โค้ด

  • ยานสองลำ (ผู้เล่น 2 คน) — แยก ship2 + ship2_x อีกชุด หรือเก็บยานทั้งหมดเป็น list แล้ววนจัดการทีเดียว แบบไหนดีกว่า เพราะอะไร
  • HUD ที่โตขึ้น — โชว์ score, lives และเวลาที่เหลือ ด้วย game.Text หลายป้าย ค่าไหนอยู่มุมไหน ผูกกับตัวแปรไหน
  • ตำแหน่งเริ่มแบบสุ่ม — สุ่ม x ตอนเริ่มเกมแทน 365 ตายตัว แล้วบีบไม่ให้ยานเกิดนอกจอ
  • กับดัก global — ลองลบบรรทัด global แล้วดูว่า error อะไร อธิบายว่าเพราะอะไร
  • ชนขอบแล้วหยุดความเร็ว — ทำให้ยานไม่ค้างที่ขอบ (ดูสไลด์ "ต่อยอด: ชนขอบแล้วหยุดความเร็ว")

ของเล่นในสไลด์: จูน 3 ค่า

ลากแถบแล้วดูยาน "เร่ง · ไถล" ทันที (กดค้าง 1.4 วินาที แล้วปล่อย วนซ้ำ) · ใช้สูตรเดียวกับ step 2 ก่อนไปแก้บนบอร์ดจริง

ACCEL 1.4 FRICTION 0.80 MAX_SPEED 13

แถบล่างของกล่องคือ ship_speed · ส้ม = กดอยู่ · ฟ้า = ปล่อยแล้ว

fit-css

core

core

core

core

core

core

core

core

core

core

core

← Roadmap (TOC)