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

AI 1.5 - VẬN ĐỘNG - LOCOMOTION

  1.5 - Vận động - Locomotion

Vận động - Locomotion

Các loài vật (bao gồm cả con người) đều có hệ thống cơ xương rất phức tạp (hệ thống vận động - locomotor system), giúp chúng ta có khả năng di chuyển cả cơ thể bằng việc sử dụng hệ thống cơ và xương. Chúng ta biết nơi để đặt chân khi trèo lên một cái thang, bậc thang, hoặc trên đất không bằng phẳng, và chúng ta biết cách giữ cơ thể thăng bằng để giữ ổn định tất cả những tư thế khác mà chúng ta muốn thực hiện. Chúng ta có thể làm tất cả những điều này nhờ vào xương, cơ, khớp, và các mô của chúng ta, một tập miêu tả toàn bộ hệ thống vận động của chúng ta (locomotor system). 


Bây giờ đặt chúng vào trong viễn cảnh phát triển game. Hãy nói chúng ta có một nhân vật con người, kẻ đó cần đi trên cả bề mặt bằng phẳng và nhấp nhô đấy, hoặc trên chỗ đất hơi dốc, và chúng ta chỉ có một hoạt cảnh cho một chu trình "đi" (walk). Nếu không có hệ thống vận động cho nhân vật ảo của chúng ta, kết quả sẽ như hình dưới đây:

Bước lên bậc thang khi không dùng hệ thống vận động

Đầu tiên chúng ta sẽ làm hoạt cảnh nhân vật bước đi tiến về phía trước. Giờ đây nhân vật biết nó đang xuyên qua bề mặt. Vì thế, hệ thống phát hiện va chạm (collision) sẽ kéo nhân vật lên trên bề mặt để ngăn chặn việc đi xuyên vào bề mặt vật thể. Đây là cách chúng ta thường dùng để thực hiện việc chuyển động trên một bề mặt nhấp nhô. Thậm chí dù nó không trông hoặc cảm giác có vẻ gì là thật cả, nó vẫn làm được và dễ thực thi.

Chúng ta hãy xem làm thế nào để chúng ta thật sự bước lên cầu thang nhé. Chúng ta đặt chân của mình vững chắc lên các bậc thang, và dùng lực đẩy phần còn lại của cơ thể lên trên để chuẩn bị bước tiếp theo. Đây là cách mà chúng ta làm trong thực tế, bằng hệ thống vận động tiến bộ của chúng ta.



Tuy nhiên, nó lại không quá đơn giản để thực thi mức độ hiện thực này trong các trò chơi. Chúng ta sẽ phải cần nhiều hoạt cảnh cho các kịch bản khác nhau, bao gồm leo thang, đi/chạy lên cầu thang,... Vì thế, chỉ những studio lớn mới có nhiều hoạt cảnh để thực hiện điều này trong quá khứ, cho tới khi chúng ta đến được với một hệ thống tự động.

Bước lên bậc thang khi dùng hệ thống vận động

May thay, Unity 3D đã có phần hỗ trợ để thực hiện vấn đề này, một hệ thống chuyển động - locomotor system.

Hệ thống vận động trong Unity

Hệ thống này có thể trộn một cách tự động vào chu kỳ đi/chạy của nhân vật, và điều chỉnh các chuyển động của xương chân, để đảm bảo rằng các bước chân đặt chính xác trên nền đất. Nó cũng có thể điều chỉnh các hoạt cảnh gốc, tạo ra một tốc độ và hướng cụ thể trên bất kỳ bề mặt, bước đi bất kỳ và cả độ dốc.

Chúng ta sẽ xem cách dùng hệ thống vận động này để áp dụng các chuyển động thực vào cho các nhân vật AI của chúng ta trong chương sau AI 2..

Thứ Tư, 29 tháng 1, 2014

Project RPG BÀI 9. XUẤT THÔNG TIN NHÂN VẬT



Đã xong giai đoạn lưu trữ thông tin nhân vật khởi tạo ban đầu, bây giờ là xuất thông tin đó ra dưới dạng câu lệnh thông báo trong hộp thoại Console của Unity (Debug.Log). :)



