Chủ Nhật, 29 tháng 12, 2013

COOKBOOK - HIỂN THỊ BẢN ĐỒ THU NHỎ


  - Hiển thị bản đồ thu nhỏ -
Trong hầu hết các game, một góc nhìn rộng trong game rất có giá trị để định vị và bắt thông tin. Bản đồ thu nhỏ là một thứ tuyệt vời giành cho người chơi có thể mở rộng góc nhìn cả trong chế độ góc nhìn thứ nhất hoặc ba.



Chuẩn bị 
Maze.unitypackage

Cách để làm

B1. Nhấp phải vào vùng trống trong thẻ Project, chọn Import Package | Custom Package và mở file Maze.unitypackage vừa tải. Double click vào Demo để mở scene này lên.

B2. Nhấp chọn 3rd Person Controller ở thẻ Hierarchy, tại thẻ Inspector, nhấp vào ô giá trị phía sau Layer và chọn Add Layer, gõ vào User Layer 8 là NoMap.


  

B3. Nhấp chuột chọn lại 3rd Person Controller ở thẻ Hierarchy và điều chỉnh Layer thành NoMap.


B4. Tại thẻ Hierarchy, nhấp nút Create | Camera và đổi tên thành Map Camera.


B5. Nhấp chuột chọn Map Camera vừa tạo, tại thẻ Inspector điều chỉnh các thông số như hình sau:


B6. Tại thẻ Project, nhấp nút Create | C# Script và đặt tên là GenerateMap. Double click vào file vừa tạo và chèn đoạn code sau vào:

using UnityEngine;
using System.Collections;

public class GenerateMap : MonoBehaviour {
    public Transform target;
    public Texture2D marker;
    public float camHeight = 1.0f;
    public bool freezeRotation = true;
    public float camDistance = 2.0f;
    public enum ha {left, center, right};
    public enum va {top, middle, bottom};
    public ha horizontalAlignment = ha.left;
    public va verticalAlignment = va.top;
    public enum sd {pixels, screen_percentage};
    public sd dimensionsIn = sd.pixels;
    public int width = 50;
    public int heigth = 50;
    public float xOffset = 0f;
    public float yOffset = 0f;
   
    void Start(){
        Vector3 angles = transform.eulerAngles;
        angles.x = 90;
        angles.y = target.transform.eulerAngles.y;
        transform.eulerAngles = angles;
        Draw();
    }
    void Update(){
        transform.position = new Vector3(target.transform.position.x, target.transform.position.y + camHeight, target.transform.position.z);
        camera.orthographicSize = camDistance;       
        if (freezeRotation){
            Vector3 angles = transform.eulerAngles;
            angles.y = target.transform.eulerAngles.y;
            transform.eulerAngles = angles;
        }
    }
    void Draw(){
        int hsize = Mathf.RoundToInt(width * 0.01f * Screen.width);
        int vsize = Mathf.RoundToInt(heigth * 0.01f * Screen.height);
        int hloc = Mathf.RoundToInt(xOffset * 0.01f * Screen.width);
        int vloc = Mathf.RoundToInt((Screen.height - vsize) - (yOffset * 0.01f * Screen.height));  

        if(dimensionsIn == sd.screen_percentage){
            hsize = Mathf.RoundToInt(width * 0.01f * Screen.width);
            vsize = Mathf.RoundToInt(heigth * 0.01f * Screen.height);
        } else {
            hsize = width;
            vsize = heigth;
        }
       
        switch(horizontalAlignment){
        case ha.left:
            hloc = Mathf.RoundToInt(xOffset * 0.01f * Screen.width);
            break;
        case ha.right:
            hloc = Mathf.RoundToInt((Screen.width - hsize) - (xOffset * 0.01f * Screen.width));
            break;
        case ha.center:
            hloc = Mathf.RoundToInt(((Screen.width * 0.5f) - (hsize * 0.5f)) - (xOffset * 0.01f * Screen.height));
            break;
        }
        switch(verticalAlignment){
        case va.top:
            vloc = Mathf.RoundToInt((Screen.height - vsize) - (yOffset * 0.01f * Screen.height));
            break;
        case va.bottom:
            vloc =Mathf.RoundToInt(yOffset * 0.01f * Screen.height);
            break;
        case va.middle:
            vloc = Mathf.RoundToInt(((Screen.height * 0.5f) - (vsize * 0.5f)) - (yOffset * 0.01f * Screen.height));
            break;
        }
        camera.pixelRect = new Rect(hloc,vloc,hsize,vsize);
    }
    void OnGUI(){
        Vector3 markerPos = camera. camera.WorldToViewportPoint(target.position);
        int pointX =  Mathf.RoundToInt((camera.pixelRect.xMin + camera.pixelRect.xMax) * markerPos.x);
        int pointY =  Mathf.RoundToInt(Screen.height - (camera.pixelRect.yMin + camera.pixelRect.yMax) * markerPos.y);
        GUI.DrawTexture( new Rect(pointX-(marker.width * 0.5f),pointY-(marker.height * 0.5f),marker.width,marker.height), marker, ScaleMode.StretchToFill, true, 10.0f);
    }
}


B7. Kéo thả file C# vừa tạo vào Map Camera, nhấp chọn Map Camera và qua thẻ Inspector điều chỉnh các thông số như sau:


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


Còn nữa...

- Sử dụng Render Texture (Pro only)
Nếu bạn đang sử dụng phiên bản pro, bạn có thể dùng Render Texture và hiển thị bản đồ của bạn bằng cách dùng GUI. DrawTexture hoặc Graphics.DrawTexture. Xem thêm tại đây:
http://docs.unity3d.com/Documentation/Components/class-RenderTexture.html

