web analytics

Flutter Project : สร้างเกม Tertis ด้วย Flutter ตอนที่ 2

cover_ep2

สวัสดีครับ บล็อกนี้ต่อตอน ทำ tertis ด้วย Flutter ตอนที่ 1

Flutter Project : ทำเกม Tertis ด้วย Flutter ตอนที่ 1

 

ลากบล็อกในแนวดิ่ง

เราสามารถลากบล็อกในแนวตั้ง เพื่อให้บล็อกลงมาด้านล่างที่ ground ก็ได้
โดยใช้ onVerticalDragEnd() จากนั้นก็เลื่อนให้บล็อกลงมาที่ ground

 

วิธีการง่ายๆของการเลื่อนบล็อก ลงมาที่ ground คือวนลูปเรียก moveBlockDown() ที่เราทำไว้แล้วในตอนที่แล้ว
เลื่อนจนกว่า มันจะเลื่อนไม่ได้

 

ลองรัน

a15

 

การหมุนบล็อก

มาทำสว่นของการหมุนบล็อกบ้าง น่าจะเรียกได้ว่าเป็นส่วนที่ยากที่สุดของเกมนี้
โดยบล็อกของผมตอนนี้มี 4 แบบ แต่ละแบบหมุนได้ประมาณนี้

6a

 

เพิ่มตัวแปรให้กับ class Block
coordinatesBlockAreaStart คือ เก็บตำแหน่งมุมซ้ายบนของบล็อก ใน Game Area
List<List<Coordinate>> coordinates คือ ตำแหน่งของบล็อกที่หมุนในแต่ละแบบ
int currentRotateIndex คือ index ที่บอกว่า ตอนนี้บล็อกหมุนอยู่มุมอันไหน
แล้วก็มี method สำหรับ get ค่า block ที่มุมปัจจุบัน แล้วก็บล็อกมุมถัดไป

ส่วน rotate() จะเรียกเมื่อต้องการหมุนบล็อก ขอจะเว้นไว้ก่อนนะ

 

พอเรามีความสามารถการหมุน ดังนั้นเวลาเราจะเข้าถึงตำแหน่งของบล็อกต้องใช้จากตำแหน่งของบล็อกมุมปัจจุบัน

 

ทีนี้ก็ต้องมานิยามว่า บล็อกแต่ละอันมี การหมุนได้กี่แบบ แต่ละแบบเป็นแบบไหน เช่น Block S มี 2 แบบ
โดยการเก็บตำแหน่งต้นแบบของบล็อกในแต่ละมุมจะเก็บเป็นขนาด 4×4

7

 

กลับมาที่ rotate() ที่ค้างไว้ ที่นี่เราจะทำการหมุนบล็อกกัน โดยการหมุนก็คือเรียก มุมถัดไปแล้วเอาตำแหน่งของมุมซ้ายบนใน Game Area บวกด้วยตำแหน่งต้นแบบ แล้วอะพเดทตำแหน่งบน Game Area

 

แน่นอนว่า เราจะต้องอัพเดทค่าของ coordinatesBlockAreaStart ด้วยทุกครั้งที่บล็อกเลื่อน

 

กรณีของการเลื่อน ซ้าย ขวาก็ต้องอัพเดทค่า coordinatesBlockAreaStart ของบล็อกด้วย

 

สุดท้ายการหมุมบล็อก จะใช้วิธีกดคลิกที่หน้าจอเกมได้เลย วิธีการคือเพิ่ม onTap
แล้วเรียก rotateBlock() จากนั้น ก็ copy ค่าอัพเดทไปที่ Game Area Temp ด้วย

 

ทีนี้ เนื่องจากตอนนี้ มีบล็อกที่หมุนได้อยู่รูปแบบเดียว คือแบบ S
ดังนั้น ขอสร้างมาแค่ แบบเดียวก่อน

 

ลองรัน

a16

 

ปุ่มควบคุม

จริงเกม Tertis เราจะถนัดเล่นกับจอย ที่มีปุ่มกดมากกว่า ดังนั้นผมเลยอยากทำปุ่มให้กดเลื่อนซ้ายขวาบ้าง
ทำแป้นสำหรับควบคุม ใส่ไว้ด้านล่าง ส่วน method เลื่อน เราก็ทำไว้หมดแล้ว

a10

 

ทำบล็อกหมุนแบบอื่นๆ

มาเขียนคลาสอื่นๆเพิ่มเติมกัน เพื่อทำบล็อกรูปแบบที่เหลือให้หมุนได้ เช่นบล็อก L หมุนได้ 4 แบบ

7

 

ลองทดสอบเฉพาะบล็อก L

a18

 

เมื่อเขียนคลาสนิยามการหมุนของทุกแบบจะสามารถเล่นเกมได้แล้ว แต่ยังลบแถวที่บล็อกเต็มแล้วไม่ได้