B1. Chắc rằng bạn đang ở Scene, Character Generator (bao gồm 1 Main Camera và __GameSettings đã gắn script GameSettings), ấn Ctrl + S để save scene này lại.

B2. Vào File | New Scene (Ctrl + N) để tạo scene mới và save scene này lại với tên là Level1.


B3. Double click vào C# GameSettings và xóa tất cả đi, chèn lại đoạn code sau vào:

using UnityEngine;
using System.Collections;
using System;

public class GameSettings : MonoBehaviour {

    void Awake(){
        DontDestroyOnLoad(this);
    }
    // Use this for initialization

    public void SaveCharacterData(){
        GameObject pc =GameObject.Find("pc");

        PlayerCharacter pcClass = pc.GetComponent<PlayerCharacter>();
        PlayerPrefs.DeleteAll();
        PlayerPrefs.SetString("Player Name", pcClass.Name);


        for(int cnt = 0; cnt < Enum.GetValues(typeof(AttributeName)).Length; cnt++){
            PlayerPrefs.SetInt (((AttributeName)cnt).ToString() + " - Base Value", pcClass.GetPrimaryAttribute(cnt).BaseValue);
            PlayerPrefs.SetInt (((AttributeName)cnt).ToString() + " - EXP To Level", pcClass.GetPrimaryAttribute(cnt).ExpToLevel);
        }

        for(int cnt = 0; cnt < Enum.GetValues(typeof(VitalName)).Length; cnt++){
            PlayerPrefs.SetInt (((VitalName)cnt).ToString() + " - Base Value", pcClass.GetVital(cnt).BaseValue);
            PlayerPrefs.SetInt (((VitalName)cnt).ToString() + " - EXP To Level", pcClass.GetVital(cnt).ExpToLevel);
            PlayerPrefs.SetInt (((VitalName)cnt).ToString() + " - Cur Value", pcClass.GetVital(cnt).CurValue);

            //PlayerPrefs.SetString(((VitalName)cnt).ToString() + " - Mobs", pcClass.GetVital(cnt).GetModifyingAttributesString());

        }
       
        for(int cnt = 0; cnt < Enum.GetValues(typeof(SkillName)).Length; cnt++){
            PlayerPrefs.SetInt(((SkillName)cnt).ToString() + " - Base Value", pcClass.GetSkill(cnt).BaseValue);
            PlayerPrefs.SetInt(((SkillName)cnt).ToString() + " - EXP To Level", pcClass.GetSkill(cnt).ExpToLevel);

            //PlayerPrefs.SetString(((SkillName)cnt).ToString() + " - Mobs", pcClass.GetSkill(cnt).GetModifyingAttributesString());

        }
    }

    public void LoadCharacterData(){
        GameObject pc = GameObject.Find("pc");
       
        PlayerCharacter pcClass = pc.GetComponent<PlayerCharacter>();

        pcClass.Name = PlayerPrefs.GetString("Player Name", "Name me");

        for(int cnt = 0; cnt < Enum.GetValues(typeof(AttributeName)).Length; cnt++){
            pcClass.GetPrimaryAttribute(cnt).BaseValue = PlayerPrefs.GetInt (((AttributeName)cnt).ToString() + " - Base Value", 0);
            pcClass.GetPrimaryAttribute(cnt).ExpToLevel = PlayerPrefs.GetInt (((AttributeName)cnt).ToString() + " - EXP To Level", 1);
        }

        for(int cnt = 0; cnt < Enum.GetValues(typeof(VitalName)).Length; cnt++){
            pcClass.GetVital(cnt).BaseValue = PlayerPrefs.GetInt (((VitalName)cnt).ToString() + " - Base Value", 0);
            pcClass.GetVital(cnt).ExpToLevel = PlayerPrefs.GetInt (((VitalName)cnt).ToString() + " - EXP To Level", 0);

            pcClass.GetVital(cnt).Update();

            //get the stored value for the curValue for each of the Vital
            pcClass.GetVital(cnt).CurValue = PlayerPrefs.GetInt(((VitalName)cnt).ToString() + " - Cur Value", 1);
        }

        for(int cnt = 0; cnt < Enum.GetValues(typeof(SkillName)).Length; cnt++){
            pcClass.GetSkill(cnt).BaseValue = PlayerPrefs.GetInt(((SkillName)cnt).ToString() + " - Base Value", 0);
            pcClass.GetSkill(cnt).ExpToLevel = PlayerPrefs.GetInt(((SkillName)cnt).ToString() + " - EXP To Level", 0);

        }
        //output curValue for each of the Vital
        for(int cnt = 0; cnt < Enum.GetValues(typeof(SkillName)).Length; cnt++){
            Debug.Log(((SkillName)cnt).ToString() + " : " + pcClass.GetSkill(cnt).BaseValue+ " - " + pcClass.GetSkill(cnt).ExpToLevel );
        }
    }
}