- Sử dụng bản đồ thu nhỏ với mục đích khác
Bạn có thể dễ dàng sử dụng chức năng này để tạo góc nhìn hướng từ trên xuống mặt đất làm bản đổ cho các thể loại game đua xe. Chỉ cần định vị và hướng camera vào nhân vật và gắn thêm biểu tượng đánh dấu.


Bài viết nên xem
Cookbook - HƯỚNG DẪN TẠO HIỆU ỨNG TRANH TRONG TRANH

Chủ Nhật, 22 tháng 12, 2013

Project RPG BÀI 7. TÙY BIẾN GIAO DIỆN VỚI GUI.SKIN VÀ GUI.STYLE


Bài viết này mình sẽ hướng dẫn các bạn cách tùy biến giao diện của menu Khởi Tạo Nhân Vật đã tạo ở bài viết trước với thư viện GUI trong Unity 3D. Giao diện đồ họa trong game có thể nói là phần khá quan trọng trong khâu lập trình game.



B1. Double click vào file CharacterGenarator rồi xóa tất cả đi và chèn lại đoạn code sau vào:

using UnityEngine;
using System.Collections;
using System; //used for Enum class

public class CharacterGenerator : MonoBehaviour {
private PlayerCharacter _toon;
private const int STARTING_POINTS = 350;
private const int MIN_STARTING_ATTRIBUTE_VALUE = 10;
private const int STARTING_VALUE = 50;
private int pointsLeft;

private const int OFFSET = 5;
private const int LINE_HEIGHT = 20;

private const int STAT_LABEL_WIDTH = 100;
private const int BASEVALUE_LABEL_WIDTH = 30;
private const int BUTTON_WIDTH = 20;
private const int BUTTON_HEIGHT = 20;

private int statStartingPos = 40;

public GUISkin mySkin;


// Use this for initialization
void Start () {
_toon = new PlayerCharacter();
_toon.Awake();

pointsLeft = STARTING_POINTS;
for(int cnt = 0; cnt < Enum.GetValues(typeof(AttributeName)).Length; cnt++){
_toon.GetPrimaryAttribute(cnt).BaseValue = STARTING_VALUE;
pointsLeft -= (STARTING_VALUE - MIN_STARTING_ATTRIBUTE_VALUE);
}
_toon.StatUpate();
}

// Update is called once per frame
void Update () {

}

void OnGUI(){
GUI.skin = mySkin; 
DisplayName();
DisplayPointsLeft();
DisplayAttributes();
DisplayVitals();
DisplaySkills();
}

private void DisplayName(){
GUI.Label(new Rect(10, 10, 50, 25),"Name:", GUI.skin.GetStyle("styleMiddle"));
_toon.Name = GUI.TextField(new Rect(65, 10, 100, 25), _toon.Name );

}

private void DisplayAttributes(){
for(int cnt = 0; cnt < Enum.GetValues(typeof(AttributeName)).Length; cnt++){
GUI.Label(new Rect( OFFSET, //x
                   statStartingPos + (cnt * LINE_HEIGHT), //y
                   STAT_LABEL_WIDTH, //width
                   LINE_HEIGHT //height
                  ), ((AttributeName)cnt).ToString());
GUI.Label(new Rect( STAT_LABEL_WIDTH + OFFSET, //x
                   statStartingPos + (cnt * LINE_HEIGHT), //y
                   BASEVALUE_LABEL_WIDTH, //width
                   LINE_HEIGHT //height
                  ), _toon.GetPrimaryAttribute(cnt).AdjustedBaseValue.ToString(), GUI.skin.GetStyle("styleMiddle"));
if(GUI.Button(new Rect( OFFSET + STAT_LABEL_WIDTH + BASEVALUE_LABEL_WIDTH, //x
                       statStartingPos + (cnt*BUTTON_HEIGHT), //y
                      BUTTON_WIDTH, //width
                      BUTTON_HEIGHT //height
                      ),"-")){
if(_toon.GetPrimaryAttribute(cnt).BaseValue > MIN_STARTING_ATTRIBUTE_VALUE){
_toon.GetPrimaryAttribute(cnt).BaseValue--;
pointsLeft++;
_toon.StatUpate();
}
}

if(GUI.Button(new Rect( OFFSET + STAT_LABEL_WIDTH + BASEVALUE_LABEL_WIDTH + BUTTON_WIDTH, //x
                       statStartingPos + (cnt*BUTTON_HEIGHT), //y
                       BUTTON_WIDTH, //width
                       BUTTON_HEIGHT //height
                      ),"+")){
if(pointsLeft > 0){
_toon.GetPrimaryAttribute(cnt).BaseValue++;
pointsLeft--;
_toon.StatUpate();
}
}
}
}

private void DisplayVitals(){
for(int cnt = 0; cnt < Enum.GetValues(typeof(VitalName)).Length; cnt++){
GUI.Label(new Rect( OFFSET, //x
                   statStartingPos + ((cnt + 7)* LINE_HEIGHT), //y
                   STAT_LABEL_WIDTH, //width
                   LINE_HEIGHT //height
                  ), ((VitalName)cnt).ToString());
GUI.Label(new Rect( OFFSET + STAT_LABEL_WIDTH, 
                   statStartingPos + ((cnt + 7) * LINE_HEIGHT), 
                   BASEVALUE_LABEL_WIDTH, 
                   LINE_HEIGHT
                  ), _toon.GetVital(cnt).AdjustedBaseValue.ToString(),GUI.skin.GetStyle("styleMiddle"));

}

}

private void DisplaySkills(){
for(int cnt = 0; cnt < Enum.GetValues(typeof(SkillName)).Length; cnt++){
GUI.Label(new Rect( OFFSET + STAT_LABEL_WIDTH + BASEVALUE_LABEL_WIDTH + BUTTON_WIDTH * 2 + OFFSET * 2, //x
                   statStartingPos + (cnt * LINE_HEIGHT), //y
                   STAT_LABEL_WIDTH, //width
                   LINE_HEIGHT //height
                  ), ((SkillName)cnt).ToString());
GUI.Label(new Rect( OFFSET + STAT_LABEL_WIDTH + BASEVALUE_LABEL_WIDTH + BUTTON_WIDTH * 2 + OFFSET * 2 + STAT_LABEL_WIDTH, //x
                   statStartingPos + (cnt * LINE_HEIGHT), //y
                   BASEVALUE_LABEL_WIDTH, //width
                   LINE_HEIGHT //height
                  ), _toon.GetSkill(cnt).AdjustedBaseValue.ToString(),GUI.skin.GetStyle("styleMiddle"));

}

}

private void DisplayPointsLeft(){
GUI.Label(new Rect(250, 10, 100, 25), "Points Left: " + pointsLeft.ToString(),GUI.skin.GetStyle("styleMiddle"));

}
}


