Thứ Năm, 8 tháng 5, 2014

Project RPG BÀI 16. HIỂN THỊ TÊN KHI XÁC ĐỊNH MỤC TIÊU



Ở bài viết Project RPG BÀI 4. Xác định mục tiêu, chúng ta đã tạo ra script Targetting để chọn đối tượng trong phạm vi của nhân vật bằng phím Tab và thay đổi mục tiêu qua lại giữa các đối tượng. Ở bài viết này, chúng ta sẽ tạo ra một script mới dựa theo script Targetting nhưng có thêm phần hiển thị tên mục tiêu cùng thanh máu thay vì chỉ thay đổi màu sắc như ở bài trước.


B1. Double click vào scene Level1 ở thẻ Project để tiếp tục làm việc với scene này.

B2. Vào Edit | Project Settings | Tags and Layers và qua thẻ Inspector, mục Tags nhập vào Element 0 là Enemy.


B3. Tại thẻ Project, nhấp chọn mob_Slug ở thư mục Prefabs và qua thẻ Inspector, điều chỉnh tag là Enemy.

B4. Tại thẻ Hierarchy, đổi tên 3 đối tượng mob_Slug thành mob_Slug 1, mob_Slug 2, mob_Slug 3 và chỉnh tag cho cả 3 là Enemy. (Lưu ý: không nên chọn tất cả rồi đặt tag trong một lần vì có thể dính tag cho đối tượng con là Armateur)

 

B5. Tại thẻ Hierarchy, nhấp chọn Create | 3D Text và đặt tên là Name.


B6. Nhấp chọn Name ở thẻ Hierarchy vừa tạo ở bước trên và qua thẻ Inspector, điều chỉnh như sau:


B7. Kéo thả Name vào mob_Slug 1 rồi qua thẻ Inspector, điều chỉnh tọa độ và Mesh Render của Name như sau:


B8. Tại thẻ Hierarchy, nhấp chọn mob_Slug 1 và qua thẻ Inspector, bấm chọn nút Apply để các mob_slug còn lại đều có thêm Name giống như mob_Slug 1 vừa điều chỉnh.

 


B9. Nhấp phải vào thư mục Script và chọn Create | C# Script và đặt tên là TargetMob. Double click vào file này và xóa tất cả đi, chèn lại đoạn code sau vào:

using UnityEngine;
using System.Collections;
using System.Collections.Generic;

public class TargetMob : MonoBehaviour {
    public List<Transform> targets;
    public Transform selectedTarget;
   
    private Transform myTransform;
    // Use this for initialization
    void Start () {
        targets = new List<Transform>();
        selectedTarget = null;
        myTransform = transform;
        AddAllEnemies();
    }
   
    public void AddAllEnemies(){
        GameObject[] go = GameObject.FindGameObjectsWithTag("Enemy");
       
        foreach(GameObject enemy in go)
            AddTarget(enemy.transform);
    }
   
    public void AddTarget(Transform enemy){
        targets.Add (enemy);
    }
   
    private void SortTargetsByDistance(){
        targets.Sort(delegate(Transform t1, Transform t2){
            return Vector3.Distance(t1.position, myTransform.position).CompareTo(Vector3.Distance(t2.position, myTransform.position));
        });
       
    }
   
    private void TargetEnemy(){
        if(selectedTarget == null){
            SortTargetsByDistance();
            selectedTarget = targets[0];
        }
        else{
            int index = targets.IndexOf (selectedTarget);
           
            if(index < targets.Count - 1){
                index++;
            }
            else{
                index = 0;
            }
            DeselectTarget();
            selectedTarget = targets[index];
        }
        SelectTarget();
    }
   
    private void SelectTarget(){
        Transform name = selectedTarget.FindChild ("Name");

        if (name == null) {
            Debug.LogError("Could not find the Name on " + selectedTarget.name);
            return;
        }

        name.GetComponent<TextMesh>().text = selectedTarget.GetComponent<Mob>().Name;
        name.GetComponent<MeshRenderer>().enabled = true;
        selectedTarget.GetComponent<Mob>().DisplayHealth();

        Messenger<bool>.Broadcast("show mob vitalbars", true);
    }

    private void DeselectTarget(){
        selectedTarget.FindChild ("Name").GetComponent<MeshRenderer> ().enabled = false;
       
        selectedTarget = null;
        Messenger<bool>.Broadcast("show mob vitalbars", false);
    }

    // Update is called once per frame
    void Update () {
        if(Input.GetKeyDown(KeyCode.Tab)){
            TargetEnemy();
        }
    }
}

B10. Kéo thả file C# TargetMob vào Game Master ở thẻ Hierarchy.


B11. Double click vào file C# Mob và xóa tất cả đi chèn lại đoạn code bên dưới vào:

 using UnityEngine;
using System.Collections;

public class Mob : BaseCharacter {
    public int curHealth;
    public int maxHealth;
  
    // Use this for initialization
    void Start () {
    //    GetPrimaryAttribute ((int)AttributeName.Constituion).BaseValue = 100;
    //    GetVital((int)VitalName.Health).Update ();

        Name = "Slug Mob";
    }
  
    // Update is called once per frame
    void Update () {

    }

    public void DisplayHealth(){
        Messenger<int, int>.Broadcast("mob health update", curHealth, maxHealth);

    }
}


B12. Tại thẻ Project, nhấp chọn mob_Slug 1 rồi qua thẻ Inspector, điều chỉnh Cur Health và Max Health như sau:


B13. Thực hiện tương tự với các mob_Slug còn lại nhưng nhập vào các giá trị Cur Health khác nhau còn Max Health giống bước trên.


B14. Double click vào file C# VitarBar nằm trong thư mục HUDClasses, xóa tất cả đi và chèn lại đoạn code sau vào:

/// <summary>
/// VitalBar.cs
///
/// This class is responsble for displaying a vita for the player character or a mob...
/// </summary>
using UnityEngine;
using System.Collections;

public class VitalBar : MonoBehaviour {
    public bool _isPlayerHealthBar;
   
    private int _maxBarLength;
    private int _curBarLength;
    private GUITexture _display;

    void Awake(){
        _display = gameObject.GetComponent<GUITexture>();
    }

    // Use this for initialization
    void Start () {

        _maxBarLength = (int)_display.pixelInset.width;

        _curBarLength = _maxBarLength;
        _display.pixelInset = CalculatePosition ();
        OnEnable();
    }
   

    //This method is called when the gameobject is enabled
    public void OnEnable(){
        if(_isPlayerHealthBar)
            Messenger<int, int>.AddListener("player health update", OnChangeHealthBarSize);
        else{
            ToggleDisplay(false);
            Messenger<int, int>.AddListener("mob health update", OnChangeHealthBarSize);
            Messenger<bool>.AddListener("show mob vitalbars", ToggleDisplay);
        }
    }
   
    //this method is called when the gameobject is disabled
    public void OnDisable(){
        if(_isPlayerHealthBar)
            Messenger<int, int>.RemoveListener("player health update", OnChangeHealthBarSize);
        else{
            Messenger<int, int>.RemoveListener("mob health update", OnChangeHealthBarSize);
            Messenger<bool>.RemoveListener("show mob vitalbars", ToggleDisplay);
        }
    }
   
    //This method will calculate the total size of the healthbar in relation to the % of health the target has left
    public void OnChangeHealthBarSize(int curHealth, int maxHealth){
        //Debug.Log("We heard an event: curHealth = " + curHealth + " - maxHealth = " + maxHealth);
        _curBarLength = (int)((curHealth / (float)maxHealth) * _maxBarLength);    //this calculate the current bar length based on the player health %

        //_display.pixelInset = new Rect(_display.pixelInset.x, _display.pixelInset.y, _curBarLength, _display.pixelInset.height);
        _display.pixelInset = CalculatePosition();
    }
   
    //setting the health bar to the player or mob
    public void SetPlayerHealth(bool b){
        _isPlayerHealthBar = b;
    }

    private Rect CalculatePosition(){
        float yPos = _display.pixelInset.y / 2 - 10;

        if(!_isPlayerHealthBar){
            float xPos = (_maxBarLength - _curBarLength) - (_maxBarLength / 4 + 10);
            return new Rect(xPos, yPos, _curBarLength, _display.pixelInset.height);
        }

        return new Rect(_display.pixelInset.x, yPos, _curBarLength, _display.pixelInset.height);

    }

    private void ToggleDisplay (bool show){
        _display.enabled = show;

    }
}


B15. Save scene và ấn nút Play để kiểm tra thành quả. Ấn Tab để thay đổi qua lại giữa các mục tiêu.

Thứ Ba, 6 tháng 5, 2014

DEVGAME 1.7 - IN THÔNG BÁO LÊN CONSOLE



Một cách cơ bản nhất để kiểm tra hàm có thực hiện hay không là in thông báo lên console để kiểm tra.


Trở lại cửa sổ web Script Reference, gõ vào khung search dòng chữ "print to console". Khi đó bạn sẽ thấy trong kết quả tìm kiếm có dòng "Debug.Log - Logs message to the Unity Console" ( Thông báo ghi chú lên Console ). Nhấp chọn vào kết quả này để xem cách thức sử dụng của hàm này. Ngoài ra, lần sau, bạn cũng có thể giành chút thời gian để xem qua hàm MonoBehaviour.print cũng tương tự như Debug.Log và dễ nhớ hơn.

B1. Chèn dòng code tô đỏ dưới đây vào hàm OnMouseDown( ) như sau:

#pragma strict

function OnMouseDown ( ) {

    Debug.Log("This object was picked");

}


B2. Lưu file script lại.

Phần hướng dẫn trên web có để là "Hàm được gọi mỗi khi nhấp chuột lên button của GUI Element hoặc Collider". Ở đây chúng ta không sử dụng GUI nên chúng ta phải tạo collider.

B3. Trở lại Unity Editor, nhấp chọn Cube ở thẻ Hierarchy và qua thẻ Inspector bạn sẽ thấy nó đã có collider sẵn:



B4. Để dễ nhấp trúng khối hợp hơn bạn nên tắt phần dịch chuyển đi. Tại thẻ Hierarchy, nhấp chọn Cube Parent và qua thẻ Inspector điều chỉnh như sau:


B5. Kéo thả file Js (JavaScript) MousePick vào Cube ở thẻ Hierarchy như sau:
 

B6. Nhấn nút Play và thử nhấp vào Cube.

B7. Nhấn Ctrl + Shift + C để xem thông báo trong hộp thoại Console.
 

Lưu ý: Nếu không thấy thông báo xuất hiện, chắc rằng bạn đã kéo thả file Js PickUp vào Cube, không phải Cube Parent nhé. Vì Cube Parent không có collider.

Vật thể không cần phải hiện hình hoặc được render mới có thể nhấp chọn. Điều này giúp tăng tính sáng tạo thêm trong các môi trường game.

B8. Nhấp chọn Cube ở thẻ Hierarchy và nhớ vị trí của nó.

B9. Qua thẻ Inspector, bỏ chọn Mesh Render để ẩn khối hộp đi.


Khối hộp đã được ẩn khỏi khung nhìn Game. Trong khung nhìn Scene, mặc dù nó vẫn còn thấy các cạnh màu xanh của khối hộp khi nhấp vào Box Collider ở thẻ Inspector.


B10. Nhấp nút Play và rà chuột nhấp chọn vào chỗ của khối hộp đã được ẩn.

Bạn sẽ thấy dù khối hộp không hiển thị lên màn hình nhưng khi nhấp chuột thông báo vẫn xuất hiện trên Console.

B11. Nhấp nút Play để thoát khỏi quá trình chạy game. Nhấp chọn Cube ở thẻ Hierarchy và qua thẻ Inspector mở Mesh Render lên lại và tắt nút kế bên tên Cube đi như sau:


Khi đó bạn sẽ thấy khối hộp không còn thấy nữa trên khung nhìn Scene kể cả khi bấm vào Box Collider, còn trên thẻ Hierarchy bạn sẽ thấy Cube chuyển sang màu tối hơn


B12. Hãy nhấn nút Play và nhấp chuột vào vùng mà bạn đã nhớ vị trí của khối hộp, lúc này sẽ không có thông báo xuất hiện khi nhấp trúng vào khối hộp nữa.

