เขียนเกมด้วย LibGDX : 5 – Simple Game ภาคจบ
หลังจากบทความที่แล้ว เขียนเกมด้วย LibGDX #4 – Simple Game ตอนแรก ได้นำเสนอตัวอย่างการเขียนเกมส์ด้วย LibGDX โดยยกตัวอย่าง Simple Game จาก LibGDX Wiki มาให้ดู ตอนนี้แอพพลิเคชันของเรา สามารถที่จะแสดงรูปถังน้ำได้แล้ว รวมถึงมีเสียงดนตรี(เสียงฝนตก) นั่นเอง ต่อไป ก็มาถึงการใส่ Action ให้กับถังน้ำ คือทำให้ถังน้ำขยับครับ รวมถึงวาดรูปเม็ดฝน และรายละเอียดส่วนอื่นๆของเกมครับ จบบทความนี้ จะได้ Simple Game ที่สามารถนำไปเล่นได้เลยครับ
บทความตอนอื่นๆ ในซีรีย์ เขียนเกมด้วย LibGDX ติดตามอ่านได้ตามลิงค์ข้างล่างเลยครับ
- เขียนเกมด้วย libGDX #1 – สร้างโปรเจ็ค
- เขียนเกมด้วย LibGDX #2 – Hello World
- เขียนเกมด้วย LibGDX #3 – Render และการรับ input
- เขียนเกมด้วย LibGDX #4 – Simple Game ภาคแรก
- เขียนเกมด้วย LibGDX #5 – Simple Game ภาคจบ
- เขียนเกมด้วย LibGDX #6 – Simple Game ภาคพิเศษ
- เขียนเกมด้วย LibGDX #7 - Simple Game - scene2d.ui
- เขียนเกมด้วย LibGDX #8 - Simple Game - Actor
ทำให้ถังน้ำขยับ (คลิ๊ก/touch)
หลังจาก render ถังน้ำได้แล้ว ต่อไปก็ถึงเวลาบังคับถังน้ำกันแล้วครับ เกมนี้จะไม่สามารถลากถังน้ำได้นะครับ ทำได้เพียงแตะที่หน้าจอ หรือว่าคลิ๊กเมาท์เท่านั้นถังน้ำก็จะย้ายไปยังตำแหน่งนั้นๆ โค๊ด render()
จะได้ดังนี้ (วางไว้หลัง batch.end()
)
เริ่มแรก เราเช็คว่ามีการคลิกที่หน้าจอ หรือว่ามีการแตะที่หน้าจอมือถือหรือไม่ โดยใช้ Gdx.input.isTouched()
ต่อมาก็แปลงหน่วย x,y ตำแหน่งที่เมาท์หรือมือแตะหน้าจอ ไปเป็นหน่วยของ camera เนื่องจากตำแหน่ง x,y จากเมาท์ กับตำแหน่ง x,y ใน Game World ของเรานั้นมันจะแตกต่างกัน
Gdx.input.getX()
และ Gdx.input.getY()
จะได้ค่าตำแหน่งเมาท์คลิก/มือแตะหน้าจอ เพื่อที่จะแปลงหน่วยไปเป็นหน่วยที่ camera ใช้ใน Game World เราจึงต้องใช้เมธอด camera.unproject()
โดยส่ง Vector3
เป็น argument ไป, Vector3 คือ เวคเตอร์ 3 มิติ มีแกน x, y และ z โดยเราได้สร้างเวคเตอร์ จากนั้นเซตตำแหน่ง x และ y ให้กับ Vector3 ฉะนั้น Vector3 ก็จะมี x,y ตำแหน่งเดียวกับที่เราใช้นิ้วแตะหรือว่าเมาท์คลิกนั้นเอง สุดท้ายเปลี่ยนตำแหน่งของถังน้ำ ไปยังตำแหน่งที่แตะนิ้วหรือเมาท์คลิก
*Note: ไม่ควรสร้าง Object ใหม่ทุกๆครั้ง แบบที่ Vector3 ทำนะครับ ควรจะประกาศ touchPos เป็น field ในคลาส Drop
แทนการสร้าง Object ใหม่ทุกๆครั้งจะดีกว่า
ทำให้ถังน้ำขยับ (Keyboard)
อันด้านบนเป็นการทำให้ถังน้ำขยับ จากการใช้เมาท์คลิกหรือว่าแตะที่หน้าจอให้ถังน้ำเคลื่อนที่ แต่ว่าสำหรับ วิธีนี้จะเป็นการบังคับให้ถังน้ำขยับ โดยใช้คีย์บอร์ดครับ โดยจะใช้ลูกศรซ้ายขวา ของคีย์บอร์ดเป็นตัวบังคับ โค๊ดก็ตามข้างล่างเลย
เมธอด Gdx.input.isKeyPressed()
สำหรับเช็คว่ามีการกดแป้นคีย์บอร์ดหรือไม่ โดยรับ argument เป็น คีย์นั้นๆ เช่น Gdx.input.isKeyPressed(Keys.LEFT)
เช็คว่ามีการกดแป้นปุ่มลูกศรซ้ายหรือไม่ เมธอด Gdx.graphics.getDeltaTime()
จะได้ค่า DeltaTime : ค่าเวลาระหว่างเฟรมล่าสุด กับเฟรมปัจจุบัน ในหน่วยวินาที (หากงง ลองดูบทความเก่าครับ มีพูดถึงอยู่นิดหน่อย) โดยสิ่งที่เราจะทำการเปลี่ยนเมื่อมีการกดคีย์บอร์ดก็คือ เมื่อมีการกดลูกศรซ้าย ตำแหน่ง x ก็จะถูกลบไป 200 รูปมันก็จะเลื่อนไปซ้าย 200 * delTaTime ครับ อาจจะเลื่อนไปประมาณ 1-2 หน่วย ต่อเฟรม
และก็เช็คให้แน่ใจว่า ถังน้ำของเราจะไม่ตกขอบครับ (กรณีที่มันอยู่ซ้ายสุดแล้ว เราไปกดลูกศรซ้ายให้มันอีก )
เพิ่มเม็ดฝน
สำหรับเม็ดฝน เราก็จะใช้คลาส Rectangle เหมือนกับถังน้ำ แต่ต่างกันที่ จะมีเม็ดฝนตกลงมาหลายๆเม็ด ทำให้เราต้องใช้ list มาช่วยนะครับ จะได้ดังนี้
คลาส Array
อันนี้เป็นคลาสของ libgdx com.badlogic.gdx.utils.Array
นะครับ คนละตัวกับ java.utils
นะครับโดยตัว Array
นี้จะทำงานคล้ายๆกับ ArrayList
ของ Java Collection เลย แต่อาจจะมีฟังค์ชันเพิ่มเข้ามาเพื่อความง่าย
ต่อมา เราก็ต้องมีการเก็บเวลาล่าสุดที่เม็ดฝน ตกลงมาครับ ประกาศเป็น field ภายในคลาสเลย เพื่อเอาไว้คำนวณให้เม็ดอื่นๆตกมาครับ เช่น เช็คว่าเมื่อเม็ดล่าสุดตกลงมา 1วิ ค่อยให้อีกเม็ดตกลงมาตาม
สำหรับเวลา lastDropTime เราจะเก็บเวลาเป็นหน่วย นาโนวินาทีนะครับ เลยต้องประกาศเป็น long
สำหรับการสร้างเม็ดฝน ผมได้สร้างเมธอดใหม่ขึ้นมา ชื่อว่า spawnRaindrop()
ภายในเมธอด จะสร้างออปเจ็ค Rectangle
ขึ้นมาใหม่ และก็กำหนดตำแหน่ง x, y ให้กับมันแบบสุ่มนะครับ จากนั้นก็เพิ่มมันเข้าไปใน Array ที่ได้ประกาศไว้ก่อนหน้านี้แล้ว
เมธอดด้านบน ดูแล้วก็ธรรมดานะครับ ไม่มีอะไรพิเศษน่าจะเข้าใจไม่ยาก ส่วน MathUtils
นั้นเป็นคลาสของ libgdx นะครับ โดยในตัวอย่างคือจะสุ่มเม็ดฝนอยู่ที่แกน x ระหว่าง x = 0 ถึง x = (800 - 64) ส่วน TimeUtils
คือคลาสอีกคลาสของ libgdx เช่นกัน สำหรับแสดงเวลาล่าสุดครับ โดยในทีนี้เป็นหน่วย นาโนวินาที
ในส่วนเมธอด create()
สร้างออปเจ็ค Array<Rectangle>()
ในชื่อ raindrops และก็เริ่มทำการโปรยเม็ดฝนเลยครับ ฮ่าๆ
ต่อไป ก็ทำการเพิ่มโค๊ดนิดหน่อยที่เมธอด render()
เพื่อเช็คว่าเวลาผ่านมาเท่าไหร่แล้ว ถึงกำหนดที่จะปล่อยเม็ดฝนเม็ดถัดไปหรือยัง
1000000000 ในหน่วย นาโนวินาที มีค่าเท่ากับ 1 วินาทีนะครับ ก็คือเม็ดแรกตกมาแล้ว 1 วิ เม็ดถัดมาถึงจะตกตาม แล้วก็นับ 1 วิ อีกเม็ดตกตาม ไปเรื่อยๆครับ
ต่อไป เม็ดฝน ยังอยู่นิ่งไม่ได้ขยับ เพราะเรายังไมไ่ด้เปลี่ยนแปลงค่า y ในเมธอด render()
โดยในทีนี้กำหนดความเร็วมันเท่ากับ 200 pixels/ยูนิต/วินาที หากเม็ดฝนพ้นขอบจอ เราก็จะลบมันทิ้งออกจาก array ครับ
ทีนี้เราต้องการจะ render เม็ดฝน โดยใช้ SpriteBatch
เหมือนกับถังน้ำเลยครับ โค๊ด render ก็จะประมาณนี้
สุดท้าย หากเม็ดฝนโดยถังน้ำ เราจะให้มันเล่นเสียงเม็ดฝน แล้วก็ทำการลบเม็ดฝนนั้นออกจาก array โค๊ดก็จะได้ประมาณนี้
เมธอด Rectangle.overlaps()
คือเมธอดที่เช็คว่า Rectangle (สี่เหลี่ยมผืนผ้า) พื้นที่มันทับซ้อนกับสี่เหลี่ยมอันอื่นหรือไม่ ในกรณีเราแค่เช็ค หากมันทับซ้อนกัน ก็แค่ให้เล่นเสียง แล้วก็ลบมันออกจาก array เท่านั้น (นึกถึง เม็ดฝนตกลงมา แล้วเราเอาถังมารองน้ำครับ ก็มีเสียงน้ำหยด แล้วน้ำนั้นก็มาอยู่ในถัง)
Cleaning Up
ผู้เล่นสามารถที่จะปิดแอพพลิเคชันของเราได้ตลอดเวลา สำหรับตัวอย่างนี้ก็ไม่มีอะไรมาก อย่างไรก็ตาม โดยทั่วไปแล้วเราควรจะช่วย clean up สิ่งที่ไม่ใช้แล้วครับ ทุกๆคลาสของ libgdx นั้นได้ implements อินเตอร์เฟส Disposable
และมีเมธอด dispose()
สำหรับทำความสะอาดสิ่งที่ไม่ได้ใช้แล้ว เช่นในตัวอย่าง Texture, Music SpriteBatch เราก็ clean up มันด้วยเมธอด dispose()
ตามตัวอย่างนี้ครับ โดย implements ApplicationListener#dispose()
เมื่อเราเรียกเมธอด dispose
แล้ว เราก็จะไม่สามารถใช้คลาสเหล่านี้ได้แล้ว. จะเห็นว่า เราเรียก dispose()
ในเมธอด ApplicationListener#dispose()
เนื่องจาก dispose
มันจะถูกเรียกสุดท้าย ก่อนปิดแอพครับ หากไม่เข้าใจ ลองดู Life Cycle ของ LibGDX เพิ่มเติมครับ
รองรับ Pause/Resume
สำหรับแอพแอนดรอยส์ เราสามารถที่จะ pause และ resume แอพได้ทุกเวลา เช่น กดปุ่ม Home หรือว่ามีสายเข้า ในตัวอย่างหากมีสายเข้าขึ้นมา แล้วกลับมาเล่นต่อ เกมส์ก็จะเล่นได้ต่อจากที่เราออกไปครับ โดยในต้นฉบับ เค้าให้เราทำเป็นการบ้านครับ ว่าเราจะ implements เมธอด ApplicationListener.pause()
และ ApplicationListener.resume()
อย่างไร เมื่อเวลาผู้เล่น resume กลับมาก็ให้แตะที่หน้าจอตรงไหนก็ได้ เกมก็จะดำเนินต่อไป ลองคิดดูครับ :)
เมื่อถึงตรงนี้ ทุกคนจะสามารถเล่นเกม Simple Game ได้แล้วนะครับ โดยมีเม็ดฝนตกลงมา เราก็เลื่อนถังเพื่อไปเก็บเม็ดฝนกันครับ สำหรับใครที่อยากลองปรับแก้ไข ก็ลองปรับเปลี่ยนให้เม็ดฝนตกลงถี่ขึ้นทุกๆครั้งที่เก็บลงถังได้ หรือเมื่อเวลาผ่านไปนานขึ้น ฝนก็จะตกถี่ขึ้น ลองเล่นๆกันดูนะครับ
โค๊ดทั้งหมด
- Authors
-
Chai Phonbopit
เป็น Web Dev ในบริษัทแห่งหนึ่ง ทำงานมา 10 ปีกว่าๆ ด้วยภาษาและเทคโนโลยี เช่น JavaScript, Node.js, React, Vue และปัจจุบันกำลังสนใจในเรื่องของ Blockchain และ Crypto กำลังหัดเรียนภาษา Rust