B2. Tại thẻ Project, nhấp nút Create | Folder và đặt tên là Skin.


B3. Nhấp phải vào thư mục Skin và chọn Create | GUI Skin và đặt tên là mySkin.


B4. Tải các file Skin.rar này về (Bấm vào Tệp | Tải Xuống - Ctrl + S), giải nén và kéo thả các file .png vào folder Skin vừa tạo.


B5. Bấm chuột vào mySkin đã tạo ở bước 3, tại thẻ Inspector bấm vào mục Button và kéo thả các file btn vào background của Normal, Hover và Active như sau:


B6. Vẫn ở trong mục Button, click chuột vào Border và điều chỉnh Left = 4, Right = 4 như sau:

 

B7. Vẫn ở mục Button, đổi Alignment thành Middle Center, Image Position thành Text Only.


B8. Qua mục Label và thiết lập các giá trị như hình sau:

Bấm vào hình để phóng to

B9. Qua mục Text Field và tiếp tục thiết lập các giá trị như hình sau:

Bấm vào hình để phóng to

B10. Cuối cùng qua mục Custom Style, nhập giá trị Size là 1, đặt Name là styleMiddle và điều chỉnh các giá trị như hình sau:

Bấm vào hình để phóng to
 B11. Nhấp chọn Main Camera ở thẻ Project, kéo thả mySkin ở thư mục Skin vào thẻ Inspector như sau:


B12. Ấn nút Play và xem thành quả đạt được.

Thứ Tư, 18 tháng 12, 2013

AI 1.3 - BẦY, ĐÀN VÀ LŨ (PHẦN II)

  1.3 - Bầy, đàn và lũ ( Phần II )

Tìm đường A*

Có nhiều game, trong đó bạn có thể tìm các con quỷ hoặc địch mà theo sau người chơi, hoặc đi đến một điểm riêng khi tránh các chướng ngại. Chẳng hạn, chúng ta nhìn về một trò game RTS một cách hình thức. Các bạn có thể chọn một nhóm các đơn vị và nhấp vào một vị trí nơi bạn muốn chúng di chuyển hoặc nhấp vào các đơn vị địch để tấn công chúng. Đơn vị của bạn khi đó cần tìm ra đường để đến đích mà không đụng vào các chướng ngại. Đơn vị địch cũng cần có thể làm cùng như thế. Các chướng ngại có thể khác nhau từ các đơn vị khác nhau. Chẳng hạn, một đơn vị hạm đội trên không có thể băng qua đồi, trong khi đất hoặc các đơn vị pháo binh cần tìm ra đường quanh nó.


A* (phát âm là « A sao ») là thuật toán tìm đường được sử dụng rộng rãi trong game, vì sự biểu diễn và độ chính xác của nó. Hãy nhìn vào một ví dụ để thấy làm sao nó làm việc. Hãy nói chúng ta muốn đơn vị của mình di chuyển từ điểm A đến điểm B, nhưng có một bức tường trên đường, và nó không thể di chuyển thẳng đến mục tiêu. Vì thế, cần tìm ra một con đường đến điểm B khi đang tránh bức tường.

Hình : Top-down view of our map

Chúng ta đang tìm một ví dụ 2D đơn giản. Nhưng cùng ý tưởng có thể được áp dụng vào môi trường 3D. Để tìm ra đường từ điểm A đến điểm B, chúng ta cần biết nhiều về bản đồ chẳng hạn như vị trí của vật thể. Vì điều đó chúng ta có thể chia toàn bộ bản đồ thành những lát nhỏ, tiêu biểu là gắn lưới cho bản đồ, như việc biểu diễn trong hình sau :

Map represented in a 2D grid

Các ô cũng có thể là một trong những hình khác như lục giác hoặc tam giác. Nhưng chúng ta sẽ chỉ dùng ô vuông ở đây, nó đơn giản và đầy đủ cho mục đích của chúng ta. Đại diện cho toàn bộ bản đồ lưới, tìm kiếm khu vực đơn giản hơn, và đây là một bước quan trọng trong việc tìm đường. Chúng ta giờ đây có thể liên quan đến bản đồ của chúng ta trong một mảng 2D nhỏ. Bản đồ của chúng ta bây giờ biểu diễn bằng 5 x 5 ô vuông, có tổng cộng 25 ô. Chúng ta có thể tìm kiếm con đường tối ưu để đến đích. Làm sao chúng ta làm được điều này ? Bằng việc tính toán điểm số di chuyển của mỗi ô kề sát với ô bắt đầu, là một ô trên bản đồ không có một chướng ngại, và rồi chọn ô có giá trị thấp nhất.

