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