\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\

vvvv – plugin 编写入门教程

vvvv-plugin 入门教程

本篇文章的目标是让即使身份是设计师的朋友, 也会在连接patch时偶尔想到会不会自己写个plugin会更方便些.

从一道应用题开始:甲.乙两人同时从两地出发,相向而行,距离是50千米。甲每小时走3千米,乙每小时2千米,甲带着一条狗,狗每小时走5千米。这只狗同时和甲一起出发,当它碰到乙后,便回头跑向甲;碰到甲后又回头跑向乙,如此往复,直到两人相遇。问,小狗一共跑了多少千米?

 

一、我们纯初学

这只是一道简单的应用题, 我们希望有一个节点,可以让我们输入了距离,速度这些参数之后,节点可以输出小狗跑的距离。

 

1. 双击vvvv空白处, 输入template.

按住CTRL再点击Template(Value), 创建一个新的Plugin

 

2. 右键这个新的节点,进入编程模式。PluginInfo区域用来定义节点的基本信息。但我们初学,先不管它。我们最关心的是怎么定义距离、速度这些输入pin脚,和小狗跑步距离这个输出pin脚。它们是在fields & pins区域里定义的。

#region fields & pins

[Input("People Distance")]                //输入Pin脚名称为People Distance

ISpread<double> Distance_In;              //double是小数类型,int是整数类型.

                                          //Distance_In是我们自定义的变量名

[Input("A Speed")]

ISpread<double> ASpeed_In;

[Input("B Speed")]

ISpread<double> BSpeed_In;

[Input("Dog Speed", DefaultValue = 0.0)]  //DefaultValue = 0.0说明Dog Speed这个pin脚默认值为0

ISpread<double> DogSpeed_In;

[Output("Dog Distance")]                 //Output

ISpread<double> DogDistance_Out;

[Import()]

ILogger FLogger;                         //调试信息输出. 我们不是程序员,此处略去.

#endregion fields & pins

 

3. 编码。我们的代码要写在Evaluate这个函数里才可以被执行,现在我们来计算小狗运动的距离,并且输出它。

public void Evaluate(int SpreadMax)

       {

           /*

           这里必须要插句嘴, vvvv的节点默认都是要操作Spreads的, 所以我们

           在写节点时要考虑如果输入了很多数据, 我们要输出多少数据.在这里

           如果我们输入了10组不同的数据, 就需要输出10个小狗运动的距离

           */

           //设置我们的输出数量.

           DogDistance_Out.SliceCount = SpreadMax;

           //遍历计算每组数据. for,if这种程序语法, C#教程看一会就明白了

           for (int i = 0; i < SpreadMax; i++)

           {

              //计算相遇时间

              double t = Distance_In[i] / (ASpeed_In[i] + BSpeed_In[i]);

              //输出距离

              DogDistance_Out[i] = DogSpeed_In[i] * t;

           }

}

4. Ctrl + S保存。新写的节点就可以用了。测试一下。

 

二、 哎,有那么点意思。

1.  PluginInfo区域的参数我们简单介绍一下。

#region PluginInfo

[PluginInfo(Name = "DogRun",    //Plugin显示的名字

   Category = "Value",             //Plugin的所属目录

   Version = "0.0.1",              //Plugin的版本号

   Help = "Help children against theri homoworks", //Plugin的帮//助信息, 挑选节点时,当鼠标放在这个Plugin上会显示此信息

   Tags = "dog, run",       //Plugin的标签

   Author = "agalloch21"    //Plugin的作者

)]

#endregion PluginInfo

2.  fields & pins区域,格式是固定的。

[Input("Dog Speed", DefaultValue = 0.0)]

ISpread<double> DogSpeed_In;

1)       Input表示这是一个输入pin脚, Output则是输出。

2)       “Dog Speed”是这个pin脚的名称,鼠标放在pin脚上即可看到。

3)       DefaultValue是这个pin脚的一个属性, 这个pin脚还可以有MaxValue、MinValue、IsSingle等很多属性。这里不详细讲。

4)       ISpread类型表示pin脚传入传出的是一个铺展(Spreads),这里只有ISpread和IDiffSpread两种类型可以选。IDiffSpread类型的优势在于当这个pin脚的值改变了, 你可以收到通知.

5)       double是小数类型, 说明这个pin脚传入的数据是小数。其它类型:int(整数),  bool(布尔值),  string(字符串)类型。

6)       DogSpeed_In是我们自定义的变量,下面写代码时会用到它。

3.  SpreadMax

SpreadMax的值等于输入的这些数据中最多的个数。比如输入了10个距离, 9个甲乙的速度, 8只小狗的速度, 那么SpreadMax的值为10.

三、 我有点晕, 但我还想继续往下看。

这次我们的需求提高了,我们希望模拟这只小狗的运动过程,我们输入甲乙的距离,甲乙的速度,小狗的速度,然后希望根据时间实时输出小狗和甲乙的位置。

1. 修改pin脚。

根据需求, 我们需要有甲乙距离、甲的速度、乙的速度、小狗的速度这4个输入pin脚,需要有小狗的位置、甲的位置、乙的位置这3个输出pin脚。我们可以更高端些,仿照LFO节点让我们的DogRun节点有Pause和Reset的功能,所以我们再加上Pause和Reset的输入pin脚。 这样改变后的fields & pins就是这样子

