252 lines
9.6 KiB
C#
252 lines
9.6 KiB
C#
using UnityEngine;
|
||
using UnityEngine.UI;
|
||
using UnityEngine.Video;
|
||
using System.Collections;
|
||
|
||
namespace GameFramework
|
||
{
|
||
/// <summary>
|
||
/// 剧情 PV 播放器 —— 点击 StoryReplayButton 后弹出全屏视频覆盖层播放 PV。
|
||
/// 播放完毕自动关闭,也可点击"跳过"按钮立即关闭。
|
||
///
|
||
/// 搭建:
|
||
/// 1. 在 MainMenu Canvas 下创建 VideoOverlay(初始 SetActive(false))
|
||
/// 2. 子物体:BG (全屏黑色 Image)、VideoDisplay (RawImage)、SkipButton (Button)
|
||
/// 3. 挂 VideoPlayer 组件,VideoClip 赋值为 pv.mp4
|
||
/// 4. 挂本脚本,Inspector 中关联各引用
|
||
/// </summary>
|
||
public class StoryPVPlayer : MonoBehaviour
|
||
{
|
||
[Header("覆盖层")]
|
||
[Tooltip("视频覆盖层根节点(初始隐藏)")]
|
||
[SerializeField] private GameObject videoOverlay;
|
||
|
||
[Header("视频显示")]
|
||
[Tooltip("全屏 RawImage 用于显示视频画面")]
|
||
[SerializeField] private RawImage videoDisplay;
|
||
|
||
[Header("跳过按钮")]
|
||
[Tooltip("右下角跳过按钮")]
|
||
[SerializeField] private Button skipButton;
|
||
|
||
[Header("视频播放器")]
|
||
[Tooltip("VideoPlayer 组件(挂在 VideoOverlay 或自身上)")]
|
||
[SerializeField] private VideoPlayer videoPlayer;
|
||
|
||
private RenderTexture _renderTexture;
|
||
private bool _isPlaying = false;
|
||
|
||
|
||
/// <summary>
|
||
/// 懒加载:确保所有引用都已初始化。
|
||
/// 使用 GetComponentsInChildren(true) 递归搜索(包括 inactive 子物体)。
|
||
/// </summary>
|
||
private void EnsureReferences()
|
||
{
|
||
// videoOverlay:默认就是自身(StoryPVPlayer 挂在 VideoOverlay 上)
|
||
if (videoOverlay == null)
|
||
{
|
||
// 递归搜索子物体中名为 "VideoOverlay" 的 GameObject
|
||
var transforms = GetComponentsInChildren<Transform>(true);
|
||
foreach (var t in transforms)
|
||
{
|
||
if (t.name == "VideoOverlay")
|
||
{
|
||
videoOverlay = t.gameObject;
|
||
break;
|
||
}
|
||
}
|
||
// 如果没找到,说明本脚本就挂在 VideoOverlay 上
|
||
if (videoOverlay == null)
|
||
videoOverlay = gameObject;
|
||
}
|
||
|
||
// 查找 RawImage(递归搜索 videoOverlay 的子物体,包括 inactive)
|
||
if (videoDisplay == null && videoOverlay != null)
|
||
{
|
||
var displays = videoOverlay.GetComponentsInChildren<RawImage>(true);
|
||
if (displays.Length > 0)
|
||
videoDisplay = displays[0];
|
||
}
|
||
|
||
// 查找 SkipButton(递归搜索,按名称匹配避免找到其他按钮)
|
||
if (skipButton == null && videoOverlay != null)
|
||
{
|
||
var buttons = videoOverlay.GetComponentsInChildren<Button>(true);
|
||
foreach (var b in buttons)
|
||
{
|
||
if (b.name == "SkipButton")
|
||
{
|
||
skipButton = b;
|
||
skipButton.onClick.AddListener(Stop);
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
|
||
// 查找 VideoPlayer:先查自身,再查 videoOverlay
|
||
if (videoPlayer == null)
|
||
videoPlayer = GetComponent<VideoPlayer>();
|
||
if (videoPlayer == null && videoOverlay != null)
|
||
videoPlayer = videoOverlay.GetComponent<VideoPlayer>();
|
||
|
||
// 订阅播放完成事件(只订阅一次)
|
||
if (videoPlayer != null)
|
||
videoPlayer.loopPointReached -= OnVideoFinished;
|
||
if (videoPlayer != null)
|
||
videoPlayer.loopPointReached += OnVideoFinished;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 播放 PV:先激活覆盖层 → 初始化引用 → 创建 RenderTexture → 等一帧 → 开始播放。
|
||
/// </summary>
|
||
public void Play()
|
||
{
|
||
Debug.Log($"[StoryPVPlayer] Play() called, _isPlaying={_isPlaying}, gameObject.activeSelf={gameObject.activeSelf}");
|
||
|
||
if (_isPlaying)
|
||
{
|
||
Debug.Log("[StoryPVPlayer] 已在播放中,忽略本次点击");
|
||
return;
|
||
}
|
||
|
||
// 关键:本脚本所在的 GameObject 可能初始为 inactive,必须先激活才能启动协程。
|
||
// 这里先把 gameObject.active 设为 true,让 MonoBehaviour 能正常工作。
|
||
gameObject.SetActive(true);
|
||
|
||
// 确保所有引用已初始化(可能在 Inspector 中未赋值,通过递归搜索查找)
|
||
EnsureReferences();
|
||
|
||
// 确保 videoOverlay 被赋值
|
||
if (videoOverlay == null)
|
||
videoOverlay = gameObject;
|
||
|
||
// 激活覆盖层
|
||
if (videoOverlay != null && videoOverlay != gameObject)
|
||
videoOverlay.SetActive(true);
|
||
|
||
Debug.Log($"[StoryPVPlayer] 引用检查:videoOverlay={videoOverlay != null}, videoDisplay={videoDisplay != null}, videoPlayer={videoPlayer != null}");
|
||
|
||
if (videoPlayer == null || videoDisplay == null || videoOverlay == null)
|
||
{
|
||
Debug.LogError($"[StoryPVPlayer] 引用未配置!videoOverlay={videoOverlay != null}, videoDisplay={videoDisplay != null}, videoPlayer={videoPlayer != null}。请检查 Inspector。");
|
||
if (videoOverlay != null) videoOverlay.SetActive(false);
|
||
return;
|
||
}
|
||
|
||
// 检查视频文件
|
||
if (videoPlayer.clip == null)
|
||
{
|
||
Debug.LogWarning("[StoryPVPlayer] VideoClip 未设置,尝试从 Resources 加载...");
|
||
videoPlayer.clip = Resources.Load<VideoClip>("pv");
|
||
if (videoPlayer.clip == null)
|
||
{
|
||
Debug.LogError("[StoryPVPlayer] 无法加载 pv 视频文件!请将 pv.mp4 放入 Resources 文件夹,或在 Inspector 中赋值 VideoClip。");
|
||
videoOverlay.SetActive(false);
|
||
return;
|
||
}
|
||
}
|
||
|
||
// 创建 RenderTexture(匹配视频分辨率,如果未知则用屏幕分辨率)
|
||
int width = videoPlayer.clip != null ? (int)videoPlayer.clip.width : Screen.width;
|
||
int height = videoPlayer.clip != null ? (int)videoPlayer.clip.height : Screen.height;
|
||
|
||
if (_renderTexture != null)
|
||
{
|
||
_renderTexture.Release();
|
||
Destroy(_renderTexture);
|
||
}
|
||
|
||
_renderTexture = new RenderTexture(width, height, 0);
|
||
videoPlayer.targetTexture = _renderTexture;
|
||
videoDisplay.texture = _renderTexture;
|
||
|
||
Debug.Log("[StoryPVPlayer] 启动协程,延迟一帧播放");
|
||
// 启动协程,等待一帧后再播放(Unity VideoPlayer 在 SetActive 同帧内调用 Play 无效)
|
||
StartCoroutine(PlayVideoCoroutine());
|
||
}
|
||
|
||
/// <summary>
|
||
/// 延迟一帧后播放视频(解决 VideoPlayer 在激活同帧内调用 Play 无效的问题)。
|
||
/// </summary>
|
||
private IEnumerator PlayVideoCoroutine()
|
||
{
|
||
_isPlaying = true;
|
||
Debug.Log($"[StoryPVPlayer] PlayVideoCoroutine started, activeSelf={gameObject.activeSelf}");
|
||
|
||
// 等待一帧,让 VideoPlayer 完成初始化
|
||
yield return null;
|
||
|
||
Debug.Log($"[StoryPVPlayer] 协程等待一帧后:videoPlayer={videoPlayer != null}, isActive={gameObject.activeSelf}");
|
||
|
||
if (videoPlayer == null)
|
||
{
|
||
Debug.LogError("[StoryPVPlayer] 协程中 videoPlayer 为 null,无法播放");
|
||
_isPlaying = false;
|
||
yield break;
|
||
}
|
||
|
||
// 播放
|
||
videoPlayer.Play();
|
||
Debug.Log($"[StoryPVPlayer] 已调用 videoPlayer.Play(), isPlaying={videoPlayer.isPlaying}");
|
||
|
||
// 暂停背景音乐
|
||
if (AudioManager.Instance != null)
|
||
AudioManager.Instance.PauseBGM();
|
||
|
||
Debug.Log("[StoryPVPlayer] 开始播放 PV");
|
||
}
|
||
|
||
/// <summary>
|
||
/// 停止播放:停止 VideoPlayer → 释放 RenderTexture → 隐藏覆盖层。
|
||
/// </summary>
|
||
public void Stop()
|
||
{
|
||
_isPlaying = false;
|
||
|
||
if (videoPlayer != null)
|
||
videoPlayer.Stop();
|
||
|
||
if (_renderTexture != null)
|
||
{
|
||
_renderTexture.Release();
|
||
Destroy(_renderTexture);
|
||
_renderTexture = null;
|
||
}
|
||
|
||
if (videoDisplay != null)
|
||
videoDisplay.texture = null;
|
||
|
||
if (videoOverlay != null)
|
||
videoOverlay.SetActive(false);
|
||
|
||
// 恢复背景音乐
|
||
if (AudioManager.Instance != null)
|
||
AudioManager.Instance.UnpauseBGM();
|
||
|
||
Debug.Log("[StoryPVPlayer] PV 播放已停止");
|
||
}
|
||
|
||
/// <summary>
|
||
/// 视频播放完毕回调(自动关闭覆盖层)。
|
||
/// </summary>
|
||
private void OnVideoFinished(VideoPlayer source)
|
||
{
|
||
Debug.Log("[StoryPVPlayer] PV 播放完毕");
|
||
Stop();
|
||
}
|
||
|
||
private void OnDestroy()
|
||
{
|
||
if (videoPlayer != null)
|
||
videoPlayer.loopPointReached -= OnVideoFinished;
|
||
|
||
if (_renderTexture != null)
|
||
{
|
||
_renderTexture.Release();
|
||
Destroy(_renderTexture);
|
||
}
|
||
}
|
||
}
|
||
}
|