Có bốn ô kề nhau với người chơi, nếu chúng ta không xét những bước di chuyển chéo. Bây giờ, chúng ta cần biết hai số để tính điểm di chuyển cho mỗi ô đó. Hãy gọi chúng là G và H, với G là giá trị di chuyển từ ô đầu đến ô hiện tại, và H là giá trị để đến ô đích từ ô hiện tại.

Bằng cách thêm G và H, chúng ta có thể có điểm cuối cùng của ô đó; hãy gọi nó là F. Vì thế chúng ta sẽ dùng công thức này: F = G + H.

Valid adjacent tiles

Trong ví dụ này, chúng ta sẽ dùng một cách đơn giản gọi là chiều dài Manhattan (cũng được biết như là hình học Taxicab), trong đó chúng ta chỉ tính tổng số ô giữa ô đầu và ô đích để biết khoảng cách giữa chúng.


Calculating G

Hình trên chỉ ra những tính toán của G theo 2 cách khác nhau. Chúng ta chỉ thêm một đối tượng (giá trị để di chuyển một ô) đến điểm G của ô trước khi đến điểm G hiện tại của ô hiện tại. Chúng ta có thể cho những giá trị khác nhau đối với các ô khác nhau. Chẳng hạn, chúng ta có thể muốn cho giá trị di chuyển cao hơn đối với các di chuyển chéo (nếu chúng ta đang xét đến chúng), hoặc để chỉ những ô đang bị chiếm, hãy nói một cây cầu hoặc một đường bùn. Giờ chúng ta biết làm sao để có G. Chúng ta nhìn vào phép tính H. Hình sau biểu thị giá trị H khác nhau từ những ô đầu khác nhau đến ô đích. Bạn có thể cố tính các ô vuông giữa chúng để hiểu làm sao chúng ta có những giá trị đó.

 
Calculating H

Vì thế, bây giờ chúng ta biết làm sao để có G và H. Hãy đi trở lại ví dụ đầu để tìm ra đường ngắn nhất từ A đến B. Đầu tiên chúng ta chọn ô bắt đầu, và xét những ô liền kề hợp lý, chỉ ra trong hình sau. Rồi chúng ta tính điểm G và H của mỗi ô, chỉ ra trong những góc trái bên dưới và bên phải của ô theo thứ tự. Và rồi điểm F cuối cùng, mà G + H chỉ ra góc trái bên trên. Hiển nhiên, ô đến bên phải trực tiếp của ô đầu có điểm F thấp nhất.

Vì thế, chúng ta chọn ô này như bước di chuyển tiếp theo của chúng ta, và lưu giữ ô trước đó như ô cha của nó.

Ô cha này sẽ có ích về sau, khi chúng ta theo để vết phía đường cuối cùng của chúng ta.

Starting position

Từ ô hiện tại, chúng ta làm bước phát triển tương tự lần nữa, xác định các ô liền kề đúng. Lần này chỉ có hai ô liền kề hợp lý ở trên và dưới. Ô bên trái là ô mà chúng ta đã xác định, và có chướng ngại ở ô bên phải. Chúng ta tính G, H, và rồi điểm F của những ô liền kề mới đó. Lần này chúng ta có bốn ô ở bản đồ của chúng ta với tất cả các ô có cùng điểm, sáu. Vì thế, cái nào là ô chúng ta chọn? Chúng ta có thể chọn bất kỳ trong chúng. Điều đó thật sự chẳng là vấn đề trong ví dụ này, nếu chúng có cùng điểm số. Thông thường, chúng ta chỉ chọn ô được thêm vào gần đây để liệt kê ô liền kề của chúng ta. Điều này vì sau đó, chúng ta sẽ dùng vài cấu trúc dữ liệu, như một danh sách để lưu trữ các ô đó, mà đang được xem xét cho bước tiếp theo. Vì thế, việc kết nối đến ô gần nhất được thêm vào danh sách có thể nhanh hơn việc tìm kiếm thông qua danh sách để đến một ô riêng mà được thêm vào trước đó.

Trong phần demo này, chúng ta sẽ chỉ chọn ngẫu nhiên ô cho phần test tiếp theo của chúng ta, để chứng minh rằng nó có thể tìm ra đường đi ngắn nhất.

Second Step

Vì vậy, chúng ta chọn ô được tô đỏ này. Chúng ta kiểm tra lần nữa ở những ô liền kề. Trong bước này, chỉ có một ô liền kề mới với một điểm F được tính của 8. Vì thế, điểm thấp nhất ngay bây giờ vẫn là 6. Chúng ta có thể chọn bất kỳ ô nào với điểm 6.


 
Third Step

Vì thế, chúng ta chọn một ô ngẫu nhiên từ tất cả những ô với điểm 6. Nếu chúng ta lặp lại bước tiến này cho tới khi chúng ta đến ô đích, chúng ta sẽ kết thúc với một bảng hoàn toàn với tất cả các điểm cho mỗi ô hợp lý.

 
Reach target

Bây giờ chúng ta phải làm để lưu vết điểm đầu từ ô đích bằng cách dùng ô cha (parent). Nó sẽ cho ta một con đường như hướng dẫn dưới đây:

 
Lưu vết - Path traced back