a19

 

ลบแถวที่บล็อกเต็ม

เมื่อบล็อกลงมาที่ ground จนเต็มแล้ว เราจะต้องลบแถวนั้นออก ผมเรียกว่า clear complete line

วิธีการคือทุกครั้งที่บล็อกลงมาที่ ground ต้องเช็คเงื่อนไข ว่าแถวนั้นมีบล็อกเต็มหรือไม่

 

แล้วก็ตอนที่เราบังคับลากในแนวดิ่งให้มันลงมาที่พื้นก็เช็คด้วย

 

การเช็คเงื่อนไขของแถวก็คือวนลูปเช็คแถวใน Game Area ว่า แถวไหนที่ไม่มีที่ว่างเลย แสดงว่าแถวนั้นต้องลบออก พอลบออกแล้วต้องเพิ่มแถวเปล่าๆด้านบน

 

ลองรัน

a11

 

เพิ่มบล็อกแท่งยาว

มาเพิ่มบล็อกอีกตัว ผมขอเรียกว่า l small block

ก่อนอื่นสร้างคลาสนิยามตามปกติ ซึ่งมันหมุนได้ 2 แบบ

 

เพิ่มมันใน BlockProvider เพื่อให้สุ่มได้

 

มาแล้วจ้า

9

 

แต่พอเลื่อนๆไปด้านขวาสุดแล้วกดหมุน จะเจอปัญหาหมุนไม่ได้ เพราะมันหมุนแล้ว index มันเลยขอบ Game Area

8

 

วิธีแก้คือ ก่อนหมุมต้องเช็คก่อนว่ามันหมุนแล้วมันเกินขอบของ Game Area หรือไม่ ถ้าเกินก็ปรับให้มันเข้ามาซะ
โดยเราใช้บล็อกต้นแบบ 4×4 ดังนั้นถ้ามันเลยขอบขวา ก็ปรับให้มัน -4 ช่อง มันก็จะหมุนได้แล้ว
แต่ถ้าเกินขอบด้านซ้ายก็กำหนด col=0

 

จะได้ประมาณนี้

a12

 

ทำเมนูเริ่มเกมใหม่

มาเพิ่มเมนู restart เกมกันครับ

 

โดยเมนูก็ไม่มีอะไรมาก คือมีชื่อเกมแล้วก็ปุ่ม NEW GAME พอกดปุ่มแล้วจะเรียก restart()
ตนนี้ขอค้าง method restart() ไว้ก่อน

 

ตอนนี้ พอมีเมนูเกมขึ้นมาพื้นที่เกมก็จะลดลงทำให้ผมคิดว่าควรจะปรับ Area unit ให้พื้นที่เกมเล็กลงเล็กน้อย แต่จำนวนแถวมากขึ้น

 

10

 

ทำช่องแสดงคะแนน

เพิ่มช่องคะแนน เช่น ถ้าเราปิดแถวที่เต็มได้ เราก็จะได้คะแนน โดยจะขอ Hardcode ไว้ก่อน ยังไม่เอาค่ามาแสดงจริง
ก่อนอื่นวาด Widget ขึ้นมา ผมให้มันแสดงอยู่ด้านขวาของ Game Area อีกช่องคือส่วนที่บอกว่าบล็อกต่อไปคือบล็อกอะไร

 

ได้แบบนี้

11

 

ใบ้บล็อกถัดไป

อีกอันที่ต้องมีคือคำใบ้บล็อกถัดไป ว่าบล็อกอะไร
ดังนั้นมันคือ GameArea อีกตัวนึง โดยมีขนาด 4×4 ตามต้นแบบ ผมเรียกว่า Game Area Next Block
แล้วก็มี object Block อีกอันสำหรับเก็บค่าบล็อกต่อไป

 

กำหนดขนาด Area unit ของ Game Area Next Block

 

 

init ค่าให้ GameAreaNextBlock ขนาด 4×4
ส่วน copyBlockToGameAreaNextBlock คือคัดลอดค่าตำแหน่งต้นแบบของ Block ใส่ให้ GameAreaNextBlock

 

จากนั้นก็เพิ่มใน initState
initGameAreaNextBlock() เรียกเพื่อเคลียค่าของ GameAreaNextBlock
แล้วก็สร้างบล็อกใหม่ให้กับ blockNext

 

ที่ process() ก็ทำเหมือนกัน คือ initGameAreaNextBlock();
แล้ว copy ค่าใส่ GameAreaNextBlock

 

ที่ onBlockToGround() พอบล็อกถึง ground แล้วก็ให้สุ่มบล็อกถัดไปมารอเลย

 

ต่อไปคือเชื่อม GameAreaNextBlock กับ Widget ให้แสดงผล หลักการคล้ายกับ GameArea ก่อนหน้านี้

 

ลองรัน

a15

 