B4. Nhấp phải vào folder Script và chọn Create | C# Script rồi đặt tên là GameMaster. Double click vào C# Script vừa tạo và chèn đoạn code sau vào:

using UnityEngine;
using System.Collections;

public class GameMaster : MonoBehaviour {
    public GameObject playerCharacter;
    public GameObject gameSettings;
    public Camera mainCamera;

    public float zOffset;
    public float yOffset;
    public float xRotOffset;

    private GameObject _pc;
    private PlayerCharacter _pcScript;

    // Use this for initialization
    void Start () {
        _pc = Instantiate(playerCharacter, Vector3.zero, Quaternion.identity) as GameObject;
        _pc.name = "pc";

        _pcScript = _pc.GetComponent<PlayerCharacter>();

        zOffset = -2.5f;
        yOffset = 2.5f;
        xRotOffset = 22.5f;
        mainCamera.transform.position = new Vector3 (_pc.transform.position.x, _pc.transform.position.y + yOffset, _pc.transform.position.z + zOffset);
        mainCamera.transform.Rotate(xRotOffset, 0, 0);

        LoadCharacter();
    }

    public void LoadCharacter(){
        GameObject gs = GameObject.Find("__GameSettings");

        if(gs == null){
            GameObject gs1 = Instantiate(gameSettings, Vector3.zero, Quaternion.identity) as GameObject;
            gs1.name = "__GameSettings";
        }
            GameSettings gsScript = GameObject.Find ("__GameSettings").GetComponent<GameSettings>();

            //loading the character data
            gsScript.LoadCharacterData();


    }
}



B5. Vào Game Object | Create Empty và đổi tên thành  Game Master. Kéo thả file C# GameMaster ở thẻ Project vừa tạo ở bước trên vào Game Master ở thẻ Hierarchy.

B6. Nhấp chọn Game Master ở thẻ Hierarchy. Kéo thả Player Character Prefabs trong thư mục Prefabs ở thẻ Project vào mục Player Character. Và chọn Game Master ở thẻ Hierarchy. Kéo thả Main Camera vào script ở thẻ Inspector như sau:


B7. Tại thẻ Project, nhấp chọn Create | Folder và đặt tên là Assets. Tải file Human.rar này về rồi giải nén ra. Kéo thả thư mục Human vào folder Assets vừa tạo trong Unity.


B8. Kéo thả Player Character Prefabs ở thẻ Project vào Hierarchy.


B9. Kéo thả HumanAnimated ở thẻ Project vào Player Character Prefabs ở thẻ Hierarchy.


B10. Kéo thả Player Character Prefabs ở thẻ Hierarchy vào Player Character Prefabs ở thẻ Project.


B11. Xóa Player Character Prefabs ở thẻ Hierarchy đi.


B12. Vào Game Object | Create Other | Directional Light và chỉnh thông số ở thẻ Inspector như sau:


B13. Vào File | Build Settings, trong bảng Scenes in build chọn Targetting Example và ấn Delete. Kéo thả Level1 ở thẻ Project / Scenes vào vị trí bên dưới Character Generator.unity. Sau đó tắt cửa sổ này đi.


B14. Double click vào file C# Character Generator và chỉnh dòng code được khoanh đỏ thành hình dưới đây:


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


B16. Ấn Ctrl + S để save scene Level1 này lại. Double click vào scene Charater Generator ở thẻ Project, kéo thả __GameSettings ở thẻ Hierarchy vào prefab Game Settings ở thẻ Project.


