🟥 目标
1️⃣ 玩家网络实例化
即把玩家要操控的角色生成出来。GameManager里面Start时实例化角色。
2️⃣ 实现战斗数据不重置
原先只有两个人,再加入一个人,会切换到3人的场景。但原先的两个人当前战斗数据会被重置,这是不合理的。
我们来让之前的玩家数据不被重置。
a、PlayerManager 声明的静态字段LocalPlayerInstance 记录本地玩家实例化的。
Awake赋值,并在加载新场景时,不销毁当前角色。
b、但在重新加载新场景时,GameManager 脚本 Start时会再次实例化角色,于是一个玩家就有了两个角色。所以GameManager Start时根据判断 LocalPlayerInstance 是否为空,确定是否已经实例化了角色。
3️⃣ 位置重置
若从3人房切换到2人房,房间变小了。若切换后原先在地面上的角色悬空了,会出Bug。
所以增加判断:若切换后没踩在地板上,则位置回到出生位置(原点高空)。
方法:PlayerManager 脚本Start时增加加载完场景时的委托,增加的委托事件判断当前角色位置
4️⃣ 激光碰撞判断
解决当跳跃时触发激光,会造成自身减血的现象。
这是因为激光与自身角色控制器的触发器检测碰撞了,我们要检测激光不是接触的自身才行,避免自己碰撞掉血。解决办法:
- 设置自身的Tag,触发检测Tag再决定是否减血。
- PlayerManager脚本判断射线碰撞到的角色是否是自己,不是的话再减血(此处采用)
if (other.GetComponentInParent<PhotonView>().IsMine) return; Health -= 0.1f;
现在我们可以测试看看,如果发现连接不上,就换成中国区Appid试试,我隔了一天申请通过了,现在你申请的应该也好了。
🟧 代码编写
🚩 PlayerManager
using UnityEngine; using Photon.Pun; public class PlayerManager : MonoBehaviourPunCallbacks, IPunObservable { #region Parameters public GameObject beams; //记录本地玩家实例化 public static GameObject LocalPlayerInstance; public float Health = 1f; //当用户开火时,为True bool IsFiring; #endregion #region Mono CallBacks void Awake() { beams.SetActive(false); if (PhotonNetwork.IsConnected == true && photonView.IsMine) LocalPlayerInstance = gameObject; DontDestroyOnLoad(gameObject); } void Start() { //当重新加载场景时,检测改脚本所赋值的角色是否踩在空中,若是,则回到出生点重新降落。 UnityEngine.SceneManagement.SceneManager.sceneLoaded += OnSceneLoaded; if (PhotonNetwork.IsConnected && photonView.IsMine) GetComponent<Skode_CameraWork>().OnStartFollowing(); } void Update() { if (Health <= 0f) GameManager.ins.Skode_LeaveRoom(); if (PhotonNetwork.IsConnected == true && photonView.IsMine) ProcessInputs(); if (IsFiring != beams.activeSelf) beams.SetActive(IsFiring); } void OnTriggerEnter(Collider other) { if (!photonView.IsMine && !other.CompareTag("beam")) return; Health -= 0.1f; } void OnTriggerStay(Collider other) { if (!photonView.IsMine && !other.CompareTag("beam")) return; //乘以增量时间,防止因为帧率FPS不同,扣血不同(举例:不乘,每帧执行一次扣血,卡的人比流畅的人扣血少) Health -= 0.1f * Time.deltaTime; } #endregion #region PUN CallBacks public override void OnDisable() { base.OnDisable(); UnityEngine.SceneManagement.SceneManager.sceneLoaded -= OnSceneLoaded; } public void OnPhotonSerializeView(PhotonStream stream, PhotonMessageInfo info) { if (stream.IsWriting) { // 我们拥有这个角色:把我们的数据发送给其他人 stream.SendNext(IsFiring); stream.SendNext(Health); } else { // 网络角色,接收数据 IsFiring = (bool)stream.ReceiveNext(); Health = (float)stream.ReceiveNext(); } } #endregion #region Private Methods void OnSceneLoaded(UnityEngine.SceneManagement.Scene scene, UnityEngine.SceneManagement.LoadSceneMode loadingMode) { CalledOnLevelWasLoaded(scene.buildIndex); } void CalledOnLevelWasLoaded(int level) { // 检查我们是否在竞技场外面,如果是这样,移动到竞技场中心的安全地带 if (!Physics.Raycast(transform.position, -Vector3.up, 5f)) { transform.position = new Vector3(0f, 5f, 0f); } } void ProcessInputs() { //鼠标左键 if (Input.GetButtonDown("Fire1")) IsFiring = true; if (Input.GetButtonUp("Fire1")) IsFiring = false; } #endregion }
🚩 GameManager
using UnityEngine.SceneManagement; using Photon.Pun; using Photon.Realtime; using UnityEngine; public class GameManager : MonoBehaviourPunCallbacks { public static GameManager ins; [Tooltip("用来代表玩家的预置体")] public GameObject playerPrefab; #region Mono CallBacks private void Awake() { ins = this; } private void Start() { //若本地玩家第一次进游戏场景,还未生成角色,则生成一个。 //且这个角色通过使用PhotonNetwork.Instantiate进行同步 if (PlayerManager.LocalPlayerInstance == null) PhotonNetwork.Instantiate(playerPrefab.name, new Vector3(0f, 5f, 0f), Quaternion.identity, 0); } #endregion #region Public Methods //离开服务器房间Btn public void Skode_LeaveRoom() { //不允许主客户端离开房间 if (!PhotonNetwork.IsMasterClient) PhotonNetwork.LeaveRoom(); } #endregion #region PUN Callbacks /// <summary> /// 本地玩家离开服务器房间时回调 /// </summary> public override void OnLeftRoom() { // 加载大厅场景 SceneManager.LoadScene(0); } /// <summary> /// 其他玩家连接房间时的回调(不是自己) /// </summary> public override void OnPlayerEnteredRoom(Player other) { print(other.NickName + " 加入房间"); //根据当前玩家的人数,主客户端加载对应场景 if (PhotonNetwork.IsMasterClient) LoadArena(); } //当玩家离开房间或变得不活动时调用。 public override void OnPlayerLeftRoom(Player other) { print(other.NickName + " 离开了房间"); //根据当前玩家的人数,主客户端加载对应场景 if (PhotonNetwork.IsMasterClient) LoadArena(); } #endregion #region Private Methods void LoadArena() { //Photon分为主客户端、其他客户端。只有主客户端才可进行加载房间。 //根据当前房间人数加载对应场景。该方法会使其他玩家会自动加入主客户端的房间 PhotonNetwork.LoadLevel("Room for " + PhotonNetwork.CurrentRoom.PlayerCount); } #endregion }
🟨 血条、昵称显示
1、创建UI
在人物下创建3D Slider血条,并且有文本框用于显示名字。
若是滑动条即使是满值也不到头,只需改下滑动条下Fill Area 的 Right值就好了。
2、代码赋值
将下代码挂到机器人身上,并赋值。
using UnityEngine; using UnityEngine.UI; public class PlayerUI : MonoBehaviour { #region Parameters [Tooltip("显示玩家名字的UI文本")] public Text playerNameText; [Tooltip("健康值Slider")] public Slider playerHealthSlider; [Tooltip("用于获取生命值赋值给血条")] public PlayerManager target; #endregion #region Mono Callbacks void Start() { playerNameText.text = target.photonView.Owner.NickName; } void Update() { playerHealthSlider.value = target.Health; } #endregion }
若现在还没把机器人放到 Resources里,现在就要放进去了。已经放进去的Apply一下。
将 Resources 里的机器人预制体赋值给GameManager,删掉面板的机器人。
各个场景的 GameManager都赋值一下,接下来要进行测试了。
还有,记得关闭离线模式哦
🟩 最终效果展示:
落头上了哈哈
好啦,本系列暂告一段落🧸