บทเรียน Noob หน่อยครับเป็นการสร้างระบบ Melee Combat เบื้องต้นแบบง่ายๆ พร้อม Logic ของศัตรูหรือระบบ AI ในการเข้ามาต่อสู้ประชิดตัวกับเราด้วย Unity แบบง่ายครับ
ก่อนจะเริ่มต้นยังไงก็ไปศึกษาการเขียนเกมด้วย Unity ให้เสร็จสรรพก่อนนะครับจะได้ไม่งง และช่วงหลังเราจะเปลี่ยนจาก Javascript เป็น C# เป็นหลักครับ (อันที่จริงใช้ด้วยกันได้ เขียนเหมือนกันต่างแค่ตอนประกาศตัวแปร และบางคำสั่ง)
[บทเรียนย้อนหลัง: เขียนเกมด้วย Unity]
ให้ศึกษาบทเรียนก่อนหน้าก่อนเรื่องของการ สร้าง Animator Controller เบื้องต้นเกี่ยวกับ State ของการเคลื่อนไหว Animation ต่างๆ ครับ
ตัวอย่างนี้ผมจะสร้าง Character ของผมเองขึ้นมาก่อนคือ BoonMee แต่จะไม่ใช้ระบบ Animation แบบ Legacy แล้วจะเป็น Mecanim แทน
วางตัวละครของเราลงไปในฉากของ Scene View ครับ ทำการสร้าง Character Controller, Capsule Collider และ RigidBody ให้เรียบร้อยและกำหนดค่าตามตัวอย่างครับ ตามด้วยเอา Main Camera ไปไว้ใน Layer ของ Player บน Hierachy
ต่อมาให้ Select และทำการ Rigged ตัวโมเดลเป็น Humanoid แล้วสร้าง Animator Controller ขึ้นมา ตามบทเรียนก่อนหน้า
กำหนด Parameter ตามนี้ โดย State ทั้งหมดคือ ยืน->วิ่ง->กระโดด->โจมตี ตามภาพ
ตัวเส้น Transition นั้นมีการ เรียก Condition ตามชื่อตัวแปรครับ เช่น
Idle -> Run ก็ใช้ IsWalking = true และ Run -> Idle ก็ใช้ IsWalking = false
ส่วนของการตาย ก็ใช้ Idle -> Dying เลือก Death เช่นกัน การโจมตีก็ใช้ Bool เป็น Attack สำหรับการ ง้างค้อนโจมตี (คือการตั้ง Parameter ในบทเรียนนี้ มันเป็นอะไรที่ตั้งชื่อตัวแปรแบบ clean และง่ายๆ อีกทั้ง Make Sense สุดๆครับ ถ้ายังมีคนไม่เข้าใจ แล้วยังถามจุดนี้ ผมจะตอบว่า ให้ไปดูบทเรียนก่อนหน้า: เขียนเกม 3 มิติด้วย Unity การใช้ Animator Controller หรือไม่ก็คงต้องตอบว่า ไม่ต้องทำหรอก! ทำไปทำอย่างอื่นเถอะครับ)
ออกแบบ ฉากให้มีพื้น และ Plane เป็นน้ำทะเลตั้งชื่อ “Sea” บท Inspector
ตรวจสอบเรื่อง Trigger กันดีๆ นะครับ
สร้างไฟล์ C# ขึ้นมาครับ ชื่อว่า Player.cs ใช้กับตัวละครของเรา
using UnityEngine; using System.Collections; public class Player : MonoBehaviour { Animator anim; public float speed = 6.0F; public float jumpSpeed = 12.0F; public float gravity = 13.0F; private Vector3 moveDirection = Vector3.zero; public bool Running = false; public bool Jumping = false; public bool Death = false; public float rotationSpeed = 100.0F; void Start(){ anim = GetComponent <Animator> (); Time.timeScale = 1; } void Update() { CharacterController controller = GetComponent<CharacterController>(); if (controller.isGrounded) { moveDirection = new Vector3(Input.GetAxis("Horizontal"), 0, Input.GetAxis("Vertical")); moveDirection = transform.TransformDirection(moveDirection); moveDirection *= speed; if (Input.GetButton("Jump")){ moveDirection.y = jumpSpeed; Jumping = true; }else{ Jumping = false; } if (Input.GetKey((KeyCode.D)) || Input.GetKey("right") || Input.GetKey((KeyCode.A)) || Input.GetKey("left") || Input.GetKey((KeyCode.D)) || Input.GetKey("up") || Input.GetKey((KeyCode.W)) || Input.GetKey("down") || Input.GetKey((KeyCode.S)) ){ Running = true; }else{ Running = false; } if(Running == true){ //anim.SetTrigger("Running"); anim.SetBool ("IsWalking", true); }else{ anim.SetBool ("IsWalking", false); } if(Jumping == true){ //anim.SetTrigger("Running"); anim.SetBool ("IsJumping", true); }else{ anim.SetBool ("IsJumping", false); } } //Attack if (Input.GetMouseButtonDown (0)) { anim.SetBool ("Attack", true); //Debug.Log ("Attack"); } else { anim.SetBool ("Attack", false); } //Mouse movement float translation = Input.GetAxis("Vertical") * speed; float rotation = Input.GetAxis("Horizontal") * rotationSpeed; translation *= Time.deltaTime; rotation *= Time.deltaTime; transform.Translate(0, 0, translation); transform.Rotate(0, rotation, 0); moveDirection.y -= gravity * Time.deltaTime; controller.Move(moveDirection * Time.deltaTime); } void OnTriggerEnter(Collider theCollision){ if (theCollision.gameObject.name == "Sea") { Death = true; if (Death == true) { anim.SetTrigger ("Death"); } //Application.LoadLevel ("Stage1"); } else { Death = false; } } }
ลอง Run แล้ววิ่งตกน้ำดูสักที ถ้าตายแปลว่าโอเค ไม่ก็ลองวิ่งแล้วกระโดด หรือ คลิกเมาส์โจมตีครับ
ทีนี้ อาวุธที่อยู่มือขวานี่ ผมจะใช้วิธี Noob ซึ่งเทคนิคนี้แล้วแต่คน หรือแล้วแต่รูปทรงของ โมเดลนะครับ จะใช้ Mesh Collider ก็ได้ครับง่ายด้วยแต่พอศัตรูเดินมาชนเรายังไม่ทันกดตี มันโดนอาวุธเราก็ตายซะและ เลยใช้เทคนิดนี้ครับ
ออกแบบ Sphere Collider และ Set Trigger ให้มันเป็นแบบตัวอย่างในรูปครับ
ที่วางตำแหน่งนั้น ก็เพื่อให้มันหมุนเวลาเกิดการเหวี่ยงค้อนไปอยู่บนหัวของศัตรูทีละครั้งครับ เหมือน ค้อนเป็นแค่ การหลอก แต่ตัวที่ทำเลือดลดจริงๆ คือ Sphere Collider ครับ
ดูการเหวี่ยงนะครับ
ระหว่างที่ค้อนเหวี่ยง sphere จะถูกเด้งไปข้างหน้าข้างหลังไปโดนศัตรูตามจำนวนครั้งที่ทุบครับ
ต่อมาเรามาสร้าง ศัตรูดีกว่า วางโมเดลลงไปครับ สร้าง Character Controller, RigidBody, Capsule Collider เหมือนตัว Player ของเราได้เลย
ออกแบบ Animator controller ดังนี้ครับ เพื่อใช้กับ Mecanim ตามตัวอย่าง
สังเกต Parameter ดีๆ นะครับ คงจะไม่อธิบายแล้ว นำไปใช้สร้าง Transition กันได้เลย ตัวแปร Make Sense เข้าใจง่ายสุดๆ
สร้าง C# ไฟล์ขึ้นมาตั้งชื่อว่า Enemy.cs ครับ เขียนคำสั่งตามนี้
using UnityEngine; using System.Collections; public class Enemy : MonoBehaviour { public float Distance; public Transform Target; float lookAtDistance = 10.0f; float HitAtDistance = 1.0f; float attackRange = 5.0f; float moveSpeed = 3.0f; float Damping = 6.0f; public int Health = 30; Animator anim; public bool dead = false; // Use this for initialization void Start () { anim = GetComponent <Animator> (); } // Update is called once per frame void Update () { Distance = Vector3.Distance(Target.position, transform.position); if(Distance < lookAtDistance){ var rotation = Quaternion.LookRotation(Target.position - transform.position); transform.rotation = Quaternion.Slerp(transform.rotation, rotation, Time.deltaTime * Damping); } if (Distance > lookAtDistance) { //Idle anim.SetBool ("FoundPlayer", false); anim.SetBool ("AttackPlayer", false); } else { //Turn to Player var rotation = Quaternion.LookRotation(Target.position - transform.position); transform.rotation = Quaternion.Slerp(transform.rotation, rotation, Time.deltaTime * Damping); } if (Distance < (attackRange + 0.9) && Distance > 0.95f) { anim.SetBool ("FoundPlayer", true); transform.Translate (Vector3.forward * moveSpeed * Time.deltaTime); } else { anim.SetBool ("FoundPlayer", false); anim.SetBool ("AttackPlayer", true); } if (Distance <= 0.95f) { anim.SetBool ("AttackPlayer", true); } else { anim.SetBool ("AttackPlayer", false); } if (Health <= 0) { dead = true; } else { dead = false; } if (dead == true) { death(); } } void OnTriggerEnter(Collider theCollision){ if (theCollision.gameObject.name == "Weapon") { Health = Health-2; Debug.Log ("HP: "+Health); //Health } } void death(){ lookAtDistance = 0.0f; HitAtDistance = 0.0f; attackRange = 0.0f; anim.SetBool ("AttackPlayer", false); anim.SetBool ("FoundPlayer", false); anim.SetTrigger ("Died"); } }
เราจะสร้างให้ศัตรูมองตามเราก่อน เมื่อเราเข้าใกล้ระยะ 10.0f ตามตัวแปร lookAtdistance ลองเดินไประยะ 10.0f โดยประมาณครับ มันจะหันมามองเราแบบหลอนๆ
จ้องหลอนๆ
สังเกตในคำสั่งนี้จะเป็นการบอกให้ว่าถ้าเราเข้าใกล้มันเกินระยะโจมตี มันจะวิ่งมาหาเราแล้ว พักระยะไว้ 0.9f แล้วเปลียนสถานะเป็น Attack ใส่เราครับ
if (Distance < (attackRange + 0.9) && Distance > 0.95f) { anim.SetBool ("FoundPlayer", true); transform.Translate (Vector3.forward * moveSpeed * Time.deltaTime); } else { anim.SetBool ("FoundPlayer", false); anim.SetBool ("AttackPlayer", true); }
ผมได้เขียน ฟังก์ชัน OnTriggerEnter() ไว้ให้แล้วว่าถ้า ศัตรูถูก Sphere Collider ของเราฟาดเอาพลังมันจะลด ถ้าพลังหมด มันจะ Trigger Animator เป็น Died ครับ
void OnTriggerEnter(Collider theCollision){ if (theCollision.gameObject.name == "Weapon") { Health = Health-2; Debug.Log ("HP: "+Health); //Health } }
และ
void death(){ lookAtDistance = 0.0f; HitAtDistance = 0.0f; attackRange = 0.0f; anim.SetBool ("AttackPlayer", false); anim.SetBool ("FoundPlayer", false); anim.SetTrigger ("Died"); }
ลองคลิกเมาส์สู้กับมัน โจมตีไปเรื่อยๆ
หมีนอนตายแน่นิ่งละ
จบครับบทเรียนนี้ Noob พอไหม แต่ก็ใช้ได้ครับเอาไปประยุกต์ใช้ได้กับหลายๆ เกมได้อย่างสบายๆ และ Algorithm ต่างๆ ก็ประยุกต์จาก Document ของ Unity เท่านั้นส่วน Logic ก็คิดเองครับ ดังนั้นหากใครคิด Logic ได้ดีกว่าก็แลกเปลี่ยนความรู้ได้ครับ
Source Code: เนื่องจาก Asset ทั้งหลายผมมีการจ่ายเงินซื้อมาดังนั้นให้ไม่ได้ครับ รบกวนศึกษากันอย่างเดียวละกัน