1. 程式人生 > >Unity3D教學 開發簡單版第一人稱射擊遊戲 可以多人聯機(附原始碼)

Unity3D教學 開發簡單版第一人稱射擊遊戲 可以多人聯機(附原始碼)

簡介:

這一篇文章主要是和大家分享如何製作一個屬於自己的“第一人稱射擊遊戲”,而且是要可以多人聯機的。這個遊戲屬於比簡單的,大神可以直接無視,如果有做錯的地方請大家多多指點,我也是剛學如何做遊戲。程式碼是用C#編寫,主要實現的功能有三個:第一人稱移動控制、角色控制(如射擊)、TCP服務端和客戶端。我將專案命名為FPS。

遊戲運作流程:

服務端

1. 建立服務端

2. 從各個客戶端接收資料,然後廣播給所有客戶

客戶端

1. 連線至服務端

2. 從服務端接收到其他客戶的資料(比如當前位置,是否有開槍等等),然後更新到遊戲上

3. 傳送自己當前在遊戲的狀態給服務端

聯機效果圖:

服務端


客戶端


完整原始碼與已編譯好的遊戲程式:

下載

C#指令碼簡介:

1. FirstPersonControl.cs

這個指令碼是用來做第一人稱控制的

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

public class FirstPersonControl : MonoBehaviour {

	// Use this for initialization
	void Start () {
        // 獲取攝像頭物件
        mCamera = transform.Find("Main Camera");
        // 獲取右手物件
        mRightHand = transform.Find("RightHand");
        // 獲取腳步聲播放組建
        mAudio = transform.GetComponent<AudioSource>();
	}

    // Update is called once per frame
    void Update() {
        if (UpdateMovement()) {
            PlayStepSound();
        } else {
            StopPlayStepSound();
        }

        UpdateLookAt();
    }

    // 第一人稱控制器的常量與變數
    private Transform mCamera;
    private Transform mRightHand;
    private AudioSource mAudio;
    public float mMoveSpeed = 8;         // 物體移動速度
    public float mMouseSensitivity = 5;     // 滑鼠旋轉的敏感度
    public float mMinimumX = 325;       // 向下望的最大角度
    public float mMaximumX = 45;        // 向上望的最大角度
    public float mMinimumY = -360;         // 向左望的最大角度
    public float mMaximumY = 360;       // 向右望的最大角度
    private Vector3 _curRotation = new Vector3(0,0,0);       // 當前旋轉角度

    // 更新移動位置
    private bool UpdateMovement() {
        float distance = mMoveSpeed * Time.deltaTime;   // 移動距離
        
        // 前
        if (Input.GetKey(KeyCode.W)) {
            float x = Mathf.Sin(transform.eulerAngles.y * Mathf.Deg2Rad) * distance;
            float z = Mathf.Cos(transform.eulerAngles.y * Mathf.Deg2Rad) * distance;
            transform.Translate(new Vector3(x,0,z), Space.World);
            return true;
        }

        // 後
        if (Input.GetKey(KeyCode.S)) {
            float x = Mathf.Sin(transform.eulerAngles.y * Mathf.Deg2Rad) * distance;
            float z = Mathf.Cos(transform.eulerAngles.y * Mathf.Deg2Rad) * distance;
            transform.Translate(new Vector3(-x, 0, -z), Space.World);
            return true;
        }

        // 左
        if (Input.GetKey(KeyCode.A)) {
            transform.Translate(new Vector3(-distance, 0, 0));
            return true;
        }

        // 右
        if (Input.GetKey(KeyCode.D)) {
            transform.Translate(new Vector3(distance, 0, 0));
            return true;
        }

        return false;
    }

    // 更新攝像頭指向位置
    private void UpdateLookAt() {
        // 左右旋轉
        _curRotation.y = _curRotation.y + Input.GetAxis("Mouse X") * mMouseSensitivity;
        _curRotation.y = Mathf.Clamp(_curRotation.y, mMinimumY, mMaximumY);

        // 設定身體
        Vector3 rotation = transform.eulerAngles;
        rotation.y = _curRotation.y;
        transform.eulerAngles = rotation;

        // 上下旋轉
        _curRotation.x = _curRotation.x  - Input.GetAxis("Mouse Y") * mMouseSensitivity;
        _curRotation.x = Mathf.Clamp(_curRotation.x, mMinimumX, mMaximumX);

        // 設定攝像頭
        mCamera.localEulerAngles = new Vector3(_curRotation.x, 0, 0);

        // 設定右手
        rotation = mRightHand.eulerAngles;
        rotation.x = _curRotation.x;
        mRightHand.eulerAngles = rotation;
    }

