1、游戲管理器
游戲管理器負責管理游戲的整體流程,還可以系統管理用於游戲的全局數據以及游戲中判斷勝敗的條件。
游戲管理器並不是單一的模塊,更像是能控制游戲的功能集合。
1)怪獸出現邏輯:專門設置一些位置用於隨機生成怪獸。
public Transform[] points; points = GameObject.Find("MonsterPos").GetComponentsInChildren<Transform>();
2)訪問游戲管理器:聲明GameManager類的變量,在Start函數中分配為局部變量。
private GameManager gameMgr; gameMgr = GameObject.Find("GameManager").GetComponent<GameManager>();
3)單例模式:該模式下只有一個對象,可以獲得全局訪問。
public static GameManager instance = null; void Awake() { //將GameManager帶入實例
instance = this; }
4)對象池:對於需要在游戲中反復生成的模型,可以在游戲一開始運行並加載場景時就全部生成,運行時可從中直接取出使用。
void Start () { //生成怪獸並保存到對象池
for(int i = 0;i<maxMonster;i++) { GameObject monster = (GameObject)Instantiate(monsterPrefab); monster.name = "Monster_" + i.ToString(); monster.SetActive(false); monsterList.Add(monster); } }
5)共享函數:聲音處理
//聲音共享函數
public void PlaySfx(Vector3 pos,AudioClip sfx) { //如果靜音選項為true,則立即停止聲音
if (isSfxMute) return; //動態生成游戲對象
GameObject soundObj = new GameObject("Sfx"); soundObj.transform.position = pos; //向生成的游戲對象添加AudioSource組件
AudioSource audioSource = soundObj.AddComponent<AudioSource>(); //設置AudioSource屬性
audioSource.clip = sfx; audioSource.minDistance = 10; audioSource.maxDistance = 30; audioSource.volume = sfxVolumn; audioSource.Play(); //聲音結束播放后,刪除之前動態生成的游戲對象
Destroy(soundObj, sfx.length); }
2、射線投射
射線投射不但常用於發射邏輯,還可用於游戲中的傳感器以及通過鼠標移動、旋轉玩家角色。
射線無法用肉眼觀察,可以使用Debug.DrawRay()函數使射線在場景視圖中顯示。
void Update() { //使用以下函數在場景中顯示射線
Debug.DrawRay(FirePos.position,FirePos.forward * 10, Color.green); }
使怪獸被射線擊中時受傷
RaycastHit hit; if (Physics.Raycast(FirePos.position, FirePos.forward, out hit, 10)) { if (hit.collider.tag == "Monster") { //SendMessage函數要傳遞的參數數組
object[] _params = new object[2]; _params[0] = hit.point;//被射線擊中的位置
_params[1] = 20;//怪獸受到的傷害值
hit.collider.gameObject.SendMessage("OnDamage", _params, SendMessageOptions.DontRequireReceiver); } }
激光束
使用Line Renderer組件,因為Line Renderer將在Player的中心位置生成,所以不需要勾選Use World Space選項。
using UnityEngine; using System.Collections; public class LaserBeam : MonoBehaviour { private Transform tr; private LineRenderer line; private RaycastHit hit; void Start () { tr = GetComponent<Transform>(); line = GetComponent<LineRenderer>(); line.useWorldSpace = false; line.enabled = false; //設置頭部寬度和尾部寬度
line.SetWidth(0.3f,0.01f); } void Update () { Ray ray = new Ray(tr.position + (Vector3.up * 0.02f),tr.forward); Debug.DrawRay(ray.origin, ray.direction * 10, Color.blue); if (Input.GetMouseButtonDown(0)) { //設置Line Renderer的初始位置
line.SetPosition(0,tr.InverseTransformPoint(ray.origin)); //(局部坐標基准Transform組件).InverseTransformPoint(全局坐標) //將物體被射線擊中的位置設置為Line Renderer的終點位置
if (Physics.Raycast(ray,out hit,100)) { line.SetPosition(1,tr.InverseTransformPoint(hit.point)); } else line.SetPosition(1, tr.InverseTransformPoint(ray.GetPoint(100))); StartCoroutine(ShowLaserBeam()); } } IEnumerator ShowLaserBeam() { line.enabled = true; yield return new WaitForSeconds(Random.Range(0.01f,0.2f)); line.enabled = false; } }
3、制作Player
1)玩家移動
using UnityEngine; using System.Collections; [System.Serializable] public class Anim { public AnimationClip idle; public AnimationClip runForward; public AnimationClip runBackward; public AnimationClip runRight; public AnimationClip runLeft; } public class PlayerCtrl : MonoBehaviour { private Transform tr; public float moveSpeed = 5; public float rotSpeed = 100; public Anim anim;//要顯示到檢視視圖的動畫變量 public Animation _animation;//Animation組件對象的變量 public int hp = 100; //聲明委托和事件 public delegate void PlayerDieHandler(); public static event PlayerDieHandler OnPlayerDie; //Player的生命初始值 private int initHp; //Player的生命圖像 public Image imgHpBar; public Text txtHp; //private GameMgr gameMgr; void Start () { //設置生命值 initHp = hp; tr = GetComponent<Transform>(); //查找位於自身下級的Animation組件並分配到變量 _animation = GetComponentInChildren<Animation>(); //保存並運行Animation組件的動畫片段 _animation.clip = anim.idle; _animation.Play(); //gameMgr = GameObject.Find("GameManager").GetComponent<GameMgr>(); } void Update () { float h = Input.GetAxisRaw("Horizontal"); float v = Input.GetAxisRaw("Vertical"); Vector3 dir = h * Vector3.right + v * Vector3.forward;//前后左右移動方向向量 tr.Translate(dir.normalized * moveSpeed * Time.deltaTime,Space.Self);//向量方向*速度*時間,以局部坐標為基准移動 tr.Rotate(Vector3.up * rotSpeed * Time.deltaTime * Input.GetAxis("Mouse X")); //以鍵盤輸入值為基准,執行要操作的動畫 //動畫合成:讓動畫片段之間平滑過渡,用到CrossFade合成函數 if (v >= 0.1f) { _animation.CrossFade(anim.runForward.name, 0.3f); } else if (v <= -0.1f) { _animation.CrossFade(anim.runBackward.name, 0.3f); } else if (h <= -0.1f) { _animation.CrossFade(anim.runLeft.name, 0.3f); } else if (h >= 0.1f) { _animation.CrossFade(anim.runRight.name, 0.3f); } else { _animation.CrossFade(anim.idle.name, 0.3f); } } void OnTriggerEnter(Collider other) { if (other.gameObject.tag == "Punch") { hp -= 10; //調整Image的fillAmount屬性,以調整生命條長度 float xueliang = (float)hp / (float)initHp; imgHpBar.fillAmount = xueliang; txtHp.text = "<color=#ffffff>" + (xueliang * 100).ToString() + "%</color>"; Debug.Log("Player HP =" + hp.ToString()); if (hp <= 0) { PlayerDie(); } } } void PlayerDie() { Debug.Log("Player Die!!"); //GameObject[] monsters = GameObject.FindGameObjectsWithTag("Monster"); //foreach (GameObject monster in monsters) //{ // monster.SendMessage("OnPlayerDie",SendMessageOptions.DontRequireReceiver); //} //或者 //觸發事件 OnPlayerDie(); //更新游戲管理器的isGameover變量以停止生成怪獸 //gameMgr.isGameOver = true; GameMgr.instance.isGameOver = true; } }
使用CharacterController組件后代碼
float h = Input.GetAxisRaw("Horizontal"); float v = Input.GetAxisRaw("Vertical"); movDir = (tr.forward * v) + (tr.right * h);//計算移動方向 movDir.y -= 20 * Time.deltaTime;//更改y值使其受重力影響下落 tr.Rotate(Vector3.up * rotSpeed * Time.deltaTime * Input.GetAxis("Mouse X")); controller.Move(movDir * moveSpeed * Time.deltaTime);//移動玩家
2)攝像機追蹤
將該腳本添加到主攝像機下,並把Player拖拽到targetTr變量。
using UnityEngine; using System.Collections; public class FollowCam : MonoBehaviour { public Transform tr; public Transform targetTr;//要追蹤的游戲對象的Trans變量 public float dist = 10;//與攝像機之間的距離 public float height = 3;//設置攝像機高度 public float dampTrace = 20;//實現平滑追蹤的變量 void Start () { tr = GetComponent<Transform>(); } //要追蹤的目標游戲對象停止移動后,調用LaterUpdate函數 void LateUpdate () { //將攝像機放置在被追蹤的目標后方的dist距離的位置 //將攝像機向上抬離height tr.position = Vector3.Lerp(tr.position, //起始位置 targetTr.position - (targetTr.forward * dist) + (Vector3.up * height), //終止位置 Time.deltaTime * dampTrace); //內插時間 //使攝像機朝向游戲對象 tr.LookAt(targetTr.position); } }
4、網絡管理器
Network View組件提供與其他玩家通信的功能,必須已經在網絡上生成游戲或已訪問游戲,所以其前提是游戲房間已經生成或已連接其他游戲客戶端。
1)通過GUI類實現畫面輸入所需UI及制作網絡玩家
using UnityEngine; using System.Collections; public class NetworkManager : MonoBehaviour { //連接IP private const string ip = "127.0.0.1"; //連接Port端口號 private const int port = 30000; //是否使用NAT功能 private bool _useNat = false; public GameObject player; void OnGUI() { //判斷當前玩家是否連接網絡 if (Network.peerType == NetworkPeerType.Disconnected) { //生成啟動游戲服務器按鈕 if (GUI.Button(new Rect(20,20,200,25),"Start Server")) { //初始化游戲服務器 Network.InitializeServer(20,port,_useNat);//連接數,端口號,是否用NAT } //生成連接游戲的按鈕 if (GUI.Button(new Rect(20, 50, 200, 25), "Connect to Server")) { //連接游戲服務器 Network.Connect(ip, port); } } else { //初始化服務器時輸出信息 if (Network.peerType == NetworkPeerType.Server) { GUI.Label(new Rect(20, 20, 200, 25), "Initialization Server..."); //查看連接網絡的用戶數 GUI.Label(new Rect(20, 50, 200, 25), "Clint Count = " + Network.connections.Length.ToString()); } //以客戶端身份連接時輸出信息 if (Network.peerType == NetworkPeerType.Client) { GUI.Label(new Rect(20, 20, 200, 25), "Connect to Server"); } } } //游戲服務器初始化正常完成時創建player void OnServerInitialized() { CreatePlayer(); } //有玩家連接服務器時調用 void OnConnectedToServer() { CreatePlayer(); } void CreatePlayer() { //隨機生成玩家的初始位置 Vector3 pos = new Vector3(Random.Range(-20,20),0,Random.Range(-20,20)); //網絡上動態生成玩家 Network.Instantiate(player,pos,Quaternion.identity,0); } }
2)攝像機追蹤邏輯
先導入SmoothFollow腳本(Import Package→Utility),並把target變量修改為public。掛在Player預設。
using UnityEngine; using System.Collections; //為了使用SmoothFollow腳本,需要先聲明命名空間 using UnityStandardAssets.Utility; public class NetPlayerCtrl : MonoBehaviour { private Transform tr; private NetworkView _networkView; void Awake () { tr = GetComponent<Transform>(); _networkView = GetComponent<NetworkView>(); //查看NetworkView組件是否為自己的Player組件 if (_networkView.isMine) { //設置Main.Camera要追蹤的對象 Camera.main.GetComponent<SmoothFollow>().target = tr; } } }
禁用服務器端的玩家移動腳本,使其不要根據鍵盤輸入值執行移動邏輯。
//如果當前玩家是遠程服務器端的玩家,則禁用此腳本 this.enabled = GetComponent<NetworkView>().isMine;
3)平滑的同步處理
在檢視面板,把NetPlayerCtrl腳本拖拽到NetworkView組件的Observed屬性。
修改腳本,使NetworkView組件不會在每個SendRate周期都向Transform組件傳送數據,而是調用NetPlayerCtrl腳本內的OnSerializeNetworkView函數。
//聲明接收傳送位置信息時使用的變量並設置初始值 private Vector3 currPos = Vector3.zero; private Quaternion currRot = Quaternion.identity; void Update() { if (_networkView.isMine) { } else//遠程玩家 { //將遠程玩家平滑地移動旋轉到NetworkView傳送來的目標位置 tr.position = Vector3.Lerp(tr.position,currPos,Time.deltaTime * 10); tr.rotation = Quaternion.Slerp(tr.rotation,currRot,Time.deltaTime * 10); } } //NetworkView組件調用的回調函數
//BitStream stream:序列化數據並保存到數據流
//NetworkMessageInfo info:保存從網絡接收的信息
void OnSerializeNetworkView(BitStream stream,NetworkMessageInfo info) { if (stream.isWriting) { Vector3 pos = tr.position; Quaternion rot = tr.rotation; //數據傳送 stream.Serialize(ref pos); stream.Serialize(ref rot); } else { Vector3 revpos = Vector3.zero; Quaternion revrot = Quaternion.identity; //數據接收 stream.Serialize(ref revpos); stream.Serialize(ref revrot); currPos = revpos; currRot = revrot; } }