UNET連線遊戲(六){你快掛囉,HUD_UI續}

本篇使用的是5.3版後有UNET的UNITY
目標:基本連線遊戲機制+敵方玩家血條(UI)
1.延續前篇(UNET連線遊戲(五){死神之眼我看的見你的名子,HUD_UI})
現在我們必須要讓敵方UI與他自己的HP兩產生互動,但在這邊其實我方也是相對的;意思是其實不管是誰的血量都應該要告知SERVER後同步給所有玩家,所以接下來我們要大刀闊斧的修正程式內容。
首先是玩家自己的UI實際上也應該要像敵方UI一般生成帶入,只是型態有所不同,因此這裡我們要做的事情是把之前做好的我方HPBAR(綠色一大條的)也掛上先前的UIReset.cs還有NetowrkIdentity的套件
unet-hit-035
隨後將PLAYER_ID放到HP階層下當成子物件,拖曳到Resources資料夾製作成預製物,並刪除場景上的HP
unet-hit-036

2.既然這些UI也都屬於SERVER管理生成,就如同子彈一般要將其加入NetworkManager的清單內
unet-hit-037
3.接下來就是改寫UIReset.cs的內容如下

using UnityEngine;
using System.Collections;
using UnityEngine.UI;
public class UIReset : MonoBehaviour {
	RectTransform UISET;
	public GameObject playerName;
	public float offsetXY = 0f;
	GameObject playerCamera;
	Vector3 pos;
	Vector3 pos_scr;
	// Use this for initialization
	void Start () {
		Reset ();
	}
	void FixedUpdate () {
		if (playerName) {
			if(offsetXY == 0)UIpos ();
		} else
			Destroy (this.gameObject);
	}
	void Reset(){
		UISET = this.transform.GetComponent<RectTransform> ();
		UISET.localScale = new Vector3 (1, 1, 1);
		UISET.offsetMin = new Vector2 (0+offsetXY,0+offsetXY);
		UISET.offsetMax = new Vector2 (0-offsetXY,0-offsetXY);
		if (offsetXY == 0) {
			transform.FindChild ("ID").GetComponent<Text> ().text = this.gameObject.name;
			playerCamera = GameObject.FindWithTag ("LocalCamera");
		} else {
			transform.FindChild ("PLAYER_ID").GetComponent<Text> ().text = this.gameObject.name;
		}
	}
	void UIpos(){
		pos = new Vector3 (playerName.transform.position.x , playerName.transform.position.y + playerName.GetComponent<Collider>().bounds.size.y*0.75f, playerName.transform.position.z);
		pos_scr = playerCamera.GetComponent<Camera>().WorldToScreenPoint (pos);
		//Debug.Log (pos_scr);
		if (pos_scr.z > 0 ) {
			UISET.anchorMin = new Vector2 (pos_scr.x / Screen.width - 0.05f, pos_scr.y / Screen.height - 0.01f);
			UISET.anchorMax = new Vector2 (pos_scr.x / Screen.width + 0.05f, pos_scr.y / Screen.height);
		} else {
			UISET.anchorMin = new Vector2 (1, 1);
		}
	}
}

由於兩種UI的不同樣貌所以在第8行加上宣告offsetXY用來調整大小,順便在19、30的位置利用有沒有這個調整參數辨別是自己的UI還是敵方的UI做出不同的處理。
4.並且刪除PlayerID.cs中20~23的程式碼(理由是這個UI現在一開始並不存在於場景中,更改名稱的動作交給UIReset.cs裡面34行的位置來處理)

using UnityEngine;
using System.Collections;
using UnityEngine.Networking;
using UnityEngine.UI;
public class PlayerID : NetworkBehaviour {
	void Start () {
		GetNetIdentity ();
	}
	// Update is called once per frame
	void Update () {
	}
	void GetNetIdentity(){
		this.gameObject.name = "PLAYER_" + GetComponent<NetworkIdentity> ().netId.ToString();
		//if (isLocalPlayer) {
		//	GameObject player = GameObject.Find ("PLAYER_ID");
		//	player.GetComponent<Text> ().text = ID ();
		//}
	}
	public string ID(){
		return this.gameObject.name;
	}
}

5.血條產生的最後一步是玩家人物身上的EnemyHP.cs改寫

