บทเรียนการพัฒนาเกม 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 ดูการทำงานของมัน
คงไม่ยากใช่ไหมครับกับบทเรียนนี้




