使用代码制作弹幕动画 - SJTU-Art-Center/ACLiveConsole GitHub Wiki

0770 弹幕开发

行动代号:DBS

弹幕系统由三个队列(Queue)组成:接收队列 _danmakuQueue,弹幕池队列 DanmakuPool 和显示文字队列 danmakuLabels (只针对 气泡右下 样式)。每一次进队与出队由计时器精确控制。图中显示了一个弹幕的全程处理时间默认为 13s。

其中接收队列使用了 copyliu 的 弹幕姬 源码,并进行了适应性修改。保留弹幕姬源码模型是为了日后维护的方便。

果然,在 4 月 10 日,B 站更新了 弹幕协议v2AC Live Console 3.4 以前的版本不能正常地接收弹幕。在 3.6 版本中采用了 弹幕姬 1.1.1.28 版本的 DanmakuLoader.cs 和 DanmakuModel.cs 。

0771 弹幕动画

由于弹幕是随即产生,随即消失的,所以我们不能使用 XAML 前端代码设计界面(静态的),必须使用后台代码产生弹幕动画。

使用 WPF 的好处是其可以使用 GPU 渲染窗体,产生更为流畅的动画。相较于传统的 Winform 更加高级。

0772 弹幕样式定义

FocalDepthHover.xmal.cs

public partial class FocalDepthHover : Window {
    
    // Line 33
    public enum DanmuStyle { Plain, Bubble, BubbleFloat, BubbleCorner };

}

DanmuStyle 枚举类型定义了弹幕的几种样式,默认样式有:

代码 样式
Plain 普通样式
Bubble 气泡样式
BubbleFloat 随机位置产生悬浮的气泡
BubbleCorner 在角落处产生堆叠的气泡

0772.A PlainBubble原理

参数定义见 0764 弹幕样式