Trường hợp tương tự sẽ xuất hiện nếu bạn bật trở lại và tắt Box Collider đi vì lúc đó không có collider để kiểm tra va chạm nữa. Nên thông báo sẽ không hiện lên hộp thoại Console.

Thứ Năm, 1 tháng 5, 2014

ProjectGame 1.3 - Tạo class CharacterController và SpriteManager



Trong bài viết này, chúng ta tạo file Javascript mới để điều khiển việc di chuyển của nhân vật và các ảnh chuyển động cho từng động tác của nhân vật. Chúng ta có thể code bằng Unitron (Mac), UniSciTE (Windows), hoặc MonoDevelop. MonoDevelop là công cụ biên tập chính được thiết kế cho code C# và môi trường .NET, vì vậy nếu bạn quen C# hơn bạn có thể yêu thích nó. Tuy nhiên, chúng ta sẽ code bằng JavaScript bởi vì nó có nhiều hàm nhanh hơn và debug tốt hơn, như tìm hoặc thay thế từ trong toàn bộ project bằng cách nhấn tổ hợp phím Command + Shift + Fin (Mac) hoặc Control + Shift + Fin (Windows) và tự động hoàn thành từ tìm kiếm. Nếu bạn đã biết JavaScript thì việc chuyển sang C# khá dễ dàng.


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


Chuẩn bị

Bây giờ, chúng ta sẽ bắt tay vào code, nhưng trước tiên, chúng ta phải thiết lập lại đôi chút.

B1. Tại thẻ Project, vào thư mục Scripts và xóa tất cả các file đi.

B2. Tiếp theo, chúng ta phải thiết lập lại Unity để sử dụng MonoDevelop như trình biên tập code mặc định. Bằng cách vào Edit | Preferences.

B3. Chúng ta sẽ thấy các mục thiết lập cho Unity trong cửa sổ vừa hiện lên. Trong tab General, vào External Script Editor và thay đổi Use build-in editor thành MonoDevelop bằng cách nhấp chọn nút Browse và chọn tìm đến thư mục cài đặt Unity và chọn Unity | MonoDevelop | MonoDevelop.exe.


MonoDevelop là một "Môi trường phát triển tích hợp" (IDE) cơ bản được thêm vào từ Unity 3.X, nó có môi trường code và debug tốt hơn. Chúng ta có thể tìm hiểu thêm về cách điều chỉnh qua trang web của Unity:

http://unity3d.com/support/documentation/Manual/HOWTO-MonoDevelop.html


Bắt tay vào làm việc

B1. Tại thẻ Project, nhấp phải vào thư mục Scripts và chọn Create | JavaScript và đặt tên là CharacterController2D.



B2. Double click vào file JavaScript CharacterController2D để mở file này lên và bắt đầu code.

B3. Trước tiên chúng ta sẽ tiến hành khai báo các biến cần thiết như sau:

public var f_speed : float = 5.0;
public var loopSprites : SpriteManager[];
private var in_direction : int;

Ở đây, biến f_speed biểu thị cho tốc độ của nhân vật và chúng ta thiết lập nó là public vì vậy chúng ta co thể tùy chỉnh nó trong thẻ Inspector ở Unity Editor. Mãng loopSprite của lớp SpriteManager sẽ kiểm soát quá trình cập nhật texture của sprite khi chuyển động, chúng ta sẽ tạo ra lớp này sau. Biến in_direction lưu hướng của nhân vật, biến này chỉ mang 2 giá trị là 1 (sprite kế tiếp bên phải) và -1 (sprite kế tiếp bên trái).

B4. Tiếp theo, chúng ta sẽ bổ sung code sau vào hàm Start( ) đã mặc định được tạo sẵn.

public function Start() : void {
       in_direction = 1;
       //Initialization Sprite Manager
       for (var i : int = 0; i<loopSprites.length; i++) {
              loopSprites[i].init();
       }
       //Update Main Camera to the character position
       Camera.main.transform.position = new Vector3(transform.position.x, transform.position.y,Camera.main.transform.position.z);
}

B5. Tiếp theo, chúng ta sẽ bổ sung đoạn code sau vào hàm Update( ) cũng đã được mặc định tạo sẵn:

// Update is called once per frame
public function Update () : void {
       if (Input.GetButton("Horizontal")) {
              //Walking
              in_direction = Input.GetAxis("Horizontal") < 0 ? -1: 1;
              rigidbody.velocity = new Vector3((in_direction*f_speed), rigidbody.velocity.y, 0);
              //Reset Stay animation frame back to the first frame
              loopSprites[0].resetFrame();

              //Update Walking animation while the character is walking
              loopSprites[1].updateAnimation(in_direction, renderer.material);
       } else {
              //Stay
              //Reset Walking animation frame back to the first frame
              loopSprites[1].resetFrame();
              //Update Stay animation while the character is not walking
              loopSprites[0].updateAnimation(in_direction, renderer.material);
       }
}

B6. Sau đó chúng ta sẽ tạo thêm hàm LateUpdate( ) phía bên dưới hàm Update( ). Chúng ta sẽ sử dụng hàm này để cập nhật vị trí camera sau khi nhân vật di chuyển bằng cách điều chỉnh tọa độ để theo sau nhân vật.

public function LateUpdate() : void {
       //Update Main Camera
       Camera.main.transform.position = new Vector3(transform.position.x, transform.position.y, Camera.main.transform.position.z);
}


B7. Kế tiếp, chúng ta sẽ tạo lớp SpriteManager để quản lý các ảnh chuyển động của chúng ta trong file JavaScript CharacterController2D vừa tạo ở các bước trên. Tiếp tục bổ sung đoạn code sau vào bên dưới hàm LateUpdate( ) trong file script chúng ta đang làm.
class SpriteManager {
       public var spriteTexture : Texture2D; //Set Texture use for a loop animation such as walking, stay, etc.
       public var in_framePerSec : int; //Get frame per sec to calculate time
       public var in_gridX : int; //Get max number of Horizontal images
       public var in_gridY : int; //Get max number of Vertical images
       private var f_timePercent : float;
       private var f_nextTime : float; //Update time by using frame persecond
       private var f_gridX : float;
       private var f_gridY : float;
       private var in_curFrame : int;
     