แสดงคะแนนแถว

กำหนดตัวแปรเก็บค่า

 

ที่ removeLineOnGameArea() จะถูกเรียกเมื่อแถวนั้นถูกลบ ดังนั้นเราก็แค่เพิ่มค่า countLine ที่นี่

 

เอาตัวปร countLine ไปใส่ที่ช่องคะแนน

 

ลองรัน

a16

 

เริ่มเกมใหม่

มาทำที่ค้างไว้อีกนิด คือ ปุ่มเริ่มเกมใหม่
เขียน method restart() โดยมันจะกำหนดค่าเริ่มต้นใหม่ทั้งหมดให้กับเกม

a17

 

ทำให้บล็อกเริ่มจากตรงกลาง

ตอนนี้บล็อกจะเริ่มจากตำแหน่ง 0,0 ซึ่งมันคือมุมบนซ้าย แต่ที่ถูกต้องควรเริ่มจาก มุมบนกลาง
ดังนั้นเรามาปรับตรงนี้อีกนิดครับ

ก่อนอื่นกำหนดค่าให้กับ coordinatesBlockAreaStart ให้เริ่มจาก col ตรงกลางจอ ซึ่งคือ COUNT_COL/2
การใช้ COUNT_COL~/ 2 คือมันจะหาร 2 แล้วแปลงเป็น int ให้เลย สะดวกดีนะ

 

แต่แค่นี้จะยังไม่เสร็จ เพราะปกติ coordinatesBlockAreaStart จะใช้ตอน rotate แต่การนำตำแหน่งแสดงจะใช้จาก currentCoordinatesOnGameArea ในคลาส Block
ดังนั้นต้องเพิ่ม method เพิ่มอัพเดทค่า currentCoordinatesOnGameArea โดยบวกค่า coordinatesBlockAreaStart  เข้าไป

 

จากนั้นก็เรียก method update ตอนสร้างบล็อก แล้วทำแบบนี้กับบล็อกทุกรูปแบบ

 

ลองรัน

a18

 

ทำหน้า Game Over

สุดท้ายคือเช็คจบเกม แล้วแสดง dialog ว่า game over

เพิ่มตัวแปรกำหนดว่า เกมกำลังรันอยู่หรือไม่

 

ก่อนอื่นเขียน dialog แสดง Game over ขึ้นมา เป็นอะไรก็ได้ แค่มีปุ่ม restart เกม

 

ทีนี้ กรณี game over คือกรณีที่บล็อกมันล้นจนเกินขอบบนของ Game Area นั่นหมายความว่า
ตอนเรา copy ค่า จาก block ไปใส่ GameArea มันจะต้องทับค่าเดิมที่มีบล็อกอยู่แล้ว
ดังนั้น หาก AreaUnit ไม่ว่าง มันทับไม่ได้หมายความว่าล้นจอแล้วนั่นเอง

ดังนั้นตอนเรานำ block ลงที่ Ground ก็แค่เช็คว่ามันทำสำเร็จหรือไม่ ถ้าไม่ก็แสดง game over

 

จะได้แบบนี้

a19

 

 

จบแล้ว

สรุปการทำเกม Tertis ใช้หลักการรันไปเรื่อยๆคล้ายๆเกมงู มีหลักการยุ่งยากกว่านิดหน่อย
โดยหลักการที่ผมใช้คือมี GameArea 3 อัน อันแรกสำหรับ Ground Block อันที่สองสำหรับ Block ที่กำลังเลื่อน อันที่สามสำหรับใบ้ Next Block
การเลื่อนบล็อกใช้วิธีลบ แถวสุดท้ายใน game area แล้วแทรกแถวใหม่ด้านบน
ส่วนการหมุนบล็อก ก็เก็บตัวต้นแบบไว้ในขนาด 4×4 และเก็บค่าตำแหน่งบนซ้ายของบล็อกใน GameArea แล้วนำตัวต้นแบบมาทับกับบล็อกเดิม
สุดท้ายก็ทำปุ่มกับทำส่วนของการลากบล็อก

โค้ดอยู่ใน Github

https://github.com/benznest/tertis-game-flutter

 

ส่วนที่ยังเหลือ

ตอนนี้เกมก็เล่นได้แล้ว แต่ก็มีส่วนที่ยังขาดอยู่ ก็คือ
1. ยังไม่ได้ทำ responsive ให้รองรับทุกหน้าจอ
2. ส่วนของการลากยังไม่ smooth พอ
3. ยังไม่มีการเก็บ High score
4. บล็อกแบบอื่นๆ เช่น แบบ S กลับด้าน , L กลับด้าน
5. การหมุนของบล็อกยังไม่ smooth เช่น บล็อก l small
6. ยังไม่ได้ทำให้มันเร่งเวลาตาม level

ว่างๆเดี๋ยวจะทำต่อนะ