web analytics

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

cover_ep1

สวัสดีครับ จากบล็อกตอนก่อนหน้านี้ ผมได้ลองทำแอปเล็กๆ ลักษณะเป็นเกม sudoku , เกมงู , เกม XO ด้วย Flutter ครับ เพื่อเป็นการคลายเครียดแล้วก็ฝึกเขียน flutter แบบสนุกๆครับ โดยบล็อกนี้ผมจะบันทึกทำเกม Teris ด้วย Flutter ครับ

บล็อกทำเกม sudoku

Flutter Project : ทำเกม Sudoku ด้วย Flutter

 

เริ่มต้น

เกม tertis เป็นเกมต่อบล็อกจากบนลงล่าง ลักษณะการทำงานจะรันไปเรื่อยๆ ดังนั้นผมจึงใช้หลักการคล้ายๆกับที่ใช้ในเกมงูครับ

เริ่มต้นโปรเจคเปล่าๆ

1

 

ก่อนอื่นขอกำหนดขนาดตารางก่อน ประมาณ 14×10 ช่อง ช่องละ 36

กำหนดสีคร่าวๆ

 

สร้างตาราง

พื้นที่ของเกม ที่บล็อกจะเคลื่อนที่จะเป็นลักษณะแบบตาราง โดยผมจะเรียกมันว่า Game Area

 

วาดตาราง ขนาดตามที่กำหนดแล้วใส่ขอบ

2a

 

สร้างบล็อก

ลองสร้างบล็อก แล้ววาดลงไปที่ Game Area ครับ

ก่อนอื่นนิยาม คลาส AreaUnit ซึ่งจำเก็บตำแหน่งย่อยๆในตาราง ว่ามีสีอะไร มีบล็อกอยู่หรือไม่

 

ประกาศตัวแปร gameArea ซึ่งมันคือ AreaUnit 2 มิติ

 

ทีนี้ที่ initState กำหนดค่าให้กับ AreaUnit ลงไป โดยกำหนด ว่าสมมุติมีบล็อกนึงอยู่ใน Game Area
โดยลอง hardcode ลงไป

 

กลับไปที่ build() เชื่อมตัวแปร gameArea กับ Widget
ก็คือถ้าใน AreaUnit มีค่า available=false ก็เอาสีจาก AreaUnit มาใส่ แต่ถ้าไม่ใส่สีดำ

 

ลองรัน บล็อกมาแล้วจ้า อันนี้คือหลักการวาดบล็อกลงบน Game Area นั่นเอง ง่ายๆเท่านี้เลย

3

 

ทำให้บล็อกเลื่อนลงมา

ตอนนี้เราเอาบล็อกมาวาดใน Game Area ได้แล้ว ตอนนี้เราจะทำให้บล็อกขยับลงมา
ทฤษฎีที่ผมจะลองใช้ตอนนี้คือ สร้าง timer มาเพื่อรันแอปทุกๆ 0.5 วินาที
โดยหลักการขยับองบล็อก ตัว game area มันคือ list ดังนั้นมันสามารถลบและแทรกได้ง่ายๆ เราก็นำแถวล่างสุดออกไปแล้วไปแทรกแถวใหม่ด้านบน จากนั้นก็วาดหน้าจอใหม่ เท่านี้บล็อกก็ขยับลงมาแถว และทุกๆ 0.5 วินาทีมันก็จะขยับลงมา 1 แถว

สร้างตัวแปร timer กำหนดค่าตัวแปร
baseRowAvailable คือแถวที่เราจะเอาออก

 

เรียกตัวแปร timer ให้ทำงาน ที่ initState โดยจะทำงานทุกๆครั้งจะเรียก method ชื่อว่า process()

 

ที่ process() เราก็ทำตามทฤษฎี คือ copy list แถวนึงมา ตอนนี้คือแถวล่างสุด แล้วลบแถวนั้นทิ้ง จากนั้นก็ไปแทรกด้านบนสุด

 

ลองรัน บล็อกเลื่อนแล้ว

a1

 

 

ลองเพิ่มบล็อกบนพื้น

บล็อกที่ลงมาล่างสุดอยู่มันจะไม่ขยับไปไหน ผมขอเรียกมันว่า Ground
โดยผมจะสมมุติว่ามีบล็อกอยู่บน ground อยู่ 2 แถวล่างสุด เป็นบล็อกสีแดง

 