       public function init () : void {
              f_timePercent = 1.0/in_framePerSec;
              f_nextTime = f_timePercent; //Update time by using frame persecond
              f_gridX = 1.0/in_gridX;
              f_gridY = 1.0/in_gridY;
              in_curFrame = 1;
       }

       public function updateAnimation (_direction : int, _material : Material) : void {
              //Update material
              _material.mainTexture = spriteTexture;
              //Update frame by time
              if (Time.time>f_nextTime) {
                     f_nextTime = Time.time + f_timePercent;
                     in_curFrame++;
                     if (in_curFrame>in_framePerSec) {
                            in_curFrame = 1;
                     }
              }
              _material.mainTextureScale = new Vector2 (_direction * f_gridX, f_gridY);
              var in_col : int = 0;
              if (in_gridY>1) {
                     //If there is more than one grid on the y-axis update the texture
                     in_col= Mathf.Ceil(in_curFrame/in_gridX);
              }
              if (_direction == 1) { //Right
                     _material.mainTextureOffset = new Vector2(((in_curFrame)%in_gridX) * f_gridX, in_col*f_gridY);
              } else { //Left
                     //Flip Texture
                     _material.mainTextureOffset = new Vector2(((in_gridX + (in_curFrame)%in_gridX)) * f_gridX, in_col*f_gridY);
              }
       }

       public function resetFrame () :void {
              in_curFrame = 1;
       }
}

B8. Bây giờ, ấn Ctrl + S để save file script này lại, kéo thả nó vào Player ở thẻ Hierarchy. Nhấp chọn Player và qua thẻ Inspector điều chỉnh Size = 2 trong Loop Sprites và các mục khác như sau:


B9. Save scene lại, và ấn nút Play để kiểm tra thành quả. Bạn hãy ấn phím mũi tên qua trái / qua phải hoặc nút A / D để di chuyển qua trái hoặc qua phải.


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

Chúng ta vừa tạo một đoạn script để kiểm soát chuyển động của và xử lý các động tác của nhân vật. Trước tiên chúng ta thiết lập biết in_direction là 1 bởi vì chúng ta muốn nhân vật lúc bắt đầu sẽ quay mặt sang bên phải. Khi đó, mảng hình ảnh sẽ được lặp và khởi tạo trong lớp SpriteManager tùy theo độ dài mà chúng ta đặt. Chúng ta sẽ sử dụng main camera từ scene hiện tại thông qua cú pháp Camera.main. Cú pháp này cho phép ta truy cập đến đối tượng Main Camera từ bất kì đâu chúng ta muốn và khi đó chúng ta hướng vị trí của camera theo nhân vật. Tiếp theo, chúng ta sẽ đặt đoạn code vào hàm Update( ) đã được mặc định tạo sẵn, tương tự như hàm Start( ). Hàm này sẽ điều khiển quá trình chuyển động khi nhân vật từ bước đi sang nhảy và cập nhật các ảnh chuyển động.

Sau đó, chung ta sử dụng lớp Input để phát hiện khi người chơi nhấn phím bấm từ bàn phím. Chúng ta làm tất cả việc điều khiển nhân vật trong hàm Update( ). Đầu tiên, chúng ta sử dụng if (Input.GetButton("Horizontal")) { } để kiểm tra việc người chơi đã nhấn phím Horizontal (mặc định trong Unity là A, D, ←, →) và chúng ta di chuyển nhân vật nếu phím được nhấn. Dòng đầu tiên của hàm if kiểm tra hướng của nhân vật in_direction =Input.GetAxis("Horizontal") < 0 ? -1: 1; có nghĩa là nếu nhấn nút Horizontal, chúng ta sẽ lấy giá trị tọa độ từ hàm Input.GetAxis("Horizontal"). Hàm Input.GetAxis sẽ trả về giá trị từ -1 đến 1 biểu thị cho việc người chơi đang nhấn nút. Sau đó, chúng ta kiểm tra giá trị có nhỏ hơn 0 hay không. Nếu có thì hàm trả về -1 (di chuyển qua trái), nếu không thì trả về 1 (di chuyển qua phải). Khi đó dòng rigidbody.velocity = new Vector3((in_direction*f_speed), rigidbody.velocity.y, 0); chúng ta đã gán hướng và tốc độ vào rigidbodyvelocity. Chúng ta không gán giá trị cho trục Z bởi vì nhân vật của chúng ta không di chuyển theo hướng đó.

Cuối cùng, chúng ta tạo lớp SpriteManager cho file Javascript của chúng ta để điều khiển việc xử lý các vật liệu gán cho sprite bằng cách sử dụng tối đa số bức ảnh chúng ta đã tính với thời gian khi chạy mỗi bức ảnh. Hãy nhìn qua lớp SpriteManager. spriteTexture cơ bản dùng để thiết lập vật liệu của các sprite được lấy từ class. Những texture này sẽ được gọi và gán vào vật liệu chính khi nhân vật thay đổi di chuyển, như từ bước đi sang đứng yên, bước đi sang nhảy và vân vân.... in_framePerSec là tổng số ảnh texture của sprite, sẽ được tính để hiển thị trong khung hình kế tiếp. in_gridX là số dòng của sprite texture, và in_gridY là số cột, để tính toán Tiling và Offset của texture như chúng ta đã tìm hiểu ở bài viết trước. Chúng ta thiết lập private cho các biến f_timePercent, f_nextTime, f_gridX, f_gridY, và in_curFrame, mà chúng ta sử dụng để tính toán trong hàm updateAnimation( ). Tiếp theo, chúng ta có hàm init( ). Hàm này cơ bản là thiết lập các biến. Sau đó, hàm updateAnimation( ) sẽ lấy vật liệu và hướng nhìn của nhân vật để xử lý và cập nhật các ảnh chuyển động. Cuối cùng, chúng ta có hàm resetFrame( ) để thiết lập lại số khung hình chuyển động về 1.


Kiến thức bổ sung

Input Manager