Đây là khái niệm của tìm đường A* bằng nút thắt, không hiển thị bất kỳ mã. A* là một khái niệm quan trọng trong khu vực tìm đường AI, nhưng từ Unity 3.5, có một cặp của những đặc điểm mới như việc mạng lưới điều hướng tự động và chi tiết trong chương 8, Navigation Mesh. Những đặc điểm này làm cho việc tìm đường trong game của chúng ta dễ dàng hơn. Quả thật, thậm chí bạn có thể cần biết về A* để thực thi việc tìm đường cho nhân vật AI của bạn. Tuy nhiên, việc biết cách hệ thống hiển nhiên hoạt động phía sau scene sẽ giúp bạn trở thành một nhà lập trình AI chắc chắn. Không may, những đặc điểm điều hướng tiến triển đó trong Unity chỉ có thể trong phiên bản Pro vào thời điểm này.

Chủ Nhật, 15 tháng 12, 2013

Project RPG BÀI 6. KHỞI TẠO NHÂN VẬT

 
Ở bài viết trước mình đã cung cấp file code về các thông số vật lý của nhân vật để làm dữ liệu cho việc khởi tạo các tính chất vật lý của nhân vật. Bài viết này mình sẽ sử dụng các cơ sở dữ liệu ở bài viết trước để tạo nên menu khởi tạo các thông số cho nhân vật.



B1. Nhấp phải vào folder Character Classes và chọn Creat | C# Script và đặt tên là PlayerCharacter. Double click vào file vừa tạo và chèn đoạn code sau vào:

public class PlayerCharacter : BaseCharacter {

}

B2. Nhấp phải vào folder Character Classes và chọn Creat | C# Script và đặt tên là CharacterGenerator. Double click vào file vừa tạo và chèn đoạn code sau vào:

using UnityEngine;
using System.Collections;
using System;                 //used for Enum class

public class CharacterGenerator : MonoBehaviour {
    private PlayerCharacter _toon;
    private const int STARTING_POINTS = 350;
    private const int MIN_STARTING_ATTRIBUTE_VALUE = 10;
    private const int STARTING_VALUE = 50;
    private int pointsLeft;

    private const int OFFSET = 5;
    private const int LINE_HEIGHT = 20;

    private const int STAT_LABEL_WIDTH = 100;
    private const int BASEVALUE_LABEL_WIDTH = 30;
    private const int BUTTON_WIDTH = 20;
    private const int BUTTON_HEIGHT = 20;

    private int statStartingPos = 40;

    // Use this for initialization
    void Start () {
        _toon = new PlayerCharacter();
        _toon.Awake();

        pointsLeft = STARTING_POINTS;
        for(int cnt = 0; cnt < Enum.GetValues(typeof(AttributeName)).Length; cnt++){
            _toon.GetPrimaryAttribute(cnt).BaseValue = STARTING_VALUE;
            pointsLeft -= (STARTING_VALUE - MIN_STARTING_ATTRIBUTE_VALUE);
        }
        _toon.StatUpate();
    }
    
    // Update is called once per frame
    void Update () {

    }

    void OnGUI(){
        DisplayName();
        DisplayPointsLeft();
        DisplayAttributes();
        DisplayVitals();
        DisplaySkills();
    }

    private void DisplayName(){
        GUI.Label(new Rect(10, 10, 50, 25),"Name:");
        _toon.Name = GUI.TextField(new Rect(65, 10, 100, 25), _toon.Name );

    }

    private void DisplayAttributes(){
        for(int cnt = 0; cnt < Enum.GetValues(typeof(AttributeName)).Length; cnt++){
            GUI.Label(new Rect( OFFSET,                                    //x
                                statStartingPos + (cnt * LINE_HEIGHT),     //y
                                STAT_LABEL_WIDTH,                         //width
                                LINE_HEIGHT                                //height
                               ), ((AttributeName)cnt).ToString());
            GUI.Label(new Rect( STAT_LABEL_WIDTH + OFFSET,                 //x
                                statStartingPos + (cnt * LINE_HEIGHT),     //y
                                BASEVALUE_LABEL_WIDTH,                     //width
                                LINE_HEIGHT                                //height
                               ), _toon.GetPrimaryAttribute(cnt).AdjustedBaseValue.ToString());
            if(GUI.Button(new Rect( OFFSET + STAT_LABEL_WIDTH + BASEVALUE_LABEL_WIDTH,     //x
                                    statStartingPos + (cnt*BUTTON_HEIGHT),                 //y
                                   BUTTON_WIDTH,                                         //width
                                   BUTTON_HEIGHT                                        //height
                                   ),"-")){
                if(_toon.GetPrimaryAttribute(cnt).BaseValue > MIN_STARTING_ATTRIBUTE_VALUE){
                    _toon.GetPrimaryAttribute(cnt).BaseValue--;
                    pointsLeft++;
                    _toon.StatUpate();
                }
            }

            if(GUI.Button(new Rect( OFFSET + STAT_LABEL_WIDTH + BASEVALUE_LABEL_WIDTH + BUTTON_WIDTH,     //x
                                    statStartingPos + (cnt*BUTTON_HEIGHT),                                 //y
                                    BUTTON_WIDTH,                                                         //width
                                    BUTTON_HEIGHT                                                        //height
                                   ),"+")){
                if(pointsLeft > 0){
                    _toon.GetPrimaryAttribute(cnt).BaseValue++;
                    pointsLeft--;
                    _toon.StatUpate();
                }
            }
        }
    }

