在 Unity 中实现一个 音频控制器&字幕控制器(VoiceController&SubtitleController),可以用于管理角色对话、旁白等语音播放,并同步显示对应的字幕。

实现步骤

音频控制器 VoiceController

基本组件结构
  • 音频组件:使用 AudioSource 播放语音
public class VoiceController : MonoBehaviour
{
    public static VoiceController Instance;
    [Header("音频源")]
    public AudioSource audioSource;

    public bool playing;
    [Header("对应的英文语音文件")]
    public List<AudioClip> audioFileEng;
}
核心逻辑
  • 获取目标文件(中文或者英文)
/// <summary>
/// 播放音频文件
/// </summary>
/// <param name="targetClip"></param>
public void playTargetAudio(AudioClip targetClip)
{
    Debug.Log(targetClip.name);
    audioSource.Stop();
    if (targetClip == null)
    {
        return;
    }
    if (PlayerPrefs.GetString("Language") == "ENG")
    {
        try
        {
            targetClip = Enumerable.First(Enumerable.Where(audioFileEng, (AudioClip textA) => textA.name == targetClip.name.Replace("CHI", "ENG")));
        }
        catch
        {
            Debug.Log("Eng Audio File not find");
        }
    }
    audioSource.clip = targetClip;
    StopCoroutine("playAudio");
    StartCoroutine("playAudio");
}
  • 播放语音
/// <summary>
/// 播放音频协程
/// </summary>
/// <returns></returns>
private IEnumerator playAudio()
{
    playing = true;
    audioSource.Play();
    while (audioSource.isPlaying)
    {
        yield return new WaitForEndOfFrame();
    }
    playing = false;
    yield return new WaitForEndOfFrame();
}

字幕控制器 VoiceController

基本组件结构
  • 字幕UI:UnityEngine.UI.Text 显示字幕
  • 数据管理:用TextAsset
public class VoiceController : MonoBehaviour
{
    public static SubtitleController Instance;

    [Header("当前字幕文件")]
    public TextAsset suptitleFile;
    [Header("显示UIText")]
    public Text suptitleUI;
    [Header("当前字幕数组")]
    public string[] suptitleByTime;
    /// <summary>
    /// 当前播放时间
    /// </summary>
    private int currentSec;
    /// <summary>
    /// 播放状态
    /// </summary>
    public bool playing;

    [Header("对应的英文字幕文件")]
    public List<TextAsset> subtitleFileEng;
 }
核心逻辑
  • 获取目标文件(中文或者英文)
/// <summary>
/// 播放字幕文件
/// </summary>
/// <param name="file"></param>
public void PlayTargetFile(TextAsset file)
{
    StopCoroutine("autoPlay");
    suptitleFile = file;
    if (PlayerPrefs.GetString("Language") == "ENG")
    {
        try
        {
            suptitleFile = Enumerable.First(Enumerable.Where(subtitleFileEng, (TextAsset textA) => textA.name == file.name.Replace("CHI", "ENG")));
        }
        catch
        {
            Debug.Log("Eng Suptitle File not find");
        }
    }
    readAllString();
    StartCoroutine("autoPlay");
}
  • 读取TextAsset目标文件
    /// <summary>
    /// 读取当前字幕文件中分段字幕
    /// </summary>
    private void readAllString()
    {
        suptitleByTime = suptitleFile.ToString().Split("|"[0]);
    }
  • 播放字幕
    /// <summary>
    /// 播放字幕协程
    /// </summary>
    /// <returns></returns>
    private IEnumerator autoPlay()
    {
        currentSec = 0;
        playing = true;
        for (int i = 0; i < suptitleByTime.Length; i++)
        {
            string[] timeAndSuptitle = suptitleByTime[i].Split("\n"[0]);
            string[] array = timeAndSuptitle[0].Split(new string[1] { " --> " }, StringSplitOptions.None);
            if (array[0] == "")
            {
                continue;
            }
            int num = timeToint(array[0]) - currentSec;
            int playTime = timeToint(array[1]) - timeToint(array[0]);
            currentSec += num + playTime;
            yield return new WaitForSecondsRealtime(num);
            suptitleUI.text = "";
            for (int j = 1; j < timeAndSuptitle.Length; j++)
            {
                suptitleUI.text += timeAndSuptitle[j];
                if (j != timeAndSuptitle.Length - 1)
                {
                    suptitleUI.text += "\n";
                }
            }
            if (Enumerable.Last(suptitleUI.text) == "\n"[0])
            {
                suptitleUI.text = suptitleUI.text.Remove(suptitleUI.text.Length - 1);
            }
            yield return new WaitForSecondsRealtime(playTime);
            suptitleUI.text = "";
        }
        playing = false;
        yield return new WaitForEndOfFrame();
    }

  • 字符串转换播放时间
    /// <summary>
    /// 字符串转换播放时间
    /// </summary>
    /// <param name="timeString"></param>
    /// <returns></returns>
    private int timeToint(string timeString)
    {
        string[] array = timeString.Split(":"[0]);
        return int.Parse(array[0]) * 60 * 60 + int.Parse(array[1]) * 60 + int.Parse(array[2]);
    }
  • 关闭字幕
    /// <summary>
    /// 结束字幕
    /// </summary>
    public void stopAll()
    {
        StopCoroutine("autoPlay");
        suptitleUI.text = "";
    }

案例测试

字幕文件

# GAME1_CHI_SCRIPT1.txt
|00:00:01 --> 00:00:05
陳師傅,開工前記得跟足規矩戴好安全裝備。
|00:00:05 --> 00:00:10
黃色標記嘅係必須品,㩒實扳機掣睇清楚點用
# GAME1_ENG_SCRIPT1.txt
|00:00:01 --> 00:00:05
Master Chen, remember to wear safety equipment in full order before starting work.
|00:00:05 --> 00:00:10
The yellow mark is a must, and the trigger is clearly used.

项目配置

  • 字幕控制器

在这里插入图片描述

  • 音频控制器

在这里插入图片描述

  • 播放事件

在这里插入图片描述

最终效果

请添加图片描述
以上我们就完成了这个简单的实现,我们可以在这个基础上再设计一个结构清晰、可扩展的集成控制器类,利用Unity的音频和UI系统,结合协程和事件,实现语音和字幕的同步播放,同时提供必要的用户配置选项和扩展点。

Logo

分享前沿Unity技术干货和开发经验,精彩的Unity活动和社区相关信息

更多推荐