Trong Unity, chúng ta có thể tùy chỉnh thiết lập Input Manager bằng cách vào Edit | Project Settings | Input. Trong thẻ Inspector, nhấp chọn Axes và bạn sẽ thấy Size: 17, là độ dài mảng của tất cả các input. Nếu có hơn 17 input, chúng ta có thể nhập số vào đây (mặc định là 17). Tiếp theo chúng ta sẽ thấy có 17 tên từ Horizontal đến Jump như thiết lập mặc định. Mỗi cái sẽ có các biến riêng chúng ta có thể điều chỉnh.

Chúng ta có thể thông tin thêm tại website của Unity:

http://unity3d.com/support/documentation/Components/classInputManager.html

Trong file script của chúng ta, chúng ta sử dụng hàm Input.GetButton("Horizontal"). GetButton dùng để kiểm tra nếu các nút Horizontal được nhấn hay không. Horizontal là tên của input đầu tiên như chúng ta đã thấy ở ảnh minh họa bên trên. Chúng ta cũng có thể dùng Input.GetKey("left") để làm việc đó. Nó cũng có kết quả tương tự với Input.GetButton, nhưng khác nhau là GetKey chỉ phát hiện khi phím chỉ định được nhấn. Vì vậy GetButton dễ cho việc người dùng thay đổi phím trong suốt quá trình chơi. 2 nút Negative Button và Positive Button sẽ gửi giá trị âm và dương, thích hợp cho việc điều hướng như lên, xuống, trái, phải. Ngoài ra còn có biến Dead, biến này sẽ thiết lập bất kì số nào nhỏ hơn từ giá trị của biến này đến 0, rất thích hợp cho việc sử dụng joystick. Cũng có thể điều chỉnh Type thành key/mouse và bật biến Snap để khi nhận được giá trị đối nghịch tọa độ sẽ trả về 0.


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

COOKBOOK - HIỂN THỊ BIỂU TƯỢNG CÁC MÓN ĐỒ ĐƯỢC NHẶT



Có rất nhiều loại đồ vật mà người chơi có thể nhặt và lưu trữ trong suốt quá trình chơi game như chìa khóa, bình máu, những món đồ tăng điểm.... Một lớp PickUp tổng quát để thực hiện chức năng nhặt những món đồ sẽ rất hữu dụng, và sử dụng GUI để hiển thị biểu tượng của các món đồ có thể được thực hiện thông qua code C# là List < T > để liệt kê các đối tượng.


Chuẩn bị

PickUp.rar


Cách để làm

B1. Vào File | New Project và đặt tên cho project mới là PickUp. Và đánh dấu vào ô Character Controller.





B2. Kéo thả 2 thư mục ảnh vừa tải ở phần Chuẩn bị về vào thẻ Project trong Unity.



B3. Tại thẻ Hierarchy, nhấp chọn nút Create | Terrain và qua thẻ Inspector điều chỉnh Position như hình dưới đây:



B4. Vẫn đang chọn Terrain ở thẻ Hierarchy, qua thẻ Inspector, điều chỉnh như hình sau:



B5. Tại thẻ Project, nhấp chọn nút Create | C# Script và đặt tên là PickUp. Double click vào file này và xóa tất cả đi rồi chèn đoạn code sau vào:

using UnityEngine;
using System.Collections;

public class PickUp : MonoBehaviour
{
    public enum PickUpCategory
    {
        KEY, HEALTH, SCORE
    }
   
    public Texture icon;
    public int points;
    public string fitsLockTag;
    public PickUpCategory catgegory;
   
}


B6. Kéo thả 3rd Person Controller trong thư mục Standard Assets | Character Controllers ở thẻ Project vào thẻ Hierarchy.



B7. Tạo file C# với tên là GeneralInventory và kéo thả file C# này vào 3rd Person Controller ở thẻ Project.

 using UnityEngine;
using System.Collections;
using System.Collections.Generic;

public class GeneralInventory : MonoBehaviour
{
    const int ICON_HEIGHT = 32;
    private List<PickUp> inventory = new List<PickUp>();
  
    private void OnGUI()
    {
        // restrict display to left of screen
        Rect r = new Rect(0,0,Screen.width/2, ICON_HEIGHT);
        GUILayout.BeginArea(r);
        GUILayout.BeginHorizontal();
      
        DisplayInventory();
      
        GUILayout.FlexibleSpace();
        GUILayout.EndHorizontal();
        GUILayout.EndArea();
    }
  
    private void DisplayInventory()
    {
        foreach (PickUp item in inventory)
        {
            GUILayout.Label( item.icon );
        }
    }
  
    private void OnTriggerEnter(Collider hitCollider)
    {  
        if( "pickup" == hitCollider.tag )
        {
            PickUp item = hitCollider.GetComponent<PickUp>();
            inventory.Add( item );
            Destroy ( hitCollider.gameObject );
        }
    }
}


B8. Nhấp chọn 3rd Person Controller ở thẻ Hierarchy và qua thẻ Inspector điều chỉnh như sau:



B9. Tại thẻ Hierarchy, nhấp chọn nút Create | Cube và đặt tên là Cube-health. Kéo thả file C# PickUp ở thẻ Project vào Cube-health vừa tạo ở thẻ Hierarchy. Nhấp chọn Cube-health và qua thẻ Inspector điều chỉnh như sau:


B10. Thực hiện tương tự B9 để tạo ra các Cube-key và Cube-health với các icon khác nhau. Nhớ điều chỉnh Categories cho phù hợp nhé.


B11. Kéo thả các file ảnh pickup-items vào từng khối hộp để gán vật liệu cho chúng. VD: Cube-key có icon là key_yellow_icon thì kéo file key_yellow làm vật liệu.


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


Nguyên lí làm việc

Lớp PickUp không chứa hàm nhưng nó bao gồm nhiều biến public hữu dụng. Nó còn có một kiểu dữ liệu liệt kê - enum để phân loại đồ vật thành 3 nhóm là: KEY, HEALTH và SCORE. Mỗi đồ vật được lớp PickUp định nghĩa với một biểu tượng, một điểm số nguyên, một chuỗi (cho mỗi món đồ chìa khóa, tag ), và tùy theo món đồ vật mà nó sẽ được xếp vào mỗi mục phân loại đồ vật khác nhau.