#region fields & pins

       //这里为了避免太复杂,我们只计算一组数据,即使有很多数据输入.

       [Input("Distance", IsSingle = true)]//IsSingle表明即使输入了很多数据, 该pin脚也只取第一个值

       IDiffSpread<double> Distance_In;   //这里我们用IDiffSpread类型, 希望在这个值改变时得到通知

       [Input("ASpeed", IsSingle = true)]

       IDiffSpread<double> ASpeed_In;

       [Input("BSpeed", IsSingle = true)]

       IDiffSpread<double> BSpeed_In;

       [Input("Dog Speed", IsSingle = true)]

       IDiffSpread<double> DogSpeed_In;

       [Input("Pause", IsSingle = true)]

       ISpread<bool> Pause_In;

       [Input("Reset", IsSingle = true, IsBang = true)] //IsBang表明这个pin脚是Bang类型的

       ISpread<bool> Reset_In;

       [Output("Dog Position")]    //Output

       ISpread<double> DogPosition_Out;

       [Output("A Position")]

       ISpread<double> APosition_Out;

       [Output("B Position")]

       ISpread<double> BPosition_Out;

2. 成员变量是个好东西。

因为每时每刻甲乙和小狗的位置都在改变,所以我们需要有一个地方保存他们当前的位置,下一次计算时要用到。成员变量适时出现。

       //定义成员变量, 并且初始化

       double a_position_ = 0;         //记录A君的当前位置

       double b_position_ = 0;         //记录B君的当前位置

       double dog_position_ = 0;       //记录小狗的当前位置

       double dog_direction_ = 1;      //记录小狗此时的运动方向

       bool   meet_ = false;              //甲乙两人是否相遇

成员变量可以简单理解为定义在Evaluate 函数外面的变量,它一直存在,可以用来存储数据。定义在Evaluate函数里面的变量在Evaluate 函数运行完之后都会消亡,所以不能用来存储数据。

 3. 编码。

       //called when data for any output pin is requested

       public void Evaluate(int SpreadMax)

       {

           //设置输出数量为1,我们只计算一组数据

           DogPosition_Out.SliceCount = 1;

           APosition_Out.SliceCount = 1;

           BPosition_Out.SliceCount = 1;

           //如果输入的数据变化了或者"Reset"pin脚设为true了,那么我们要重新模拟小狗的运动

           if(Distance_In.IsChanged    //只有IDiffSpread类型才有IsChanged标志,当Distance这个pin脚的值发生改变时,IsChanged的值为true

           || ASpeed_In.IsChanged || BSpeed_In.IsChanged

           || DogSpeed_In.IsChanged

           || Reset_In[0] == true)

           {

              a_position_ = 0;

              b_position_ = Distance_In[0];

              dog_position_ = 0;

              dog_direction_ = 1;

              meet_ = false;

           }

           /*因为我们只计算一组数据, 所以读写数据时加上下标[0]表示我们操作的是第一个数据.*/

           if(Pause_In[0] == false && meet_ == false)    //如果没有暂停的话并且甲乙两人没有相遇的话

           {

              //更新两人和小狗的位置

              a_position_ = a_position_ + ASpeed_In[0];

              b_position_ = b_position_ - BSpeed_In[0];

              dog_position_ = dog_position_ + dog_direction_*DogSpeed_In[0];

              //如果两人相遇

              if(a_position_ >= b_position_)

              {

                  a_position_ = Distance_In[0] / (ASpeed_In[0] + BSpeed_In[0]) * ASpeed_In[0];

                  b_position_ = a_position_;

                  meet_ = true;

              }

              //如果小狗跟A相遇了, 折返

               if(dog_position_ <= a_position_)

              {

                  double temp_t = (a_position_ - dog_position_) / (ASpeed_In[0] + DogSpeed_In[0]);

                  double temp_p = dog_position_ + temp_t * DogSpeed_In[0] + temp_t * DogSpeed_In[0];

                  dog_position_ = temp_p < b_position_ ? temp_p : b_position_;

                  dog_direction_ = 1;

              }

              //如果小狗跟B相遇了, 折返

              if(dog_position_ >= b_position_)

              {

                  double temp_t = (dog_position_ - b_position_) / (BSpeed_In[0] + DogSpeed_In[0]);

                  double temp_p = dog_position_ - temp_t * DogSpeed_In[0] - temp_t * DogSpeed_In[0];

                  dog_position_ = temp_p > a_position_ ? temp_p : a_position_;

                  dog_direction_ = -1;

              }

              //输出他们三个的位置

              DogPosition_Out[0] = dog_position_;

              APosition_Out[0] = a_position_;

              BPosition_Out[0] = b_position_;

           }

           //FLogger.Log(LogType.Debug, "hi tty!");

       }

四、 都是代码,真没劲。写出个这节点有什么用啊

         上面确实代码比较多,我也不太好讲,程序语法还是要找一本C#的书踏实的了解一下,一天就够。接下来我去用刚才新建的DogRun节点画点东西出来,以期望可以留住你们的眼球。


          这是用DogRun画出来的一个图形,如果你觉得还不错,还剩点兴趣,请跳回第三章,把仅剩的兴趣献给代码吧。不要执着在小狗折返,速度计算这些代码,你需要在意的是vvvv-Plugin的框架。弄明白了Plugin的结构,照葫芦画瓢,就可以自己动手去写一个节点啦!

 

Selected projects and research