วิธีแก้ปัญหาการชนของวัตถุแข็งแบบ 2D
(sassnow.ski)- การ แก้ปัญหาการชนของวัตถุแข็ง ในฟิสิกส์เกม 2D คือปัญหาในการคำนวณการเปลี่ยนแปลงความเร็วเพื่อไม่ให้วัตถุที่แตะหรือซ้อนทับกันอยู่แล้วทะลุผ่านกันในเฟรมถัดไป
- ลูปของเกมจะอัปเดตตำแหน่งจาก ความเร็ว และ Δt ในทุกเฟรม ดังนั้นหากเรขาคณิตซ้อนทับกันที่ตำแหน่งใหม่ วัตถุก็จะทะลุผ่านกันหากไม่มีการจัดการเพิ่มเติม
- การชนไม่ใช่แค่การ สัมผัสกัน เท่านั้น แต่ต้องดูด้วยว่าวัตถุที่สัมผัสกันยังคงเคลื่อนที่เข้าหากันตามความเร็วปัจจุบันหรือไม่
- การพิจารณาว่ากำลังเคลื่อนที่ออกจากพื้นผิวหรือไม่นั้นดูได้จากเครื่องหมายของดอตโปรดักต์ระหว่าง เวกเตอร์ปกติ (normal) กับความเร็ว โดยถ้าเป็นบวกแปลว่ามีองค์ประกอบไปในทิศเดียวกัน ถ้าเป็นลบแปลว่ามีองค์ประกอบไปในทิศตรงข้าม
- ในกรณีของวัตถุสองชิ้น สิ่งสำคัญกว่าความเร็วของแต่ละชิ้นคือ ความเร็วสัมพัทธ์ และเวกเตอร์ปกติของการชน โดยเมื่ออยู่ในสถานะสัมผัสกันและความเร็วปกติสัมพัทธ์เป็นลบ ก็ถือว่าเป็นการชนได้
ขอบเขตของฟิสิกส์วัตถุแข็งและการแก้ปัญหาการชน
- หัวข้อที่กล่าวถึงคือ ฟิสิกส์วัตถุแข็ง (rigid body physics) ซึ่งจัดการกับวัตถุที่ไม่เปลี่ยนรูปแม้จะได้รับแรง
- ในโลกความจริง วัตถุทุกชนิดล้วนเปลี่ยนรูปได้ในระดับโมเลกุล ดังนั้นวัตถุแข็งสมบูรณ์จึงไม่มีอยู่จริง
- ในการจำลองฟิสิกส์ส่วนใหญ่ การคำนวณรายละเอียดการเปลี่ยนรูปเช่นนี้ทำได้ยากมากหรือมีต้นทุนสูง
- หากวัตถุดูสมจริงเพียงพอ การลดรูปให้เป็นวัตถุแข็งก็เป็นแนวทางที่ใช้งานได้จริง
- การจัดการการชนในเกมเอนจินมักแบ่งเป็นสองขั้นตอน
- การตรวจจับการชน (collision detection): ตัดสินว่าวัตถุใดในฉากกำลังชนกันอยู่
- การแก้ปัญหาการชน (collision resolution): กำหนดสถานะหลังการชนจากทิศทางการเคลื่อนที่ ความเร็ว วัสดุ และปัจจัยอื่นของวัตถุที่ชนกัน
- จุดเน้นในที่นี้ไม่ใช่ขั้นตอนการหาว่ามีการตัดกันทางเรขาคณิตหรือไม่ แต่เป็น การแก้ปัญหาการชน ที่ใช้กำหนดการเคลื่อนที่หลังชน
การเกิดการชนในลูปของเกม
- เกมส่วนใหญ่จะคำนวณตำแหน่งของวัตถุในฉากซ้ำ ๆ ภายในลูปใหญ่
- ในแต่ละรอบ ตำแหน่งของวัตถุจะถูกอัปเดตตาม ความเร็ว (velocity) ปัจจุบัน
- ความเร็วเป็นปริมาณเวกเตอร์ที่มีทั้งขนาดและทิศทาง
- ความยาวของลูกศรแทนความเร็วเชิงขนาด ส่วนทิศที่ลูกศรชี้แทนทิศทางการเคลื่อนที่
- การเปลี่ยนตำแหน่งในช่วงเวลา Δt แสดงด้วย การกระจัด (displacement)
- การกระจัดก็เป็นปริมาณเวกเตอร์ที่มีทั้งขนาดและทิศทางเช่นกัน
- หากลูปของเกมทำงาน 60 ครั้งต่อวินาที ค่า Δt จะเท่ากับ 1/60 วินาที
- ตำแหน่งใหม่ได้มาจากการนำการกระจัดที่คำนวณจากความเร็วปัจจุบันไปบวกกับตำแหน่งเดิม
- หากตำแหน่งใหม่ของวัตถุสองชิ้นทำให้เรขาคณิตของทั้งคู่ซ้อนทับกัน วัตถุก็จะทะลุและผ่านกันไปหากไม่มีการจัดการเพิ่มเติม
ค่าที่การแก้ปัญหาการชนพยายามหา
- เป้าหมายของการแก้ปัญหาการชนคือกำหนด การเปลี่ยนแปลงความเร็ว ของแต่ละวัตถุเพื่อให้วัตถุไม่ทะลุผ่านกันอีกเมื่อการจำลองดำเนินต่อไป
- ความเร็วก่อนและหลังการชนแสดงด้วยสัญลักษณ์ต่อไปนี้
v_a,i,v_b,i: ความเร็วก่อนชนของวัตถุa,bv_a,f,v_b,f: ความเร็วหลังชนของวัตถุa,bΔv_a,Δv_b: การเปลี่ยนแปลงความเร็วของแต่ละวัตถุที่เกิดจากการชน
- ท้ายที่สุดแล้ว การแก้ปัญหาการชนก็คือปัญหาในการหาค่า Δv_a และ Δv_b
- หากต้องการให้การชนดูสมจริง การเปลี่ยนแปลงความเร็วที่เลือกต้องสอดคล้องกับกฎฟิสิกส์ที่เกี่ยวข้อง
แค่สัมผัสกันยังบอกไม่ได้ว่าเป็นการชน
- การที่วัตถุสองชิ้นแตะกันไม่ได้หมายความว่ากำลัง ชน กันเสมอไป
- การชนคือสถานการณ์ที่หากยังเคลื่อนที่ต่อไปตามความเร็วปัจจุบัน วัตถุจะทะลุผ่านกัน
- แม้จะเป็นฉากสัมผัสเดียวกัน ก็อาจเป็นการชนหรือไม่ชนก็ได้ ขึ้นอยู่กับทิศทางความเร็วของวัตถุทั้งสอง
- ดังนั้นเงื่อนไขของการชนจึงต้องมีสองอย่างพร้อมกัน
- เรขาคณิตของวัตถุต้องแตะหรือซ้อนทับกันอยู่
- วัตถุต้องยังคง เคลื่อนที่ในทิศทางที่ก่อให้เกิดการชน
เวกเตอร์ปกติของพื้นผิวและทิศทางที่เคลื่อนที่ออก
- การพิจารณาว่าวัตถุกำลังเคลื่อนที่ออกจากพื้นผิวใดสามารถดูได้จาก ทิศทางเวกเตอร์ปกติ (normal direction) ของพื้นผิวนั้น
- ทิศทางเวกเตอร์ปกติจะตั้งฉากกับพื้นผิว และชี้ไปในทิศที่ออกห่างจากพื้นผิวโดยตรง
- สำหรับพื้นผิวเรียบ ทิศทางเวกเตอร์ปกติจะเหมือนกันทุกจุด
- สำหรับพื้นผิวโค้ง ทิศทางเวกเตอร์ปกติจะต่างกันในแต่ละจุด
- บนเส้นรอบวงของวงกลม ทิศทางจากจุดศูนย์กลางไปยังจุดบนเส้นรอบวงนั้นคือทิศทางเวกเตอร์ปกติ
- ทิศทางเวกเตอร์ปกติแสดงด้วย เวกเตอร์ที่ผ่านการนอร์มัลไลซ์ ซึ่งมีความยาว 1
- เวกเตอร์ที่มีความยาว 1 เรียกอีกอย่างว่าเวกเตอร์หนึ่งหน่วย (unit vector)
- เพื่อแสดงว่าเป็นเวกเตอร์ที่ผ่านการนอร์มัลไลซ์แล้ว อาจใส่สัญลักษณ์
^ไว้เหนือชื่อตัวแปร
- ทิศทางเวกเตอร์ปกติที่จุดใดจุดหนึ่งจะตั้งฉากกับ เส้นสัมผัส (tangent) ของพื้นผิว ณ จุดนั้น
ใช้ดอตโปรดักต์เพื่อพิจารณาองค์ประกอบของทิศทาง
- เมื่อต้องการคำนวณว่าเวกเตอร์หนึ่งชี้ไปในทิศเดียวกับอีกเวกเตอร์มากน้อยแค่ไหน สามารถใช้ ดอตโปรดักต์ (dot product) ได้
- สำหรับเวกเตอร์สองมิติ ดอตโปรดักต์คือผลรวมของผลคูณระหว่างองค์ประกอบที่สอดคล้องกัน และผลลัพธ์เป็น สเกลาร์ ไม่ใช่เวกเตอร์
- ในเชิงเรขาคณิต ดอตโปรดักต์สามารถมองได้ว่าเป็นความยาวสเกลาร์ของการฉายเวกเตอร์หนึ่งไปบนอีกเวกเตอร์หนึ่ง คูณด้วยความยาวของเวกเตอร์เป้าหมายที่ใช้ฉาย
- เครื่องหมายของดอตโปรดักต์บอกความสัมพันธ์ของทิศทางระหว่างเวกเตอร์สองตัว
- หากมุมระหว่างเวกเตอร์ทั้งสองน้อยกว่า 90° ดอตโปรดักต์จะเป็น บวก และโดยทั่วไปถือว่าชี้ไปในทิศทางคล้ายกัน
- หากมุมมากกว่า 90° ดอตโปรดักต์จะเป็น ลบ และโดยทั่วไปถือว่าชี้ไปในทิศตรงข้าม
- หากมุมเท่ากับ 90° พอดี ดอตโปรดักต์จะเป็น 0
- หากดอตโปรดักต์ระหว่างเวกเตอร์ความเร็วของวัตถุกับเวกเตอร์ปกติของพื้นผิวเป็นบวก แสดงว่าวัตถุกำลังเคลื่อนที่ออกจากพื้นผิวนั้น
การนำไปใช้กับการชนของวัตถุสองชิ้น
- เมื่อมีวัตถุสองชิ้น เช่น กล่องสองใบ ก็จะมีเวกเตอร์ความเร็วแยกสำหรับแต่ละวัตถุ
- ในกรณีนี้จะใช้ ความเร็วสัมพัทธ์ (relative velocity) แทนการดูความเร็วของแต่ละชิ้นแยกกัน
- ความเร็วสัมพัทธ์คือผลต่างของความเร็วของวัตถุทั้งสอง
- ในเชิงเรขาคณิต มันคือเวกเตอร์ที่ชี้จากปลายของ
v_bไปยังปลายของv_a - ตัวอย่างเช่น หากรถสองคันชนประสานงากันโดยแต่ละคันวิ่งที่ 50km/h ภายใต้เงื่อนไขเดียวกัน จะเทียบได้กับรถคันหนึ่งพุ่งชนรถที่หยุดนิ่งด้วยความเร็ว 100km/h
- ทิศทางที่แทนพื้นผิวในการชนแสดงด้วย เวกเตอร์ปกติของการชน (collision normal)
- วิธีคำนวณเวกเตอร์ปกติของการชนจะแตกต่างกันตามรูปร่างหรือเรขาคณิตของวัตถุที่ชนกัน
- ตัวอย่างในที่นี้คือ vertex-edge collision ซึ่งเป็นกรณีที่จุดหรือมุมของวัตถุหนึ่งชนกับขอบของอีกวัตถุหนึ่ง
- ใน vertex-edge collision เวกเตอร์ปกติของการชนจะตั้งฉากกับขอบนั้น
- ตามธรรมเนียม หากแทนวัตถุเป็น
a,bเวกเตอร์ปกติของการชนจะชี้ไปทางวัตถุa- จะเรียกวัตถุใดว่า
aหรือbก็ได้ ตราบใดที่ใช้ให้สอดคล้องกันตลอดการคำนวณ
- จะเรียกวัตถุใดว่า
นิยามการชนด้วยความเร็วปกติสัมพัทธ์
- หากคำนวณดอตโปรดักต์ของความเร็วสัมพัทธ์
v_abกับเวกเตอร์ปกติของการชนn^ก็จะตัดสินได้ว่าวัตถุทั้งสองกำลังเคลื่อนที่ในทิศทางที่ก่อให้เกิดการชนหรือไม่ - ค่านี้เรียกว่า ความเร็วปกติสัมพัทธ์ (relative normal velocity)
- เป็นองค์ประกอบของความเร็วสัมพัทธ์ในทิศของเวกเตอร์ปกติของการชน
- ในที่นี้เครื่องหมายของมันสำคัญ แต่ในภายหลังก็ยังมีบทบาทสำคัญในการคำนวณแรงที่เกิดขึ้นระหว่างการชนด้วย
- เครื่องหมายของความเร็วปกติสัมพัทธ์ใช้แยกสถานะของการชน
- หากค่าเป็น บวก แสดงว่าวัตถุทั้งสองกำลังแยกออกจากกันอยู่แล้ว
- หากค่าเป็น ลบ แสดงว่าวัตถุทั้งสองยังคงพุ่งเข้าชนกันอยู่
- สรุปแล้ว การชนจะเกิดขึ้นเมื่อจุดของวัตถุหนึ่งแตะกับอีกวัตถุหนึ่ง และ ความเร็วปกติสัมพัทธ์ มีค่าเป็นลบ
1 ความคิดเห็น
ความคิดเห็นจาก Hacker News
บทความนี้มุ่งไปที่คนที่ไม่ใช่นักพัฒนาเกมและไม่มีพื้นฐานคณิตศาสตร์แข็งแรงนักเหมือนผม ดังนั้นผมจึงอธิบายแนวคิดบางอย่างที่คนมีประสบการณ์ด้านนี้อาจมองว่าแทบจะชัดเจนอยู่แล้วค่อนข้างยาว ถ้ามีคำถาม ผมยินดีตอบครับ
ผู้เริ่มต้นพัฒนาเกมมักเข้าใจผิดว่าการจัดการการชนต้องใช้ การคำนวณการชนของวัตถุแข็งเกร็ง หรือเอนจินฟิสิกส์ 2D อย่าง Box2D ถ้าคุณอยากทำเกมบิลเลียดหรือเกมที่กล่องถล่มลงมาแบบ Angry Birds ก็ใช่ แต่ถ้าเป็นแพลตฟอร์มเมอร์ 2D แค่ตรวจจับการชนด้วยการเปรียบเทียบสี่เหลี่ยมที่จัดแนวตามแกน แล้วปรับพิกัด X/Y ของตัวละครเพื่อย้อนการซ้อนทับ หรือกำหนดความเร็วแกน Y หลังจากกระโดด/ลงพื้น ก็เพียงพอแล้ว วิธีนี้ยังทำให้ปรับจูนความรู้สึกในการควบคุมตัวละครได้ละเอียดกว่า และมีแรงเฉื่อยอยู่ด้วย แต่โดยทั่วไปไม่ใช่แรงเฉื่อยที่สมจริงทางฟิสิกส์ หากมือใหม่พยายามใช้ฟิสิกส์สมจริง การเคลื่อนไหวมักจะลอย ๆ และไม่น่าพอใจ
ตัวอย่างทิวทอเรียลที่เริ่มด้วยแนวทางง่าย ๆ แบบไม่ใช้เอนจินฟิสิกส์: https://www.love2d.org/wiki/Tutorial:Baseline_2D_Platformer
ผมค่อย ๆ ซ้อนองค์ประกอบอย่างจุดและเส้นขึ้นไปเรื่อย ๆ ใส่สเต็ปย่อย ๆ กับเส้นดีบักแบบมองเห็นได้ไว้เยอะมาก ผลลัพธ์เลยกระตุกและช้ามาก แต่ก็พอทำงานได้ในระดับหนึ่ง
มีแผนจะอ่านและอธิบาย XPBD (Extended Position Based Dynamics - http://mmacklin.com/xpbd.pdf) ต่อไปไหมครับ? แนวคิดนี้ดูเหมือนจะได้รับความสนใจมากขึ้นเรื่อย ๆ และผมเคยใช้ผ่าน https://github.com/Jondolf/bevy_xpbd ใน Bevy แล้วค่อนข้างประสบความสำเร็จ ดูเสถียรกว่าแนวทางทั่วไป
ถ้าเพิ่ม RSS feed ให้ติดตามต่อได้จะดีมากครับ
ขอถามด้วยความสงสัยว่าใช้ เครื่องมือ อะไรในการทำหน้านั้นครับ?
พูดตามตรง ตอนแรกที่เห็นชื่อโดเมนแล้วรู้ว่า top-level domain เป็น “.ski” ผมนึกว่าเป็นเว็บของคนที่เขียน Mechanical Watch [1] กับบทความเจ๋ง ๆ อื่น ๆ เสียอีก ปรากฏว่าเป็นคนละคนโดยสิ้นเชิง แต่คุณภาพใกล้เคียงกันเลย ใน top-level domain “.ski” นี้มีสูตรลับอะไรอยู่หรือเปล่านะ :)
ผู้เขียนของ https://ciechanow.ski ที่พวกเราชอบกันที่นี่ ก็เป็นโปรแกรมเมอร์ชาวโปแลนด์ที่ทำงานอยู่ที่ Apple
องค์ประกอบสำคัญของเกมนี้คือเศษซากอวกาศสามารถเคลื่อนที่ได้ภายในสนาม และสามารถนำมาใช้แบบสร้างสรรค์ เช่น ขังฝ่ายตรงข้ามหรือขัดขวางไม่ให้บรรลุเป้าหมายได้ ในฐานะส่วนหนึ่งของโปรเจกต์ ผมตั้งใจจะข้ามการใช้เอนจินเกมไปเลย เพราะอยากสอนโครงสร้างแอปพลิเคชันให้ลูกเพิ่มอีกหน่อย และถึงภายหลังจะใช้เอนจินเกมสำเร็จรูป อย่างน้อยก็อยากให้ได้ผ่านกระบวนการลงมือทำทุกอย่างเองสักครั้ง ทุกอย่างยังโอเคจนกระทั่งเริ่มเข้าใกล้การตรวจจับและจัดการการชน จากนั้นสถานการณ์ก็แย่ลงอย่างรวดเร็ว แม้ผมจะมีพื้นฐานคณิตศาสตร์เชิงทฤษฎี แต่ก็ถูกกรณีขอบจำนวนมหาศาลถาโถมจนรับมือไม่ไหวอย่างรวดเร็ว สุดท้ายเลยยอมแพ้และตัดสินใจใช้ Box2D ผมไม่ใช่นักพัฒนาเกมมืออาชีพ แต่มีประสบการณ์พัฒนาซอฟต์แวร์มากกว่า 20 ปีและมีพื้นฐานคณิตศาสตร์ ถึงอย่างนั้นก็ยังพลาดเพราะประเมินปัญหานี้ต่ำเกินไป พูดด้วยคำพูดดูเหมือนง่าย แต่พอลงรายละเอียด ความซับซ้อนเหมือนจะโตขึ้นแบบทวีคูณ
วิธีทั่วไปในการทำเกมยิงด้วยการเปรียบเทียบสี่เหลี่ยมแบบง่ายมากอยู่ที่นี่: https://kidscancode.org/blog/2016/08/pygame_shmup_part_3/
อย่างไรก็ตาม ถ้าวัตถุเศษซากอวกาศต้องชนและรวมกลุ่มกันอย่างสมจริง และคุณต้องการให้ยานของผู้เล่นผลักกลุ่มวัตถุหนัก ๆ ได้ยาก การใช้ไลบรารีฟิสิกส์ก็สมเหตุสมผล
[1]https://m.youtube.com/watch?v=lS_qeBy3aQI&pp=ygUSVmVybGV0IGl...
[2]https://m.youtube.com/watch?v=3HjO_RGIjCU&pp=ygUSdmVybGV0IGl...
นั่นเป็นยุคที่ Flash มีอยู่ทุกที่
โค้ด: https://github.com/vandrieu/canvas-bouncing-ball
ลอจิกการชนอยู่ใน src/collision.ts
ผลลัพธ์/เดโม: https://vandrieu.github.io/canvas-bouncing-ball/
ถ้าเป็นไปได้ ช่วยเพิ่มไลเซนส์ให้ได้ไหม?
แต่ในเกม มักใช้การอัปเดตฟิสิกส์ที่บางครั้งลดลงถึง 30Hz พร้อมวิธี Euler-Cromer แบบตามอำเภอใจ จึงต้องใช้แนวทางที่ค่อนข้างต่างออกไป
ผมใช้เวลาหลายเดือน แต่ก็เหมือนเพิ่งขูดผิวของสิ่งที่เกินพื้นฐานที่เป็นที่รู้จักกันไปนิดเดียว การสร้างเอนจินที่เสถียรซึ่งวัตถุไม่กดทะลุกันหรือสั่นเป็นโพรงกระต่ายที่ไม่มีที่สิ้นสุด และบทความที่เน้นคณิตศาสตร์หนัก ๆ ที่ผมหาได้ก็แทบไม่ได้แตะเรื่องนี้ ผมทำความเข้าใจคณิตศาสตร์จากซีรีส์บทความเก่าของ Christ Hecker
http://www.chrishecker.com/Rigid_Body_Dynamics
ส่วนที่ยากคือ การชนของโพรเจกไทล์ ผมควรจะเอาตำแหน่งปัจจุบันของกระสุนกับตำแหน่งใน time step ถัดไป แล้วดู hitbox ของศัตรูในลักษณะเดียวกันเพื่อตรวจว่าตัดกันหรือไม่ แต่ผมตรวจแค่ time step ปัจจุบันเท่านั้น ดังนั้นกระสุนจึงอาจพุ่งผ่านศัตรูไปได้อย่างกับมีเวทมนตร์! เรื่องงี่เง่าจริง ๆ สักวันหนึ่งผมอาจกลับไปแก้ก็ได้