Unity Photon PUN2 玩家生成报错 空引用与预制体加载失败排查
问题描述
开发Unity多人游戏时参考Blackthornprod的多人教程实现Photon 2玩家生成逻辑,出现玩家无法正常生成的问题,运行时报错如下:
- 启动游戏直接抛出
NullReferenceException: Object reference not set to an instance of an object,报错位置为PlayerSpawner.Start () (at Assets/PlayerSpawner.cs:18) - 点击方向箭头切换玩家头像时新增两类报错:
DefaultPool failed to load "Player". Make sure it's in a "Resources" folder. Or use a custom IPunPrefabPool.(UnityEngine.Debug:LogError (object))NullReferenceException空引用错误,报错位置为PlayerSpawner.Start () (at Assets/PlayerSpawner.cs:20)
相关代码
所有代码、技术术语、API标识均保持原样未修改:
玩家生成逻辑脚本 PlayerSpawner.cs
using System.Collections; using System.Collections.Generic; using UnityEngine; using Photon.Pun; public class PlayerSpawner : MonoBehaviour { public GameObject[] playerPrefabs; public Transform[] spawnPoints; public PlayerItem _playerItem; private void Start() { int randomNumber = Random.Range(0, spawnPoints.Length); Debug.Log("1"); Transform spawnPoint = spawnPoints[randomNumber]; Debug.Log("2"); GameObject playerToSpawn = playerPrefabs[(int)PhotonNetwork.LocalPlayer.CustomProperties["playerAvatar"]]; Debug.Log("3"); PhotonNetwork.Instantiate(playerToSpawn.name, spawnPoint.position, Quaternion.identity); Debug.Log("4"); } }
玩家头像选择/房间玩家信息脚本 PlayerItem.cs
using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.UI; using Photon.Pun; using Photon.Realtime; public class PlayerItem : MonoBehaviourPunCallbacks { public Text playerName; public Color highlightColor; public GameObject leftArrowButton; public GameObject rightArrowButton; ExitGames.Client.Photon.Hashtable playerProperties = new ExitGames.Client.Photon.Hashtable(); public Image playerAvatar; public GameObject[] avatars; Player player; private void Start() { } public void SetPlayerInfo(Player _player) { playerName.text = _player.NickName; player = _player; UpdatePlayerItem(player); } public void ApplyLocalChanges() { leftArrowButton.SetActive(true); rightArrowButton.SetActive(true); } public void OnClickLeftArrow() { if ((int)playerProperties["playerAvatar"] == 0) { playerProperties["playerAvatar"] = avatars.Length - 1; } else { playerProperties["playerAvatar"] = (int)playerProperties["playerAvatar"] - 1; } PhotonNetwork.SetPlayerCustomProperties(playerProperties); } public void OnClickRightArrow() { if ((int)playerProperties["playerAvatar"] == avatars.Length - 1) { playerProperties["playerAvatar"] = 0; } else { playerProperties["playerAvatar"] = (int)playerProperties["playerAvatar"] + 1; } PhotonNetwork.SetPlayerCustomProperties(playerProperties); } public override void OnPlayerPropertiesUpdate(Player targetPlayer, ExitGames.Client.Photon.Hashtable changedProps) { if (player == targetPlayer) { UpdatePlayerItem(targetPlayer); } } void UpdatePlayerItem(Player player) { if (player.CustomProperties.ContainsKey("playerAvatar")) { playerAvatar.sprite = avatars[(int)player.CustomProperties["playerAvatar"]].GetComponent<SpriteRenderer>().sprite; playerProperties["playerAvatar"] = (int)player.CustomProperties["playerAvatar"]; } else { playerProperties["playerAvatar"] = 0; } } }
大厅管理/场景跳转脚本 LobbyManager.cs
using System.Collections; using System.Collections.Generic; using UnityEngine; using Photon.Pun; using Photon.Realtime; using UnityEngine.UI; public class LobbyManager : MonoBehaviourPunCallbacks { public InputField roomInputField; public GameObject lobbyPanel; public GameObject roomPanel; public Text roomName; public RoomItem roomItemPrefab; public List<RoomItem> roomItemList = new List<RoomItem>(); public Transform contentObject; public float timeBetweenUpdates = 1.5f; float nextUpdateTime; public List<PlayerItem> playerItemsList = new List<PlayerItem>(); public PlayerItem playerItemPrefab; public Transform playerItemParent; public GameObject playButton; private void Start() { PhotonNetwork.JoinLobby(); } public void OnClickCreate() { if (roomInputField.text.Length >= 1) { PhotonNetwork.CreateRoom(roomInputField.text, new RoomOptions() { MaxPlayers = 10, BroadcastPropsChangeToAll = true }); } } public override void OnJoinedRoom() { lobbyPanel.SetActive(false); roomPanel.SetActive(true); roomName.text = "Room: " + PhotonNetwork.CurrentRoom.Name; UpdatePlayerList(); } public override void OnRoomListUpdate(List<RoomInfo> roomList) { if (Time.time >= nextUpdateTime) { UpdateRoomList(roomList); nextUpdateTime = Time.time + timeBetweenUpdates; } } void UpdateRoomList(List<RoomInfo> list) { foreach (RoomItem item in roomItemList) { Destroy(item.gameObject); } roomItemList.Clear(); foreach (RoomInfo room in list) { RoomItem newRoom = Instantiate(roomItemPrefab, contentObject); newRoom.SetRoomName(room.Name); roomItemList.Add(newRoom); } } public void JoinRoom(string roomName) { PhotonNetwork.JoinRoom(roomName); } public void OnClickLeaveRoom() { PhotonNetwork.LeaveRoom(); } public override void OnLeftRoom() { lobbyPanel.SetActive(true); roomPanel.SetActive(false); } public override void OnConnectedToMaster() { PhotonNetwork.JoinLobby(); } void UpdatePlayerList() { foreach (PlayerItem item in playerItemsList) { Destroy(item.gameObject); } playerItemsList.Clear(); if (PhotonNetwork.CurrentRoom == null) { return; } foreach (KeyValuePair<int, Player> player in PhotonNetwork.CurrentRoom.Players) { PlayerItem newPlayerItem = Instantiate(playerItemPrefab, playerItemParent); newPlayerItem.SetPlayerInfo(player.Value); if (player.Value == PhotonNetwork.LocalPlayer) { newPlayerItem.ApplyLocalChanges(); } playerItemsList.Add(newPlayerItem); } } public override void OnPlayerEnteredRoom(Player newPlayer) { UpdatePlayerList(); } public override void OnPlayerLeftRoom(Player otherPlayer) { UpdatePlayerList(); } private void Update() { if (PhotonNetwork.IsMasterClient && PhotonNetwork.CurrentRoom.PlayerCount >= 1) { playButton.SetActive(true); } else { playButton.SetActive(false); } } public void OnClickPlayButton() { PhotonNetwork.LoadLevel("Game"); } }
问题排查与修复方案
三个报错对应三个独立问题,按顺序修复即可:
- 第18行启动时空引用
对应代码行是Transform spawnPoint = spawnPoints[randomNumber];,问题是Inspector面板中PlayerSpawner组件的spawnPoints数组未赋值,或数组内存在空元素。修复方式是在Unity编辑器中选中挂载PlayerSpawner的物体,把场景里所有生成点拖到spawnPoints数组中,确认没有空引用。 - DefaultPool加载"Player"失败
Photon 2的PhotonNetwork.Instantiate方法默认只能加载存放在Resources文件夹下的预制体,且预制体名称必须和代码传入的字符串完全一致。修复方式:- 在项目Assets目录下新建名为
Resources的文件夹(大小写完全匹配) - 把
playerPrefabs数组引用的所有玩家预制体都放到这个Resources文件夹内 - 确认每个玩家预制体的名称和数组内引用的物体名完全一致,不要出现代码传"Player"但预制体实际命名为其他值的情况
- 在项目Assets目录下新建名为
- 第20行切换头像时空引用
对应代码行是GameObject playerToSpawn = playerPrefabs[(int)PhotonNetwork.LocalPlayer.CustomProperties["playerAvatar"]];,问题有两个:一是playerPrefabs数组未在Inspector赋值,二是玩家刚进入游戏场景时playerAvatar自定义属性未完成初始化,直接强转取值会返回空。修复分两步:- 在Inspector给
PlayerSpawner的playerPrefabs数组按头像索引顺序赋值所有玩家预制体,数组长度要和PlayerItem里的avatars数组长度完全一致 - 修改
PlayerSpawner.cs的Start逻辑,增加属性存在判断、索引越界兜底,修改后代码如下:
- 在Inspector给
private void Start() { // 校验生成点配置 if (spawnPoints == null || spawnPoints.Length == 0) { Debug.LogError("未配置生成点"); return; } int randomNumber = Random.Range(0, spawnPoints.Length); Transform spawnPoint = spawnPoints[randomNumber]; // 兜底玩家头像索引,默认取0 int avatarIndex = 0; if (PhotonNetwork.LocalPlayer.CustomProperties.ContainsKey("playerAvatar")) { avatarIndex = (int)PhotonNetwork.LocalPlayer.CustomProperties["playerAvatar"]; // 防止索引越界 avatarIndex = Mathf.Clamp(avatarIndex, 0, playerPrefabs.Length - 1); } GameObject playerToSpawn = playerPrefabs[avatarIndex]; PhotonNetwork.Instantiate(playerToSpawn.name, spawnPoint.position, Quaternion.identity); }
额外隐藏问题:当前PlayerItem.cs里点击左右箭头切换头像时,没有给playerProperties做初始值兜底,第一次点击时如果playerAvatar属性不存在,强转也会触发空引用,可以在Start方法里加一行playerProperties["playerAvatar"] = 0;做初始值。
内容的提问来源于stack exchange,提问作者Rusty Lloyd Santos
相关产品推荐
相关产品推荐