    // 播放腳步聲
    private void PlayStepSound() {
        if (!mAudio.isPlaying) {
            mAudio.Play();
        }
    }

    // 停止播放聲音
    private void StopPlayStepSound() {
        if (mAudio.isPlaying) {
            mAudio.Stop();
        }
    }
}


2. Character.cs

角色控制指令碼,主要是用來控制開槍等角色動作

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

public class Character : MonoBehaviour {

    // Use this for initialization
    void Start() {
        // 獲取攝像頭物件
        mCamera = transform.Find("Main Camera");
        // 獲取右手物件
        mRightHand = transform.Find("RightHand");
        // 獲取槍聲播放元件
        mGunAudio = transform.Find("RightHand/Pistol").GetComponent<AudioSource>();
        // 獲取火花效果
        mFireEffect = transform.Find("RightHand/Pistol/FireEffect").GetComponent<ParticleSystem>();
        // 獲取網路元件
        mNetwork = transform.GetComponent<Network>();
    }

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

        // 傳送當前狀態到服務端,然後服務端就會轉發給其他客戶
        mNetwork.SendStatus(transform.position, transform.eulerAngles, 
        mCamera.eulerAngles, mRightHand.eulerAngles, _isShooted, _hp);

        // 處理伺服器發過來的資料包,資料包裡裝著其他客戶的資訊
        ProcessPackage();
    }

    private Transform mCamera;
    private Transform mRightHand;
    private AudioSource mGunAudio;
    public GameObject mPiece;       // 開槍後撞擊產生的碎片
    private ParticleSystem mFireEffect;     // 開槍後的火花
    private bool _isShooted;    // 判斷是否開了槍
    private Network mNetwork;       // 網路元件
    public GameObject mEnemyCharacter;  // 其他客戶的例項
    private Hashtable _htEnemies = new Hashtable();    // 其他客戶的控制指令碼

    // 開槍
    private void UpdateFire() {
        if (Input.GetButtonUp("Fire1")) {
            // 射擊音效與畫面
            PlayShotSound();

            // 播放火花效果
            PlayFireEffect();

            // 判斷射擊位置
            RaycastHit hit;
            if (Physics.Raycast(mCamera.position, mCamera.forward, out hit, 100)) {
                // 被槍擊中的地方會有碎片彈出
                DrawPieces(hit);
            }

            // 設定開槍判斷
            _isShooted = true;
        } else {
            // 設定開槍判斷
            _isShooted = false;
        }
    }

    // 播放槍聲
    private void PlayShotSound() {
        mGunAudio.PlayOneShot(mGunAudio.clip);
    }

    // 畫碎片
    private void DrawPieces(RaycastHit hit) {
        for (int i = 0; i < 5; ++i) {
            GameObject p = Transform.Instantiate(mPiece);

            // 碎片撞擊到物體後的反彈位置
            Vector3 fwd = mCamera.forward * -1;
            p.transform.position = hit.point;
            p.GetComponent<Rigidbody>().AddForce(fwd * 100);

            // 0.3秒後刪除
            Destroy(p, 0.3f);
        }
    }

    // 播放火花效果
    private void PlayFireEffect() {
        mFireEffect.Play();
    }

    // 人物變數
    private int _hp = 100;

    // 受到傷害
    public void GetHurt() {
        _hp -= 10;

        if (_hp <= 0) {
            // 復活
            Revive();
        }
    }

    // 復活
    private void Revive() {
        _hp = 100;
        transform.position = new Vector3(0,1,0);
    }

    // 處理資料包
    private void ProcessPackage() {
        Network.Package p;

        // 獲取資料包直到完畢
        while (mNetwork.NextPackage(out p)) {
            // 確定不是本機,避免重複
            if (mNetwork._id == p.id) {
                return;
            }

            // 獲取該客戶相對應的人物模組
            if (!_htEnemies.Contains(p.id)) {
                AddEnemyCharacter(p.id);
            }

            // 更新客戶的人物模型狀態
            EnemyCharacter ec = (EnemyCharacter)_htEnemies[p.id];

            // 血量
            ec.SetHP(p.hp);

            // 移動動作
            ec.Move(p.pos.V3, p.rot.V3, p.cameraRot.V3, p.rightHandRot.V3);

            // 開槍
            if (p.isShooted) {
                ec.Fire();
            }
        }
    }

    // 增加客戶的人物模組
    private EnemyCharacter AddEnemyCharacter(int id) {
        GameObject p = GameObject.Instantiate(mEnemyCharacter);
        EnemyCharacter ec = p.GetComponent<EnemyCharacter>();
        
        // 修改ID
        ec.SetID(id);

        // 加入到雜湊表
        _htEnemies.Add(id, ec);

        return ec;
    }

    // 刪除客戶的人物模組
    private void RemoveEnemyCharacter(int id) {
        EnemyCharacter ec = (EnemyCharacter)_htEnemies[id];
        ec.Destroy();
        _htEnemies.Remove(id);
    }

    // 刪除所有客戶的人物模組
    public void RemoveAllEnemyCharacter() {
        foreach (int id in _htEnemies.Keys) {
            EnemyCharacter ec = (EnemyCharacter)_htEnemies[id];
            ec.Destroy();
        }
        _htEnemies.Clear();
    }
}