จากทฤษฎีของเรา ที่ว่าจะลบแถวล่างสุดออก แล้วไปแทรกบนสุดจะใช้ไม่ได้ เพราะถ้าลบล่างสุดบล็อกบน ground จะหายไป
ดังนั้น แถวที่เราควรจะลบคือแถวเปล่าๆ ก่อนจะถึง ground มันคือ index count_row – 3

 

ลองรัน ก็พอได้อยู่นะ

a3

 

ทีนี้ปัญหาคือ เมื่อลองเพิ่มบล็อกบน ground แบบที่ฝั่งขวาอยู่สูงกว่าที่เราจะวาง บล็อกมันไม่สามารถลงไปที่ ground ที่เราต้องการได้ ดังนั้นวิธีใช้ baseRowAvailable จึงใช้ไม่ได้

a2

 

ทำ Game Area คู่ขนาน

วิธีที่ดีกว่า คือ สร้างอีก game area คู่ขนานโดย ตัวนี้จะเก็บค่าเฉพาะตำแหน่งของบล็อกเท่านั้น ผมจะเรียกมันว่า Game Area Temp
ส่วน Game Area ก่อนหน้านี้จะเก็บเฉพาะ block บน ground

 

ดังนั้น เราจะต้อง init ค่าเพิ่มอีกตัว โดยจะลองสมมุติค่าลงไปด้วย

 

ที่ initState ก็เรียก init ทั้ง 2 Game Area

 

เพื่อแยกการทำงานเป็นสัดส่วนมากขึ้น  สร้าง method moveGameAreaTempDown() แล้วย้ายการทำงานส่วนของเลื่อนบล็อกมาที่นี่ โดยเราจะใช้หลักการเดิมคือ ย้ายแถวล่างสุดไปไว้ด้านบน แต่ทำที่ gameAreaTemp เพราะมันเก็บแค่ตำแหน่งบล็อกที่กำลังเลื่อนลงมาเท่านั้น

 

ต่อมาทำการเชื่อม Game Area Temp เข้ากับ  Area Unit
โดยทำการเช็ค ถ้าใน GameArea ไม่มีบล็อกก็ไปเช็คใน GameAreaTemp แต่ถ้าไม่มีอีกก็ใส่สีดำนั่นเอง

 

แก้ปัญหาไปได้ 1 อัน เริ่มเข้าใจไอเดียแล้วนะ

a4

 

สร้างคลาสของบล็อก

ตอนนี้บล็อกของเรา hardcode ใน method แต่ทว่าในเกมจริงๆมีบล็อกหลายแบบ ดังนั้นเราควรสร้าง class มาจัดการ บล็อกไปเลย

กำหนด class  ชื่อว่า Coordinate จะเก็บตำแหน่ง row,col
class Block ก็เก็บตำแหน่ง และสีของบล็อกตัวเอง

 

ดังนั้นเราต้องอัพเดทตำแหน่งของบล็อก ใน object ของบล้อกนั้นทุกครั้งที่ขยับ

 

สร้าง method createBlock() ก็นิยามบล็อกคร่าวๆ เช่น สีและตำแหน่ง

 

ประกาศตัวแปร block

 

ตอนเริ่มเกม ก็ให้สร้าบล็อก กำหนดค่าให้ block

 

ทีนี้ เราก็ต้องเชื่อม object block กับ Game Area Temp ด้วย เพราะจะได้นำไปแสดงได้ถูกต้อง
ก็คือ copy ค่าใน object block ไปอัพเดทใน Gae Area Temp

 

ที่ process() ก็เรียก
initGameAreaTemp() เพื่อล้างค่า
moveBlockDown() เพื่อเลื่อนบล็อกลง 1 ตำแหน่ง
copyBlockTogameAreaTemp() คัดลอกตำแหน่งบล็อก

 

ลองรันได้ผลลัพธ์เดิม แต่การทำงานเป็นคลาสและเป็นสัดส่วนมากขึ้น พร้อม scale แล้ว

a5

 

ทำบล็อกกระทบพื้น

ต่อมา ทำให้บล็อกเลื่อนลงมาถึง ground หรือถึงบล็อกที่อยู่บน ground ก็ให้มันไม่ขยับและกลายเป็น ground พร้อมส่งบล็อกใหม่ลงมา

