ตัวอย่างการเขียนแอปพลิเคชันดึงข้อมูล API ที่สร้างจาก MySQL และ PHP หรือภาษาอื่นๆ ที่อยู่ในรูปของ JSON มาแสดงผลผ่าน ScrollView ใน Unity
งานบางงานเราจำเป็นจะต้องใช้ Feature ของ Unity 3D แต่การออกแบบ UI ลูกค้ากลับอยากให้เราสร้างหน้า UI เป็นเหมือน Native Application ที่เรียกว่า RecyclerView หรือ ListView แบบ Android Native หรือ UiTableView แบบ iOS โดยมีข้อมูลให้เราดึงแล้วล่ะเป็น API ที่แสดงผลจาก JSON
ในตัวอย่างนี้ Partner อย่างทีม Trialation Team ได้ออกแบบ BackEnd Data ในรูปแบบของ JSON API ให้แล้วซึ่งเราสามารถเรียกผ่าน URL ได้ปกติอย่าง https://mydomain.com/api/ หรืออะไรก็ช่าง แค่ขอให้ Format ของข้อมูลเป็นรูปแบบนี้
{ "success": true, "response": [ { "ID": "31", "title": "British Tea Pot", "thumbnail": "http:\/\/www.demo.com\/api\/uploads\/thumbnails\/2019_12_06_09_36_12.png", "download": "http:\/\/www.demo.com\/api\/uploads\/downloads\/2019_12_06_09_36_12.obj", "category": "1", "content": "\u0e01\u0e32\u0e19\u0e49\u0e33\u0e23\u0e49\u0e2d\u0e19 \u0e23\u0e32\u0e0a\u0e27\u0e07\u0e28\u0e4c\u0e2a\u0e01\u0e38\u0e25\u0e1c\u0e39\u0e49\u0e14\u0e35", "x": "0", "y": "0", "z": "0", "rotateX": "0", "rotateY": "0", "rotateZ": "0", "scaleX": "0", "scaleY": "0", "scaleZ": "0", "date": "2019-12-07 08:42:35" } ], "err": null }
หรือถ้า Preview แล้วขอให้อยู่ในรูปแบบของข้อมูลดังกล่าว โดยใน [ ] ของ Array ต้องมีข้อมูลเยอะๆ เข้าไว้
ให้เราเปิด Unity ขึ้นมาแล้วออกแบบหน้าจอบน Hierarchy ดังต่อไปนี้:
ให้ทำการสร้าง Canvas ขึ้นแล้ว แล้วใน Canvas ให้สร้าง ScrollView เป็น Child ของ Canvas เพื่อจัดลำดับของการแสดงผลในข้อมูลให้เป็นระเบียบ
ทำการตั้งค่า Canvas ดังนี้ ไปที่ Inspector แล้วปรับแต่งตามตัวอย่าง
เราจะตั้งค่า Canvas ส่วน render Mode เป็น “Screen Space – Overlay” และ ตั้งค่า Canvas Scaler ส่วนของ UI Scale Mode เป็น Scale With Screen Size
หลังจากนั้นให้ทำการคลิกขวาที่ Scroll View -> ViewPort -> Content แล้วสร้าง Button ขึ้นมาตั้งชื่อว่า ListItem เพิ่ม Child ลูกของ Button ที่ชื่อ ListItem ด้วย ภาพประกอบ (Image), ชื่อหัวข้อของรายการ (TextTitle) และ คำอธิบายซึ่งจะมีหรือไม่มีก็ได้
สร้าง Script C# ใหม่ขึ้นมาชื่อว่า ListItemController ใส่ไว้ใน ListItem โดยมี Code ต่อไปนี้:
using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.UI; public class ListItemController : MonoBehaviour { public Image Icon; public Text Name, Description; }
ทำการลาก Image ที่เป็น Child ของปุ่ม ListItem บน Hierarchy ไปวางที่ Icon และลาก TextTitle ใน Hierarchy ไปวางใน Name ส่วน ARTextDetail ไปวางที่ Desscription ตามภาพ
หน้าจอของแอปพลิเคชันเราจะเป็นดังนี้
เมื่อเสร็จสิ้นขั้นตอนนี้แล้ว ให้ไปใช้ Library ชื่อ SimpleJSON จากบทความ เขียนเกมด้วย Unity ดึงค่า Web Services JSON ด้วย SimpleJSON ซึ่งจะไม่ขออธิบายขั้นตอนอะไร แค่กระชับสั้นๆ ว่าให้สร้างโฟลเดอร์ว่า “Plugins” ใน Assets ของ Project แล้วเอาไฟล์ในภาพไปวางไว้ในโฟลเดอร์ดังกล่าว:
คลิกที่ ViewPort บน Hierarchy ทำการเพิ่ม Script C# ชื่อว่า ListController ใส่ Code ดังนี้:
using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.UI; using UnityEngine.Networking; using SimpleJSON; public class ListController : MonoBehaviour { public GameObject ContentPanel; public GameObject ListItemPrefab; [TextArea] public string jsonData; ArrayList TempleArray; void Start() { //JSON string url = "http://youdomain.com/api/"; UnityWebRequest www = new UnityWebRequest(url); StartCoroutine(FetchData(url)); } IEnumerator FetchData(string URL) { UnityWebRequest www = UnityWebRequest.Get(URL); yield return www.SendWebRequest(); if (www.isNetworkError || www.isHttpError) { Debug.Log(www.error); } else { jsonData = www.downloadHandler.text; JSONNode jsonNode = SimpleJSON.JSON.Parse(jsonData); for (int i = 0; i < jsonNode["response"].Count; i++) { GameObject newtemple = Instantiate(ListItemPrefab) as GameObject; ListItemController controller = newtemple.GetComponent<ListItemController>(); newtemple.transform.parent = ContentPanel.transform; newtemple.transform.localScale = Vector3.one; controller.Name.text = jsonNode["response"][i]["title"].ToString(); string getJSONImg = jsonNode["response"][i]["thumbnail"].ToString(); string replaceQuote = getJSONImg.Replace("\"", ""); string urlImg = replaceQuote.Replace("\\", ""); UnityWebRequest wwwTexture = UnityWebRequestTexture.GetTexture(urlImg); yield return wwwTexture.SendWebRequest(); if (wwwTexture.isNetworkError || wwwTexture.isHttpError) { Debug.Log(wwwTexture.error); } else { Texture2D myTexture = ((DownloadHandlerTexture)wwwTexture.downloadHandler).texture; controller.Icon.sprite = Sprite.Create(myTexture, new Rect(0, 0, myTexture.width, myTexture.height), new Vector2(0, 0)); } } } } }
พิจารณาแต่ละส่วน เราทำการประกาศการทำงานร่วมกับ SimpleJson และ Networking ดังนี้บน Header
using UnityEngine.Networking; using SimpleJSON;
เราจะทำการ Clone Prefabs ของรายการ คือเจ้า ListItem เป็น GameObject ตัวหนึ่งเก็บในตัวแปรชื่อ ListItemPrefab
public GameObject ListItemPrefab;
เรียกใช้ UnityWebRequest โดยคำสั่ง:
UnityWebRequest www = new UnityWebRequest(url);
และไปเรียกฟังก์ชันที่ผมได้ทำการ Implement ขึ้นมาคือ FetchData โดยส่วน Param เว็บไซต์ไปผ่านตัวแปร url
StartCoroutine(FetchData(url));
ใน FetchData() นั้นจะประกอบไปด้วยกระบวนการเรียก node ของ json ผ่านฟังก์ชันของ SimpleJson ดังนี้:
jsonData = www.downloadHandler.text; JSONNode jsonNode = SimpleJSON.JSON.Parse(jsonData); for (int i = 0; i < jsonNode["response"].Count; i++) { //วน Loop เรียกข้อมูล อ้างอิงตัวแปร i }
โดยเราจะทำการ Clone ตัว Prefab ของ ListItem ผ่านตัวแปร ListItemPrefab หรือเจ้ารายการขึ้นมาก่อน แล้วจึง Map ข้อมูลของ node array ที่เรียกจาก json เข้าไปในรายการ ListItem จากการเรียก GetComponent ของ ListItemController มาเก็บในตัวแปร controler แต่ละตัวอ้างอิงรอบของการวนลูป จำนวน Array ที่เรียกจาก Json
GameObject newtemple = Instantiate(ListItemPrefab) as GameObject; ListItemController controller = newtemple.GetComponent<ListItemController>(); newtemple.transform.parent = ContentPanel.transform; newtemple.transform.localScale = Vector3.one; controller.Name.text = jsonNode["response"][i]["title"].ToString();
ส่วนของการดึงข้อมูลรูปภาพ เราต้องตัด String และ Replace String เล็กน้อยสไตล์บ้านๆ แล้วใช้วิธีการเรียก WebRequestTexture อีกรอบไปเก็บในตัวแปร Texture2D ชื่อ myTexture แล้วค่อยให้แต่ละแถว (controller) ไป Map ข้อมูลโหลด Sprite เข้าไปโดยดึง Path รูปภาพที่เป็น URL
string getJSONImg = jsonNode["response"][i]["thumbnail"].ToString(); string replaceQuote = getJSONImg.Replace("\"", ""); string urlImg = replaceQuote.Replace("\\", ""); UnityWebRequest wwwTexture = UnityWebRequestTexture.GetTexture(urlImg); yield return wwwTexture.SendWebRequest(); if (wwwTexture.isNetworkError || wwwTexture.isHttpError) { Debug.Log(wwwTexture.error); } else { Texture2D myTexture = ((DownloadHandlerTexture)wwwTexture.downloadHandler).texture; controller.Icon.sprite = Sprite.Create(myTexture, new Rect(0, 0, myTexture.width, myTexture.height), new Vector2(0, 0)); }
เป็นอันเสร็จ
กลับไปที่ Hierarchy คลิกที่ Content แล้วเพิ่ม Component ต่อไปนี้ลงไป และตั้งค่าตามตัวอย่าง
คลิกที่ ListItem หรือรายการใน Hierarchy ทำการสร้าง Prefab ของมันซะ แล้วซ่อน GameObject ตัวแม่ไว้หรือลบเลยก็ได้
ทดสอบโดยการ Run ตัว Editor ของเราดูสิว่ามันทำงานได้จริงหรือเปล่า?
ทำงานได้ 100% โอเค จะเห็นว่าตัวอย่างนี้ก็สามารถเอาไปประยุกต์กับงานในอนาคตที่ต้องเขียนหน้าจอที่ ลูกค้าขอให้เหมือน Native Application ได้
ปล. แล้วถามว่า Performance ดีเท่า Native ไหมขอตอบตรงๆ ว่าไม่ดีเท่า Native ออกจะได้ไซส์ไฟล์ที่ใหญ่กว่าเกินจำเป็นเพราะ Unity ต้องรันผ่าน Virtual Machine ของมันอีกทียังไง ก็จะมีความหน่วงกว่า Native App
Performance ก็อยู่ที่การ Optimised ว่าจะเร็วแค่ไหน
ส่วนใครที่บอกว่าจะเขียนจาก Unity แทน Native App ทั้งหมด ขอแนะนำว่าไม่ค่อยโอเคนะครับ คือไม่ใช่ว่ามันทำไม่ได้เหรอ แต่คุณควรจะไปศึกษา Native App เพิ่มดีกว่า เช่น Kotlin, Swift, Java เพราะถ้าเราใช้ Unity มันเกินความจำเป็นอ่ะครับ แต่ถ้าดึงดันจะในการจะสร้าง Native App ส่วนตัวสำหรับผมนะมันก็เหมือนคำพูดที่ว่า:
“คุณขับรถเบนซ์ไปตัดหญ้าทำไม”