3.Network.cs

網路元件,用來建立服務端或客戶端。服務端負責廣播從各個客戶端接收的資料給所有客戶。客戶端則是接收從服務端接收其他客戶的資料。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
using System.Net;
using System.Net.Sockets;

public class Network : MonoBehaviour {

	// Use this for initialization
	void Start () {
        // 獲取人物元件
        mCharacter = transform.GetComponent<Character>();
        // 資料包大小
        _packageSize = PackageSize(); 
	}
	
	// Update is called once per frame
	void Update () {

	}

    // 網路設定UI
    void OnGUI() {
		GUI.Box (new Rect(0,0,800,60),"網路設定");

        if (_connected) {
		    GUI.Label (new Rect(10,25,100,25), _isServer ? "已建立服務端":"已連線服務端");
        } else if (!_connected) {
		    GUI.Label (new Rect(10,25,100,25), "未連線");
        }

		GUI.Label (new Rect(130,25,20,25), "IP:");
        GUI.Label (new Rect(270,25,40,25), "埠:");
        GUI.Label (new Rect(380,25,40,25), "ID:");

		if (!_connected && !_isServer) {
			_ip = GUI.TextField (new Rect (150, 25, 100, 25), _ip, 100);
            _port = System.Convert.ToInt32 (GUI.TextField (new Rect (310, 25, 50, 25), _port.ToString (), 100));
            _id = System.Convert.ToInt32 (GUI.TextField (new Rect (420, 25, 100, 25), _id.ToString(), 100));
		} else {
			GUI.TextField (new Rect (150, 25, 100, 25), _ip, 100);
            GUI.TextField (new Rect (310, 25, 50, 25), _port.ToString (), 100);
            GUI.TextField (new Rect (420, 25, 100, 25), _id.ToString(), 100);
        }

        if (!_connected && !_isServer) {
            if (GUI.Button(new Rect(540,25,100,25), "開啟服務端")) {
                StartServer();
                ConnectServer();
            }

            if (GUI.Button(new Rect(660,25,100,25), "連線至服務端")) {
                ConnectServer();
            }
        } else {
            if (_isServer) {
                if (GUI.Button(new Rect(540,25,100,25), "關閉服務端")) {
                    StopServer();
                }
            } else if (_connected) {
                if (GUI.Button(new Rect(540,25,100,25), "取消連線")) {
                    DisconnectServer();
                }
            }
        }
	}

    // 服務端和客戶端的共有變數
    private Character mCharacter; // 人物元件
    private bool _connected=false;  // 判斷是否已經建立連線或開啟服務端
    private bool _isServer=false;   // 判斷本程式建立的是服務端還是客戶端
    private string _ip = "127.0.0.1";           // 主機IP
    private int _port = 18000;             // 埠
    public int _id = 1 ;           // 人物id
    List<Package> _packages=new List<Package>();        // 資料包
    private int _packageSize;        // 資料包大小

    // 資料包
    [Serializable]
    public struct Package {
        public int id;
        public Vector3Serializer pos;   // 人物位置
        public Vector3Serializer rot;   // 人物旋轉角度
        public Vector3Serializer cameraRot;     // 攝像頭旋轉角度
        public Vector3Serializer rightHandRot;  // 右手旋轉角度
        public bool isShooted;      // 判斷是否有開槍
        public int hp;      // 血量
    }