    private void DisplayVitals(){
        for(int cnt = 0; cnt < Enum.GetValues(typeof(VitalName)).Length; cnt++){
            GUI.Label(new Rect( OFFSET,                                             //x
                                statStartingPos + ((cnt + 7)* LINE_HEIGHT),         //y
                                STAT_LABEL_WIDTH,                                     //width
                                LINE_HEIGHT                                            //height
                               ), ((VitalName)cnt).ToString());
            GUI.Label(new Rect( OFFSET + STAT_LABEL_WIDTH,
                                statStartingPos + ((cnt + 7) * LINE_HEIGHT),
                                BASEVALUE_LABEL_WIDTH,
                                LINE_HEIGHT
                               ), _toon.GetVital(cnt).AdjustedBaseValue.ToString());
            
        }

    }

    private void DisplaySkills(){
        for(int cnt = 0; cnt < Enum.GetValues(typeof(SkillName)).Length; cnt++){
            GUI.Label(new Rect( OFFSET + STAT_LABEL_WIDTH + BASEVALUE_LABEL_WIDTH + BUTTON_WIDTH * 2 + OFFSET * 2,             //x
                                statStartingPos + (cnt * LINE_HEIGHT),                                                         //y
                                STAT_LABEL_WIDTH,                                                                             //width
                                LINE_HEIGHT                                                                                    //height
                               ), ((SkillName)cnt).ToString());
            GUI.Label(new Rect( OFFSET + STAT_LABEL_WIDTH + BASEVALUE_LABEL_WIDTH + BUTTON_WIDTH * 2 + OFFSET * 2 + STAT_LABEL_WIDTH,     //x
                                statStartingPos + (cnt * LINE_HEIGHT),                                                                     //y
                                BASEVALUE_LABEL_WIDTH,                                                                                     //width
                                LINE_HEIGHT                                                                                                //height
                               ), _toon.GetSkill(cnt).AdjustedBaseValue.ToString());
            
        }
        
    }

    private void DisplayPointsLeft(){
        GUI.Label(new Rect(250, 10, 100, 25), "Points Left: " + pointsLeft.ToString());

    }
}

B3. Tạo thẻ Project, nhấp chọn nút Creat và chọn Folder và đặt tên là Scenes. Ấn Ctrl + Shift + S để save scene hiện tại lại với tên là Targetting và kéo thả vào thư mục Scenes.


B4. Vào File | New Scene, sau đó ấn Ctrl + S để save scene này lại và đặt tên là Character Generator.


B5. Tại thẻ Project, kéo thả file C# Script CharacterGenerator vào MainCamera ở thẻ Hierarchy.

B6. Double click vào file BaseCharacter ở thẻ Project | Script | Character Classes và chèn thêm đoạn code được tô đỏ như sau:

    private void SetupVitals(){
        for(int cnt = 0; cnt < _vital.Length; cnt++){
            _vital[cnt] = new Vital();
        }
        SetupVitalModifiers();
       
    }
    private void SetupSkills(){
        for(int cnt = 0; cnt < _skill.Length; cnt++){
            _skill[cnt] = new Skill();
        }
        SetupSkillModifiers();
       
    }

B7. Bấm Ctrl + S để save scene Character Generator lại. Ấn nút Play để kiểm tra thành quả.

Thứ Ba, 10 tháng 12, 2013

Cookbook - HIỆU ỨNG TẠO HẠT

 - Hiệu ứng tạo hạt -

Với phiên bản Unity 3.5 trở đi, rất nhiều hiệu ứng hữu dụng được tạo ra bởi Particle System ( Hệ thống tạo hạt ). Nhiều hiệu ứng cần phải viết script đều được cung cấp trong hệ thống Shuriken particle.


Chuẩn bị

Trước tiên hãy tìm hiểu qua về các thiết lập và khung giá trị của particle system trước khi sử dụng nó.
- Energy : Khoảng thời gian các hạt tồn tại trong game cho đến khi nó biến mất.
- Looping : Số lượng các hạt sẽ được khởi tạo lại khi hạt cuối cùng biến mất.
- Speed, direction, rotation : Mỗi hạt đều có hướng di chuyển khác nhau, tùy theo điều chỉnh tốc độ, hướng di chuyển hay góc độ quay của từng hạt.

Ngoài ra còn có các điều chỉnh phụ phía sau khung nhập giá trị như:
- Constant : Các hạt đều mang các thông số tùy chỉnh giống nhau.
- Curve : Các thông số của các hạt sẽ thay đổi dựa theo độ lồi lõm của đồ thị (VD: các hạt ban đầu từ từ to hơn theo thời gian hoặc ngược lại).
- Random number between maximum and minimum constants: Thông số của các hạt sẽ thay đổi dựa theo 2 giá trị hằng số người dùng nhập vào.
-Random number between two curve: Thông số của các hạt sẽ thay đổi dựa theo 2 đồ thị người dùng thiết lập.

Hình dưới đây là 2 đồ thị điều chỉnh kích thước của các hạt, các hạt lúc mới tạo thành sẽ có kích thước nhỏ ( từ 0.0  đến 0.2 ), và trở nên to dần sau 3 giây ( trục ngang - x ).
Kích thước của các hạt được lấy ngẫu nhiên trong vùng giữa 2 đồ thị, vì thế càng về cuối, giá trị này sẽ thay đổi trong khoảng từ 1.0  đến 2.8 so với ban đầu.



Cách để làm

B1.  Tạo project mới (File | New project) và đánh dấu vào ô Particle.unityPackage.


B2. Tại thẻ Hierarchy, nhấp chọn nút Creat | Particle System.

B3. Nhấp chuột chọn Particle System ở thẻ Hierarchy, qua thẻ Inspector, bấm chuột vào hình bánh răng kế bên component Particle System và chọn Reset.