Các đối tượng trong game đều được gắn script PickUp. Trong bài viết này, những khối hộp với các hình ảnh để mô tả phân loại đồ vật của chúng, nhưng các đồ vật người chơi gặp trong thế giới game có thể các món đồ tương tác ba chiều hoặc bất kể là gì. Mỗi món đồ trong game với script PickUp cần điều chỉnh thuộc tính một cách thích hợp. Ví dụ như, chìa khóa màu vàng phải có biểu tượng là chìa khóa màu vàng, được đặt category là KEY và phải đặt Fits Lock Tag là chiếc rương mà nó có thể mở.

Nhân vật của người chơi được gắn script GeneralInventory. Script này sử dụng GUI với 2 hàm chính: đầu tiên là List<T> thu nhận thông tin các món đồ vật được người chơi đang có, và hiển thị ra màn hình các biểu tượng của từng món đồ đó thông qua hàm OnGUI. Hàm chính thứ hai là để kiểm tra va chạm với đồ vật có thể nhặt thông qua hàm OnTriggerEnter, và mỗi đối tượng được đặt tag là pickup khi va chạm sẽ được thêm vào danh sách hành trang.


Còn nữa

Các đồ vật được nhặt nhưng không hiển thị

Với các mục phân loại đồ vật khác nhau, việc xử lý khi va chạm cũng sẽ khác nhau. Như việc người chơi nhặt được món đồ Health thì điểm sẽ được cộng vào máu của người chơi và khi đó món đồ sẽ bị mất đi thay vì được thêm vào hành trang. Việc này cần dùng đến hàm If trong hàm OnTriggerEnter để quyết định sẽ làm gì khi một món đồ được nhặt.

Xóa món đồ từ danh sách nhặt List<>

Khi bạn mở một cánh cửa cần chìa khóa thì chiếc chìa khóa trong hành trang bạn phải mất sau khi đã mở, vì vậy bạn cần phải xóa nó ra khỏi danh sách hành trang. Để thực hiện điều này, bạn sẽ cần dùng đến hàm If bên trong hàm OnTriggerEnter để phát hiện va chạm. Ở đây mình sử dụng chìa khóa vàng để mở cửa và xóa nó ra khỏi danh sách hành trang.






if( "yellowDoor" == hitCollider.tag )
    OpenDoor(hitCollider.gameObject);


Hàm OpenDoor sẽ cần được khai báo món đồ nào trong danh sách có thể mở được cửa và nếu tìm thấy món đồ đó sẽ xóa nó khỏi danh sách hành trang. Sau đó cửa sẽ mở thông qua hàm sau:

     private void OpenDoor(GameObject doorGO)
    {
        // search for key to open the tag of doorGO
        int colorKeyIndex = FindItemIndex(doorGO.tag);
        if( colorKeyIndex > -1 )
        {
            // remove key item
            inventory.RemoveAt( colorKeyIndex );
          
            // now open the door ...
            doorGO.animation.Play ("open");
        }
    }
  
    private int FindItemIndex(string doorTag)
    {
        for (int i = 0; i < inventory.Count; i++)
        {
            PickUp item = inventory[i];
            if( item.fitsLockTag == doorTag )
                return i;
        }
      
        // not found, return -1
        return -1;
    }

Thứ Hai, 28 tháng 4, 2014

AI 2.3 - FSM FRAMEWORK


  2.3 - FSM FRAMEWORK - Khuôn mẫu FSM

Trong bài viết cuối của AI 2, chúng ta sẽ tìm hiểu về 2 FSM khác nhau đã có sẵn trong thư mục Scenes là AdvanceFSM và SimpleFSM để hiểu rõ hơn về cấu trúc cũng như sự khác biệt nhau.


Lớp AdvanceFSM

Lớp AdvanceFSM cơ bản dùng để quản lý tất cả các FSMState đã được cung cấp và giữ quá trình cập nhật các chuyển đổi với trạng thái hiện tại. Vì vậy, việc đầu tiên trước khi sử dụng sườn máy trạng thái có sẵn chính là khai báo các chuyển đổi và trạng thái mà chúng ta thực hiện cho các xe tăng AI.

Code trong file C# AdvancedFSM được viết như sau:

using UnityEngine;
using System.Collections;
using System.Collections.Generic;

public enum Transition
{
    None = 0,
    SawPlayer,
    ReachPlayer,
    LostPlayer,
    NoHealth,
}

public enum FSMStateID
{
    None = 0,
    Patrolling,
    Chasing,
    Attacking,
    Dead,
}


Ngoài ra còn có danh sách các đối tượng FSMState và 2 biến cục bộ để lưu trữ ID hiện tại của lớp FSMState cũng như trạng thái hiện tại của FSMState:

private List<FSMState> fsmStates;

    //The fsmStates are not changing directly but updated by using transitions
    private FSMStateID currentStateID;
    public FSMStateID CurrentStateID {
        get {
           return currentStateID;
        }
    }


Hàm AddFSMState và DeleteState có công dụng thêm và xóa tình trạng của lớp FSM trong danh sách riêng biệt của chúng ta. Khi hàm PerformTransition được gọi, biến CurrentState sẽ được cập nhật với trạng thái mới tùy theo sự chuyển đổi.


Lớp FSMState

Lớp FSMState quản lý các chuyển đổi sang các trạng thái khác. Nó có từ điển được gọi là map để lưu trữ các cặp giá trị khóa chính của các sự chuyển đổi và các trạng thái. Vi dụ: SawPlayer vạch ra những chuyển đổi đến trạng thái rượt đuổi - Chasing, còn LostPlayer liên kết với trạng thái đang tuần tra - Patrolling và vân vân...

Code trong file C# FSMState như sau:

using UnityEngine;
using System.Collections;
using System.Collections.Generic;
public abstract class FSMState
{
        protected Dictionary<Transition, FSMStateID> map = new
        Dictionary<Transition, FSMStateID>();
...


Hai hàm AddTransitiona và DeleteTransition thực hiện công việc thêm và xóa các chuyển đổi từ từ điển trạng thái chuyển đổi đối tượng - map. Hàm GetOutputState tìm các đối tượng trong map và trả về trạng thái dựa theo các chuyển đổi được nhận.

Lớp FSMState còn 2 hàm cơ sở để các lớp con cần thực hiện. Các hàm đó như sau:

...
public abstract void Reason(Transform player, Transform npc);
public abstract void Act(Transform player, Transform npc);
...


Hàm Reason kiểm tra khi trạng thái cần phải chuyển đổi đến trạng thái khác. Và hàm Act thực hiện nhiệm vụ thi hành những công việc cho biến currentState như di chuyển tới trước điểm mốc và sau đó rượt và tấn công người chơi. Cả hai hàm yêu cầu di chuyển dữ liệu của người chơi và NPC (Non Player Character - Nhân vật máy điều khiển), tất cả đều được lớp này xử lý.


Các lớp trạng thái

Không giống như trong ví dụ về SimpleFSM, các trạng thái của xe tăng AI được viết thành các lớp (file script) riêng biệt nhau liên kết đến lớp FSMState như AttackState, ChaseState, DeadState, và PatrolState, mỗi cái với sự thực hiện của 2 hàm Reason và Act. Hãy xem qua file C# PatrolState như một ví dụ điển hình.


Lớp PatrolState

Lớp này bao gồm 3 phần chính: Hàm tạo (Constructor), hàm Reason và hàm Act.

Code trong file C# PatrolState như sau:

using UnityEngine;
using System.Collections;

public class PatrolState : FSMState
{
    public PatrolState(Transform[] wp)
    {
        waypoints = wp;
        stateID = FSMStateID.Patrolling;

        curRotSpeed = 1.0f;
        curSpeed = 100.0f;
    }

    public override void Reason(Transform player, Transform npc)
    {
        //Check the distance with player tank
        //When the distance is near, transition to chase state
        if (Vector3.Distance(npc.position, player.position) <= 300.0f)
        {
            Debug.Log("Switch to Chase State");
            npc.GetComponent<NPCTankController>().SetTransition(Transition.SawPlayer);
        }
    }

    public override void Act(Transform player, Transform npc)
    {
        //Find another random patrol point if the current point is reached
       
        if (Vector3.Distance(npc.position, destPos) <= 100.0f)
        {
            Debug.Log("Reached to the destination point\ncalculating the next point");
            FindNextPoint();
        }

        //Rotate to the target point
        Quaternion targetRotation = Quaternion.LookRotation(destPos - npc.position);
        npc.rotation = Quaternion.Slerp(npc.rotation, targetRotation, Time.deltaTime * curRotSpeed);

        //Go Forward
        npc.Translate(Vector3.forward * Time.deltaTime * curSpeed);
    }
}


Hàm tạo lấy giá trị từ mảng waypoints và lưu chúng vào mảng cục bộ và khi đó nó được khởi tạo các thuộc tính như di chuyển và tốc độ xoay. Hàm Reason kiểm tra khoảng cách của xe tăng AI với xe tăng của người chơi. Nếu xe tăng của người chơi trong tầm, nó sẽ thiết lập transition ID thành SawPlayer bằng cách sử dụng hàm SetTransition của lớp NPCTankController theo code trong file C# NPCTankController như sau:

  public void SetTransition(Transition t)
  {
        PerformTransition(t);
  }


Nó được bao bởi hàm PerformTransition của lớp AdvanceFSM. Hàm này sẽ cập nhật biến trạng thái hiện hành - CurrentState, với hàm nhận trách nhiệm chuyển đổi, sử dụng đối tượng Transition, và từ điển trạng thái chuyển đổi đối tượng - map từ lớp FSMState. Hàm Act cập nhật điểm mốc của xe tăng AI, xoay xe tăng và di chuyển thẳng đến điểm mốc đó. Các lớp trạng thái khác cũng dựa theo mẫu này với các reason và act khác nhau. Chúng ta đã được thấy chúng qua bài viết trước Máy trạng thái - FSM đơn giản, vì vậy mình sẽ không nói lại ở bài này.


Lớp NPCTankController

Xe tăng AI, lớp NPCTankController sẽ liên kết đến lớp AdvanceFSM. Đây là cách chúng ta thiết lập các trạng thái cho các xe tăng máy.

  private void ConstructFSM()
  {
        ...


        PatrolState patrol = new PatrolState(waypoints);
        patrol.AddTransition(Transition.SawPlayer, FSMStateID.Chasing);
        patrol.AddTransition(Transition.NoHealth, FSMStateID.Dead);

        ChaseState chase = new ChaseState(waypoints);
        chase.AddTransition(Transition.LostPlayer, FSMStateID.Patrolling);
        chase.AddTransition(Transition.ReachPlayer, FSMStateID.Attacking);
        chase.AddTransition(Transition.NoHealth, FSMStateID.Dead);

        AttackState attack = new AttackState(waypoints);
        attack.AddTransition(Transition.LostPlayer, FSMStateID.Patrolling);
        attack.AddTransition(Transition.SawPlayer, FSMStateID.Chasing);
        attack.AddTransition(Transition.NoHealth, FSMStateID.Dead);

        DeadState dead = new DeadState();
        dead.AddTransition(Transition.NoHealth, FSMStateID.Dead);

        AddFSMState(patrol);
        AddFSMState(chase);
        AddFSMState(attack);
        AddFSMState(dead);
  }


Đây là cái đẹp của việc sử dụng FSM framework. Khi các trạng thái tự quản lý mà không cần các lớp riêng, lớp NPCTankController chỉ cần gọi hàm Reaso và Act của trạng thái hiện tại đang được kích hoạt. Điều này loại trừ việc bạn phải viết một danh sách dài về các câu lệnh điều kiện if / else và switch. Thay vào đó, giờ đây các trạng thái được gói gọn đẹp đẽ vào các lớp riêng biệt, làm cho code dễ quản lý hơn về số lượng trạng thái để thực hiện cũng như các chuyển đổi giữa chúng dù là các project lớn ngày càng phức tạp hơn.