    // 獲取包大小
    private int PackageSize() {
        Package p = new Package();
        byte[] b;
        Serialize(p, out b);
        return b.Length;
    }

    // 可序列化的Vector3
    [Serializable]
	public struct Vector3Serializer {
		public float x;
		public float y;
		public float z;

		public void Fill(Vector3 v3) {
			x = v3.x;
			y = v3.y;
			z = v3.z;
		}

		public Vector3 V3
		{ get { return new Vector3(x, y, z); } }
	}

    // 序列化資料包
    public bool Serialize(object obj, out byte[] result) {
        bool ret = false;
        result = null;

        try {
            MemoryStream ms = new MemoryStream();
            BinaryFormatter bf = new BinaryFormatter();
            bf.Serialize(ms, obj);
            result = ms.ToArray(); 
        
            ret = true;
        } catch (Exception e) {
            ret = false;
            Debug.Log(e.Message);
        }

        return ret;
    }

    // 反序列化資料包
    public bool Deserialize(byte[] data,out object result) {
        bool ret = false;
        result = new object();

        try {
            MemoryStream ms = new MemoryStream(data);
            BinaryFormatter bf = new BinaryFormatter();
            result = bf.Deserialize(ms);
        
            ret = true;
        } catch (Exception e) {
            ret = false;
            Debug.Log(e.Message);
        }

        return ret;
    }

    // 服務端變數
    TcpListener _listener;
    
    // 儲存已連線客戶的結構體
    private struct Client {
        public TcpClient client;
        public byte[] buffer;               // 接收緩衝區
        public List<byte> pendingData;    // 還未處理的資料
    }

    // 客戶列表
    List<Client> _clients = new List<Client>();

    // 開啟服務端
    private void StartServer() {
        try {
            _listener = new TcpListener(IPAddress.Any, _port);
            _listener.Start();
            _listener.BeginAcceptSocket(HandleAccepted, _listener);

            _isServer = true;
        } catch (Exception e) {
            Debug.Log(e.Message);
        }
    }

    // 停止停止服務端
    private void StopServer() {
        try {
            _listener.Stop();

            // 清空客戶列表
            lock (_clients) {
                foreach (Client c in _clients) {
                    RemoveClient(c);
                }
                _clients.Clear();
            }

            // 清空資料包
            lock (_packages) {
                _packages.Clear();
            }
            
            _isServer = false;
        } catch (Exception e) {
            Debug.Log(e.Message);
        }
    }

    // 處理客戶端連線的回撥函式
    private void HandleAccepted(IAsyncResult iar) {
        if (_isServer) {
            TcpClient tcpClient = _listener.EndAcceptTcpClient(iar);
            Client client = new Client();
            client.client = tcpClient;
            client.buffer = new byte[tcpClient.ReceiveBufferSize];
            client.pendingData = new List<byte>();

            // 把客戶加入到客戶列表
            lock (_clients) {
                AddClient(client);
            }

            // 開始非同步接收從客戶端收到的資料
            tcpClient.GetStream().BeginRead(
                    client.buffer,
                    0,
                    client.buffer.Length,
                    HandleClientDataReceived,
                    client);

            // 開始非同步接收連線
            _listener.BeginAcceptSocket(HandleAccepted, _listener);
        }
    }

    // 從客戶端接收資料的回撥函式
    private void HandleClientDataReceived(IAsyncResult iar) {
        try {
            if (_isServer) {
                Client client = (Client)iar.AsyncState;
                NetworkStream ns = client.client.GetStream();
                int bytesRead = ns.EndRead(iar);

                // 連線中斷
                if (bytesRead == 0) {
                    lock (_clients) {
                        _clients.Remove(client);
                    }
                    return;
                }

                // 儲存資料
                for (int i=0; i<bytesRead; ++i) {
                    client.pendingData.Add(client.buffer[i]);
                }

                // 把資料解析成包
                while (client.pendingData.Count >= _packageSize) {
                    byte[] bp = client.pendingData.GetRange(0, _packageSize).ToArray();
                    client.pendingData.RemoveRange(0, _packageSize);

                    // 把資料包分發給所有客戶
                    lock(_clients) {
                        foreach (Client c in _clients) {
                            c.client.GetStream().Write(bp, 0, _packageSize);
                            c.client.GetStream().Flush();
                        }
                    }
                }

                // 開始非同步接收從客戶端收到的資料
                client.client.GetStream().BeginRead(
                        client.buffer,
                        0,
                        client.buffer.Length,
                        HandleClientDataReceived,
                        client);
            }
        } catch (Exception e) {
            Debug.Log(e.Message);
        }
    }