层数n对应于f的n-1次方(n<=#L)。

根据近大远小的规律,前面的物体移动得快,后面的物体移动得慢,这里设置的情况是最前面的弹幕为 T 的一半,移动时间极限为 T (字体大小为 0 时,这是不可能达到的)。

由于字号与高斯模糊半径是等量纲的量,两者比例是相对应的,而第一层是不模糊的,同样的高斯模糊半径的极限为 B 。

这样就通过伪 3D 方式造出了 3D 效果。

0772.B BubbleFloat原理

在屏幕中央一定比例的区域内随机生成气泡起点,使用二次缓动函数缓出,移动窗口高度的三分之一。

0772.C BubbleCorner原理

屏幕占比应当尽量小,因为计算的是右下边距。

气泡弹出时会有一个字号从 5 至 S 的动画,所有的气泡都在第 1 层。淡入+字号+悬浮+淡出 动画组合。

0772.D BottomBar, BottomBarWithUserName 原理

弹幕池选中的弹幕弹出由两个故事板组成,第一部分是从无到有,第二部分是从有至无,该部分动画保持速度。底端滚动条的颜色由气泡颜色决定,高度由屏幕占比决定。

底部滚动条(含用户名)样式将增加一个用户名弹幕条目进入滚动队列,该弹幕为细体。

0773 创建动画基本元素

动画基本元素是 Animation 类的派生类,处于下面的事件中:

// Line 211
private void ReceiveDanmu(object sender, ReceivedDanmuArgs e)
{

//...

switch (DM_Style)
    {
    case DanmuStyle.Bubble:
    case DanmuStyle.Plain:
    //...

    //故事板 Line 248
    Storyboard storyboard = new Storyboard();

    //添加X轴方向的动画 Line 251
    DoubleAnimation doubleAnimation = new DoubleAnimation(this.Width, -label.Content.ToString().Length * hoverLayers.ElementAt(temp_ord).TextSize / 0.75,new Duration(TimeSpan.FromSeconds(hoverLayers.ElementAt(temp_ord).Hover_time)));
    Storyboard.SetTarget(doubleAnimation, label);
    Storyboard.SetTargetProperty(doubleAnimation, new PropertyPath("(Canvas.Left)"));
    storyboard.Children.Add(doubleAnimation);

    //故事板即将开始 Line 259
    storyboard.Completed += new EventHandler(Storyboard_over);
    storyboard.Begin();

    //...
    break;
    }

//...

}

Line 249 => 创建故事板(Storyboard)。

Line 252 => 创建一个双精度型动画(DoubleAnimation)。不同的数值类型 WPF 提供了不同类的动画。参数:(From, To, Duration)

在新建动画时可以设置两个参数:开始时间 BeginTime 和缓动函数 EasingFunction。关于标准缓动函数的定义详见 Easing Functions 。值得一提的是,在弹幕设置选单里,数值变化采用了不同的缓动函数。

Line 253 => 设置动画目标。绑定到标签 label 上。

Line 254 => 设置动画目标属性路径。将属性路径(PropertyPath)设定为标签在 Canvas 中的左边距值(Left)。

Line 255 => 向故事板添加动画对象。

Line 260 => 添加故事板结束动作,Storyboard_over 被建立为 EventHandler ,结束后将会销毁该弹幕,以防止内存泄漏。

Line 261 => 故事板开始,其涵盖的动画随即开始。

故事板可以含有多个动画,并设置不同的开始时间和结束时间。

0774 接入接口

当你写入一个新的弹幕样式,需要在DanmuStyle 枚举类型里添加你的定义 MyStyle,并且在主窗口 MainWindow.xaml.cs 的复选框 ComboBoxStyle 控件中加入复选框项 ComboBoxItem ,其 Selected 的行为如下:

FocalDepthHover.DM_Style = FocalDepthHover.DanmuStyle.MyStyle;

0775 气泡样式

气泡样式(tipLabel)是在 XAML 文件内定义的。由一个圆角矩形和一个带填充的三角形组成。由于没有使用合并路径,导致该气泡在调整其不透明度时,三角形会盖不住圆角矩形的描边出现 BUG。如果要解决这个问题,需要纯后台的代码实现,比较复杂,所以这里偷懒了,欢迎修 BUG 。

0776 堆叠式气泡样式

堆叠式弹幕的特点是,一次只发送一个弹幕,所以需要通过一个队列来存储弹幕,并设置计时器,每 0.5s 出队一个弹幕输出。

气泡右下样式与底部滚动条样式属于这一类型的弹幕。

0777 重新生成层 Regen() 函数 窗体间的弹幕通道

主窗口的设置更改后,会将 SettingChanged 改为 true,这样在接收到下一个弹幕后,会调用 Regen() 函数,重新生成 List<HoverLayer> 的实例化对象。

窗体间的弹幕通道是通过 ReceicedDanmu 实现的。MainWindow 发出信号,由 FocalHoverLayer 的实例化对象处理弹幕事件。

0778 词云

这一部分采用了 WordCloud nuget 包,没有时间去阅读相关文献,根据预想,应该是一个动态的词云,前后可以显示每一个词的地位变化。但是由于时间有限,只能做到静态的切换,真正要做到这一点需要从头写代码,并且使用一篇论文里相关度较高词云的生成算法(该算法无法计算遮罩词云)。

弹幕出队后,如果正在收集弹幕词云(wcCollecting),将会启用 JIEBA.NET 分词器将弹幕分成一些词语收入 danmudic 词云词典中(里面包含每个词语的文本与出现数量,使用的 Dictionary<string,int> 是基于红黑树的一个词典)。开始生成后,先执行剪枝操作,对禁用词进行删除。之后再复制为 DecDic 降序序列词典,存储统计数据,判定完是否是彩色词云后就会调用 WordCloud 开始生成词云,进度条开始显示。

生成完成后的词云将会被存入内存流中,存储到本地的同时(可以在 Resources/WordCloud.png 看到),被放在公屏上。

如果启用了自动生成,这个时候就会开始下一轮的生成,淡出原来的词云,淡入新的词云。否则等待计时器触发下一轮生成。

0779 天选之人

这一部分阐述弹幕抽奖的原理。

B站弹幕姬获取弹幕后,出队后立刻检测是否符合抽奖弹幕,如果符合,则同时:构造 NameLabel 存储用户名和弹幕发出时间并加入展示队列 queueNameLabel,并且加入存储层 GiftingNameList。当抽奖完毕后将使用 RNGCryptoServiceProvider 真随机数生成器循环性地生成几个随机数选出天选之人,并输出到中奖者显示。

动画部分,抽奖中的用户名是滚动展示的,滚动时间由相邻弹幕发出时间差计算,刷的弹幕越快,动画滚动越快,这一部分保持时间。但是这部分有一个BUG,当0.5s内同时有两个弹幕出现时,抽奖中的显示很有可能卡住,暂时不做解决。中奖者,如果用户名的长度超过了屏幕长度,则会滚动显示,分为两个故事板:中段到左端,左端到结束,先慢后快。

结束后,抽奖会有记录,记入 Gift.txt 中。

⚠️ **GitHub.com Fallback** ⚠️