B17. Save scene này lại, double click vào scene Level1, tại thẻ Hierarchy, nhấp chuột chọn Game Master và kéo thả Game Settings ở thẻ Project / Prefabs vào thẻ Inspector như sau:


B18. Double click vào file C# Vital và sửa ExpToLevel thành 40 như hình sau:


B19. Double click vào scene Character Generator và ấn nút Play để kiểm tra thành quả. Ấn Ctrl + Shift + C để mở hộp thoại Console.

Thứ Tư, 22 tháng 1, 2014

AI 1.4 - CÂY HÀNH VI - BEHAVIOR TREES


  1.4 - Cây hành vi - Behavior Trees

Cây hành vi - Behavior Trees
Cây hành vi là các kỹ thuật dùng để thể hiện và điều khiển tính logic phía sau các nhân vật AI. Nó trở nên nổi tiếng đối với các trình ứng dụng trong các game AAA, chẳng hạn như Halo và Spore.


http://www.7outof10.co.uk/wp-content/uploads/2011/11/HaloAnniversary_banner1.jpg
Ảnh trong game Halo

Trước đây, chúng ta chỉ nói ngắn gọn về FSM. FSM đưa ra một cách rất đơn giản để định tính logic của một nhân vật AI, dựa vào trạng thái (state) và sự thay đổi trạng thái (transition) khác nhau của chúng.

Tuy nhiên, FSM được cho là khó khăn trong việc sắp xếp và tái sử dụng logic. Chúng ta cần thêm các trạng thái (state) và nhiều sự thay đổi trạng thái (transition), để hỗ trợ tất cả các kịch bản, mà chúng ta muốn cân nhắc cho nhân vật AI của mình. Vì thế, chúng ta cần tiến gần đến sự co giãn khi giải quyết vấn đề rắc rối này. Cây hành vi là cách hay hơn để thực thi các nhân vật AI có khả năng sẽ trở nên càng ngày càng phức tạp.

Các thành phần cơ bản của một cây hành vi chính là nhiệm vụ (task), đối với FSM thì các trạng thái chính là các thành phần chủ yếu. Có vài nhiệm vụ (task) khác nhau chẳng hạn như Chuỗi sự kiện (Sequence), Bộ chọn (Selector), Người trang trí (Decorator) và Song song (Parallel). Điều này rất ư là lộn xộn. Cách tốt nhất để hiểu nó chính là nhìn vào một ví dụ cụ thể. Chúng ta hãy thử chuyển ví dụ của chúng ta từ phần FSM bằng cách dùng một cây hành vi nhé. Chúng ta sẽ phá bỏ tất cả các bộ chuyển trạng thái (transition) cùng các trạng thái (state) để tạo thành các nhiệm vụ (task).

Task
Chúng ta hãy nhìn vào một nhiệm vụ của Bộ chọn nhiệm vụ (Selector task) của cây hành vi này. Bộ chọn (Selector) thể hiện bằng một hình tròn với một dấu chấm hỏi bên trong. Đầu tiên, nó sẽ chọn tấn công người chơi. Nếu nhiệm vụ tấn công (Attack) thành công, bộ chọn (Selector) hoàn tất và sẽ trở lại nút cha, nếu nó chỉ có một nút thôi. Còn nếu nhiệm vụ Attack thất bại, nó sẽ thử đến nhiệm vụ Đuổi theo (Chase). Nếu nhiệm vụ Chase thất bại nữa, nó sẽ thử nhiệm vụ Do thám (Patrol).


Selector task

Vậy còn các trận đấu thử nghiệm (test) thì sao? Chúng cũng là một phần của các nhiệm vụ (task) trong cây hành vi đấy.

Biểu đồ bên dưới sẽ chỉ ra cách dùng các nhiệm vụ Chuỗi sự kiện (Sequence task), được biểu thị bằng một hình vuông có mũi tên bên trong. Gốc của bộ chọn có thể chọn hành động của Chuỗi sự kiện đầu tiên (Sequence).

Nhiệm vụ đầu tiên của hành động Chuỗi sự kiện (Sequence) chính là kiểm tra nhân vật của người chơi có Đủ gần để tấn công hay không? (Close enough to attack?).

- Nếu chính là thế, nó sẽ bắt đầu thực hiện nhiệm vụ tiếp theo, đó chính là tấn công người chơi (Attack). Nếu nhiệm vụ tấn công (Attack) cũng trả về kết quả thành công, toàn bộ chuỗi sự kiện (Sequence) sẽ trả về kết quả thành công, và bộ chọn (Selector) được hoàn tất với hành vi này, và nó sẽ không tiếp tục các nhiệm vụ của Chuỗi sự kiện (Sequence task) khác nữa.

- Nếu không phải, thì hành động Chuỗi sự kiện (Sequence) sẽ không bắt đầu nhiệm vụ tấn công (Attack), và trả về cho bộ chọn cha trạng thái thất bại. Rồi bộ chọn sẽ chọn nhiệm vụ kế tiếp trong chuỗi sự kiện, Người chơi Mất dấu hoặc Bị giết? (Lost or Killed Player?).


Sequence Tasks

Hai thành phần chung khác chính là Song song (Parallel) Người trang trí (Decorator).

Một nhiệm vụ Song song (Parallel) sẽ thực thi tất cả các nhiệm vụ con của nó cùng một lúc đấy, trong khi Chuỗi sự kiện (Sequence) cùng nhiệm vụ Bộ chọn (Selector) chỉ thực thi các nhiệm vụ con của nó từng cái một mà thôi.

Người trang trí (Decorator) là một kiểu nhiệm vụ khác, nó chỉ có một con thôi. Nó có thể thay đổi hành vi của các nhiệm vụ của chính đứa con của nó, bao gồm việc có vận hành nhiệm vụ của con nó hay không, bao nhiêu lần nên vận hành,...

Chúng ta sẽ nghiên cứu cách thực hiện một hệ thống cây hành vi cơ bản trong AI 9. Cây hành vi - Behavior Trees. Có một bộ tích hợp miễn phí dành cho Unity với tên gọi là Behave in the Unity Asset Store. Hành vi (Behave) là một công cụ hữu ích, GUI editor miễn phí để cài đặt cây hành vi cho các nhân vật AI, và chúng ta sẽ tìm hiểu chi tiết của nó sau nhé.


Thứ Sáu, 17 tháng 1, 2014

COOKBOOK - TẠO VẬT LIỆU PHẢN CHIẾU


  - Tạo vật liệu phản chiếu -

Kim loại, sơn bóng, nhựa bóng là các vật liệu phản chiếu điển hình. Unity cũng cung cấp cho chúng ta môi trường để có thể tạo nên vật liệu phản chiếu như vậy.


Chuẩn bị

ReflectionMap.unitypackage


Cách để làm

B1. Tại thẻ Project, nhấp phải vào chỗ trống và chọn Import Package | Custom Package và mở file vừa tải lên.

B2. Double click vào reflectiveMaterialLevel để mở scene này lên. Bao gồm 1 model battery được gắn code xoay vòng, directional light và Main Camera với code xoay quanh battery.

 

B3. Tại thẻ Project, vào Assets / ReflectionMap và nhấp chuột chọn reflectionMap.jpg. Qua thẻ Inspector điều chỉnh các thông số như hình sau rồi nhấn Apply:

 

B4. Tại thẻ Project, nhấp chuột chọn vật liệu battery.mat và kéo thả reflectionMap vừa điều chỉnh ở bước trên vào Reflection Cubemap ở thẻ Inspector của vật liệu battery.mat đang chọn.

 

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


Nguyên lý làm việc...

Kênh ảnh Alpha (Alpha channel thường thấy trong các trình chỉnh sửa ảnh như Photoshop) đã tạo nên hiệu ứng phản chiếu, trong ví dụ này, các lớp vật liệu phản chiếu phụ thuộc vào mức độ sáng của từng điểm ảnh. Nghĩa là vật liệu của Cubemap phản chiếu sẽ càng dễ thấy khi kênh alpha càng trắng.

Cubemap phản chiếu được tạo nên bởi sáu tấm ảnh khác nhau, được tạo nên từ file ảnh gốc từ Texture Type khi được điều chỉnh thành Reflection. Sáu tấm ảnh được gán vào là trên, dưới, trước, sau, trái, phải.


Tham khảo thêm

Reflecting actual scene geometry

Thứ Tư, 15 tháng 1, 2014

Project RPG BÀI 8. LƯU TRỮ NHÂN VẬT KHỞI TẠO


 Chúng ta sẽ tiếp tục công việc sau khi đã khởi tạo và thiết lập các thông số phù hợp nhân vật cho người chơi. Bước tiếp theo sẽ là lưu trữ thông tin của nhân vật mà người dùng đã thiết lập lại. Công việc lưu trữ này sẽ được thực hiện thông qua bằng Prefab (Kho chứa Game Object hoặc component có thể dùng lại nhiều lần) của Unity.



B1. Chắc rằng bạn đang mở Scene Character Generator (Tại thẻ Hierarchy chỉ bao gồm 1 Main Camera đã gắn script CharacterGenerator).





B2. Nhấp phải vào folder Script ở thẻ Project và chọn Create | C# Script và đặt tên là GameSettings.

B3. 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;

public class GameSettings : MonoBehaviour {

    void Awake(){
        DontDestroyOnLoad(this);
    }
    // Use this for initialization
    void Start () {
  
    }
  
    // Update is called once per frame
    void Update () {
  
    }

    public void SaveCharacterData(){
        GameObject pc =GameObject.Find("pc");

        PlayerCharacter pcClass = pc.GetComponent<PlayerCharacter>();

        PlayerPrefs.SetString("Player Name", pcClass.Name);
        PlayerPrefs.DeleteAll();

        for(int cnt = 0; cnt < Enum.GetValues(typeof(AttributeName)).Length; cnt++){
            PlayerPrefs.SetInt (((AttributeName)cnt).ToString() + " - Base Value", pcClass.GetPrimaryAttribute(cnt).BaseValue);
            PlayerPrefs.SetInt (((AttributeName)cnt).ToString() + " - EXP To Level", pcClass.GetPrimaryAttribute(cnt).ExpToLevel);
        }

        for(int cnt = 0; cnt < Enum.GetValues(typeof(VitalName)).Length; cnt++){
            PlayerPrefs.SetInt (((VitalName)cnt).ToString() + " - Base Value", pcClass.GetVital(cnt).BaseValue);
            PlayerPrefs.SetInt (((VitalName)cnt).ToString() + " - EXP To Level", pcClass.GetVital(cnt).ExpToLevel);
            PlayerPrefs.SetInt (((VitalName)cnt).ToString() + " - Cur Value", pcClass.GetVital(cnt).CurValue);

            PlayerPrefs.SetString(((VitalName)cnt).ToString() + " - Mobs", pcClass.GetVital(cnt).GetModifyingAttributesString());

        }
      
        for(int cnt = 0; cnt < Enum.GetValues(typeof(SkillName)).Length; cnt++){
            PlayerPrefs.SetInt(((SkillName)cnt).ToString() + " - Base Value", pcClass.GetSkill(cnt).BaseValue);
            PlayerPrefs.SetInt(((SkillName)cnt).ToString() + " - EXP To Level", pcClass.GetSkill(cnt).ExpToLevel);

            PlayerPrefs.SetString(((SkillName)cnt).ToString() + " - Mobs", pcClass.GetSkill(cnt).GetModifyingAttributesString());

        }
    }

    public void LoadCharacterDate(){

    }
}



B4. Vào GameObject | Create Empty và đặt tên lại thành __GameSettings. Kéo thả file C# vừa tạo vào __GameSettings ở thẻ Hierarchy.



B5. Tại thẻ Project, nhấp chọn nút Create | Folder và đặt tên là Prefabs.

B6. Vào GameObject | Create Empty. Tại thẻ Hierarchy, đổi tên GameObject vừa tạo thành Player Character.

B7. Vẫn đang chọn Player Character, qua thẻ Inspector, bấm vào hình bánh răng sau mục Transform và chọn Reset.

 

B8. Kéo thả file C# Player Character ở thẻ Project vào Player Character ở thẻ Hierarchy.

 

B9. Nhấp phải vào thư mục Prefabs ở thẻ Project và chọn Create | Prefab, đổi tên lại thành Player Character Prefab.

 