B4. Vẫn tại thẻ Inspector, kéo thanh trượt xuống bên dưới cùng của component Particle System. Bấm chuột chọn dòng Renderer và đổi Material thành SoapBubble.


B5. Điều chỉnh StartSpeed thành 2 và StartSize là hằng số ngẫu nhiên từ 0.2 đến 1.



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

Project RPG BÀI 5.2. NỀN TẢNG NHÂN VẬT


Để tạo được các thuộc tính vật lý hay các thông số vật lý cho nhân vật, các bạn phải khai báo các thuộc tính này thành các class khác nhau, bài viết này chủ yếu khởi tạo các thuộc tính cơ bản của người chơi như Tốc độ, Trí tuệ, Sức mạnh,... tất cả đều code bằng C# và chưa thể sử dụng được liền mà phải đợi sử dụng ở Bài 6. Khởi Tạo Nhân Vật.



B1. Nhấp phải vào folder Character Classes rồi chọn Creat | C Sharp Script và đặt tên là BaseCharacter.

B2. Double click vào file C# vừa tạo và chèn đoạn code sau vào:

using UnityEngine;
using System.Collections;
using System;                    //added to access the enum class

public class BaseCharacter : MonoBehaviour {
    private string _name;
    private int _level;
    private uint _freeExp;

    private Attribute[] _primaryAttribute;
    private Vital[] _vital;
    private Skill[] _skill;

    public void Awake(){
        _name = string.Empty;
        _level = 0;
        _freeExp = 0;

        _primaryAttribute = new Attribute[Enum.GetValues(typeof(AttributeName)).Length];
        _vital = new Vital[Enum.GetValues(typeof(VitalName)).Length];
        _skill = new Skill[Enum.GetValues(typeof(SkillName)).Length];

        SetupPrimaryAttributes();
        SetupVitals();
        SetupSkills();
    }

    public string Name{
        get{ return _name;}
        set{ _name = value;}
    }
    public int Level{
        get{ return _level;}
        set{ _level = value;}
    }
    public uint FreeExp{
        get{ return _freeExp;}
        set{ _freeExp = value;}
    }
       
    public void AddExp(uint exp){
        _freeExp += exp;

        CalculateLevel();
    }

    //take avg of all of the players skills and assign that as the player lv
    public void CalculateLevel(){

    }

    private void SetupPrimaryAttributes(){
        for(int cnt = 0; cnt < _primaryAttribute.Length; cnt++){
            _primaryAttribute[cnt] = new Attribute();
        }
    }
    private void SetupVitals(){
        for(int cnt = 0; cnt < _vital.Length; cnt++){
            _vital[cnt] = new Vital();
        }
       
    }
    private void SetupSkills(){
        for(int cnt = 0; cnt < _skill.Length; cnt++){
            _skill[cnt] = new Skill();
        }
       
    }

    public Attribute GetPrimaryAttribute(int index){
        return _primaryAttribute[index];
    }

    public Vital GetVital(int index){
        return _vital[index];
    }

    public Skill GetSkill(int index){
        return _skill[index];
   
    }
    private void SetupVitalModifiers(){
        //health
        GetVital((int)VitalName.Health).AddModifier(new ModifyingAttribute(GetPrimaryAttribute((int)AttributeName.Constituion), .5f));
        //energy
        GetVital((int)VitalName.Energy).AddModifier(new ModifyingAttribute(GetPrimaryAttribute((int)AttributeName.Constituion), 1));
        //mana
        GetVital((int)VitalName.Mana).AddModifier(new ModifyingAttribute(GetPrimaryAttribute((int)AttributeName.Willpower), 1));

    }
    private void SetupSkillModifiers(){
        //melee offence
        GetSkill((int)SkillName.Melee_Offence).AddModifier(new ModifyingAttribute(GetPrimaryAttribute((int)AttributeName.Might), .33f));
        GetSkill((int)SkillName.Melee_Offence).AddModifier(new ModifyingAttribute(GetPrimaryAttribute((int)AttributeName.Nimbleness), .33f));
        //melee defence
        GetSkill((int)SkillName.Melee_Defence).AddModifier(new ModifyingAttribute(GetPrimaryAttribute((int)AttributeName.Speed), .33f));
        GetSkill((int)SkillName.Melee_Defence).AddModifier(new ModifyingAttribute(GetPrimaryAttribute((int)AttributeName.Constituion), .33f));
        //magic offence
        GetSkill((int)SkillName.Magic_Offence).AddModifier(new ModifyingAttribute(GetPrimaryAttribute((int)AttributeName.Concentration), .33f));
        GetSkill((int)SkillName.Magic_Offence).AddModifier(new ModifyingAttribute(GetPrimaryAttribute((int)AttributeName.Willpower), .33f));
        //magic defence
        GetSkill((int)SkillName.Magic_Defence).AddModifier(new ModifyingAttribute(GetPrimaryAttribute((int)AttributeName.Concentration), .33f));
        GetSkill((int)SkillName.Magic_Defence).AddModifier(new ModifyingAttribute(GetPrimaryAttribute((int)AttributeName.Willpower), .33f));
        //ranged offence
        GetSkill((int)SkillName.Ranged_Offence).AddModifier(new ModifyingAttribute(GetPrimaryAttribute((int)AttributeName.Concentration), .33f));
        GetSkill((int)SkillName.Ranged_Offence).AddModifier(new ModifyingAttribute(GetPrimaryAttribute((int)AttributeName.Speed), .33f));
        //ranged defence
        GetSkill((int)SkillName.Ranged_Defence).AddModifier(new ModifyingAttribute(GetPrimaryAttribute((int)AttributeName.Speed), .33f));
        GetSkill((int)SkillName.Ranged_Defence).AddModifier(new ModifyingAttribute(GetPrimaryAttribute((int)AttributeName.Nimbleness), .33f));
    }

