ตัวอย่าง Workshop แบบฝึกหัดเชิงปฏิบัติการพัฒนาเกมแนว Typing Game หรือเกมฝึกพิมพ์ภาษาอังกฤษด้วย Unity 3D ประกอบไปด้วย UI, EventSystem สำหรับผู้เริ่มต้น
เปิด Unity 5 ขึ้นมาครับ ทำการ New Project มาใหม่ (ผมจะละการเอาตัวละคร และ Animator ออกไปก่อนนะครับ คิดว่าคงทำกันได้)
ทำการวาง Plane ลงไปในฉากก่อนเลยครับ 2 ตัว โดย Plane ตัวแรกนั้นตั้งชื่อว่า GameOver อีกตัวนั้นหา Material มาใส่เล็กน้อยตั้งชื่อว่า Water ครับโดยการวางจะเป็นดังนี้
โดยการวาง GameOver ไว้ตำแหน่งข้างล่าง และ นำ Water วางเหนือ GameOver ครับ โดย Inspector ตัวอย่างนั้นเป็นดังนี้ครับ
และอีกตัวคือ Inspector ของ “Water” ครับ
ทำการสร้าง Script C# ใหม่ของ Water ครับชื่อวา “Water.cs” โดยใส่ Script การเลื่อน Material เช่นเคย
using UnityEngine; using System.Collections; public class Water : MonoBehaviour { float speed = 0.04f; float offset; void Start () { } void Update () { offset = Time.time * speed; GetComponent<Renderer>().material.mainTextureOffset= new Vector2(0,-offset); } }
ต่อมาให้เราสร้าง Box ขึ้นมาเป็นแท่น 3 แท่นลอยเหนือ Water ครับ จุดประสงค์คือเป็น Box ให้ตัวละครของเรากระโดดข้ามจากซ้ายสุดไปขวาสุด
(วิธีการคือหา Material ของต้นหญ้า ไปใส่ใน Box แล้วทำการ Clone Prefab มาใช้ซ้ำๆ ครับ)
กำหนด Inspector ของ Box ให้เรียบร้อยครับ
กำหนดค่าต่างๆ ตามที่สร้างไว้ตามตัวอย่างก็ได้ครับ ต่อมาเราจะสร้างตัวละครไปวางใน Scene ครับ Asset ที่ใช้ก็ QueryChan นำมา Re-Skin ใหม่แค่นั้นครับ ลางไปวางที่ตำแหน่ง Box ตัวแรกซ้ายสุด กำหนดชื่อว่า Player ครับ
วางตัวละครลงไปในฉากครับ แล้วทำการใส่ Physics -> RigidBody (กำหนด Gravity), Physics -> Character Controller และ Physics -> Capsule Collider ครับ
ส่วน Animator นั้นก็ทำง่ายก็พอครับสร้าง Parameter Bool ว่า IsJumping และ Trigger ว่า IsWinner พอ
ส่วนค่า Inspector ของ Player นั้น กำหนดตามตัวอย่างก็ได้ครับ
ต่อมานำส่วนของจุดที่ตัวละครเราไปชนแล้วจะชนะ หรือผ่านเกมซะ อาจจะเป็นรางวัลอะไรสักอย่าง ในตัวอย่างใช้ ทาโกะยากิ ยักษ์ครับตั้งชื่อว่า “Reward” วางลงไปใน Scene ที่ Box สุดท้ายครับ
กำหนด Inspector ของ Reward ดังนี้ครับ
ปรับ Main Camera ของเราที่ Inspector ส่วนของ Position X เป็น 9 ส่วนของ Position Y เป็น 3 และ ส่วนของ Position Z เป็น -3 ครับ
และปรับ Rotation ส่วนของ X เป็น 7 แกน Rotation Y เป็น 270 และ Z เป็น 0 ในหน้า Game Scene สำหรับ Preview เกมของเราจะเป็นดังภาพด้านล่างครับ
ต่อมาให้เราสร้าง UI มาไว้ในเกมครับ ไปที่เมนู GameObject->UI เลือก Raw Image และ Text มาวางในเกม ทั้ง 2 ตัว
ที่หน้า Hierarchy จะมีการสร้าง Canvas และ EventSystem ให้อัตโนมัติ
คลิกที่ Canvas ครับ แล้วตั้งค่า Inspector เป็น Scale With ScreenSize เพื่อให้เป็นการออกแบบ Layout แบบ Responsive ขยายและ Match Size ตามขนาดหน้าจอครับ
เปลี่ยนชื่อ Raw Image ใน Canvas เปลี่ยนชื่อเป็น “ClockIcon” หา texture รูปนาฬิกาสวยๆ มาใส่ และ เปลี่ยนชื่อ Text ใน Canvas เป็น TimerText และวางตำแหน่งของทั้งสองให้เหมาะสม
กดปุ่ม 2D ใน Scene View ดูก่อนครับ แล้วค่อยวางตำแหน่ง
สังเกตจากตัวอย่างนั่นคือการวางที่ตำแหน่งซ้ายบนของ Canvas หรือหน้าจอนั่นเองครับ
ต่อมาเราจะสร้างระบบ จับเวลา Count Down ครับ ให้เราสร้าง C# Script ชื่อว่า “TimeCounter.cs” ขึ้นมาครับ ใส่ไว้เป็น Component หนึ่งของ TimerText ใน Canvas ของเราครับ
using UnityEngine; using System.Collections; using UnityEngine.UI; using UnityEngine.EventSystems; public class TimeCounter : MonoBehaviour { Text counterText; float startTime = 11; public string countValue= ""; void Start () { counterText = GetComponent<Text>(); Time.timeScale = 1; } void Update () { startTime -= Time.deltaTime; int iValue = (int)startTime; iValue = Mathf.FloorToInt(startTime); if (iValue < 0) { iValue = 0; } countValue = iValue.ToString (); counterText.text = ""+countValue; } }
สังเกตครับจะเห็นว่าถ้ามีการทำงานเขียน Script ร่วมกับ UI อย่าง text ต้องมีการประกาศ
using UnityEngine.UI; using UnityEngine.EventSystems;
ทดสอบจะเห็นว่า เวลามันจะ count down จาก 11 ไปเรื่อยๆ จนถึง 0 ครับ
ต่อมาเราจะทำ Game Logic การเล่นที่เป็นหัวใจสำคัญของเกมเราครับให้เราทำการสร้าง Empty GameObject ขึ้นมา ตั้งชื่อว่า “GameSystem” วางตรงไหนก็ได้ครับใน Scene
ทำการสร้าง C# Script ขึ้นมาชื่อว่า “GameSystem.cs” ใส่ฟังก์ชันต่อไปนี้
using UnityEngine; using System.Collections; public class GameSystem : MonoBehaviour { public string randomResult =""; public bool winnerGame = false; public bool isBox = false; public bool isPassed = false; public void RandomString(){ string[] myDataStrings = new string[] {"cat", "student", "dog","elephant","bangkok","thailand","monkey","teacher","doctor","world","car","boat"}; string myRandomString = myDataStrings[Random.Range(0, myDataStrings.Length)]; randomResult = myRandomString.ToUpper (); } public void IsWinner(){ winnerGame = true; Debug.Log (winnerGame); } public void boxReset(){ isBox = true; } public void CorrectAnswer(){ isPassed = true; } }
ส่วนของ Random String คือสุ่มเอาศัพท์ที่เรากำหนดไว้ให้พิมพ์ตามจากในระบบที่เก็บอยู่ในรูปของ Array เอาออกมาเก็บลงตัวแปร randonResult พักไว้ให้ระบบเรียกไปใช้ครับที่ฟังก์ชัน
public void RandomString(){ string[] myDataStrings = new string[] {"cat", "student", "dog","elephant","bangkok","thailand","monkey","teacher","doctor","world","car","boat"}; string myRandomString = myDataStrings[Random.Range(0, myDataStrings.Length)]; randomResult = myRandomString.ToUpper (); }
ส่วน
public void boxReset(){ isBox = true; } public void CorrectAnswer(){ isPassed = true; }
ฟังก์ชัน boxReset() มีไว้สำหรับ แจ้ง reset ค่าเมื่อตัวละครยืนบน “Box” และ CorrectAnswer() มีไว้เช็คเพื่อทำการ เปลี่ยนศัพท์ใหม่เมื่อพิมพ์ถูกครับ
ดังนั้นเราจะเขียนโปรแกรมแบบ OOP กันแล้ว โดยทุก Element หรือทุก Model ที่มี C# อยู่จะมีการอ้างถึง GameSystem.cs ทุกตัวครับ
ไปที่ Player ทำการสร้าง C# ชื่อ “Player.cs” ขึ้นมาควบคุมมันครับ
using UnityEngine; using System.Collections; using System.Collections.Generic; using UnityEngine.UI; using UnityEngine.EventSystems; public class Player : MonoBehaviour { Animator anim; public float speed = 4.0F; public float jumpSpeed = 7.0F; public float gravity = 10.0F; private Vector3 moveDirection = Vector3.zero; public bool Winner = false; public bool wordshow = false; public GameSystem system; void Start(){ anim = GetComponent <Animator> (); Time.timeScale = 1; if(system ==null){ GameObject _system = GameObject.FindGameObjectWithTag("GameSystem") as GameObject; system = _system.GetComponent<GameSystem>(); } } void Update() { anim.SetBool ("IsJumping", false); CharacterController controller = GetComponent<CharacterController>(); if (controller.isGrounded) { moveDirection = new Vector3(0, 0, 0); moveDirection = transform.TransformDirection(moveDirection); moveDirection *= speed; } if (system.isPassed == true) { Character_jump(); system.boxReset(); system.isPassed = false; } moveDirection.y -= gravity * Time.deltaTime; controller.Move(moveDirection * Time.deltaTime); } public void Character_jump(){ moveDirection.y = jumpSpeed; moveDirection.z = speed; anim.SetBool ("IsJumping", true); } void OnTriggerEnter(Collider other) { switch (other.gameObject.name) { case "Reward": system.IsWinner(); break; case "GameOver": print ("Game Over"); break; default: break; } } }
สังเกต
public GameSystem system;
เราจะอ้างถึงไฟล์ GameSystem.cs โดยอ้างไว้ในตัวแปล system ครับ
ถ้าเป็นบทเรียนตอนก่อนๆ เราจะต้องลาก เจ้า GameObject ในหน้า Hierarchy ไปวางที่ช่องวางของ Player ของ Inspector แต่ในรอบนี้เราจะเขียน Code ให้มันเรียกค่าตัวนี้ไปวางอัตโนมัติครับ โดยการอ้าง Tag ของ Inspector GameObject
ให้ไปสร้าง Tag ใหม่ขึ้นมาครับ คลิกที่ Gamesystem บน Heirarchy
ทำการเพิ่ม Tag ใหม่เข้าไป 3 อย่างไว้เลยคือ GameSystem และเตรียม TypeCorrect และ TypeWrong ไว้ก่อนล่วงหน้าครับ
กลับมาที่ Code ของ Player.cs จะเห็นว่าเราจะทำการค้นหา Tag ที่ชื่อ “GameSystem” จาก Heirarchy ไปใส่ในตัวแปล system ทันทีด้วยตัวมันเองโดยที่เราไม่ต้องลากวางเลยครับ
if(system ==null){ GameObject _system = GameObject.FindGameObjectWithTag("GameSystem") as GameObject; system = _system.GetComponent<GameSystem>(); }
โดยเงื่อนไขการชน Trigger ของตัวละครนั้น
void OnTriggerEnter(Collider other) { switch (other.gameObject.name) { case "Reward": system.IsWinner(); break; case "GameOver": print ("Game Over"); break; default: break; } }
ถ้าชนกับ GameObject ที่ชื่อ “Reward” ให้เรียกฟังก์ชัน IsWinner() ที่อยู่ใน GameSystem.cs ได้เลยครับ นั่นคือ ชนะเกม
ส่วนของ GameSystem.cs นั้นจะมีการเช็คว่าพิมพ์ถูกหรือไม่ ไว้เทียบกับคำตอบที่ปรากฏถ้าถูกจะมีการ ใช้ตัวแปร isPassed มาเช็ค ร่วมกับ ตัวละครใน Player.cs ถ้าพิมพ์ถูกตัวละครจะกระโดดไปข้างหน้า
if (system.isPassed == true) { Character_jump(); system.boxReset(); system.isPassed = false; }
กลับมาที่ไฟล์ TimeCounter.cs ครับเพิ่ม GameSystem.cs เข้าไปเช่นกันโดยการประกาศ ตัวแปรเพิ่ม และแก้ไข ฟังก์ชัน Start() ตามนี้
public GameSystem system; void Start () { counterText = GetComponent<Text>(); Time.timeScale = 1; if(system ==null){ GameObject _system = GameObject.FindGameObjectWithTag("GameSystem") as GameObject; system = _system.GetComponent<GameSystem>(); } }
แล้วไปเพิ่มเงื่อนไขสำหรับ จับเวลาใหม่เมื่อตัวละครแตะโดน Box ให้ reset เวลาเป็น 11 ใหม่ตามนี้ที่ Update() ครับ
void Update () { startTime -= Time.deltaTime; int iValue = (int)startTime; iValue = Mathf.FloorToInt(startTime); if (iValue < 0) { iValue = 0; } countValue = iValue.ToString (); counterText.text = ""+countValue; if (system.isBox == true) { startTime = 11; system.isBox=false; } }
ทำการเพิ่ม Text ขึ้นมาอีกตัวใน Canvas ครับตั้งชื่อว่า “RandomText”
ทำการเขียน Code C# ลงไปใน RandomText ครับ โดยมีฟังก์ชันดังนี้
using UnityEngine.UI; using UnityEngine.EventSystems; public class RandomText : MonoBehaviour { Text ShowText; string blankText = ""; bool isBlinking = true; public GameSystem system; public bool isRandom = false; public string word_merge =""; void Start () { ShowText = GetComponent<Text>(); if(system ==null){ GameObject _system = GameObject.FindGameObjectWithTag("GameSystem") as GameObject; system = _system.GetComponent<GameSystem>(); } } public void Random_String(){ StartCoroutine(BlinkText()); system.RandomString(); } void Update(){ if (isRandom == false) { Random_String (); isRandom = true; } if (system.isBox == true) { isRandom = false; } if (system.winnerGame == true) { ShowText.text = "Stage Clear!"; isBlinking = false; StartCoroutine(stopPlay()); } word_merge = system.randomResult; } public IEnumerator BlinkText(){ while(isBlinking){ ShowText.text = blankText; yield return new WaitForSeconds(.5f); ShowText.text = word_merge; yield return new WaitForSeconds(.5f); } } public IEnumerator stopPlay(){ yield return new WaitForSeconds(5f); Time.timeScale = 0; } }
เรียกการสุ่ม Array ศัพท์ของเราจากไฟล์ GameSystem.cs ในฟังก์ชัน Start() ครับ
public void Random_String(){ StartCoroutine(BlinkText()); system.RandomString(); }
และมีการเช็คว่าถ้ามีการสุ่มไปแล้วจะไม่สุ่มอีกผ่าน Boolean ของ IsRandom ครับ
void Update(){ if (isRandom == false) { Random_String (); isRandom = true; } if (system.isBox == true) { isRandom = false; } if (system.winnerGame == true) { ShowText.text = "Stage Clear!"; isBlinking = false; StartCoroutine(stopPlay()); } word_merge = system.randomResult; }
ใส่ code ส่วนของ effect text กระพริบสักหน่อย
public IEnumerator BlinkText(){ while(isBlinking){ ShowText.text = blankText; yield return new WaitForSeconds(.5f); ShowText.text = word_merge; yield return new WaitForSeconds(.5f); } }
ทำการทดสอบครับ ศัพท์จะมาแล้ว
บทเรียนต่อไป เขียนเกมฝึกพิมพ์ภาษาอังกฤษ Typing Game ด้วย Unity ตอนที่ 2