本篇使用的是5.3版後有UNET的UNITY
目標:基本連線遊戲機制+線上怪物
1.延續前篇(UNET連線遊戲(七){原來這就是掛掉的感覺})
除了連線玩家,以及場景的布置外,還可以在場中加入一些由伺服器控制的NPC怪物增添遊戲的樂趣。首先處理場景的布置~
為了節省效能,我們開啟Window→Lighting關閉Auto Bake並烘焙貼圖後關閉即時的GI渲染,讓遊戲運行的快些節省網路效能。
2.選取全部的地板Window→Navigation按下Bake創建一個導航平面給怪物NPC使用。
3.創建一個空物件當作怪物的重生點,並掛上MonsterSpawner.cs
using UnityEngine; using System.Collections; using UnityEngine.Networking; public class MonsterSpawner : NetworkBehaviour { public GameObject monster; public int monsterNum; public float R=3; void Start () { InvokeRepeating ("CmdScanMonster",3f ,10f); } [Command] void CmdSpawnMonster(){ for(int i=0 ; i < monsterNum ; i++){ Vector3 pos = new Vector3 (this.transform.position.x + Random.Range(-R,R) , this.transform.position.y , this.transform.position.z + Random.Range(-R,R) ); GameObject mon = (GameObject)Instantiate (monster , pos ,this.transform.rotation); mon.name = "Monster"; NetworkServer.Spawn (mon); } } [Command] void CmdScanMonster(){ if (!GameObject.FindWithTag ("MONSTER")) { CmdSpawnMonster (); } } }
內容如下
InvokeRepeating (“CmdScanMonster”,3f ,10f); 開始3秒後掃描場上是否存在怪物,之後每10秒檢查一次。若無就執行創造怪物的方法。
4.怪物的造型就要發威各位的創意了,該卦的程式還是要掛上。
由於這些程式要改成也能怪物使用,稍做改寫。
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; if (this.tag == "MONSTER") { HudBar.name = "MONSTER_" + GetComponent<NetworkIdentity> ().netId.ToString (); }else 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; if (this.tag == "MONSTER") { HudBar.name = "MONSTER_" + GetComponent<NetworkIdentity> ().netId.ToString (); }else 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>(); } }
using UnityEngine; using System.Collections; using UnityEngine.Networking; using UnityEngine.UI; using UnityStandardAssets.Characters.FirstPerson; public class DamageScript : NetworkBehaviour { AudioSource damageAudio; GunController gunCtrl; FirstPersonController fpsCtrl; CharacterController charCtrl; public Image HP_BAR; public Image DEAD_BG; public const int HP_MAX = 3; public const int HPMON_MAX = 300; float rotaSpeed; [SyncVar(hook = "HpBar")]public int HP = HP_MAX; void Awake() { } void Start (){ if (this.gameObject.tag == "MONSTER") { HP = HPMON_MAX; } damageAudio = GetComponent<AudioSource>(); gunCtrl = GetComponent<GunController> (); fpsCtrl = GetComponent<FirstPersonController> (); charCtrl = GetComponent<CharacterController> (); DEAD_BG = GameObject.Find ("BG").GetComponent<Image> (); } public void TakeDamage(int D){ // if (!isServer) { // return; // } print ("GET"); if (HP > 0) { HP -= D; if (HP <= 0) { if (this.gameObject.tag == "MONSTER") { NetworkServer.Destroy (this.gameObject); Destroy (this.gameObject); }else RpcRebirth (); } } } void HpBar(int h){ print (h); if (this.gameObject.tag == "MONSTER") { if(HP_BAR)HP_BAR.fillAmount = (float)h / (float)HPMON_MAX; }else if(HP_BAR)HP_BAR.fillAmount = (float)h / (float)HP_MAX; } void Alive(bool b){ gunCtrl.alive = b; fpsCtrl.enabled = b; charCtrl.enabled = b; rotaSpeed = 270f; InvokeRepeating ("Dead",0.1f,0.02f); } void Dead (){ transform.Rotate(Vector3.right * Time.deltaTime * -30); transform.Rotate(Vector3.up * Time.deltaTime * rotaSpeed); rotaSpeed-=1.5f; DEAD_BG.color += new Color (0, 0, 0, 0.003f); } [ClientRpc] void RpcRebirth(){ Invoke ("Go",5.0f); print ("DIE"); if (isLocalPlayer)Alive (false); } void Go(){ HP = HP_MAX; if (isLocalPlayer) { DEAD_BG.color = new Color (1, 0, 0, 0); Transform spawn = NetworkManager.singleton.GetStartPosition(); transform.position = spawn.position; Alive (true); CancelInvoke ("Dead"); } } }
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(){ if (this.tag == "MONSTER") { this.gameObject.name = "MONSTER_" + GetComponent<NetworkIdentity> ().netId.ToString(); }else this.gameObject.name = "PLAYER_" + GetComponent<NetworkIdentity> ().netId.ToString(); } public string ID(){ return this.gameObject.name; } }
以上都是為了是否為怪物增加的條件判斷。
5.怪物身上重點功能導航目標,首先新增Component零件NavMeshAgent,以及球形Trigger把半徑加大為5。
6.接下來撰寫SearchPlayer.cs搜索玩家的功能
using UnityEngine; using System.Collections; using UnityEngine.Networking; public class SearchPlayer : NetworkBehaviour { NavMeshAgent Nav; public GameObject target; public bool serverMonster; // Use this for initialization void Start () { if (isServer) { serverMonster = true; Nav = GetComponent<NavMeshAgent> (); } else serverMonster = false; } // Update is called once per frame void FixedUpdate () { if (target && target != null && serverMonster) { Nav.SetDestination(target.transform.position); if (CloseYou ()) { target = null; Nav.Stop(true); } } } void OnTriggerStay(Collider other) { if(other.tag == "PLAYER" && serverMonster){ Nav.Resume (); target = other.gameObject; } } bool CloseYou(){ float dis = Vector3.Distance(transform.position, target.transform.position); if (dis < 5f) { Nav.Stop (); target.GetComponent<DamageScript> ().TakeDamage (3); return true; } else { Nav.Resume (); return false; } } }
首先判定是否為SERVER端取得導航控件,然後將導航目標設為踩進球型偵測區的PLAYER,還有是否進入到攻擊範圍的CloseYou方法,執行傷害玩家以及導航關閉的功能。將怪製成預制物件後從場景刪除,指定給生怪程式還有NetworkManager的線上成員註冊。
7.另個可以擴增的功能則是攻擊打他的玩家,因此思考下這個程式邏輯便會得到,子彈需要帶有玩家資訊的結論;玩家帶著槍槍發射子彈,所以我們要改寫GunController.cs以及AmmoController.cs的內容,首先子彈的部分:需要一個可以帶玩家資訊的欄位
using UnityEngine; using System.Collections; using UnityEngine.Networking; public class AmmoController : NetworkBehaviour { private float lifeTime; public float maxTime = 3.0f; public GameObject fromWho; 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 (); } if (other.gameObject.tag == "MONSTER") { other.gameObject.GetComponent<DamageScript>().TakeDamage (1); other.gameObject.GetComponent<SearchPlayer> ().target = fromWho; DestroyAmmo (); } } public void DestroyAmmo(){ NetworkServer.Destroy(gameObject); } }
這裡我們用fromWho來裝載這個資訊,並寫在打到怪物標籤物體時遞給怪物的目標物。
接著槍的部分:則是發射時將發射玩家的資訊貼到子彈的fromWho欄位中。
using UnityEngine; using System.Collections; using UnityEngine.Networking; public class GunController : NetworkBehaviour { private float power; private Transform playerCamera; public bool alive = true; void Start () { power = 800.0f; playerCamera = transform.FindChild("FirstPersonCharacter"); } void Update () { if(isLocalPlayer) { if(Input.GetButtonDown("Fire1") && alive) { CmdSpawnAmmo(); } } } [Command] void CmdSpawnAmmo () { GameObject instance = Instantiate (Resources.Load ("Ammo")) as GameObject; instance.name = "Ammo"; instance.transform.position = playerCamera.position + playerCamera.forward * 1.5f + playerCamera.up * -.5f; instance.GetComponent<Rigidbody> ().AddForce (playerCamera.forward * power); instance.GetComponent<AmmoController> ().fromWho = this.gameObject; NetworkServer.Spawn (instance); } }