Thứ Tư, 9 tháng 4, 2014

Project RPG BÀI 14B. DỌN DẸP FILE SKILL


Thêm chú thích và các công dụng để dễ hiểu rõ hơn phần nào về các tính năng của script Skill...


Double click vào file Skill và xóa tất cả đi, chèn lại đoạn code sau vào:

/// <summary>
/// Skill.cs
///
/// This class contain all the extra function that are needed for a skill
/// </summary>
public class Skill : ModifiedStat {
    private bool _known;            //a boolean variable to toggle if a character knowns a skill

    /// <summary>
    /// Initializes a new instance of the <see cref="Skill"/> class.
    /// </summary>
    public Skill(){
        UnityEngine.Debug.Log ("Skill created");
        _known = false;
        ExpToLevel = 25;
        LevelModifier = 1.1f;
    }

    /// <summary>
    /// Gets or sets a value indicating whether this <see cref="Skill"/> is known.
    /// </summary>
    /// <value><c>true</c> if known; otherwise, <c>false</c>.</value>
    public bool Known{
        get{ return _known; }
        set{ _known = value; }
    }
}

/// <summary>
/// This enumeration is just a list of skills the player can learn
/// </summary>
public enum SkillName{
    Melee_Offence,
    Melee_Defence,
    Ranged_Offence,
    Ranged_Defence,
    Magic_Offence,
    Magic_Defence
}

Project RPG BÀI 14A. DỌN DẸP FILE VITAL


Thêm chú thích và các công dụng để dễ hiểu rõ hơn phần nào về các tính năng của script Vital...


Double click vào file Vital và xóa tất cả đi, chèn lại đoạn script sau vào:

/// <summary>
/// Vital.cs
/// This class contain all the extra function for a character vitals
/// </summary>

public class Vital : ModifiedStat {
    private int _curValue;                //this is the current value of this vital

    /// <summary>
    /// Initializes a new instance of the <see cref="Vital"/> class.
    /// </summary>
    public Vital(){
        UnityEngine.Debug.Log ("Vital Created");
        _curValue = 0;
        ExpToLevel = 40;
        LevelModifier = 1.1f;
    }

    /// <summary>
    /// When getting the _curValue, make sure that it is not greater than our AdjustedBaseValue
    /// If it is, make it the same as our AdjustedBaseValue
    /// </summary>
    /// <value>The current value.</value>
    public int CurValue{
        get{
            if(_curValue > AdjustedBaseValue)
                _curValue = AdjustedBaseValue;
           
            return _curValue;
        }
        set{ _curValue = value; }
    }
}

/// <summary>
/// This enumerations is just a list of the vitals our character will have
/// </summary>
public enum VitalName{
    Health,
    Energy,
    Mana
}

Thứ Ba, 8 tháng 4, 2014

DEVGAME 1.5 - CẤU TRÚC CỦA MỘT TỆP MÃ - BIẾN (Phần 2)

 

Ở bài viết trước, chúng ta đã cài đặt biến kiểu số nguyên vào script SimpleRotate để điều chỉnh tốc độ quay của khối hộp. Bài viết này, chúng ta sẽ tiếp tục cài đặt biến với các kiểu dữ liệu khác nhau.

Còn nhiều thứ khác bạn cần phải tìm hiểu về biến bởi nó có vài kiểu khác nhau. Trong script SimpleRotate, chúng ta đã cài đặt tốc độ quay của khối hộp thông qua biến myDegrees = 50, chương trình sẽ mặc định biến này thuộc kiểu dữ liệu int ( số nguyên, không có phần thập phân ). Nếu bạn muốn sử dụng số thực ( số có phần thập phân ) như 50.0 thì bạn phải đổi nó sang kiểu float ( kiểu số thực ). Nhiều biến bắt buộc phụ thuộc vào cách thiết lập kiểu dữ liệu của chúng để xử lý cho các công việc tiếp theo.

Hãy mở file SimpleMove lên và thêm vài dòng script vào để tiến hành tìm hiểu.

B1. Double click vào file java SimpleMove ở thẻ Project và chèn thêm các dòng được tô đỏ như sau:

#pragma strict
var mySpeed : float;
var someString : String = "This is a test";
var someSetting : boolean = true;
var someObject : GameObject;

function Update () {
    //transform.Rotate(0,50* Time.deltaTime,0);
    transform.Translate(2* Time.deltaTime, 0,0);
}

Hãy cùng nhìn lại đoạn code vừa được thêm vào:
  • var mySpeed : float; : Định nghĩa kiểu float ( số thực ) cho biến mySpeed nhưng cài đặt giá trị.
  • var someString : String = "This is a test"; : Định nghĩa kiểu String ( chuỗi ) và cài đặt biến này mang giá trị là các ký tự nằm trong dấu ngoặc kép " ". Dạng chuỗi thường được đặt giữa dấu ngoặc kép " " và có màu đặt trưng của kiểu chuỗi trong trình biên tập code của Unity.
    >>Chú ý: Nếu bạn copy và paste văn bản kể cả dấu ngoặc kép từ trình soạn thảo văn bản, các ký tự có thể bị thay đổi, khi đó chương trình biên tập code sẽ báo lỗi, buộc bạn phải tự nhập lại đoạn văn bản.
      • var someSetting : boolean = true; : Định nghĩa biến kiểu Boolean ( luận lý, chỉ mang giá trị True hoặc False) và cài đặt nó mang giá trị True. Ở thẻ Inspector, biến Boolean được hiển thị dưới dạng kiểu hộp đánh dấu checkbox.

      B2. Lưu đoạn script lại và qua Unity Editor, nhấp chọn Cube Parent ở thẻ Hierarchy, qua thẻ Inspector, nhìn vào script SimpleMove đã được gắn ở bài viết trước và xem sự khác biệt.

        >>Mẹo: Ngay cả khi kiểu dữ liệu của biến là bất kỳ kiểu nào cũng được và không bắt buộc phải định nghĩa. Nếu bạn quyết định định nghĩa, thì bạn nên save lại để tránh việc chương trình tự động gán kiểu dữ liệu cho biến. Nếu bạn lập trình trên nền tảng IOS hay Android, bạn nên định nghĩa rõ ràng. Trong loạt bài viết về DevGame này, mình sẽ sử dụng cả hai cách trên.

          Chú ý rằng, phía trước tất cả tên biến điều có từ var. Trong Unity, var được định nghĩa cho các biến public ( biến toàn cục ). Nếu một hàm hay một biến được khai báo là public thì những lớp khác có thể gọi sử dụng trực tiếp được. Ngược lại nếu hàm hay biến đó được khai báo là private thì chỉ những thành viên trong lớp đó mới có thể sử dụng được thôi.

          B3. Bây giờ thử thêm từ private vào phía trước var someSetting và save đoạn script lại.



          B4. Nhấp chọn Cube Parent ở thẻ Hierarchy, qua thẻ Inspector và để ý rằng biến mang kiểu luận lý Some Setting đã biến mất.


          Ngoài ra, trong đoạn script vừa được thêm vào còn có:
          • varsomeObject : GameObject; : Kiểu dữ liệu xuất hiện hầu hết trong các ngôn ngữ lập trình, Unity cho phép bạn định nghĩa biến của bất kể vật thể cấp cao như GameObject, hay thấp hơn là các Transform ( xoay, kéo, co dãn đối tượng) và thậm chí là các loại đối tượng khác như Camera hoặc Light ( đèn ).  

          Một đoạn script có thể truy cập đến các biến hay hàm của đối tượng khác phải được định nghĩa như một giá trị của biến. Tuy nhiên, tên thuộc tính của vật thể là duy nhất, không được trùng nên phải đặt sao cho dễ hình dung công dụng nhất.

          B6. Nhấp chọn Cube Parent ở thẻ Hierarchy. Kéo thả Camera hoặc Directional Light ở thẻ Hierarchy vào biến Some Object ở thẻ Inspector.


          B7. Lưu đoạn script và qua Unity Editor lưu project của bạn lại với tên Demo.



          Thứ Năm, 3 tháng 4, 2014

          ProjectGame 1.1 - Platform Game Đơn Giản - Tạo Camera và Level


          Vẫn còn nhiều platform game kinh điển tồn tại trong kí ức mọi người như Mario, Sonic, Megaman... Platform game là dạng game với động tác nhảy và yếu tố trọng lực là chính, ngoài ra các yếu tố phụ khác đã góp phần làm nên tên tuổi của các platform game kinh điển trên. Bài viết này được chia ra làm 6 phần nhỏ. Tất cả đều là nền tảng 2D, để các bạn dễ hiểu hơn những kiến thức về lập trình game và dễ đi sâu hơn vào các project game sau trên nền tảng 3D.


          Mục tiêu

          >> 1.1 - Tạo Camera và Level
          1.2 - Tạo nhân vật 2D
          1.3 - Tạo class (lớp) CharacterController và SpriteManager
          1.4 - Nhảy và yếu tố vật lý
          1.5 - Tạo chìa khóa và mở cửa
          1.6 - Chèn âm thanh và nút Replay

          Tài nguyên
          GameProject1



            PG 1.1 - Platform Game đơn giản: Tạo Camera và Level

          B1. Vào File | New Project để tạo Project mới với tên là SimplePlatform như hình sau:


          B2. Giải nén file GameProject1 vừa tải ở trên và kéo thả thư mục Game Project 1 vào thẻ Project như hình sau:


          B3. Tại thẻ Project, nhấp chuột chọn Plane trong thư mục Game Project 1 | FBX và qua thẻ Inspector, điều chỉnh Scale Factor = 1. Sau đó nhấn Apply để hoàn tất việc điều chỉnh.


          B4. Kéo thả Plane vào thẻ Hierarchy và đổi tên thành Background.


          B5. Nhấp chọn Background trong thẻ Hierarchy, qua thẻ Inspector và điều chỉnh như hình sau:


          B6. Vẫn đang chọn Background, ở thẻ Inspector, nhấp phải vào Animator và chọn Remove Component.


          B7. Tại thẻ Project, nhấp chọn Create | Material và đặt tên là M_Background.


          B8. Nhấp chọn M_Background, qua thẻ Inspector, nhấp vào chữ Select và chọn Background như hình sau.


          B9. Vẫn đang chọn M_Background, điều chỉnh như sau:
          • Shader: Diffuse
          • Main Color: R = 164, G = 219, B = 225, A = 255
          • Base (RGB): Tiling X =  2, Y = 2; Offset: X = 0, Y = 0



          B10. Nhấp chọn Background ở thẻ Hierarchy, qua thẻ Inspector, tại mục Mesh Renderer | Material điều chỉnh Element 0 là M_Background.


          B11. Vào Edit | Project Setting | Tags & Layers và nhập vào các giá trị như sau:


          B12. Nhấp chọn Background ở thẻ Hierarchy, qua thẻ Inspector, chọn Tag = Background và Layer = Background như hình sau:


          B13. Tại thẻ Hierarchy, nhấp chọn Main Camera, qua thẻ Inspector, điều chỉnh Projection thành Perspective và Position như trong hình sau:


          B14. Tại thẻ Hierarchy, nhấp chọn Create | Directional Light và qua thẻ Inspector điều chỉnh như sau:


          B15. Vào GameObject | Create Empty và đổi tên thành Level. Qua thẻ Inspector, điều chỉnh các thông số như hình sau:

          B16. Tại thẻ Hierarchy, nhấp chọn nút Create | Cube và đổi tên thành Floor. Đặt tag là Floor, layer là Level như hình sau:

          B17. Thực hiện lại bước trên một lần nữa với tên cube là Wall, tag là Wall và layer là Level.


          B18. Tại thẻ Project, nhấp chọn Create | Material và đặt tên là M_Level.


          B19. Nhấp chọn M_Level vừa tạo và qua thẻ Inspector, bấm vào ô điều chỉnh màu và chỉnh các thông số màu sắc là R = 150, G = 230, B = 225, A = 255.


          B20. Kéo thả M_Level ở thẻ Project vào Wall và Floor ở thẻ Hierarchy.

           


          B21. Tại thẻ Hierarchy, kéo thả Wall và Floor vào Level như hình sau:


          B22. Tại thẻ Hierarchy, nhấp chọn Wall và ấn Ctrl + D 6 lần để tạo thêm 6 bản sao nữa. Nhấp chọn Floor và ấn Ctrl + D 2 lần để tạo ra thêm 2 bản sao nữa. Và đặt tên như hình dưới đây:


          B23. Tại thẻ Hierarchy, điều chỉnh các bản sao vừa tạo ở bước trên với tên và thông số ở thẻ Inspector như dưới đây:
          • Floor 1 : Position: x = -4, y = -9, z = 0 - Scale: x = 125, y = 15, z = 1
          • Floor 2: Position: x = -6,  y = 5, z = 0 - Scale: x = 32, y = 1, z = 1
          • Floor 3: Position: x = -25, y = 12, z = 0 - Scale: x = 19.5, y = 1, z = 1
          • Floor 4: Position: x = 14, y = 12, z = 0 - Scale: x = 20, y = 1, z = 1
          • Floor 5: Position: x = -7, y = 9, z = 0 - Scale: x = 9, y = 1, z = 1
          • Floor 6: Position: x = -31, y = 1, z = 0 - Scale: x = 6, y = 1, z = 1
          • Floor 7: Position: x = 21, y = 2, z = 0 - Scale: x = 10, y = 1, z = 1

          • Wall 1: Position: x = -49, y = 17, z = 0 - Scale: x = 36, y = 40, z = 1
          • Wall 2: Position: x = 42, y = 17, z = 0 - Scale: x = 38, y = 39, z = 1
          • Wall 3: Position: x = -7, y = 23, z = 0 - Scale: x: 1, y = 36, z = 1

          B24. Nhấn Ctrl + S để save scene này lại với tên là SimplePlatform.



          Hoàn thành mục tiêu 1.1

          Vừa rồi chúng ta đã tạo Background đặt phía sau Level, Level bao gồm các khối gạch được xếp theo tầng (Floor) và tường (Wall) để nhân vật có thể di chuyển lên các tầng và giới hạn phạm vi di chuyển của nhân vật bởi các bức tường.





          Ở đây, chúng ta điều chỉnh Main Camera ở chế độ Perspective để tạo chiều sâu cho game, khiến góc nhìn đẹp mắt hơn.


          Kiến thức bổ sung
          Phần Projection ở Main Camera có 2 giá trị là Orthographic (Góc nhìn trực quan) và Perspective (Góc nhìn phối cảnh). Ở chế độ Orthographic, vật thể sẽ không bị kéo dãn bởi khoảng cách của camera. Bởi vậy trong màn hình game, chúng ta chỉ thấy được 1 mặt của vật thể. Ở chế độ Perspective, chúng ta sẽ nhìn thấy được các mặt bên của vật thể do có chiều sâu, tựa như góc nhìn thực tế của mắt người bởi luật phối cảnh.


          Thứ Tư, 26 tháng 3, 2014

          COOKBOOK - HIỂN THỊ ĐỒNG HỒ ĐẾM NGƯỢC BẰNG GUI LABEL



          Trong nhiều game quy định thời gian làm nhiệm vụ hay kiếm thêm điểm thưởng bằng cách quy định một khoảng thời gian nhất định nào đó. Trong bài viết này, mình sẽ hướng dẫn các bạn tạo đồng hồ đếm ngược đơn giản bằng GUI.


          Cách để làm

          B1. Tạo một project mới và đặt tên là Coundown như hình sau:


          B2. Tại thẻ Project, nhấp chọn Create | C# Script và đặt tên là CountdownTimer. Double click vào file C# vừa tạo và chèn đoạn code sau vào rồi save file C# lại:

          // file: CountdownTimer.cs
          using UnityEngine;
          using System.Collections;

          public class CountdownTimer : MonoBehaviour {
              private float secondsLeft = 10f;
             
              private void OnGUI(){
                  if( secondsLeft > 0)
                      GUILayout.Label("Countdown seconds remaining = " + (int)secondsLeft     );
                  else
                      GUILayout.Label("Countdown has finished");
                 
              }
             
              private void Update(){
                  secondsLeft -= Time.deltaTime;
              }
          }


          B3. Kéo thả file C# vừa tạo vào Main Camera ở thẻ Hierarchy.


          B4. Ấn nút Play để kiểm tra thành quả.


          Nguyên lý làm việc

          Với mỗi khung hình được load, biến secondsLeft sẽ bị trừ dần bởi hàm thời gian Time.deltaTime. Khi thời gian nhỏ hơn 0, đồng hồ sẽ ngừng đếm. Giá trị của biến có thể được ép sang kiểu số nguyên một cách dễ dàng bằng cách thêm (int) vào trước tên biến.

          Thứ Ba, 11 tháng 3, 2014

          COOKBOOK - HIỂN THỊ RADAR

           

          Radar hiển thị các đối tượng khác liên quan đến người chơi, thông thường radar sẽ là hình tròn và tâm của nó là nơi player đang đứng, và mỗi chấm tròn trên radar tượng trưng cho các đối tượng khác. Radar tinh vi sẽ hiển thị các chấm tròn khác nhau với từng loại đối tượng.


          Chuẩn bị
          Radar.rar

          Cách để làm

          B1. Tại thẻ Hierarchy, nhấp chọn nút Create | Directional Light. Qua thẻ Inspector, điều chỉnh như sau:



          B2. Tiếp tục nhấp chọn nút Create | Terrain và điều chỉnh ở thẻ Inspector như sau:


          B3. Vào Assets | Import Package | Character Controller và nhấp nút Import. Tại thẻ Project, kéo thả 3rd Person Controller vào thẻ Hierarchy. Qua thẻ Inspector, điều chỉnh Position thành X = 0, Y = 1, Z = 0.

           

          B4.  Tại thẻ Hierarchy, nhấp chọn nút Create | Cube. Qua thẻ Inspector, điều chỉnh Position thành X = 0, Y = 1, Z = 5.


          B5. Vẫn đang chọn Cube, ở thẻ Inspector, bấm chọn thanh sổ phía sau Tag và chọn Add Tag. Nhập vào Size Element 0 là cube.



          B6. Nhấp chọn cube ở thẻ Hierarchy, qua thẻ Inspector, đặt tag là cube.


          B7. Tại thẻ Hierarchy, nhấp chọn Create | Cube. Qua thẻ Inspector, điều chỉnh Position thành X = -5, Y = 1, Z = 0. Và đặt tag là cube.


          B8. Tại thẻ Project, nhấp chọn nút Create | C# Script và đặt tên là Radar. Chèn đoạn code sau vào.

          using UnityEngine;
          using System.Collections;

          public class Radar : MonoBehaviour
          {
              const float MAX_DISTANCE = 20f;
              const int RADAR_SIZE = 128;
             
              public Transform playerController;
              public Texture radarBackground;
              public Texture targetBlip;
             
              private void OnGUI()
              {
                  // background displaying top left in square of 128 pixels
                  Rect radarBackgroundRect = new Rect(0,0, RADAR_SIZE, RADAR_SIZE);
                  GUI.DrawTexture(radarBackgroundRect,radarBackground);
                 
                  // find all 'cube' tagged objects
                  GameObject[] cubeGOArray = GameObject.FindGameObjectsWithTag("cube");
                 
                  // draw blips for all within distance
                  Vector3 playerPos = playerController.transform.position;
                  foreach (GameObject cubeGO in cubeGOArray) 
                  {
                      Vector3 targetPos = cubeGO.transform.position;
                      float distanceToTarget = Vector3.Distance(targetPos,playerPos);
                      if( (distanceToTarget <= MAX_DISTANCE) )
                          DrawBlip(playerPos, targetPos, distanceToTarget);
                  }
              }
             
              private void DrawBlip(Vector3 playerPos, Vector3 targetPos, float distanceToTarget)
              {
                  // distance from target to player
                  float dx =  targetPos.x - playerPos.x;
                  float dz =  targetPos.z - playerPos.z;
                 
                  // find angle from player to target
                  float angleToTarget = Mathf.Atan2(dx,dz) * Mathf.Rad2Deg;
                 
                  // direction player facing
                  float anglePlayer = playerController.eulerAngles.y;
                 
                  // subtract player angle, to get relative angle to object
                  // subtract 90
                  // (so 0 degrees (same direction as player) is UP)
                  float angleRadarDegrees =  angleToTarget - anglePlayer - 90;
                 
                  // calculate (x,y) position given angle and distance
                  float normalisedDistance = distanceToTarget / MAX_DISTANCE;   
                  float angleRadians = angleRadarDegrees * Mathf.Deg2Rad;
                  float blipX = normalisedDistance * Mathf.Cos(angleRadians);
                  float blipY = normalisedDistance * Mathf.Sin(angleRadians);   
                 
                  // scale blip position according to radar size
                  blipX *= RADAR_SIZE/2;
                  blipY *= RADAR_SIZE/2;
                 
                  // offset blip position relative to radar center (64,64)
                  blipX += RADAR_SIZE/2;
                  blipY += RADAR_SIZE/2;
                 
                  // draw target texture at calculated location
                  Rect blipRect = new Rect(blipX - 5, blipY - 5, 10, 10);
                  GUI.DrawTexture(blipRect, targetBlip);       
              }
          }


          B9. Kéo thả file C# vừa tạo vào Main Camera ở thẻ Hierarchy.

          B10. Nhấp chọn Main Camera, qua thẻ Inspector, điều chỉnh các mục trong Radar (Script) như hình sau:



          B11. Nhấp nút Play để kiểm tra thành quả.


          Nguyên lí làm việc

          Hai hằng số được định nghĩa trong đoạn code:
          • MAX_DISTANCE: Khoảng cách xa nhất mà radar có thể phát hiện được đối tượng và hiển thị lên màn hình radar.
          • RADAR_SIZE: Kích thước (Đơn vị là px) của radar sẽ hiển thị lên màn hình.

          Class Radar (file C# Radar) có 3 biến public:
          • Biến đầu tiên là để chỉ tâm của radar sẽ được đặt, ở đây là hiển thị tâm tại chỗ đứng của người chơi.
          • 2 biến còn lại là hình ảnh của màn hình radar và điểm nút chỉ đối tượng khác trong game sẽ được hiển thị trên màn hình radar.

          Trong hàm onGUI( ), sẽ hiển thị hình nền của màn hình radar. Một mảng GameObject với các đối tượng được đặt tag sẽ được duyệt qua. Với mỗi phần tử trong mảng, nếu khoảng cách của chúng nằm trong khoảng nhỏ hơn MAX_DISTANCE thì hàm DrawBlip( ) sẽ được gọi.

          Hàm DrawBlip( ) sẽ tìm tọa độ x và z của đối tượng và người chơi để tính toán khoảng cách từ tâm đến đối tượng thông qua hàm lượng giác Atan2( ) của Unity. Hướng nhìn của người chơi được xác định bởi trục Y và việc phải trừ góc quay đi 90 đều tương tự như ở bài viết Hiển thị la bàn.


          Còn nữa

          Hiển thị các đối tượng với màu khác nhau trên radar.
          Chèn thêm các biến Texture vào trong hàm DrawBlip( ) với mỗi biểu tượng khác nhau cho từng đối tượng khác nhau. Các biến này sẽ sử dụng trong hàm GUI.DrawTexture. Điều này cho phép hàm DrawBlip( ) sẽ được gọi với các vòng lặp khác nhau cho từng đối tượng được gắn tag khác nhau.

          Chủ Nhật, 9 tháng 3, 2014

          AI 2.2 - MÁY TRẠNG THÁI - FSM ĐƠN GIẢN

            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);

              }