首先需要获取弹幕的xml文件
我是在https://www.jijidown.com/下载到的,冷门的视频或者弹幕可能没有收集,需要自己去爬取。
这里以《华晨宇、苏诗丁《南屏晚钟》.xml》为例
chat.bilibili.com
12532425
0
1500
0
0
全程高能
钢琴:我总感觉不大对劲
侧脸太好看了
爱死这个侧脸了
钢琴:我总感觉不大对劲
钢琴即将损命
苏苏苏苏苏苏苏苏苏苏苏苏苏苏苏苏苏苏苏苏苏苏苏苏苏
哥哥太酷了
我爱花花
比如第一行
25.77600 就是视频的多少秒出现的
弹幕模式 看到有1/4/5;1出现最多,应该是滚动弹幕,4/5是顶端弹幕或者底端弹幕
字体大小25
颜色16777215 是10进制 的白色
时间戳 1578935002 代表这是2020-01-14 01:03:22发送的弹幕 ,应该用不到这个数据
,后面的数据应该也用不到了。
然后是弹幕内容
开搞。
首先我们知道,要显示滚动弹幕且不重复,要让前一条这行的弹幕显示完全 再显示新的弹幕,否则它就到下一行去进行这个判断,直到有能显示这行的位置
暂时不考虑超出屏幕下方的弹幕。
顶部和底部的弹幕也是这么判断。
字体统一为25在1080p下设定文本框的高度为40,1080/40=27
最多显示27行弹幕
加上Content Size Fitter组件,将横向设置为最佳,此时Text物体的宽度将随文本的长度变化。弹幕速度也由文本的宽度决定。将其作为Prefab。
创建VideoPlayer组件
将其放置到UI后方,并匹配内部,过高和过宽的屏幕都将显示黑边
创建ChatPanel空物体作为所有弹幕的父对象,右上角对齐
三个按钮 开启关闭三个位置的弹幕
编写脚本ChatManager.cs
using DG.Tweening;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Data.SqlTypes;
using System.Xml;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.Video;
public class ChatManager : MonoBehaviour
{
// Start is called before the first frame update
public VideoPlayer vp;
private string path;
public GameObject chatText;
public Transform chatPanel;
private bool isRollOff, isBottomOff, isTopOff;
private List roll, bottom, top;
//一些播放参数
private float rollRatio = 1; //滚动弹幕的播放速度,越大越快
private float botopRatio = 4; //底部/顶部弹幕的显示时间;
void Start()
{
DOTween.SetTweensCapacity(500,50);//提高最大值,弹幕真的很多
roll = new List();
bottom = new List();
top = new List();
vp.url = Application.streamingAssetsPath + "/mv.mp4";
vp.Play();
path = Application.streamingAssetsPath + "/chat.xml";
XmlDocument xmlDoc = new XmlDocument();
xmlDoc.Load(path);
XmlNodeList nodes = xmlDoc.SelectSingleNode("i").ChildNodes;
foreach (XmlElement xe in nodes)
{
if (xe.Name=="d")
{
float showTime = float.Parse(xe.GetAttribute("p").Split(',')[0]);
int type= int.Parse(xe.GetAttribute("p").Split(',')[1]);
// int size= int.Parse(xe.GetAttribute("p").Split(',')[2]);//默认字体大小25。
Color color = TenToColor(xe.GetAttribute("p").Split(',')[3]);
string chat = xe.InnerText;
StartCoroutine(InitChat(showTime, type, color, chat));
}
}
}
IEnumerator InitChat(float showTime,int type,Color color, string chat)
{
yield return new WaitForSeconds(showTime);
GameObject go = Instantiate(chatText, chatPanel);
Text te = go.GetComponent();
te.color = color;
te.text = chat;
yield return new WaitForEndOfFrame();
if (type == 1)//滚动
{
if (isRollOff)
{
Destroy(go);
}
else
{
float y = 0;
foreach (var item in roll)
{
RectTransform t = item.GetComponent();
if (t.localPosition.y == y)
{
if (t.localPosition.x + t.sizeDelta.x >= 0)
{
y -= 40;
}
}
}
go.transform.localPosition = new Vector3(0, y);
roll.Add(go);
go.transform.DOLocalMoveX(-1920 - go.GetComponent().sizeDelta.x, MoveTime(chat))
.SetEase(Ease.Linear)
.OnComplete(() =>
{
roll.Remove(go);
Destroy(go);
});
}
}
else if (type == 4)//底部
{
if (isBottomOff)
{
Destroy(go);
}
else
{
float y = -1040;
foreach (var item in bottom)
{
RectTransform t = item.GetComponent();
if (t.localPosition.y == y)
{
y += 40;
}
}
bottom.Add(go);
go.transform.localPosition = new Vector3(-960 - go.GetComponent().sizeDelta.x / 2, y);
go.transform.DOScale(1, botopRatio).OnComplete(() =>
{
bottom.Remove(go);
Destroy(go);
});
}
}
else if (type == 5)//顶部
{
if (isTopOff)
{
Destroy(go);
}
else
{
float y = 0;
foreach (var item in top)
{
RectTransform t = item.GetComponent();
if (t.localPosition.y == y)
{
y -= 40;
}
}
top.Add(go);
go.transform.localPosition = new Vector3(-960 - go.GetComponent().sizeDelta.x / 2, y);
go.transform.DOScale(1, botopRatio).OnComplete(() =>
{
top.Remove(go);
Destroy(go);
});
}
}
else//其他高级弹幕就不显示了,有余力的同学可以去尝试
// 比如
//["0.32","0.52","1-1","9","@@@@@@@@@@@@@@@@@@@@@@@",0,0,"0.32","0.52",500,0,1,"SimHei",1]
//类型为7的弹幕不清楚他的意思
{
Destroy(go);
}
}
//颜色转换
Color TenToColor(string s)
{
Color nowColor;
string s16 = Convert.ToString(int.Parse(s), 16);
ColorUtility.TryParseHtmlString("#" + s16,out nowColor);
return nowColor;
}
//时间戳转日期 1970年是起始
public DateTime UnixTimestampToDateTime(DateTime target, long timestamp)
{
DateTime start = new DateTime(1970, 1, 1, 0, 0, 0, target.Kind);
return start.AddSeconds(timestamp);
}
float MoveTime(string chat)//根据字数返回时长
{
return 15/rollRatio/ Mathf.Log(chat.Length+8, 16)-2;
}
public void OnRollClick()
{
isRollOff = !isRollOff;
if (isRollOff)
{
foreach (var item in roll)
{
Destroy(item);
}
roll.Clear();
}
}
public void OnBottomClick()
{
isBottomOff = !isBottomOff;
if (isBottomOff)
{
foreach (var item in bottom)
{
Destroy(item);
}
bottom.Clear();
}
}
public void OnTopClick()
{
isTopOff = !isTopOff;
if (isTopOff)
{
foreach (var item in top)
{
Destroy(item);
}
top.Clear();
}
}
}
没有什么难点,关键的地方就是判断当前位置是否可以放置新的弹幕。