วิธีการก็คือ เมื่อบล็อกใน Game Area Temp เลื่อนลงมาถึง ground ก็แค่ย้ายค่ามันไปใส่ใน Game Area ก็จบเกม

สร้าง method สำหรับเช็คว่ามันชน ground หรือยัง

 

ที่ process() หลังจากขยับบล็อกก็เช็คว่ามันชน ground หรือไม่ ถ้าชนก็ copy ค่าไปใส่ใน Game Area
พร้อมกับสร้าง block ใหม่

 

ลองรัน

a6

 

ทำบล็อกขยับซ้าย-ขวา

การขยับบล็อกก็คืออัพเดทค่าตำแหน่งใน object ของบล็อกแล้ว copy ค่าไปใส่ใน gameAreaTemp
ขยับขวาก็ col+1 ทุกอัน ขยับซ้ายก็ col-1

 

ลองให้มันเลื่อขวาพร้อมกับเลื่อนลง ที่ process

 

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

a7

 

นั้นก็เพราะ เราควรจะไม่ขยับ ถ้ามีตำแหน่งไหนในบล็อกออกนอก GameArea ดังนั้นเราก็วนลูปเช็คก่อน ถ้าผ่านทุกตำแหน่งค่อยขยับ

 

ลองรัน ได้แล้วฮะ บล็อกเลื่อนไปชนขอบก็เลื่อนลงมาแบบสวยงาม

a8

 

ปัญหาต่อมา คือบล็อกเลื่อนขวาไปชนบล็อกอื่น แล้วดันขยับเข้าไป

a9

 

ปัญหานี้เกิดจากเราเช็คแต่ขอบข้าง เราไม่ได้เช็คว่า ถ้ามันมีบล็อกกั้นอยู่แล้วก็ไม่ควรขยับ
ก็เพิ่มเงื่อนไขให้มัน ทำทั้ง moveBlockDown() , moveBlockRight() , moveBlockLeft()

 

ลองรัน ผลออกมาสวยงาม

a10

 

ทำบล็อกให้ขยับที่ต้องการ

ต่อมาเราจะขยับบล็อกด้วยการลากของเราเอง ก็คือลากนิ้วไปทางซ้ายบล็อกก็เลื่อนมาทางซ้าย
วิธีการคือใช้ Widget GestureDetector จะมีลุกเล่นชื่อว่า onHorizontalDragUpdate(detail)
เมื่อเราลากนิ้วไปทางแนวนอน method นี้จะถูกเรียกพร้อม argument 1 ตัว ชื่อว่า detail
ใน detail จะมีค่าหลายตัวเช่นความเร็ว พื้นที่ แต่ที่เราสนใจคือ primaryData  โดยมันจะมีค่า
+ ถ้าเราลากนิ้วไปด้านขวา
– ถ้าลากนิ้วไปด้านซ้าย

เราก็เช็คจากค่านี้เราไปเรียก method move ที่ทำไว้นั่นเอง

 

ลองรัน ตอนนี้เราลากบล็อกได้เล้ว

a12

 

ปัญหาต่อมาคือ บล็อกที่เราลาก มันตอบสนองไวมาก นั่นก็เพราะ method onHorizontalDragUpdate มันถูกเรียกบ่อยมากๆ ถี่เกินจำเป็นสำหรับเรา ดังนั้น เราควรจะหน่วงเวลาสักหน่อย สัก 0.1 วินาที

ประกาศตัวแปร

ถ้า เวลายังอยู่ระหว่างหน่วงอยู่ เราจะไม่สนใจ

 

ลองรัน ดูดีขึ้นมาหน่อยนึงแล้ว

a13

 

 

ทำบล็อกรูปแบบอื่นๆ

ในเกมจริงๆจะมีบล็อกหลายสี หลายรูปแบบ เรามาเพิ่มบล็อกให้หลากหลายกัน

5

 

สร้าง class BlockProvider คลาสนี้จะทำการสุ่มบล็อกขึ้นมา
โดยตอนนี้ผมจะทำ block 4 แบบ

โดยเราก็ต้องไปสร้าง class สำหรับแต่ละบล็อก เช่น Block S

6

 

ที่ createBlock ของเกม ก็เรียกใช้ BlockProvider

 

ลองรัน บล็อกมีหลายตัวแล้ว

a14

 

ติดตามตอนต่อไป

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

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