    // 加入客戶
    private void AddClient(Client c) {
        _clients.Add(c);
    }

    // 刪除客戶
    private void RemoveClient(Client c) {
        c.client.Client.Disconnect(false);
    }

    // 客戶端變數
    Client _server;

    // 連線至服務端
    private void ConnectServer() {
        try {
            TcpClient tcpServer = new TcpClient();
            tcpServer.Connect(_ip, _port);
            _server = new Client();
            _server.client = tcpServer;
            _server.buffer = new byte[tcpServer.ReceiveBufferSize];
            _server.pendingData = new List<byte>();
            
            // 非同步接收服務端資料
            tcpServer.GetStream().BeginRead(
                _server.buffer, 
                0, 
                tcpServer.ReceiveBufferSize,
                HandleServerDataReceived,
                _server);

            _connected = true;
        } catch (Exception e) {
            Debug.Log(e.Message);
        }
    }

    // 從服務端斷開
    private void DisconnectServer() {
        try {
            lock (_server.client) {
                _server.client.Client.Close();
            }

            // 清空資料包
            lock (_packages) {
                _packages.Clear();
            }

            // 刪除所有客戶人物模型
            mCharacter.RemoveAllEnemyCharacter();

            _connected = false;
        } catch (Exception e) {
            Debug.Log(e.Message);
        }
    }

    // 從服務端讀取資料的回撥函式
    private void HandleServerDataReceived(IAsyncResult iar) {
        if (_connected) {
            Client server = (Client)iar.AsyncState;
            NetworkStream ns = server.client.GetStream();
            int bytesRead = ns.EndRead(iar);
      
            // 連線中斷
            if (bytesRead == 0) {
                DisconnectServer();
                return;
            }

            // 儲存資料
            for (int i=0; i<bytesRead; ++i) {
                server.pendingData.Add(server.buffer[i]);
            }

            // 把資料解析成包
            while (server.pendingData.Count >= _packageSize) {
                byte[] bp = server.pendingData.GetRange(0, _packageSize).ToArray();
                server.pendingData.RemoveRange(0,_packageSize);

                // 把資料轉換成包然後再儲存包列表
                object obj;
                Deserialize(bp, out obj);

                lock (_packages) {
                    _packages.Add((Package)obj);
                }
            }

            // 非同步接收服務端資料
            server.client.GetStream().BeginRead(
                server.buffer, 
                0, 
                server.client.ReceiveBufferSize,
                HandleServerDataReceived,
                server);
        }
    }
    
    // 傳送自己的當前的狀態包給服務端
    public void SendStatus(Vector3 pos, Vector3 rot,Vector3 cameraRot, 
        Vector3 rightHandRot, bool isShooted, int hp) {
        try {
            if (_connected) {     
                Package p = new Package();
                p.id = _id;
                p.pos.Fill(pos);
                p.rot.Fill(rot);
                p.cameraRot.Fill(cameraRot);
                p.rightHandRot.Fill(rightHandRot);
                p.isShooted = isShooted;
                p.hp = hp;

                // 傳送包到服務端
                byte[] bp;
                Serialize(p, out bp);

                lock (_server.client) {
                    _server.client.GetStream().Write(bp, 0, _packageSize);
                    _server.client.GetStream().Flush();
                }
            }
        } catch (Exception e) {
            Debug.Log(e.Message);

            // 斷開服務端
            DisconnectServer();
        }
    }

    // 獲取包
    public bool NextPackage(out Package p) {
        lock (_packages) {
            if (_packages.Count == 0) {
                p = new Package();
                return false;
            }

            p = _packages[0];
            _packages.RemoveAt(0);
        }

        return true;
    }
}

4.EnemyCharacter.cs

這個指令碼負責控制其他客戶的行為,比如從服務端接收到其他客戶移動或開槍的資料,就要用這個指令碼來更新其他客戶的當前行為

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

public class EnemyCharacter : MonoBehaviour {