  protected override void FSMFixedUpdate()
  {
        CurrentState.Reason(playerTransform, transform);
        CurrentState.Act(playerTransform, transform);
  }



Và đó là các công việc của khuôn mẫu FSM mà chúng ta vừa tham khảo. Trong phần tổng quát, các bước chính để sử dụng khuôn mẫu này như sau:
  1. Khai báo các chuyển đổi và các trạng thái trong lớp AdvanceFSM.
  2. Viết các lớp trạng thái liên kết đến lớp FSMState, và bổ sung các hàm Reason và Act.
  3. Viết lớp tùy chỉnh NPC AI liên kết đến lớp AdvanceFSM.
  4. Tạo các lớp trạng thái, và thêm các chuyển đổi và cặp trạng thái sử dụng hàm AddTransition của lớp FSMState.
  5. Thêm các trạng thái đã tạo vào danh sách trong lớp AdvanceFSM bằng cách sử dụng hàm AddFSMState.
  6. Gọi hàm Reason và Act của biến CurrentState trong mỗi chu trình chạy game.

Bạn có thể vọc thêm scene AdvancedFSM. Nó sẽ vận hành tương tự như SimpleFSM. Nhưng code và các lớp sẽ có tổ chức và dễ quản lý hơn.


Tổng Kết AI 2.

Trong phần 2 này, chúng ta đã học được cách vận hành của máy trạng thái trong Unity3D dựa vào game bắn xe tăng đơn giản. Chúng ta đã hiểu sơ qua cách làm việc của FSM bằng cách đơn giản nhất, sử dụng hàm switch. Và chúng ta đã học cách để sử dụng khuôn mẫu để làm AI vận hành một cách dễ quản lý và dễ mở rộng hơn.

Trong phần tiếp theo là AI 3., chúng ta sẽ tìm hiểu về xác xuất ngẫu nhiên và cách chúng ta sẽ sử dụng nó để làm cho game của chúng ta trở nên không thể đoán trước được.

Chủ Nhật, 27 tháng 4, 2014

Project RPG BÀI 15. MOB CLASS



Bài viết này mình sẽ sử dụng model mob đã download về từ bài 13 để import kẻ địch cho nhân vật vào scene.



B1. Tại thẻ Project, double click vào scene Level1 nằm trong thư mục Scenes để mở scene này lên.Ở thẻ Hierarchy sẽ bao gồm các đối tượng sau:


B2. Tại thẻ Project, vào thư mục Assets | monsterMob đã tải ở Bài 13 và nhấp chọn monsterMob, qua thẻ Inspector điều chỉnh như sau:

 

B3. Vẫn ở thẻ Project, kéo thả prefab monsterMob vào thẻ Hierarchy. Qua thẻ Inspector, nhấp phải vào Animator và chọn Remove Component. Vào Component | Miscellanious | Animation và điều chỉnh như sau:


B4. Nhấp phải vào thư mục Script ở thẻ Project và chọn Create | C# Script và đặt tên là Mob. Double click vào file C# vừa tạo và xóa tất cả đi chèn đoạn code sau vào:

using UnityEngine;
using System.Collections;

public class Mob : BaseCharacter {

    // Use this for initialization
    void Start () {
        GetPrimaryAttribute ((int)AttributeName.Constituion).BaseValue = 100;
        GetVital((int)VitalName.Health).Update ();
   
    }
   
    // Update is called once per frame
    void Update () {
        Messenger<int, int>.Broadcast("mob health update", 80, 100);
    }
}


B5. Kéo thả file C# Mob vào monsterMob ở thẻ Hierarchy.


B6. Tại thẻ Project, nhấp phải vào thư mục Prefabs và chọn Create | Prefab và đặt tên là mob_Slug.


B7. Kéo thả monsterMob ở thẻ Hierarchy vào mob_Slug ở thẻ Project | Prefabs.


B8. Nhấp chọn monsterMob ở thẻ Hierarchy và nhấn Delete.


B9. Kéo thả mob_Slug ở thẻ Project vào thẻ Hierarchy.


B10. Nhấp chọn mob_Slug ở thẻ Hierarchy, qua thẻ Inspector, nhập tọa độ vào như sau:

 

B11. Tại thẻ Hierarchy, nhấp chọn mob_Slug và ấn Ctrl + D 2 lần và kéo 2 đối tượng vừa tạo sang 2 phía như hình sau:


B12. Ấn Ctrl + S để save scene Level1 lại.

Thứ Bảy, 19 tháng 4, 2014

DEVGAME 1.6 - NHẤP CHỌN ĐỐI TƯỢNG VỚI HÀM ONMOUSEDOWN

 

Khi bạn vừa bắt đầu bước từng bước trong việc viết code ở chuỗi bài viết DevGame, hãy tạm ngưng giây lát để tìm hiểu về sự khác biệt giữa game bắn súng góc nhìn thứ nhất với những game nhấp chuột phiêu lưu đơn giản.

Thông thường, các sự chú ý đều dựa trên các vấn đề: Bạn sử dụng vũ khí để phá cửa và xông vào. Bạn bắt gặp kẻ địch trong tầm ngắm và chúng quay sang tấn công bạn. Hoặc bạn công phá những thùng gỗ với khẩu súng phóng tên lửa, và tên lửa với những yếu tố vật lý phá tan những cái thùng gỗ bất hạnh đó. Trong những thể loại game nhấp chuột đơn giản, thông thường con trỏ chuột sẽ là tâm điểm chú ý, hơn cả việc xảy ra va chạm, các yếu tố vật lý, và hướng sáng.

B1. Mở trình đơn giúp đỡ bằng cách vào Help | Scripting Reference.


B2. Gõ vào khung search dòng chữ "On Mouse Down" và nhấn enter



B3. Nhấp chọn dòng MonoBehavior.OnMouseDown.


Trở lại Unity Editor, chúng ta sẽ tạo thêm một file JavaScript mới.

B4. Nhấp phải vào thư mục My Scripts và chọn Create | JavaScript và đặt tên là MousePick.


B5. Double click vào file Java vừa tạo và xóa hàm Start đi.

#pragma strict

function Update ( ) {

}


B6. Đổi tên hàm Update thành OnMouseDown như ví dụ trong Scripting Reference.

#pragma strict

function OnMouseDown ( ) {

}


B7. Ấn Ctrl + S để save script này lại.

Ví dụ trong mục giúp đỡ Scripting Reference thực hiện chức năng hiển thị một scene mới, nhưng mục đích của chúng ta chỉ là việc hiển thị xem đối tượng có được nhấp hay không thôi. Không giống như hàm Update, hàm OnMouseDown chỉ được gọi mỗi lần nút chuột được nhấp.