首先需要获取弹幕的xml文件
我是在https://www.jijidown.com/下载到的,冷门的视频或者弹幕可能没有收集,需要自己去爬取。
这里以《华晨宇、苏诗丁《南屏晚钟》.xml》为例
<?xml version="1.0" encoding="UTF-8"?>
<i>
<chatserver>chat.bilibili.com</chatserver>
<chatid>12532425</chatid>
<mission>0</mission>
<maxlimit>1500</maxlimit>
<state>0</state>
<real_name>0</real_name>
<source>k-v</source>
<!--”弹幕出现时间/单位秒,弹幕模式,字体大小,颜色,发送时间戳,弹幕池,用户Hash,数据库ID”-->
<d p="25.77600,1,25,16777215,1578935002,0,2dbcbbd,27154357842608132">全程高能</d>
<d p="48.12600,1,25,16777215,1578935074,0,2dbcbbd,27154395814166528">钢琴:我总感觉不大对劲</d>
<d p="28.86600,1,25,16777215,1578935020,0,2dbcbbd,27154367484264448">侧脸太好看了</d>
<d p="30.66600,1,25,16777215,1578935031,0,2dbcbbd,27154373332697090">爱死这个侧脸了</d>
<d p="59.40600,1,25,16777215,1578935088,0,2dbcbbd,27154403168354304">钢琴:我总感觉不大对劲</d>
<d p="146.10600,1,25,16777215,1578935188,0,2dbcbbd,27154455602921474">钢琴即将损命</d>
<d p="202.47000,1,25,16777215,1578936519,0,d29e09db,27155153118822400">苏苏苏苏苏苏苏苏苏苏苏苏苏苏苏苏苏苏苏苏苏苏苏苏苏</d>
<d p="238.65000,1,25,16777215,1578959705,0,6a0ed7a7,27167309393559552">哥哥太酷了</d>
<d p="243.77000,1,25,16777215,1578959715,0,6a0ed7a7,27167314568806402">我爱花花</d>
</i>
比如第一行<d>
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<GameObject> roll, bottom, top;
//一些播放参数
private float rollRatio = 1; //滚动弹幕的播放速度,越大越快
private float botopRatio = 4; //底部/顶部弹幕的显示时间;
void Start()
{
DOTween.SetTweensCapacity(500,50);//提高最大值,弹幕真的很多
roll = new List<GameObject>();
bottom = new List<GameObject>();
top = new List<GameObject>();
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<Text>();
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<RectTransform>();
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<RectTransform>().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<RectTransform>();
if (t.localPosition.y == y)
{
y += 40;
}
}
bottom.Add(go);
go.transform.localPosition = new Vector3(-960 - go.GetComponent<RectTransform>().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<RectTransform>();
if (t.localPosition.y == y)
{
y -= 40;
}
}
top.Add(go);
go.transform.localPosition = new Vector3(-960 - go.GetComponent<RectTransform>().sizeDelta.x / 2, y);
go.transform.DOScale(1, botopRatio).OnComplete(() =>
{
top.Remove(go);
Destroy(go);
});
}
}
else//其他高级弹幕就不显示了,有余力的同学可以去尝试
// 比如
//<d p="6.43300,7,20,16737792,1569854839,0,a77032eb,22393737377742850">["0.32","0.52","1-1","9","@@@@@@@@@@@@@@@@@@@@@@@",0,0,"0.32","0.52",500,0,1,"SimHei",1]</d>
//类型为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();
}
}
}
没有什么难点,关键的地方就是判断当前位置是否可以放置新的弹幕。
网友评论