	// Use this for initialization
	void Start () {
        // 獲取本機玩家的物件
        mCharacter = GameObject.Find("Character").transform;
        mCharacterComponent = mCharacter.GetComponent<Character>();
        // 獲取攝像頭物件
        mCamera = transform.Find("Camera");
		// 獲取右手物件
        mRightHand = transform.Find("RightHand");
        // 獲取槍聲播放元件
        mGunAudio = transform.Find("RightHand/Pistol").GetComponent<AudioSource>();
        // 獲取火花效果
        mFireEffect = transform.Find("RightHand/Pistol/FireEffect").GetComponent<ParticleSystem>();
        // 獲取腳步聲播放元件
        mAudio = transform.GetComponent<AudioSource>();
        // 顯示血量和ID的元件
        txID = transform.Find ("ID");
        txIDText = transform.Find ("ID").GetComponent<TextMesh> ();
        txHP = transform.Find ("HP");
		txHPText = transform.Find ("HP").GetComponent<TextMesh> ();
	}
	
	// Update is called once per frame
	void Update () {
		// 摧毀物件
        if (isDestroy) {
            Destroy(gameObject);
        }

        // 更新物件屬性
        UpdataProperties();
	}
    
    private Transform mCharacter;
    private Character mCharacterComponent;
    private Transform mCamera;
    private Transform mRightHand;
    private AudioSource mGunAudio;
    private AudioSource mAudio;
    private ParticleSystem mFireEffect;     // 開槍後的火花
    public GameObject mPiece;       // 開槍後撞擊產生的碎片
    private bool isDestroy = false;

    // 銷燬角色
    public void Destroy() {
        isDestroy = true;
    }

    // 角色移動動作
    public void Move(Vector3 pos, Vector3 rot, Vector3 cameraRot, Vector3 rightHandRot) {
        if (pos != transform.position) {
            transform.position = pos;
            PlayStepSound();
        } else {
            StopPlayStepSound();
        }

        mCamera.eulerAngles = cameraRot;

        transform.eulerAngles = rot;

        mRightHand.eulerAngles = rightHandRot;
    }

    // 播放腳步聲
    private void PlayStepSound() {
        if (!mAudio.isPlaying) {
            mAudio.Play();
        }
    }

    // 停止播放聲音
    private void StopPlayStepSound() {
        if (mAudio.isPlaying) {
            mAudio.Stop();
        }
    }

    // 開槍
    public void Fire() {
        // 射擊音效與畫面
        PlayShotSound();

        // 播放火花效果
        PlayFireEffect();

        // 判斷射擊位置
        RaycastHit hit;
        if (Physics.Raycast(mCamera.position, mCamera.forward, out hit, 100)) {
            // 被槍擊中的地方會有碎片彈出
            DrawPieces(hit);

            // 判斷本機玩家是否中槍如果是就減
            print(hit.collider.name);
            if (hit.collider.name == mCharacter.name) {
                mCharacterComponent.GetHurt();
            }
        }
    } 

    // 播放槍聲
    private void PlayShotSound() {
        mGunAudio.PlayOneShot(mGunAudio.clip);
    }

    // 畫碎片
    private void DrawPieces(RaycastHit hit) {
        for (int i = 0; i < 5; ++i) {
            GameObject p = Transform.Instantiate(mPiece);

            // 碎片撞擊到物體後的反彈位置
            Vector3 fwd = mCamera.forward * -1;
            p.transform.position = hit.point;
            p.GetComponent<Rigidbody>().AddForce(fwd * 100);

            // 0.3秒後刪除
            Destroy(p, 0.3f);
        }
    }

    // 播放火花效果
    private void PlayFireEffect() {
        mFireEffect.Play();
    }

    // 人物變數
    private int _id = 1;
    private int _hp = 100;
    private Transform txID;
    private TextMesh txIDText;
    private Transform txHP;
    private TextMesh txHPText;

    // 角色id
    public void SetID(int id) {
        _id = id;
    }

    // 角色血量
    public void SetHP(int hp) {
        _hp = hp;
    }

    // 更新角色變數/屬性
    private void UpdataProperties() {
        // 顯示血量和ID
        txIDText.text = "ID:"+_id.ToString();
        txHPText.text = "HP:"+_hp.ToString();

        // 血量和ID的方向,面向著本機玩家
        txID.rotation = mCharacter.rotation;
        txHP.rotation = mCharacter.rotation;
    }
}