บทเรียนการพัฒนาเกม 3D ด้วย Unity3D กับตัวอย่างการเขียนระบบ AI ของศัตรูหรือ Enemy ให้วิ่งตามตัวละครเพื่อดำเนินการทำ Collision Detect ต่อยอดไป พร้อมตัวอย่าง
ก่อนที่เราจะศึกษาบทเรียนนี้แนะนำให้ไปศึกษาบทเรียนก่อนหน้าสำหรับ Unity 3D ก่อนที่นี่
https://www.daydev.com/category/developer/s11-game-development/%E0%B9%8Aunity-3d
รวมบทเรียนก่อนหน้านี้
- เริ่มต้นเขียนเกม 3D ด้วย Unity
- เขียนเกม 3D ด้วย Unity การจัดการ Game Object ในเกม
- เขียนเกม 3D ด้วย Unity เรียกใช้งาน Physics กับ Rigidbody
- เขียนเกม 3D ด้วย Unity ศึกษา Basic Collision Detection
- เขียนเกม Unity3D การใช้ Asset Store และการสร้าง Terrain
- การควบคุมมุมกล้องของเกมด้วย Mouse Movement บน Unity3D
- Unity 3D การ Movement ตัวละครทั้ง Run, Jump และ Attack
ตัวอย่างนี้เราจะสร้าง Project ใหม่และเป็นการทบทวน การควบคุมตัวละครไปด้วยดังนั้นเราจะอ้างอิงบทเรียนเรื่อง Movement character มาใช้ในตัวอย่างนี้
โดยตัวอย่างนี้ผมจะดาวน์โหลด Asset Store น่ารักๆ มาใช้คือเจ้านี่ครับ (ภาพด้านล่าง)ระหว่างที่เรากำลังวางแผนนั้นให้ไปออกแบบ Terrain และ Skybox ให้สวยงามก่อนก็ยังได้
นำตัวละครของเราวางลงไปยัง Scene View สร้าง Character Control ให้เรียบร้อย และทำ Character Controller ให้เสร็จครับตามบทเรียนที่แล้ว
คำสั่งของ Players.js ก็เหมือนเดิมครับ
#pragma strict var rotationSpeed : float = 10; var walkspeed : float= 7; var gravity : float = 50; private var yRot : float; var body : Transform; function Update () { var Controller : CharacterController = GetComponent(CharacterController); var vertical : Vector3 = transform.TransformDirection(Vector3.forward); var horizontal : Vector3 = transform.TransformDirection(Vector3.right); var height : Vector3 = transform.TransformDirection(Vector3.up); if(Input.GetKeyDown("space")){ PlayerJump(); } if(Input.GetMouseButtonDown(0)) animation.Play("Attack"); Debug.Log("Pressed Left click."); if(Input.GetMouseButtonDown(1)) Debug.Log("Pressed right click."); if(Input.GetMouseButtonDown(2)) Debug.Log("Pressed middle click."); if(Input.GetAxis("Vertical") || Input.GetAxis("Horizontal")){ animation.CrossFade("Walk",0.2); animation["Walk"].speed = walkspeed/10; Controller.Move((vertical *(walkspeed * Input.GetAxis("Vertical"))) * Time.deltaTime); Controller.Move((horizontal *(walkspeed * Input.GetAxis("Horizontal"))) * Time.deltaTime); }else{ animation.CrossFade("Wait",0.2); } if(Input.GetAxis("Mouse X")){ yRot += 10 * Input.GetAxis("Mouse X"); } transform.rotation = Quaternion.Euler(0, yRot, 0); //Controller.Move(height * -gravity * Time.deltaTime); Controller.Move(height * gravity * Time.deltaTime); } function LateUpdate(){ if(Input.GetAxis("Vertical") == 0){ if(Input.GetAxis("Horizontal") > 0){ body.localEulerAngles.y = 180; }else if(Input.GetAxis("Horizontal") < 0){ body.localEulerAngles.y = 0; } }else if(Input.GetAxis("Vertical") > 0){ if(Input.GetAxis("Horizontal") > 0){ body.localEulerAngles.y = 135; }else if(Input.GetAxis("Horizontal") < 0){ body.localEulerAngles.y = 45; } }else if(Input.GetAxis("Vertical") < 0){ if(Input.GetAxis("Horizontal") == 0){ body.localEulerAngles.y = -90; }else if(Input.GetAxis("Horizontal") > 0){ body.localEulerAngles.y = -135; }else if(Input.GetAxis("Horizontal") < 0){ body.localEulerAngles.y = -45; } } } function PlayerJump (){ gravity = 10; yield WaitForSeconds(0.5); gravity = -50; }
ต่อมานำ Models ของเจ้า Slime ไปวางบน SceneView ครับ
กำหนด Animation มันให้เรียบร้อย
ใส่ Character Controller ลงไปในตัว Slime เช่นกันครับ
การทำงานของ Enemy AI คือให้ระบบประมวลผลว่าถ้า ตัวละครของเราเข้าใกล้ ระยะที่มันเห็น มันจะวิ่งไล่ตัวละครของเราแบบเอาเป็นเอาตายครับ แต่ถ้าตัวละครวิ่งหนีเกินระยะการมองเห็นของมันมันก็จะหยุดนิ่งเข้าสู่ภาวะ Idle อยู่กับที่ ซึ่งการเขียน Javascript นั้นต้องเขียนไฟล์ EnemyAi.js ใหม่ขึ้นมาตามนี้ครับ
#pragma strict var Distance : float; var Target : Transform; var lookAtDistance = 25.0; var attackRange = 15.0; var moveSpeed = 2.0; var Damping = 6.0; function Update () { Distance = Vector3.Distance(Target.position, transform.position); if(Distance < lookAtDistance){ lookAt(); } if(Distance > lookAtDistance){ //Idle } if(Distance < attackRange){ attack(); } } function lookAt() { var rotation = Quaternion.LookRotation(Target.position - transform.position); transform.rotation = Quaternion.Slerp(transform.rotation, rotation, Time.deltaTime * Damping); } function attack() { transform.Translate(Vector3.forward * moveSpeed * Time.deltaTime); animation.Play("Attack"); }
อธิบายแบบง่ายๆ เพราะไล่ code ก็ไม่น่ายาก นั่นก็คือ สร้าง Distance ขึ้นมาเป็นระยะแบบเปิดlookAtDistance เป็นค่าที่ตัว Enemy จะเห็นเราตั้งไว้ที่ 25.0 ระยะให้เทียบกับ Scene View ทีหลัง เงื่อนไขคือถ้า Distance ที่เรา Follow อยู่วิ่งเข้ามาแล้วค่าน้อยกว่า attackRange ที่สร้างไว้คือ 15.0 จะเข้าเงื่อนไข if(Distance < attackRange) วิ่งไปที่ฟังก์ชัน Attack(); เพื่อไล่งับตัวละครของเราทันทีครับ ในฟังก์ชัน lookAt(); นั้นเป็นการบังคับให้ตัวศัตรูคอยคำนวณระยะของมันนิ่งๆ รอตัวละครเราไปเข้าใกล้
ก่อนจะทดสอบ Run เกมต้องใส่ค่าบางอย่างที่ ตัว Character Controller ของ Enemy ก่อนครับนั่นคือ Target ให้เราลากตัว Player ของเราจาก Hierarchy ไปใส่ได้เลย
เป็นการกำหนดให้ศัตรูไล่งับอะไรบางอย่างถ้าในเกมเราคือตัวละคร แต่ถ้าเรามี Slime สีแดงอีกตัว แล้วใส่ Target เป็น Slime สีแดง เจ้า ศัตรูเราจะไปไล่ Slime สีแดงแทนครับ
ทดสอบ Run เกมดู
วีดีโอ Preview ดูการทำงานของมัน
คงไม่ยากใช่ไหมครับกับบทเรียนนี้