2.2 - Máy trạng thái - FSM đơn giản |
Bài viết này, chúng ta sẽ cùng nghiên cứu các phương thức hoạt động cũng như tính năng chiến đấu của các xe tăng AI trong scene SimpleFSM. Bạn có thể tìm thấy scene này trong source code đã cung cấp ở bài viết AI 2.1.
Thiết lập waypoint
Tiếp theo, chúng ta sẽ đặt bốn object Cube vào các vị trí ngẫu nhiên,
đó chính là các điểm mốc mà xe của AI sẽ chạy đến theo thứ tự ngẫu nhiên, tên của những điểm này được gom chung lại thành WandarPoints.
WanderPoints |
Đây là thuộc tính của đối tượng
WanderPoint :
WanderPoint properties |
Một điều cần lưu ý ở đây, chính là việc
cần đặt tag cho những điểm đó với tên là WandarPoint. Chúng ta
sẽ liên kết các tag này lại khi chúng ta thiết lập các điểm mốc cho xe tăng AI. Như bạn
có thể thấy các thuộc tính của nó, một điểm mốc ở đây chỉ là một Cube Object đã bị vô hiệu hóa Mesh Renderer, và đối tượng Box Collider bị loại bỏ. Thậm
chí chúng ta có thể dùng một empty object cũng được, tất cả những gì chúng ta cần
từ một điểm mốc chính là vị trí cùng dữ liệu về sự biến đổi của nó. Nhưng chúng
ta đang dùng Cube objects ở đây, để chúng ta có thể dễ nhìn thấy các điểm mốc khi làm việc.
Lớp
FSM ảo
Tiếp theo, chúng ta sẽ thực thi một lớp
ảo, định nghĩa các phương thức xe tăng AI của địch phải thực thi.
Code trong file FSM.cs như sau :
using UnityEngine;
using System.Collections;
public class FSM : MonoBehaviour
{
//Player Transform
protected Transform playerTransform;
//Next destination position of the NPC Tank
protected Vector3 destPos;
//List of points for patrolling
protected GameObject[] pointList;
//Bullet shooting rate
protected float shootRate;
protected float elapsedTime;
//Tank Turret
public Transform turret { get; set; }
public Transform bulletSpawnPoint { get; set; }
protected virtual void Initialize() { }
protected virtual void FSMUpdate() { }
protected virtual void FSMFixedUpdate() { }
// Use this for initialization
void Start ()
{
Initialize();
}
// Update is called once per frame
void Update ()
{
FSMUpdate();
}
void FixedUpdate()
{
FSMFixedUpdate();
}
}
Tất cả các xe tăng của địch cần biết vị
trí xe tăng của người chơi, điểm đến tiếp theo của chúng, và danh sách của các điểm mốc để chọn lựa hướng đi, khi chúng đang tuần tra. Một khi xe tăng của người chơi
bước vào phạm vi, chúng sẽ xoay đối tượng turret (tháp pháo) và rồi bắt đầu bắn từ điểm viên đạn
xuất phát với tốc độ bắn của chúng.
Các lớp kế thừa cũng cần thực thi ba hàm : Initialize, FSMUpdate, và FSMFixedUpdate. Đó chính là các lớp
ảo, mà xe tăng AI của chúng ta đang thực thi.
Xe
tăng AI của địch
Bây giờ hãy nhìn vào đoạn code thực sự
cho các xe tăng AI của chúng ta. Lớp SimpleFSM kế thừa từ lớp ảo FSM.
Đoạn code nằm trong file SimpleFSM.cs như
sau:
using UnityEngine;
using System.Collections;
public class SimpleFSM : FSM
{
public enum FSMState
{
None,
Patrol,
Chase,
Attack,
Dead,
}
//Current state that the NPC is reaching
public FSMState curState;
//Speed of the tank
private float curSpeed;
//Tank Rotation Speed
private float curRotSpeed;
//Bullet
public GameObject Bullet;
//Whether the NPC is destroyed or not
private bool bDead;
private int health;
Ở đây, chúng ta đang khai báo vài biến
mới. Xe tăng AI của chúng ta sẽ có bốn trạng thái khác nhau : Patrol,
Chase, Attack, và Dead. Cơ bản chúng ta đang thực thi FSM được miêu tả như
trong ví dụ ở AI 1.1 - Giới thiệu về AI.
FSM của xe tăng AI của
địch
Trong hàm Initialize, chúng ta
thiết lập các thuộc tính của xe tăng AI bằng các giá trị mặc định. Rồi chúng ta
lưu lại các vị trí của các điểm mốc trong các biến cục bộ. Chúng ta lấy các điểm mốc đó từ scene bằng cách dùng hàm FindGameObjectsWithTag, cố tìm các đối
tượng đó bằng tag WandarPoint.
//Initialize the Finite state machine for the NPC tank
protected override void Initialize ()
{
curState = FSMState.Patrol;
curSpeed = 150.0f;
curRotSpeed = 2.0f;
bDead = false;
elapsedTime = 0.0f;
shootRate = 3.0f;
health = 100;
//Get the list of points
pointList =
GameObject.FindGameObjectsWithTag("WandarPoint");
//Set Random destination point first
FindNextPoint();
//Get the target enemy(Player)
GameObject objPlayer =
GameObject.FindGameObjectWithTag("Player");
playerTransform = objPlayer.transform;
if (!playerTransform)
print("Player doesn't exist.. Please add one "+
"with Tag named 'Player'");
//Get the turret of the tank
turret = gameObject.transform.GetChild(0).transform;
bulletSpawnPoint = turret.GetChild(0).transform;
}
Hàm update của chúng ta được thực thi mỗi khung hình như sau :
//Update each frame
protected override void FSMUpdate()
{
switch (curState)
{
case FSMState.Patrol: UpdatePatrolState(); break;
case FSMState.Chase: UpdateChaseState(); break;
case FSMState.Attack: UpdateAttackState(); break;
case FSMState.Dead: UpdateDeadState(); break;
}
//Update the time
elapsedTime += Time.deltaTime;
//Go to dead state is no health left
if (health <= 0)
curState = FSMState.Dead;
}
Chúng ta kiểm tra tình trạng hiện hành, và
rồi gọi hàm trạng thái thích hợp. Một khi đối tượng health trở về 0 hoặc
âm, chúng ta cho xe tăng trở về trạng thái Dead.
Trạng
thái tuần tra (Patrol)
Khi xe tăng của chúng ta đang ở trạng thái Patrol, chúng ta kiểm tra nó có hay không đã đến được điểm đích. Nếu đúng, nó sẽ tìm ra điểm đích tiếp theo. Cơ bản phương thức FindNextPoint chọn điểm đích tiếp theo một cách ngẫu nhiên từ các điểm mốc xác định. Nếu vẫn nằm trên con đường đến điểm đích hiện tại, nó sẽ kiểm tra khoảng cách đến xe tăng của người chơi. Nếu xe tăng người chơi vẫn còn ở trong phạm vị (ở đây là 300), nó sẽ đổi sang trạng thái Chase. Các đoạn code còn lại chỉ là việc chuyển hướng và di chuyển về trước của xe tăng.
protected void UpdatePatrolState()
{
//Find another random patrol point if the current
//point is reached
if (Vector3.Distance(transform.position, destPos) <=
100.0f)
{
print("Reached to the destination point\n"+
"calculating the next point");
FindNextPoint();
}
//Check the distance with player tank
//When the distance is near, transition to chase state
else if (Vector3.Distance(transform.position,
playerTransform.position) <= 300.0f)
{
print("Switch to Chase Position");
curState = FSMState.Chase;
}
//Rotate to the target point
Quaternion targetRotation =
Quaternion.LookRotation(destPos
- transform.position);
transform.rotation =
Quaternion.Slerp(transform.rotation,
targetRotation, Time.deltaTime * curRotSpeed);
//Go Forward
transform.Translate(Vector3.forward * Time.deltaTime *
curSpeed);
}
protected void FindNextPoint()
{
print("Finding next point");
int rndIndex = Random.Range(0, pointList.Length);
float rndRadius = 10.0f;
Vector3 rndPosition = Vector3.zero;
destPos = pointList[rndIndex].transform.position +
rndPosition;
//Check Range to decide the random point
//as the same as before
if (IsInCurrentRange(destPos))
{
rndPosition = new Vector3(Random.Range(-rndRadius,
rndRadius), 0.0f, Random.Range(-rndRadius,
rndRadius));
destPos = pointList[rndIndex].transform.position +
rndPosition;
}
}
protected bool IsInCurrentRange(Vector3 pos)
{
float xPos = Mathf.Abs(pos.x - transform.position.x);
float zPos = Mathf.Abs(pos.z - transform.position.z);
if (xPos <= 50 && zPos <= 50)
return true;
return false;
}
Trạng
thái rượt đuổi (Chase)
Cũng tương tự, đầu tiên xe AI sẽ kiểm tra khoảng cách đến xe tăng người chơi. Nếu đủ gần, nó sẽ chuyển sang trạng thái Attack. Nếu xe tăng người chơi đã đi xa quá, nó sẽ trở lại trạng thái Patrol.
protected void UpdateChaseState()
{
//Set the target position as the player position
destPos = playerTransform.position;
//Check the distance with player tank When
//the distance is near, transition to attack state
float dist = Vector3.Distance(transform.position,
playerTransform.position);
if (dist <= 200.0f)
{
curState = FSMState.Attack;
}
//Go back to patrol is it become too far
else if (dist >= 300.0f)
{
curState = FSMState.Patrol;
}
//Go Forward
transform.Translate(Vector3.forward * Time.deltaTime *
curSpeed);
}
Trạng
thái tấn công (Attack)
Nếu xe tăng người chơi đủ gần để tấn công xe tăng AI của chúng ta, chúng sẽ xoay đối tượng tháp pháo turret về phía xe tăng của người chơi, và rồi nổ súng. Nó sẽ trở lại trạng thái Patrol, nếu xe tăng người chơi đã ra khỏi phạm vi.
protected void UpdateAttackState()
{
//Set the target position as the player position
destPos = playerTransform.position;
//Check the distance with the player tank
float dist = Vector3.Distance(transform.position,
playerTransform.position);
if (dist >= 200.0f && dist < 300.0f)
{
//Rotate to the target point
Quaternion targetRotation =
Quaternion.LookRotation(destPos -
transform.position);
transform.rotation = Quaternion.Slerp(
transform.rotation, targetRotation,
Time.deltaTime * curRotSpeed);
//Go Forward
transform.Translate(Vector3.forward *
Time.deltaTime * curSpeed);
curState = FSMState.Attack;
}
//Transition to patrol is the tank become too far
else if (dist >= 300.0f)
{
curState = FSMState.Patrol;
}
//Always Turn the turret towards the player
Quaternion turretRotation =
Quaternion.LookRotation(destPos
- turret.position);
turret.rotation =
Quaternion.Slerp(turret.rotation, turretRotation,
Time.deltaTime * curRotSpeed);
//Shoot the bullets
ShootBullet();
}
private void ShootBullet()
{
if (elapsedTime >= shootRate)
{
//Shoot the bullet
Instantiate(Bullet, bulletSpawnPoint.position,
bulletSpawnPoint.rotation);
elapsedTime = 0.0f;
}
}
Trạng
thái chết (Dead)
Nếu xe tăng đã đến trạng thái Dead, chúng ta sẽ bật trạng thái chết và làm nó nổ tung.
protected void UpdateDeadState()
{
//Show the dead animation with some physics effects
if (!bDead)
{
bDead = true;
Explode();
}
}
Nổ tung (Explode)
Đây là một hàm sẽ mang đến một hiệu ứng nổ đẹp. Chúng ta chỉ áp dụng một ExplosionForce vào thành phần rigidbody với các hướng ngẫu nhiên, có trong đoạn code sau đây:
protected void Explode()
{
float rndX = Random.Range(10.0f, 30.0f);
float rndZ = Random.Range(10.0f, 30.0f);
for (int i = 0; i < 3; i++)
{
rigidbody.AddExplosionForce(10000.0f,
transform.position - new Vector3(rndX, 10.0f,
rndZ), 40.0f, 10.0f);
rigidbody.velocity = transform.TransformDirection(
new Vector3(rndX, 20.0f, rndZ));
}
Destroy(gameObject, 1.5f);
}
No comments:
Post a Comment