Unity基于Photon实现玩家摄像头实时显示报空引用异常求解
空引用异常排查与修复方案
核心空引用触发点
- 类内声明了全局
photonView字段,但Start()方法中重复声明了同名局部变量,赋值操作只作用于局部变量,全局photonView始终为null,后续FixedUpdate中调用photonView.RPC()时直接触发空引用。 WebCamTexture启动首帧还未完成权限申请、未输出有效画面时,webcamTexture.width/webcamTexture.height为0,直接读取参数、调用GetPixels()会触发空引用或参数错误。- 所有客户端无差别执行
FixedUpdate内的摄像头采集、编码、发送逻辑,非本地玩家不需要采集本地摄像头,会因本地webcamTexture未初始化触发空引用。 - 未做组件存在性校验:如果Quad对象未挂载Renderer组件、用户拒绝摄像头权限导致
webcamTexture初始化失败,后续调用相关接口都会触发空引用。 - 逻辑位置和性能问题:
FixedUpdate是物理帧回调,和渲染帧不同步;每帧新建Texture2D、编码JPG、调用Resources.UnloadUnusedAssets()开销极大,会导致GC卡顿、内存泄漏,极端情况也会触发资源引用异常。
修复步骤
- 去掉
Start()内photonView的局部变量声明,直接给全局字段赋值,推荐直接继承MonoBehaviourPun,用内置的photonView字段避免手动赋值出错。 - 加本地玩家判定:只有当前客户端是PhotonView所有者时,才初始化本地摄像头、执行采集编码逻辑,非本地玩家仅走RPC接收画面的逻辑。
- 加摄像头有效性校验:只有
webcamTexture处于播放状态、宽高大于0时才执行画面读取编码,避开启动首帧的空窗期。 - 缓存Renderer组件引用,不要每次调用都执行
GetComponent,减少不必要的性能开销。 - 把采集逻辑从
FixedUpdate移到LateUpdate和渲染帧同步,控制画面发送频率(10-15帧/秒足够面部显示使用),删除每帧调用Resources.UnloadUnusedAssets()的逻辑,该接口开销极大,仅适合在批量卸载资源时调用。 - 复用Texture2D采集对象,避免每帧新建对象产生大量GC,加摄像头权限申请逻辑适配移动端、WebGL平台。
- RPC发送目标改为
RpcTarget.OthersBuffered,不需要把画面发回本地玩家,本地直接显示原始摄像头画面即可,能省一半编码、带宽开销。
修复后完整代码
using System.Collections; using UnityEngine; using Photon.Pun; public class photonwebcam : MonoBehaviourPun { WebCamTexture webcamTexture; private Renderer quadRenderer; // 摄像头画面发送频率,可根据带宽情况调整 public float sendFrameRate = 15f; private float nextSendTime = 0f; private Texture2D captureTex; // JPG编码质量,0-100,值越低带宽占用越小 public int jpgQuality = 50; void Start() { quadRenderer = GetComponent<Renderer>(); if (quadRenderer == null) { Debug.LogError("Quad对象未挂载Renderer组件,无法显示画面"); return; } // 仅本地玩家初始化摄像头 if (photonView.IsMine) { StartCoroutine(RequestCameraPermission()); } } IEnumerator RequestCameraPermission() { // 等待摄像头权限授权 yield return Application.RequestUserAuthorization(UserAuthorization.WebCam); if (Application.HasUserAuthorization(UserAuthorization.WebCam)) { webcamTexture = new WebCamTexture(); quadRenderer.material.mainTexture = webcamTexture; webcamTexture.Play(); } else { Debug.LogError("用户拒绝摄像头权限,无法采集本地画面"); } } void LateUpdate() { // 非本地玩家跳过采集发送逻辑 if (!photonView.IsMine) return; // 摄像头未就绪直接返回 if (webcamTexture == null || !webcamTexture.isPlaying || webcamTexture.width <= 0 || webcamTexture.height <=0) return; // 控制发送频率 if (Time.time < nextSendTime) return; nextSendTime = Time.time + 1f / sendFrameRate; // 复用采集纹理,尺寸变化时重建 if (captureTex == null || captureTex.width != webcamTexture.width || captureTex.height != webcamTexture.height) { if (captureTex != null) Destroy(captureTex); captureTex = new Texture2D(webcamTexture.width, webcamTexture.height, TextureFormat.RGB24, false); } captureTex.SetPixels(webcamTexture.GetPixels()); captureTex.Apply(); byte[] bytes = captureTex.EncodeToJPG(jpgQuality); // 仅发送给其他玩家,不需要发回本地 photonView.RPC("ReceiveRemoteVideo", RpcTarget.OthersBuffered, bytes); } [PunRPC] public void ReceiveRemoteVideo(byte[] bytes) { if (bytes == null || quadRenderer == null) return; Texture2D remoteTex = new Texture2D(2, 2); remoteTex.LoadImage(bytes); quadRenderer.material.mainTexture = remoteTex; } void OnDestroy() { // 销毁资源避免内存泄漏 if (webcamTexture != null && webcamTexture.isPlaying) { webcamTexture.Stop(); } if (captureTex != null) Destroy(captureTex); } }
额外配置注意事项
- 挂载脚本的Quad对象必须同时挂载
PhotonView组件,且将该脚本添加到PhotonView的RPC监听列表中,否则RPC调用会失败。 - 多人房间中建议JPG质量不要超过70,否则带宽占用过高会导致房间同步卡顿。
- 该脚本挂载的Quad需要作为玩家Avatar的子物体,跟随PhotonView网络同步生成,保证每个玩家对应一个自己的面部画面Quad。
内容的提问来源于stack exchange,提问作者g g
相关产品推荐
相关产品推荐