using UnityEngine;
using System.Collections;
using UnityEngine.Networking;
using UnityEngine.UI;
public class EnemyHP : NetworkBehaviour {
	private GameObject HudBar;
	// Use this for initialization
	void Start () {
		SpawnHpBar ();
	}
	// Update is called once per frame
	void Update () {
	}
	void SpawnHpBar ()
	{
		if (!isLocalPlayer) {
			HudBar = Instantiate (Resources.Load ("HP_ENEMY")) as GameObject;
			HudBar.name = "PLAYER_" + GetComponent<NetworkIdentity> ().netId.ToString ();
			HudBar.transform.parent = GameObject.Find ("HUD").transform;
			HudBar.GetComponent<UIReset> ().playerName = this.gameObject;
		} else {
			HudBar = Instantiate (Resources.Load ("HP")) as GameObject;
			HudBar.name = "PLAYER_" + GetComponent<NetworkIdentity> ().netId.ToString ();
			HudBar.transform.parent = GameObject.Find ("HUD").transform;
			HudBar.GetComponent<UIReset> ().playerName = this.gameObject;
			HudBar.GetComponent<UIReset> ().offsetXY = 10f;
		}
		this.gameObject.GetComponent<DamageScript> ().HP_BAR = HudBar.GetComponent<Image>();
	}
}

這裡主要是SpawnHpBar ()方法執行時判斷玩家是自己或是敵方,然後從Resources資料夾產生對應的HPBAR,並且執行UI相關的參數、狀態設定。
到這裡我們應該可以看到執行時的畫面跟原先未改變前相同,只是還未同步數值
6.喝口水繼續,重頭戲這裡開始
DamageScript.cs必須改變做法,因此原本的OnCollisionEnter用來檢測是否被子彈擊中的功能砍掉,之後改由子彈負責檢測是否擊中玩家,因此在其中執行的兩個相關功能[Command]、[ClientRpc]下的CmdHitPlayer以及RpcResolveHit也可以跟著刪除。
7.並且新增一個對玩家造成傷害的功能TakeDamage統一執行傷害,還有新的[ClientRpc]功能RpcRebirth用來重置玩家位置,但最重要的部分還是HP變數的同步,這裡需要在變數前加上[SyncVar]修飾標籤,把變數轉為伺服器同步的數據,同時建立一個方法HpBar(int h)依據HP數值來刷新血條的狀態並且在[SyncVar]裡hook這個方法,詳細程式碼如下:

using UnityEngine;
using System.Collections;
using UnityEngine.Networking;
using UnityEngine.UI;
public class DamageScript : NetworkBehaviour {
	AudioSource damageAudio;
	public Image HP_BAR;
	public const int HP_MAX = 3;
	[SyncVar(hook = "HpBar")]public int HP = HP_MAX;
	void Awake()
	{
	}
	void Start (){
		damageAudio = GetComponent<AudioSource>();
                HP = HP_MAX;
	}
	public void TakeDamage(int D){
		if (!isServer) {
			return;
		}
		HP -= D;
		if (HP < 1) {
			HP = HP_MAX;
			RpcRebirth ();
		}
	}
	void HpBar(int h){
		print (h);
		if(HP_BAR)HP_BAR.fillAmount = (float)h / (float)HP_MAX;
	}
	[ClientRpc]
	void RpcRebirth(){
		HP = HP_MAX;
		if (isLocalPlayer) {
			Transform spawn = NetworkManager.singleton.GetStartPosition();
			transform.position = spawn.position;
		}
	}
}

8.最後是AmmoController.cs加入OnCollisionEnter檢測碰撞物的標籤是否為”PLAYER”,這部份我們之前有替玩家設置過;然後執行玩家身上的TakeDamage (1),造成損傷1點並且摧毀子彈DestroyAmmo ();

using UnityEngine;
using System.Collections;
using UnityEngine.Networking;
public class AmmoController : NetworkBehaviour
{
	private float lifeTime;
	public float maxTime = 3.0f;
	void Start ()
	{
		lifeTime = 0.0f;
	}
	[ServerCallback]
	void Update ()
	{
		lifeTime += Time.deltaTime;
		if( lifeTime > maxTime )
		{
			DestroyAmmo();
		}
	}
	void OnCollisionEnter(Collision other)
	{
		if (other.gameObject.tag == "PLAYER") {
			other.gameObject.GetComponent<DamageScript>().TakeDamage (1);
			DestroyAmmo ();
		}
	}
	public void DestroyAmmo(){
		NetworkServer.Destroy(gameObject);
	}
}

9.完成這一大串的程式修改後我們的人物血條就可以如期運作啦
unet-hit-039
下期預告:UNET連線遊戲(七){原來這就是掛掉的感覺}

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *