好长时间之前做过的一个项目 , 其中设计到用Unity模拟卡拉OK歌词过渡的效果 , 如下图所示 ↓ , 这里简单把原理部分分享一下.
演示效果
- 实现歌词动态调整功能
- 实现动态读取歌词文件功能
- 实现歌曲快进快退功能
- 实现歌曲单字时间匹配功能
- 实现可动态更换歌词前景色背景色功能
注:
这里为实现精准过渡效果使用的是KSC歌词文件, 并不是LRC文件哦 .
这其中我认为就是如何实现歌词部分的前景色向后景色过渡的效果了, 开始的时候我想的也是很复杂 , 使用Shader的形式实现 ,网上找了一些相关代码 , 发现不是特别理想 , 最终还是自己尝试着用Mask来实现的, 发现效果还不错 !
因为今天下班就过年回家啦! 其他细节之后会完善的 , 今天把工程文件先上传了 .
歌词效果类
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using System;
using DG.Tweening;
using DG.Tweening.Core;
/// <summary>
/// 用于显示歌词过渡的效果
/// 1. 获得路径加载并解析歌词文件信息
/// 2. 判断当前歌曲是否播放( 歌曲暂停的时候歌词效果也暂停 , 歌曲停止的时候歌词效果消失 )
/// 3. 判断歌曲快进或快退事件
/// </summary>
public class LayricPanelEffect : MonoSingleton<LayricPanelEffect>
{
#region *********************************************************************字段
//由外部传入的声音资源
[HideInInspector,SerializeField]
public AudioSource audioSource;
//歌词前景颜色;歌词后景颜色
[SerializeField]
public Color32 frontTextColor = Color.white, backTextColor = Color.black, outlineColor = Color.white;
//歌词面板的前景部分和后景部分
public RectTransform rectFrontLyricText, rectBackLyricMask;
public Slider slider;
//歌词文件路径
[HideInInspector,SerializeField]
public string lyricFilePath;
//是否开始播放当前行歌词内容
public bool isStartLyricEffectTransition = true;
//歌词调整进度 ( 纠错 )
// [HideInInspector]
public float lyricAdjust = -5f;
//歌词文本信息
// [HideInInspector]
[SerializeField,HideInInspector]
public Text _lyricText;
public Text _textContentLyric, _textLogMessage;
private Vector2 tempFrontSizeDelta, tempBackSizeDelta;
//用于访问歌词正文部分的内容在KscWord类中
private KSC.KscWord kscword = new KSC.KscWord ();
private KSC.KscWord curKscword = new KSC.KscWord ();
//内部定时器( 由外部传入参数来控制 , 用来记录歌曲播放的当前时间轴 )
private float _timer = 0.00f;
#endregion
/// <summary>
/// 初始化一些变量
/// </summary>
void InitSomething ()
{
//坚持对歌词文件进行赋值操作
if (_lyricText == null || rectFrontLyricText.GetComponent <ContentSizeFitter> () == null) {
if (rectFrontLyricText.GetComponent <Text> () == null) {
_lyricText = rectFrontLyricText.gameObject.AddComponent <Text> ();
}
_lyricText = rectFrontLyricText.GetComponent <Text> ();
//保持歌词实现自适应
rectFrontLyricText.GetComponent <ContentSizeFitter> ().horizontalFit = ContentSizeFitter.FitMode.PreferredSize;
rectFrontLyricText.GetComponent <ContentSizeFitter> ().verticalFit = ContentSizeFitter.FitMode.PreferredSize;
}
rectBackLyricMask.GetComponentInChildren <Text> ().text = _lyricText.text;
//歌词颜色的更改初始化
rectBackLyricMask.GetComponentInChildren <Text> ().color = backTextColor;
rectBackLyricMask.GetComponentInChildren <Outline> ().effectColor = outlineColor;
rectFrontLyricText.GetComponent <Text> ().color = frontTextColor;
//歌词过渡的前景部分 ( 用于判断过度遮罩的长度范围 )
tempFrontSizeDelta = rectFrontLyricText.sizeDelta;
tempBackSizeDelta = rectBackLyricMask.sizeDelta;
//是否开始当前歌词行播放标志位
isStartLyricEffectTransition = true;
}
void Awake ()
{
//初始化
InitSomething ();
}
/// <summary>
/// 控制歌词面板的显示
/// 1. 仅仅显示歌词面板信息 , 没有过渡效果!
/// </summary>
/// <param name="row">歌词正文部分行号.</param>
/// <param name="isPanelView">If set to <c>true</c> 显示面板歌词</param>
public void LyricPanelControllerView (KSC.KscWord curRowInfo, bool isPanelView)
{
// Debug.Log ("当前行是否开始=====>" + isPanelView.ToString ());
_textLogMessage.text = isStartLyricEffectTransition.ToString ();
rectBackLyricMask.sizeDelta = new Vector2 (0f, rectFrontLyricText.sizeDelta.y);
rectBackLyricMask.GetComponentInChildren <Text> ().text = _lyricText.text = "";
if (isPanelView) {
//根据时间得到当前播放的是第i行的歌词
//处理歌词面板信息 , 显示歌词
foreach (var item in curRowInfo.PerLineLyrics) {
_lyricText.text += item;
rectBackLyricMask.GetComponentInChildren<Text> ().text = _lyricText.text;
}
StartCoroutine (LyricPanelControllerEffect (curRowInfo, isPanelView));
} else {
StopAllCoroutines ();
rectBackLyricMask.sizeDelta = new Vector2 (0f, rectFrontLyricText.sizeDelta.y);
// StartCoroutine (LyricPanelControllerEffect (curRowInfo, isPanelView));
//当前歌词结束以后将歌词框初始化成0
rectBackLyricMask.GetComponentInChildren <Text> ().text = _lyricText.text = string.Empty;
}
}
/// <summary>
/// 开始实现歌此过渡效果, 仅仅效果实现
/// 1. 使用Dotween的doSizedata实现
/// 2. 动态调整蒙板的sizedata宽度
/// 3. 根据歌曲当前播放的时间进度进行调整
/// </summary>
/// <returns>The panel controller effect.</returns>
/// <param name="isPanelEffect">If set to <c>true</c> is panel effect.</param>
public IEnumerator LyricPanelControllerEffect (KSC.KscWord curRowInfo, bool isPanelEffect)
{
//当前时间歌词播放进度的百分比比例
int curWordIndex = 0;
if (isPanelEffect) {
rectBackLyricMask.DORewind ();
yield return null;
rectBackLyricMask.sizeDelta = new Vector2 (0f, rectFrontLyricText.sizeDelta.y);
//开始效果过渡
if (audioSource.isPlaying) {