วันนี้จะพาทุกคนเข้าสู่บทเรียนแบบ Step by Step สำหรับการใช้งาน Unity 5 สำหรับเขียนเกม 3D บนสมาร์ทโฟน แพลตฟอร์ม Android แบบ Step by Step ด้วย Javascript ครับ
วันนี้เราจะเริ่มต้นที่ Unity 5 ซึ่งผมจะขอละการติดตั้ง Unity 5 ออกไปเพราะคิดว่าคงอ่าน ภาษาอังกฤษกันเป็น วิธีการมันต้องทำ 2-3 ขั้นตอน ถ้าขยันอ่านก็คงทำกันได้ เราจะมาเริ่มต้นพัฒนาเกม วิ่งเก็บเหรียญ และกระโดดหลบก้อนหิน บังคับด้วยการแตะที่หน้าจออย่างเดียว เล่นบน สมาร์ทโฟน แพลตฟอร์ม Android ของ Google ดังนั้นโปรเจ็คนี้เมื่อเสร็จแล้วจะได้ไฟล์ APK มาครับ
หมายเหตุ: Assets ทั้งหมด และ Animation ทั้งหลายที่ปรากฏบน code ชุดนี้อาจจะไม่ตรงกับ Assets ของคุณเพราะผมซื้อมาจาก Asset Store เกมที่เราจะพัฒนามีชื่อว่า BoonMee (ลุงบุญมีระลึกชาติละมั้ง)
เปิด Unity 5 ขึ้นมาครับ
ลากส่วนของ Assets ที่เรามีไปวางบน Project ของเราครับ ศึกษาพวก Animation ดีๆ ว่าเป็น Legacy หรือเปล่า
ตั้งค่าตำแหน่งกล้องของเราให้ดีครับ
ในตัวอย่างกำหนด Viewport ให้อยู่ในขนาดของหน้าจอ Android มาตรฐาน ซึ่งประกอบไปด้วย 720×1280 ครับ
แปลว่าเราจะสร้างเกมที่เป็นแนว Portrait แนวตั้งบน Android ครับ
ตั้งค่า Main Camera ของเราให้อยู่ในตำแหน่งที่เราจะ control ได้ง่ายตามตัวอย่างข้างล่าง คือ Position(0,1,-10)
ต่อไปเราจะสร้าง พื้นให้เรา Create 3D Object อย่าง Cube ขึ้นมาครับ
เปลี่ยนชื่อใน Inspector ใหม่ให้เข้าใจง่ายว่าเป็นพื้นครับ แล้วทำการปรับ Scale และ Position มันตามนี้
ตั้งชื่อว่า Road ครับ หา Materials สวยๆ จาก Assets Store ฟรีๆ พวก Terrain Asset มาใช้ก็ดีครับ
ลาก Material ใน folder ไปวางบน Cube ที่ชื่อ Road ของเราเลยครับ
สร้าง Javascript ขึ้นมาใหม่ชื่อว่า Road.js ครับ ใส่ code ตามนี้
#pragma strict var speed : float = 0.5f; var offset : float; function Start () { } function Update () { offset = Time.time * speed; GetComponent.<Renderer>().material.mainTextureOffset= new Vector2(0,-offset); }
เป็นการบังคับให้ พื้น Material ของถนนเลื่อนเข้าหากล้องให้พื้นเคลื่อนไหวครับ
สำหรับตัวละครผม ย้ำนะว่า Animation มีชื่อว่าอะไรบ้างให้จำไว้สำหรับเปลี่ยนใน code นะครับ พอดีผมไปซื้อมาชื่อ Farmer ผมก็ลาก Prefabs ที่เป็น Legacy ลงไปใน Scene View
ส่วนของ Animation ของ Models ของผมที่ซื้อมามี animation ตามนี้ให้จำชื่อไว้ใช้ใน Code
ทำการเลือก Physics>Character controller และ Capsule Collider ไปคลอบตัวละครของเรา
กำหนดค่าต่างๆ ให้ Capsule และ Character คลอบตัวละครพอดีไม่จมพื้นที่ยืนอยู่
สร้าง Javascript มาใหม่ชื่อว่า PlayerMobile.js แล้วใส่ code ต่อไปนี้
#pragma strict var speed : float = 6.0; var jumpSpeed : float = 8.0; var gravity : float = 20.0; public var statejump : boolean = false; private var isGrounded : boolean = false; private var PlayerMoveDirection : Vector3 = Vector3.zero; function Start () { } function Update () { var controller : CharacterController = GetComponent(CharacterController); GetComponent.<Animation>().CrossFade("MF_Walk",3); if (controller.isGrounded) { statejump = false; if(Input.GetMouseButtonDown(0)) { PlayerMoveDirection.y = jumpSpeed; statejump = true; } } PlayerMoveDirection.y -= gravity * Time.deltaTime; controller.Move(PlayerMoveDirection * Time.deltaTime); }
คำสั่งบังคับให้ตัวละครเดินเมื่ออยู่บนพื้นคือ
GetComponent.<Animation>().CrossFade("MF_Walk",3);
และคำสั่งให้ตัวละครกระโดด เมื่อคลิกเมาส์ หรือแตะหน้าจอคือ
if (controller.isGrounded) { statejump = false; if(Input.GetMouseButtonDown(0)) { PlayerMoveDirection.y = jumpSpeed; statejump = true; } }
ทดสอบโดย ทำอะไรสักอย่างที่ว่ามาคือ แตะหน้าจอ หรือคลิกเมาส์
ขั้นตอนต่อไปให้เราสร้าง HUD (หน้าจอ Interface ของเกม ซึ่งจะเป็นการบอกว่าเราเก็บเหรียญได้มากกี่เหรียญแล้ว) ให้ลาก Object Prefab รูปเหรียญไปวางบน Hierarchy แล้วลากไปไว้ใน Main Camera
ต่อมาคือการสร้าง ระบบของเกมครับ ให้ Create Empty Object มาใหม่ตั้งชื่อว่า Gamesystem ครับ
สร้างไฟล์ GamePlay.js แล้วเขียน code นี้ลงไปครับ
#pragma strict var timeRemaining : float = 10; var lifes : int = 3; var timeExtension : float = 3f; var timeDeduction : float = 2f; var totalTimeElapsed : float = 0; var score : int = 0; public var isGameOver : boolean = false; public var GameSkin : GUISkin; function Start(){ Time.timeScale = 1; } function Update () { if(isGameOver){ return; } if(lifes <= 0){ isGameOver = true; } } function GetItems(){ score=score+1; timeRemaining += timeExtension; } function CrashMonster(){ lifes = lifes -1; timeRemaining -= timeDeduction; } function OnGUI () { var IntTimeRemaining : int = lifes; var IntScore : int = score; if(!isGameOver) { GUI.skin = GameSkin; GUI.Label(new Rect(Screen.width-(Screen.width/6), 30, Screen.width/6, Screen.height/6),""+IntTimeRemaining.ToString()); GUI.Label(new Rect((Screen.width/5)+10, 30, Screen.width/5, Screen.height/6)," "+IntScore.ToString()); }else{ Time.timeScale = 0; GUI.Box (Rect (Screen.width/4, Screen.height/4+10, Screen.width/2, Screen.height/2), "\nGAME OVER\nTOTAL SCORE: "+IntScore); // Replay Button if (GUI.Button (Rect (Screen.width/4+10, Screen.height/4+Screen.height/10+60, Screen.width/2-20,Screen.height/10), "Restart")) { Application.LoadLevel ("GAME"); } // Exit Button if (GUI.Button (Rect (Screen.width/4+10, Screen.height/4+Screen.height/10+130, Screen.width/2-20,Screen.height/10), "Main Menu")) { Application.Quit(); } } }
จะเป็นเงื่อนไข กรณีที่เรา Game Over ให้แสดงหน้าจอ GUI ถ้าไม่ก็จะแสดงคะแนน และชีวิตของเรา โดยเงื่อนไขของเราคือการวิ่งชน 3 ครั้งก็จะ Game Over ส่วน GUI นั้นให้เราสร้างตัวแปรมารับคือ GameSkin ประกาศที่
public var GameSkin : GUISkin;
และเรียกใช้ที่
if(!isGameOver) { GUI.skin = GameSkin; GUI.Label(new Rect(Screen.width-(Screen.width/6), 30, Screen.width/6,
ให้ไปหา Font สวยๆ มาใช้งานก่อน โหลดและไปวางบน Project ครับ
สร้าง GUI Skin ขึ้นมาใหม่
ตั้งค่า Label ปรกติแล้วลาก Gui Skin ของเราไปวางที่ Object Gamesystem เลยครับ
ต่อมาคือเงื่อนไขการได้คะแนน และ เสียชีวิต
function GetItems(){ score=score+1; timeRemaining += timeExtension; } function CrashMonster(){ lifes = lifes -1; timeRemaining -= timeDeduction; }
ให้เราสร้าง Prefabs ใหม่ขึ้นมาชื่อว่า Items และ Rock ครับ
ใช้ Asset จาก Terrain Assets อย่างก้อนหินมาประกอบกันให้เป็น กำแพงกั้นไม่สูงมากพอให้ตัวละครกระโดดหลบได้ แล้วใส่ Box Colider ลงไปครับ
ลาก Rocks ที่เราสร้างบน Hierarchy ไปวางบน Prefabs ชื่อ Rocks
สร้าง Javascript ขึ้นมาใหม่ชื่อว่า Flying.js ครับ ใส่ code ต่อไปนี้ ไม่มีอะไรมากแค่เป็นการบังคับให้วัตถุที่เป็น Prefabs ลอยเข้าหากล้องครับ
#pragma strict public var objectSpeed : float = -0.5f; var SecondsUntilDestroy : float = 7; private var startTime : float; function Start(){ startTime = Time.time; } function Update () { transform.Translate(0, 0, objectSpeed); } function FixedUpdate () { if (Time.time - startTime >= SecondsUntilDestroy) { Destroy(this.gameObject); } }
เช่นเดียวครับทำ เหรียญขึ้นมา แล้ว โคลนตัว Prefabs วิธีเดียวกันใส่ Javascript ไฟล์ใหม่ชื่อว่า Coinflying.js
#pragma strict public var objectSpeed : float = -0.5f; var SecondsUntilDestroy : float = 7; private var startTime : float; function Start(){ startTime = Time.time; } function Update () { objectSpeed = Random.Range(-0.03f,-0.5f); Debug.Log("objectSpeed:"+objectSpeed); transform.Translate(0, 0, objectSpeed); } function FixedUpdate () { if (Time.time - startTime >= SecondsUntilDestroy) { Destroy(this.gameObject); } }
เช่นกันสร้างไฟล์ Javascript ชื่อ TreeFlying.js ขึ้นมาใส่ code
#pragma strict public var objectSpeed : float = -0.2f; var SecondsUntilDestroy : float = 10; private var startTime : float; function Start(){ startTime = Time.time; } function Update () { transform.Translate(0, 0, objectSpeed); } function FixedUpdate () { if (Time.time - startTime >= SecondsUntilDestroy) { Destroy(this.gameObject); } }
ทำการโคลน Prefabs
เพิ่ม ไฟล์ Javascript ใหม่เข้าไปใน Gamesystem ครับชื่อ
#pragma strict public var monster : GameObject; public var items : GameObject; public var trees : GameObject; var timeElapsed : float = 0; var ItemCycle : float = 0.5f; public var ItemPowerup : boolean = true; function Start(){ } function Update () { var pos : Vector3; ItemCycle = Random.Range(0.9f,4f); timeElapsed += Time.deltaTime; if(timeElapsed > ItemCycle) { var temp : GameObject; var temp_tree : GameObject; if(ItemPowerup) { temp = Instantiate(items); pos = temp.transform.position; temp.transform.position = new Vector3(pos.x, Random.Range(-0.5,1), pos.z); }else{ temp = Instantiate(monster); pos = temp.transform.position; temp.transform.position = new Vector3(pos.x, pos.y, pos.z); } temp_tree = Instantiate(trees); pos = temp_tree.transform.position; temp_tree.transform.position = new Vector3(pos.x, pos.y, pos.z); timeElapsed -= ItemCycle; ItemPowerup = !ItemPowerup; } }
เพื่อไว้สำหรับสุ่มการปรากฏของวัตถุที่จะลอยเข้าหากล้อง แล้วลาก Prefabs ทั้งหมดไปใส่ใน Gamesystem Object ครับ
กลับไปที่ตัวละครครับ สร้างไฟล์ Javascript ขึ้นมาชื่อว่า Collision.js ใส่ code ต่อไปนี้
#pragma strict public var logic : GamePlay; function Start () { } function Update () { } function OnTriggerEnter(c : Collider){ if(c.gameObject.name == "Items(Clone)") { //Items logic.GetItems(); Debug.Log("Items"); Destroy(c.gameObject); } else if(c.gameObject.name == "Rock(Clone)") { //Boxes logic.CrashMonster(); Debug.Log("Rock"); Destroy(c.gameObject); } }
เป็นการทดสอบว่าตัวละครของเรา ชนกับอะไร เช่นถ้าชน Items(Clone) ที่เป็น Prefabs ก็จะเพิ่มจำนวน Score หรือเหรียญ และ ถ้าชน Rock(Clone) ก็จะเป็นลดชีวิต โดยอ้างอิงไปที่ตัวแปร logic ที่เป็นการเรียกไฟล์ GamePlay.js ที่อยู่บน Gamesystem Object
public var logic : GamePlay;
ให้ลากเจ้า Game Object ชื่อ Gamesystem ไปวางบนช่อง logic ของ collision ครับตามภาพข้างล่าง
ทดสอบลองเล่นเกมดูครับ
ถ้าชนหินครบ 3 ครั้งก็ Game Over
การ Export ตัวเกมของเราไปเป็นไฟล์ apk สำหรับ Android ให้ไปที่เมนู File > Build Setting
เลือก Android ตั้งค่า Player Setting พวก Bundle ID สักหน่อยครับ ใส่ Icon ก็ได้
กด Build รอสักประมาณนึง ระบบจะถามถึง Path ของ Android SDK ถ้ายังไม่มีให้ไปติดตั้งครับถ้ามีแล้วก็น่าจะอยู่ที่
C:\Users\Administrator\AppData\Local\Android\sdk
รอสักหน่อยก็จะได้ไฟล์ Apk ออกมาแล้วครับเสียบสาย USB เข้าเครื่องติดตั้ง เกมเราได้เลย
ทดสอบการเล่น
เนื่องจากบทเรียนนี้คิดว่าเป็นบทเรียนที่คนจะอ่านต่อนั้นน่าจะมีพื้นฐานมาก่อนพอประมาณ จึงรวบรัด และทำขึ้นแค่ 1 ชั่วโมง ยังไงก่อนจะพัฒนาเกมให้ลองศึกษามาบทเรียนเบื้องต้นก่อนครับ ส่วนบทเรียนนี้เป็นเพียง Guide แบบลัดขั้นตอนไว้แนะนำดีกว่าครับ
Source Code: ติดต่อได้ที่ http://www.facebook.com/daydevthailand
แค่ Source code นะครับไม่มี Assets ให้อันที่จริง พิมพ์ตามก็ไม่มีปัญหาอะไรเลยมั้ง?