สอนเขียนเกม Android ด้วย Box2D ตอนที่ 1
วันนี้ขอพูดถึงเรื่อง Box2D บน LibGDX(Android) ซักหน่อยละกันครับ โดยในบทความนี้จะพาไปรู้จักกับ Box2D ว่ามันคืออะไร? มีประโยชน์อะไร? ใช้เมื่อไหร่ และวิธีการใช้งานมีอะไรบ้าง
บทความนี้ ผมถือว่าทุกคนมีพื้นฐาน Android และ LibGDX นะครับ ทำให้บางขั้นจะไม่พูดถึงพื้นฐานมากนัก หากไม่เข้าใจตรงไหน ก็อ่านจากบทความก่อนๆ หรือสอบถามมาได้ครับ
Box2D คืออะไร
Box2D นั้นเป็น Physics Engine (2D) ที่ได้รับความนิยมมากที่สุด Engine หนึ่งเลยก็ว่าได้ ตัว Engine มีขนาดเล็ก มีประสิทธิภาพ ที่สำคัญเป็น Open Source หรือก็คือ ฟรีนั่นเอง
แต่เดิมนั้น Box2D เขียนด้วยภาษา C++ เนื่องจากมันเป็น Physics Engine ที่ยอดเยี่ยมทำให้มีคนดัดแปลงโค๊ดแล้ว port ไปเป็นภาษาอื่นๆ เช่น
จริงๆแล้ว Physics Engine มันไม่ใช่ Game Engine จะให้มัน render กราฟฟิค เรียกไฟล์รูป โหลด Game Assets ก็คงจะทำไม่ได้ มันทำได้เพียงแค่ จำลองแวดล้อมให้เป็น Physics เท่านั้น ทำให้ Game Engine ต่างๆ เหล่านี้มี Box2D รวมอยู่ใน features ของ Engine ด้วย เช่น
จะเห็นว่า Game Engine 2D ส่วนใหญ่ เลือกใช้ Box2D กันทั้งนั้นเลย ทำให้มันเหมาะมากที่จะนำมาเขียนเกมส์ โอเค มาเริ่มสร้างโปรเจ็คเกม Android ร่วมกับ Box2D เลยดีกว่า
Create Project
โปรเจ็คนี้จะเป็นการเขียนเกม Android ง่ายๆ ด้วย Box2D โดยใช้ LibGDX ฉะนั้นเริ่มแรก ก็ทำการสร้างโปรเจ็คด้วย LibGDX กันก่อนครับ เปิดตัว libGDX Project Setup ครับ หรือดาวน์โหลดได้จากลิงค์นี้ gdx-setup.jar
หากเป็นมือใหม่ LibGDX แนะนำให้อ่านนี้ก่อนครับ วิธีการสร้างโปรเจ็ค LibGDX อ่านที่นี่ครับ
จากนั้นทำการ Setup ตามรูปด้านล่างเลยครับ
จะเห็นว่าในส่วน Sub Project ผมเลือกแค่ Android เนื่องจากจะรันเฉพาะ Android เท่านั้น (หากใครอยากให้รัน Cross-Platform ก็เลือกเอาเองนะครับ)
ในส่วน Extensions นั้นเลือกเฉพาะ Box2D ในส่วนอื่นๆ ยังไม่ต้องรู้ว่ามันคืออะไร สามารถเลือก เพิ่ม หรือ ลบ ที่หลังได้ครับ
เมื่อทำการ Setup เรียบร้อยแล้ว กด Generate ครับ
ถ้าหาก Generate ผ่าน ก็จะได้แบบนี้
หากใครขึ้น error แบบข้างล่างนี้
ให้ทำการลบ gradle-wrapper
ออก ตาม path ที่มันแจ้งไว้เลยครับ อยากของผม *nix มันก็จะแจ้ง path ~/.gradle/
แล้วทำการ Generate ใหม่นะครับ อีกวิธีนึง ให้มัน error อย่างงี้แหละ เดี่ยวค่อยไปเปลี่ยนเอาเวลา import ก้ได้เหมือนกัน
จากนั้นก็ Import โปรเจ็คครับ เปิด IDE ขึ้นมาเลย จะ Eclipse หรือ IntelijIDEA ก็แล้วแต่ สำหรับบทความนี้ใช้ IntelijIDEA นะครับ ผมข้ามขั้นตอนวิธีการ import โปรเจ็ค LibGDX ไปเลยละกัน สมมติว่าผู้อ่านทุกท่านทำเป็นอยู่แล้ว ไม่เป็นกลับไปอ่านลิงค์จากด้านบนครับ :D ส่วนการ error gradle-wrapper ให้ทำการเปิดโฟลเดอร์ gradle/wrapper/
มองหาไฟล์ gradle-wrapper.properties
จากนั้นแก้โค๊ดบรรทัดสุดท้ายจาก
เป็น
หรือไม่อย่างงั้นก็เซต GRADLE_HOME เป็น path ที่ได้ติดตั้ง gradle ไว้ครับ
gradle-wrapper มันเหมือนเป็นเครื่องมือ ที่ให้เรารัน gradle ได้โดยที่เครื่องที่รัน ไม่จำเป็นต้องลง gradle เพราะมันจะใช้ gradle-wrapper แทน วิธีแก้ปัญหา gradle-wrapper บางทีก็อาจเจอใน Android Studio เหมือนกันครับ ให้ใช้วิธีเดียวกัน
เมื่อ import เสร็จแล้ว เราจะมี 2 โฟลเดอร์ นั่นก็คือ AhoyBox2D-core
และ AhoyBox2D-android
นะครับ สนใจแค่ MyGame.java
ใน Module core อย่างเดียวนะครับ ลบโค๊ดออกให้หมดเหลือไว้แค่นี้
ไฟล์ MyGame.java
Box2D Concept
ก่อนเริ่มเขียนโค๊ด เรามาสนใจทฤษฎีและคอนเซปของ Box2D กันก่อนดีกว่า ก่อนเราจะสร้างเกมด้วย Box2D เราจำเป็นจะต้องรู้จัก Class พวกนี้ครับ
Body
Body เป็นออปเจ็คหนึ่ง ใน Box2D ที่เอาไว้จัดการเกี่ยวกับฟิสิกส์ เช่น น้ำหนัก, ความเร็ว, ตำแหน่งของวัตถุ เป็นต้น (จะเห็นว่ามีแต่ค่าที่เรามองไม่เห็น จำต้องไม่ได้)
Fixture
Fixture ก็คล้ายๆกับ Body แต่ต่างกันที่มันคือวัตถุที่สามารถจับต้องได้ มองเห็นได้ เช่น รูปทรงของวัตถุ สี่เหลี่ยม, สามเหลี่ยม หรือวงกลม, แรง (ฺBody กับ Fixture มักจะมาคู่กันครับ)
World
World ผมเรียกทับศัพท์ไปเลย ตามชื่อเลยครับ มันก็คือโลกนั้นเอง โลกของ Box2D ตัวเกม เราจำเป็นจะต้องมี World เพื่อเก็บ Body, Fixture หรือค่าต่างๆที่เกี่ยวกับฟิสิกส์เอาไว้ ถือเป็นหัวใจของ Box2D เลยก็ว่าได้
เอาละทีนี้เมื่อรู้จักออปเจ็คต่างๆ ไปแบบคร่าวๆแล้ว จริงๆทฤษฎีมันคงอธิบายอะไรมากไม่ได้ ถ้าไม่ได้เห็นภาพจริงๆ และลองทำ ต่อไปคือ การที่เราจะสร้างออปเจ็คๆหนึ่งใน Box2D นั้น เราจะต้องมีขั้นตอนอย่างไรบ้าง
ขั้นตอนการสร้าง Object ใน Box2D
- สร้าง World โดยการกำหนดแรงดึงดูด ทั้งแกน x และ y โดยใช้คลาส
Vector2
- สร้าง Body 2.1 สร้าง BodyDef เพื่อกำหนดตำแหน่งให้กับ Body ก่อน (Definition Body) 2.2 จากนั้นก็สร้าง Body จาก BodyDef ที่เรากำหนดค่าไว้
- สร้าง Fixture 3.1 สร้าง FixtureDef ขึ้นมาก่อน สำหรับกำหนดคุณสมบัติให้กับวัตถุ 3.2 สร้าง Shape (รูปทรงต่างๆของวัตถุ) 3.3 จับ Shape ยัดใส่ FixtureDef เพื่อทำให้ FixtureDef มีรูปทรงตาม Shape 3.4 สร้าง Fixture จาก FixtureDef ที่กำหนดไว้ โดยใช้ Body จากข้อ2
คู่มื่อศึกษา Box2D เพิ่มเติม Box2D Manual
Create a Game
ทีนี้เมื่อเข้าใจคอนเซป ขั้นตอนและหลักการแล้ว ก็เริ่มลงมือโค๊ดเลย โดยจะไล่ไปทีละสเตปๆนะครับ เริ่มจากเปิดไฟล์ MyGame.java
ที่อยู่ใน AhoyBox2D-core
ขึ้นมา
เริ่มแรกทำการประกาศ OrthographicCamera, World และ Box2DDebugRenderer ดังนี้
1. สร้าง World
ต่อมา ทำการสร้าง World ขึ้นมาก่อน เนื่องจาก World นั้นคืออ็อปเจ็ค ที่เป็นเหมือนศูนย์รวมคอยจัดการ memory, objects รวมถึงการ simulation ต่างๆ การสร้าง World ทำได้ด้วยคำสั่งนี้
โดยเราทำการสร้างออปเจ็ค world โดยมีค่าแรงโน้มถ่วง(แรง g) อยู่ที่ 9.81 m/s^2 แต่ว่าทำไมเป็นค่าลบ (-) เนื่องจาก LibGDX นับแกน x,y โดยเริ่มจากมุมซ้ายล่างคือจุด 0, 0
2. สร้างพื้น
ต่อมาเราจะทำการสร้างพื้น แต่ว่าการสร้าง Body เราต้องทำตามขั้นตอนครับ ฉะนั้นก็ต้องเริ่มจากสร้าง พื้น Body จาก Body ที่เราได้นิยามไว้ จนได้โค๊ดแบบนี้
จากโค๊ดด้านบน ผมอธิบายไปในคอมเม้นแล้ว หากไม่เข้าใจ ให้กลับขึ้้นไปอ่านคอนเซป และขั้นตอนการสร้าง Object ใน Box2D ใหม่ครับ จะเห็นภาพมากขึ้น
ต่อมาที่เมธอด render()
ทำการเพิ่มโค๊ดนี้ลงไป
จากโค๊ดด้านบน ทำการเคลียร์หน้าจอ จากนั้นใช้ Box2DDebugRenderer
สำหรับ render โดยรับพารามิเตอร์ 2 ตัวคือ World และ Matrix ครับ ส่วนตรง world.step(dt, 8, 3);
ส่วนนี้คือ step สำหรับเช็คพวก collision (การชนกันของวัตถุ) ส่วนค่า 8, 3 คือ velocityIterations(ความเร็ว) และ positionIterations(ตำแหน่ง) ส่วนนี้ อาจจะเปลี่ยนให้อยู่ระหว่าง 1-10 ดูครับ
ทดสอบ รันโปรแกรม จะได้ไอ้กล่องสี่เหลี่ยมยาวๆ ครับ
ต่อมา ผมจะสร้างลูกบอล 1 ลูก ก็จะได้โค๊ดแบบนี้
ทำตามสเตปเลยครับ สร้าง BodyDef -> Body -> Shape -> FixtureDef -> Fixture โดย Shape คราวนี้เป็น CircleShape
ซึ่งเป็นคลาสที่ไว้แสดงวัตถุรูปวงกลมครับ มีขนาดรัศมี 50 หน่วย
ส่วน BodyDef ของลูกบอล ก็จะเป็น DynamicBody คือเป็นวัตถุที่สามารถเคลื่อนที่ได้
สุดท้าย จะเห็น restitution
คือค่าการเด้งของวัตถุ ค่า 0.5f คือ เมื่อวัตถุตกกระทบ จะเด้งขึ้นมา ครึ่งหนึ่งของความสูงเดิมเรื่อยๆ ครับ
Note! เรื่องที่คุณยังไม่รู้เกี่ยวกับ หน่วย unit ใน Box2D จะมีค่าเท่ากับ 1 เมตรนะครับ ฉะนั้น ที่สร้างลูกบอลไป มีขนาดรัศมี 50 เมตร โอ้ว ใหญ่ทับสนามฟุตบอลได้เลย :D แต่ว่าหน่วยใน LibGDX เป็น pixel ฉะนั้น เวลาเรากำหนดหน่วยทุกๆอย่าง ใน Box2D เราก็ต้อง scale ให้เป็นหน่วยเมตร ซะก่อนครับ
สร้างตัวแปรขึ้นมาใหม่ ชื่อ PPM โดยกำหนดไว้ที่ 100 คือ หน่วยใน Box2D จะถูกหารด้วย 100 ฉะนั้นลูกบอล ลูกใหม่ จะมีขนาด 50 เซนติเมตร อ่า กำลังดีละครับ ไม่ใหญ่มาก
จากนั้นก็แปลงหน่วย Unit ใหม่ เป็นดังนี้
ลองสั่งรัน แล้วสังเกต การตกกระทบ การกระเด้งของวัตถุ จากนั้นลองเปลี่ยนค่า restitution ดูครับ
สำหรับ Box2D บทความแรก ขอจบเพียงเท่านี้ก่อนครับ ฝากไว้เป็นการบ้านสำหรับผู้ที่เข้ามาอ่านครับ ลองสร้าง Body ชนิดกล่องสี่เหลี่ยม ให้เป็น DynamicBody แล้วให้มันร่วงลงมา เหมือนอย่างลูกบอลครับ ลองทำดูนะครับ แล้วเดี่ยวบทความถัดไป จะมาเฉลย รวมถึงต่อเรื่องการจับการชนกันของวัตถุ (collision), Filter(MaskBits, CategoryBits) และ ContactListener ในบทความต่อๆไปครับ
โค๊ดทั้งหมด
MyGame.java
- Authors
-
Chai Phonbopit
เป็น Web Dev ในบริษัทแห่งหนึ่ง ทำงานมา 10 ปีกว่าๆ ด้วยภาษาและเทคโนโลยี เช่น JavaScript, Node.js, React, Vue และปัจจุบันกำลังสนใจในเรื่องของ Blockchain และ Crypto กำลังหัดเรียนภาษา Rust