B10. Kéo thả Player Character ở thẻ Hierarchy vào prefab bạn vừa tạo ở thẻ Project.

 

B11. Xóa Player Character ở thẻ Hierarchy đi.

B12. Double click vào file C# CharacterGenerator, xóa tất cả đi và chèn lại đoạn script 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;

    public GameObject playerPrefab;


    // Use this for initialization
    void Start () {
        GameObject pc = Instantiate(playerPrefab, Vector3.zero, Quaternion.identity) as GameObject;
        pc.name = "pc";        //Player Character

        _toon = pc.GetComponent<PlayerCharacter>();

        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 () {
      //rotate camera to make the sky rotate.
      transform.Rotate(Vector3.up * Time.deltaTime * 3, Space.World);
    }

    void OnGUI(){
        GUI.skin = mySkin;
        DisplayName();
        DisplayPointsLeft();
        DisplayAttributes();
        DisplayVitals();
        DisplaySkills();
      
        if(_toon.Name == "" || pointsLeft > 0)
            DisplayCreateLabel();
        else
            DisplayCreateButton();
    }

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

    }
    private void DisplayCreateLabel(){  
        GUI.Label(new Rect( Screen.width / 2 - 50, statStartingPos + (10 * LINE_HEIGHT), 100, LINE_HEIGHT), "Creating...", "Button");

    }
    private void DisplayCreateButton(){  
        if(    GUI.Button(new Rect( Screen.width / 2 - 50, statStartingPos + (10 * LINE_HEIGHT), 100, LINE_HEIGHT), "Create")){

            GameSettings gsScript = GameObject.Find ("__GameSettings").GetComponent<GameSettings>();

            //Change the cur value of the vitals to the max modified value of that vital
            UpdateCurVitalValues();

            //save the character data
            gsScript.SaveCharacterData();

            Application.LoadLevel("Targetting Example");
        }
    }

    private void UpdateCurVitalValues(){
        for(int cnt = 0; cnt < Enum.GetValues(typeof(VitalName)).Length; cnt++){
            _toon.GetVital(cnt).CurValue = _toon.GetVital(cnt).AdjustedBaseValue;
        }
    }

}


 B13. Tại thẻ Project, vào thư mục Scenes, đổi tên Targetting thành Targetting Example (Nếu không bạn có thể tại về tại đây).
Vào File | Build Settings. Kéo thả 2 Scene đang có trong thư mục Scene ở thẻ Project vào Scenes to build. Sau đó tắt hộp thoại này đi.

 

B14. Double click vào file C# Attribute và xóa tất cả đi và chèn lại đoạn code sau:

 public class Attribute : BaseStat {
    private string _name;

    public Attribute(){
        _name = "";
        ExpToLevel = 50;
        LevelModifier = 1.05f;
    }

    public string Name{
        get{return _name;}
        set{_name = value;}
    }
}

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


B15. Double click vào file C# Vital và sửa ExptoLevel = 50 thành 40.

B16. Double click vào file C# ModifiedStat, xóa tất cả đi và chèn lại đ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 string GetModifyingAttributesString(){
        string temp = "";

        //UnityEngine.Debug.Log(_mods.Count);

        for(int cnt = 0; cnt < _mods.Count; cnt++){
            temp += _mods[cnt].attribute.Name;
            temp += "_";
            temp += _mods[cnt].ratio;

            if(cnt < _mods.Count - 1)
                temp += "|";

        }
      
        UnityEngine.Debug.Log(temp);
        return temp;
    }
}

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

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


B17. Double click vào file C# BaseCharacter, xóa tất cả đi và chèn lại đ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();
            _primaryAttribute[cnt].Name = ((AttributeName)cnt).ToString();
        }
    }
    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();
      
    }

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


B18. Nhấp chọn Main Camera, kéo thả Player Character Prefab ở thư mục Prefabs vào Player Prefab ở thẻ Inspector như hình sau:


B19. Để biết dữ liệu được lưu trữ ở đâu mời bạn tham khảo tại trang tài liệu của Unity bên dưới:
http://docs.unity3d.com/Documentation/ScriptReference/PlayerPrefs.html

B20. Save Scene Character Generator lại và ấn nút Play để kiểm tra thành quả!!