Files
gold_dolphin/unity/Assets/UI/StoryPVPlayer.cs
T
2026-07-06 02:10:51 +08:00

252 lines
9.6 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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);
}
}
}
}