    public void StatUpate(){
        for(int cnt = 0; cnt < _vital.Length; cnt++)
            _vital[cnt].Update();
        for(int cnt = 0; cnt < _skill.Length; cnt++)
            _skill[cnt].Update();
    }
}

Thứ Sáu, 6 tháng 12, 2013

Project RPG BÀI 5.1. THÔNG SỐ NHÂN VẬT


Để tạo được các thuộc tính vật lý hay các thông số vật lý cho nhân vật, các bạn phải khai báo các thuộc tính này thành các class khác nhau, bài viết này chủ yếu khởi tạo các thuộc tính cơ bản của người chơi như Tốc độ, Trí tuệ, Sức mạnh,... tất cả đều code bằng C# và chưa thể sử dụng được liền mà phải đợi sử dụng ở các bài viết sau.


B1. Nhấp phải vào thư mục Script ở thẻ Project rồi chọn Creat | Folder và đặt tên là Character Classes.


B2. Nhấp phải vào thư mục Character Classes rồi chọn Creat | C Sharp Script và đặt tên là BaseStat.

B3. Double Click vào file C# vừa tạo và chèn đoạn code sau vào:

 public class BaseStat {
    private int _baseValue;                //the base value of this stat
    private int _buffValue;                //the amount of the buff to this stat
    private int _expToLevel;            //the total amount of exp needed to raise this skill
    private float _levelModifier;            //the modifier applied to the exp needed to raise this skill

    public BaseStat(){
        _baseValue = 0;
        _buffValue = 0;
        _levelModifier = 1.1f;
        _expToLevel = 100;
    }

    #region Basic Setters and Getters
    //Basic Setters and Getters
    public int BaseValue{
        get{ return _baseValue;}
        set{ _baseValue = value;}

    }

    public int BuffValue{
        get{ return _buffValue;}
        set{ _buffValue = value;}
      
    }

    public int ExpToLevel{
        get{ return _expToLevel;}
        set{ _expToLevel = value;}
      
    }

    public float LevelModifier{
        get{ return _levelModifier;}
        set{ _levelModifier = value;}
      
    }
    #endregion

    private int CalculateExpToLevel(){
        return (int)(_expToLevel * _levelModifier);
    }

    public void LevelUp(){
        _expToLevel = CalculateExpToLevel();
        _baseValue++;
    }

    public int AdjustedBaseValue{
        get{ return _baseValue + _buffValue; }
    }
}


B4. Tạo thêm file C Sharp Script trong thư mục Character Classes và đặt tên là Attribute.

B5. Double Click vào file C# vừa tạo và chèn đoạn code sau vào:

public class Attribute : BaseStat {
    public Attribute(){
        ExpToLevel = 50;
        LevelModifier = 1.05f;
    }
}

public enum AttributeName{
    Might,
    Constituion,
    Nimbleness,
    Speed,
    Concentration,
    Willpower,
    Charisma
}


B6.  Tạo thêm file C Sharp Script trong thư mục Character Classes và đặt tên là ModifiedStat.

B7. Double Click vào file C# vừa tạo và chèn đoạn code sau vào:

 using System.Collections.Generic;

public class ModifiedStat : BaseStat {
    private List<ModifyingAttribute> _mods;        //A listof Attribute that modify this stat
    private int _modValue;                        //The amount added to the baseValue from the modifiers

    public ModifiedStat(){
        _mods = new List<ModifyingAttribute>();
        _modValue = 0;
    }

    public void AddModifier( ModifyingAttribute mod ){
        _mods.Add(mod);
    }

    private void CalculateModValue(){
        _modValue = 0;

        if(_mods.Count > 0)
            foreach(ModifyingAttribute att in _mods)
                _modValue += (int)(att.attribute.AdjustedBaseValue * att.ratio);
    }

    public new int AdjustedBaseValue{
        get{ return BaseValue + BuffValue + _modValue; }
    }

    public void Update(){
        CalculateModValue();
    }
}

public struct ModifyingAttribute{
    public Attribute attribute;
    public float ratio;

    public ModifyingAttribute(Attribute att, float rat){
        attribute = att;
        ratio = rat;
    }
}


B8. Tạo thêm file C Sharp Script trong thư mục Character Classes và đặt tên là Vital.

B9. Double Click vào file C# vừa tạo và chèn đoạn code sau vào:

public class Vital : ModifiedStat {
    private int _curValue;

    public Vital(){
        _curValue = 0;
        ExpToLevel = 50;
        LevelModifier = 1.1f;
    }

    public int CurValue{
        get{
            if(_curValue > AdjustedBaseValue)
                _curValue = AdjustedBaseValue;

            return _curValue;
        }
        set{ _curValue = value; }
    }
}

public enum VitalName{
    Health,
    Energy,
    Mana
}


B10. Tạo thêm file C Sharp Script trong thư mục Character Classes và đặt tên là Skill.

 

B11. Double Click vào file C# vừa tạo và chèn đoạn code sau vào:

 public class Skill : ModifiedStat {
    private bool _known;

    public Skill(){
        _known = false;
        ExpToLevel = 25;
        LevelModifier = 1.1f;
    }

    public bool Known{
        get{ return _known; }
        set{ _known = value; }
    }
}

public enum SkillName{
    Melee_Offence,
    Melee_Defence,
    Ranged_Offence,
    Ranged_Defence,
    Magic_Offence,
    Magic_Defence
}