2017-1-9 13:15:41 [显示全部楼层]
17734浏览
查看: 17734|回复: 1

[入门教程] 写给设计师的编程趣味指南-(3)​让图形跑起来(上)

[复制链接]
本帖最后由 kaka 于 2017-8-18 08:23 编辑

写给设计师的编程趣味指南-(3)​让图形跑起来(上)图1
在开始看这节之前,希望你已经熟悉了基本的函数绘图方法。否则你会被两个大头函数setup 和 draw 搞晕。
既然要做运动的图形,就要知道动画究竟是怎么产生的。
写给设计师的编程趣味指南-(3)​让图形跑起来(上)图2

上图相当吸引我,而且非常直观地揭示了动画的实现原理。
动画是魔法,是关于视觉欺骗的魔法。只是在这个信息爆炸,视频满天飞的年代,我们已经对它习以为常了。也很少有人会去惊叹,能看到动画,本身就是一件神奇的事。
在程序中制作动画,原理是相通的。我们只要考虑怎么在每页上画上不同的图形,程序就会自动去翻页,而大脑会将它脑补成动画。
下面会聊聊如何实现基本的动画运动,在此之前需要我们了解一些变量的基础知识。
变量
变量是数据的容器,它可以在程序中反复使用。
例子:
size(500, 500);
ellipse(100, 250, 50, 50);
ellipse(200, 250, 50, 50);
ellipse(300, 250, 50, 50);
ellipse(400, 250, 50, 50);
写给设计师的编程趣味指南-(3)​让图形跑起来(上)图3
这段代码没有用到变量,在屏幕上画了四个圆。仔细观察,会发现这四个圆的宽高都是一样的。既然一样,为了减少数值的重复输入,我们其实可以定义一个符号来表示,这个符号就是变量。
使用变量后的代码:
size(500, 500);
int a = 50;
ellipse(100, 250, a, a);
ellipse(200, 250, a, a);
ellipse(300, 250, a, a);
ellipse(400, 250, a, a);

写给设计师的编程趣味指南-(3)​让图形跑起来(上)图4
绘图的结果和原来完全一样!
定义了变量 a ,我们就可以很方便地改变数值。如果将a = 50,改成a=100。那所有圆的宽高都会统一变成100,无需再逐一修改数值了。变量真是个好发明。
写给设计师的编程趣味指南-(3)​让图形跑起来(上)图5
变量的创建
变量使用前需要先声明,并且需要指定它的数据类型。
int i;    i = 50;
第一句代码声明了一个变量 i。int 是一个专门用来声明变量的符号。声明时,会在电脑内存中开辟一个空间,相当于生成了一个“箱子”,专门用来存放整数数据。
第二句代表把 50 赋值给变量 i。执行这句以后,数据就会稳稳地存放在了 i 这个变量上了。
你也可以更懒一些,将两步并作一步,声明的同时完成赋值。
int i = 50;
变量名的命名比较自由,但也有讲究的地方。
变量的命名规则
  • 必须是字母和数字或下划线的组合。可以用一个字符,也可以用一个单词,
  • 区分大小写,name 和 Name 代表不同的变量
  • 尽量取的能让自己一眼看懂,首字符只能以字母开头,不要用数字和特殊字符。
  • 不能用关键字,比如 int,float
以下,都是错误的声明方式
int $a;int 89b;
以下是合法的声明
int r;int super_24;int openTheDoor;变量的类型
除了可以声明为整数数据,还可以声明为小数数据(也叫浮点数据),用关键字float。
float b = 0.5
这里要牢记自己声明数据时,究竟用了什么类型。如果用了关键字 int ,后面赋值时就不能写出 i=0.5之类的,这样程序会出错。但反过来可以,比如 float i = 5 是正确的语法,程序仍会将它识别为小数。
有些变量是系统定义好的,无需自己声明。比如前面提过的 width,height,它会自动获取屏幕的宽和高。由于这两个参数的使用频率太高了,设计者就直接把它定义成一个默认的变量,方便我们使用。类似的还有变量 PI ,它就代表圆周率。
运算符
Processing 中的常用运算符有几种:
+
-
*
\
取模,得出余数
  • 加减乘除大家都不陌生,只有 % 看起来会比较古怪,它得出的数是余数。9 % 3的结果是 0 。而 9 % 5 的结果会是 4。
  • 运算符可以在数值以及变量间使用。

int a = 1;         //声明整数变量a,赋值为1
int b = 2;         //声明了整数变量b,赋值为2
int c;                  //声明了整数变量c
c = a + b;         //将两变量相加,并赋值给c
print(c);          //输出变量c
运行结果:
写给设计师的编程趣味指南-(3)​让图形跑起来(上)图6
输出的结果不会显示在窗口,而是在下方的控制台。
  • 第四行的写法看上去很奇怪。但这是计算机赋值操作的常用格式。等号的左边要写最终赋值的变量,等号右边写上运算的过程。
  • 第五行的 print 函数可以在控制台打印出变量,常常用于检测数值的输出情况。
运算规则
Processing 里面比较麻烦的一点就是在运算的时候需要搞清变量的类型。特别需要注意的就是浮点数与整数类型之间的处理。
print(6 / 5);     //结果 1
整数与整数间运算会得出整数,6 除以 5 的实际结果是 1.6 ,但程序中的输出结果会是 1 。这比较违反直觉,程序中不会进行四舍五入的处理,而是直接舍去小数点之后的数。
print (6.0 / 5.0) ;   //结果 1.6
浮点数与浮点数之间运算会得出浮点数,实际结果是 1.6 ,程序输出结果也会是 1.6。
print (6 / 5.0) ;     //结果 1.6print (6.0 / 5) ;     //结果 1.6
最后是整数和浮点数混用,最终输出的结果会是浮点数 1.6。
  • 其实你只要记住,它这种规则的设计是为了不损失数据的精度,所以只要有一方是浮点数,结果都会是浮点数。
setup,draw函数
说了一堆铺垫的知识,终于可以玩点有意思的了。
setup和draw函数,相当于是processing里面的主函数。这两个函数非常特殊,可以控制程序的流程。稍微复杂点的程序,都要把这两个家伙写进去,它们是程序的基本框架。
格式:
void setup(){
}
void draw(){
}
用法的特殊使他们调用格式也和其他函数不一样。函数名前面要加上“void”, 代表没有”返回值”(现在无需理解,记住它就好)。函数名后再跟上小括号和大括号。
看个例子:
void setup(){
      print(1);
}
void draw(){
      print(2);
}
当按下运行按钮,控制台上会先输出数字“1”,并且不断地输出“2”,直到你按下暂停按钮或是关闭窗口。
写给设计师的编程趣味指南-(3)​让图形跑起来(上)图7
被 setup 函数用大括号包裹的代码,只会执行一次。而draw函数内的,则会不断地循环执行(默认每秒执行60次)。
由于这种特性,setup往往会用于初始化环境属性,如屏幕宽高,背景颜色,各种变量的赋值。而draw函数里面常常放绘图函数,以产生不断变化的图像。
一个平移的圆
有了draw函数的出现,我们就能开始做动画了。
Processing写动画效果的方式是“很笨拙”的。不存在某种现成的指令,比如指定某形状做曲线运动,设定好速度,移动距离,它就刷刷地生成一个动画。
我们要亲自去定义这些细节。程序需要你很明确地告诉它,每帧要画一个怎样的图形。
把下面代码敲进去(现在要开始动手敲了):
int x;
int y;
void setup(){
    size(300, 300);
      x = 0;
      y = height/2;
}
void draw(){
      background(234, 113, 107);
      noStroke();
      ellipse(x, y, 50, 50);
      x = x+1;
}

写给设计师的编程趣味指南-(3)​让图形跑起来(上)图8
这段代码展示了一个运动的圆。
前面声明了两个变量x,y。用于储存坐标的位置。变量赋值在setup函数中进行。关键代码是draw函数中的这个:
x = x + 1
不要用数学等式去看它,否则会很奇怪。这里的“=”号是一个赋值符号,代表把右边的数值放到左边的变量里。假设 x 为50,代码运行后。等号右边就等于 50+1,即51。最终结果会赋到变量x里,于是x的值就变更为51了。
顺着程序的流程走,draw函数每运行一次,x的数值就会增加1。于是每次绘图,圆形都会比上一帧多向右平移一个像素,图形因此就动起来了。
  • 为了代码有更好的可读性,大括号里面的每行代码前面应该留出一定的空间,并且尽量对齐。键入 TAB 或者若干空格可以进行缩进。
  • 程序中空格符与换行符不会影响程序,所以多打一个少打一个往往没有关系。
这里还有别的更简便的表示方法。原来要使一个变量circle自增 1,需写成
circle = circle +1
挺麻烦的,如果变量名越长,要打的字就越多。所以我们的懒人前辈就想出了这个办法。
circle++
是不是超简便?它就代表自增 1。与它相似的还有 -- ,代表自减 1 。
但如果希望自增的数量是别的,比如 2 。就要用别的表示方法
circle += 2
这个等价于
circle = circle + 2
与它作用相似的还有 -= , /=  , *= 。
运动方向
图形往哪个方向运动,取决于你怎么变化你的坐标。如果改成 y=y+1,圆就会往下运动,如果x,y都同时增加1,圆就会往右下方运动。写成减号会朝相反的方向。
int x, y;   //可同时声明多个,用逗号作区分
void setup(){
      size(300, 300);
      x = 0;
      y = 0;
}
void draw(){
      background(234, 113, 107);
      noStroke();
      ellipse(x, y, 50, 50);
      x++;
      y++;
}
写给设计师的编程趣味指南-(3)​让图形跑起来(上)图9
运动速率
还记得draw函数默认每秒运行60帧吗?按这个速率去推算,上面的圆,每秒就会向右移动60像素。
想改变图形的速率,有两个办法。一个就是增大每次 x 的变化值。
x=x+10
这样速度就比原来提升了10倍!
还有就是改变画布的刷新频率。
frameRate()
这个函数可以改变画布的播放频率,在setup函数内写上frameRate(10),就会将播放速度改成10帧每秒。较原来默认的60帧每秒变慢了6倍。
被忽视的背景
前面的例子,都是将background函数写在draw里的,有没有想过,假如写在setup里,会有什么不同?在平移运动的例子上做些改动。
int x, y;
void setup(){
      size(300, 300);
      background(234, 113, 107);
      x = 0;
      y = height/2;
}
void draw(){
      noStroke();
      ellipse(x, y, 50, 50);
      x += 1;
}

写给设计师的编程趣味指南-(3)​让图形跑起来(上)图10
奇怪了。可能不是很理解问题产生的原因。那将 noStroke 函数去掉,重新加上描边效果,看看圆的运动轨迹。
写给设计师的编程趣味指南-(3)​让图形跑起来(上)图11
原来是前面画的圆形没有被清除!由于 setup 函数只运行一次,如果将 background 写在上面,就只会填充一次背景,之后就再也不起作用了。background 函数有点像油漆桶工具,一旦使用就会覆盖当前画布的所有内容,而不是仅仅设定一个背景颜色。所以将它写在draw函数的前头,才能保证每次画新图形之前,都把上一帧的内容覆盖。这样圆就能按我们的设想跑起来了。
除了记清每个函数的作用以外,我们还得琢磨代码的位置。很多时候代码上移一行,下移一行,写在大括号内,写在大括号外,会产生截然不同的效果。代码的方向是二维的。一旦出现了bug,要往这两个维度上去调试。
  • 这种不重绘的方式,适当运用可以做出很特别的效果,复制下面代码体验一番
void setup(){
      size(400, 400);
}
void draw(){
      ellipse(width/2-mouseX, height/2-mouseX, mouseY, mouseY);
      ellipse(width/2-mouseX, height/2+mouseX, mouseY, mouseY);
      ellipse(width/2+mouseX, height/2-mouseX, mouseY, mouseY);
      ellipse(width/2+mouseX, height/2+mouseX, mouseY, mouseY);
}
写给设计师的编程趣味指南-(3)​让图形跑起来(上)图12
这里提前用到了神奇的变量 mouseX 和 mouseY ,后面再详细讲解。
抖动的圆
如果我希望圆的运动方向是不规则的怎么办?巧妙地结合random函数可以做到这点。
random是一个高频使用的函数,可以用来生成随机数。它就像神出鬼没的精灵,一旦变量和它扯上关系,你完全无法预测下一步会变成什么。
调用形式:
random(high)
high代表随机的上限,默认下限是0。比如random(10)。就会从0到10之间,随机生成一个数值(包括0,但不包括10)。
random(low,high)
如果设置两个参数,那就会返回这两个数之间的随机值。比如random(5,10)。就会从5到10之间,随机生成一个数值(包括5,但不包括10)。
看下面例子:
float x;
x = random(50,100);
print(x);
每次运行程序,都会在控制台上输出不同的数值。
  • 注意,通过random函数生成的数值为浮点类型(小数类型),如果要赋值给整型变量,要通过函数int()进行转换,转换的过程不遵循四舍五入,而是直接将小数部分舍去。因此 int(random(5)),输出的数值只有五种可能,0,1,2,3,4。
熟悉random函数的用法之后,可以直接看下面的例子。
int x, y;
void setup(){
      size(300, 300);
      x = width/2;
      y = height/2;
}
void draw(){
      background(234, 113, 107);
      noStroke();
      x += int(random(-5, 5));
      y += int(random(-5, 5));
      ellipse(x, y, 50, 50);
}
写给设计师的编程趣味指南-(3)​让图形跑起来(上)图13
之前坐标增加的数值都是固定的。所以只要增加一个随机值,圆就会往不确定的方向移动。而随机的范围越大,它就“抖动”得越厉害。因为帧与帧之间的数值变化是跳跃的,所以运动就不是平滑的。前一帧还是在(150,150),后一帧可能就“瞬移”到(170,170)的位置了。
游走的圆
那有没有可能产生平滑的运动?noise 函数可以做到这点。它比标准的 random 函数更有韵律,生成的随机数是连续的。
调用形式:
noise(t)
noise 无法定义它的输出范围。程序中规定它只能生成从 0 到 1 的浮点数。并且固定的输入只能产生固定的输出。
float x = noise(5);
float y = noise(5);
print(x, y);
写给设计师的编程趣味指南-(3)​让图形跑起来(上)图14
由于上面输入的参数都是 5,所以输出的两个结果都是相同的。那如何才能使结果产生变化呢?答案就是动态地改变输入的参数。其实可以将noise理解为一个无限长的音轨。输入的参数就好比是“当前时间”,如果我们的参数输入是连续的,输出也会是连续的。
float x, y;
void setup(){
      size(700, 100);
      x = 0;
      background(0);
}
void draw(){
      x += 1;
      y = noise(frameCount/100.0)*100;
      noStroke();
      ellipse(x, y, 2, 2);
}
写给设计师的编程趣味指南-(3)​让图形跑起来(上)图15
这个例子绘制了 y 值的变化轨迹。便于我们理解 noise 函数。
  • 其中,变量frameCount会获取当前的帧数。和前面介绍的 width ,height 不一样。它不是固定不变的。而是会从0开始,不断递增,如果用最开始演示的动图去理解,就代表翻到了第几页(也相当于程序中的时间概念)。
  • frameCount是整型变量,整型变量除以一个整型变量,程序中会默认将结果处理成整数。所以为了提高结果精度,需要将 100 写成 100.0 。整型变量除以浮点数,会得出浮点数。
  • 为了使 y 轴坐标在 0 到 100 里变化,需将noise的结果乘以 100。以此控制随机值的范围。

善于思考的人会问,为什么frameCount要除以100?直接写 frameCount 不行吗?是可以的,但这里为了更好地展示 noise 函数的特性,所以放慢了“播放速率”。下面的例子就展示了不同变化速率下,输出值的变化。
float x, y1, y2, y3, y4, y5;
void setup(){
      size(700, 500);
      x = 0;
      background(0);
}
void draw(){
x += 1;
      y1 = noise(frameCount)100;
      y2 = noise(frameCount/10.0)
100;
      y3 = noise(frameCount/100.0)100;
      y4 = noise(frameCount/1000.0)
100;
      y5 = noise(frameCount/10000.0)*100;
      noStroke();
ellipse(x, y1, 2, 2);
      ellipse(x, y2+100, 2, 2);
      ellipse(x, y3+200, 2, 2);
      ellipse(x, y4+300, 2, 2);
      ellipse(x, y5+400, 2, 2);
stroke(80);
      line(0,100,width,100);
      line(0,200,width,200);
      line(0,300,width,300);
      line(0,400,width,400);
}
写给设计师的编程趣味指南-(3)​让图形跑起来(上)图16
你可以将noise里面变化的参数理解成进度条,变化这个参数,就相当于拨动进度条。所以当这个“音轨”输入参数的变化幅度越大,输出值前后的连续性也会越弱(可以想象2倍速,5倍速,20倍速播放一段音乐或是视频会是怎样的)。当参数的变化幅度大于某个值,可能就与random函数生成的值没有太大差别了。
如果前面的例子都能理解。那画一个游走的圆,是再简单不过的事。你也会明白其中的道理。
float x, y;
void setup(){
      size(300, 300);
      x = 0;
}
void draw(){
      background(234, 113, 107);
      x = noise(frameCount/100.0 + 100)300;
      y = noise(frameCount/100.0)
300;
      noStroke();
      ellipse(x, y, 50, 50);
}
写给设计师的编程趣味指南-(3)​让图形跑起来(上)图17
现在的运动就比较有意思了,就像一个旋转摇摆的陀螺一样。
  • 其中变量 x 的 noise 里面的参数之所以要加上 100 ,是为了错开一段距离。如果xy里面 noise 函数输入的参数完全一样,又或是过于接近。x,y坐标的变化就会趋近相同。这样是为了让运动更有随机性。
用鼠标移动圆
下面终于讲到这两个自己最喜欢的两个变量了,mouseX 和 mouseY。当初在看到这两个概念的时候,就让人两眼发光。因为这是与图像发生交互最直接的方式。灵活运用它,可以写出很多好玩的程序。
例子很简单:
int x, y;
void setup(){
      size(300, 300);
      x = 0;
      y = 0;
}
void draw(){
      background(234, 113, 107);
      noStroke();
      x = mouseX;
      y = mouseY;
      ellipse(x, y, 50, 50);
}
写给设计师的编程趣味指南-(3)​让图形跑起来(上)图18
mouseX 可以实时获取鼠标的 x 坐标,mouseY可以获取y坐标。
改变下正负号,又或是交换 mouseX 和 mouseY 试试
End
通过熟悉这些命令,你已经可以简单地指挥图形的运动了。再结合上节内容,充分发挥你的想象力,就足以做出很多有意思的动效。
下一节将会看到更多丰富的实例,同时也会用上数学函数,将它和图形的运动结合起来。



962327153  学徒

发表于 2018-7-4 18:08:11

开始看不懂了。。。。
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

为本项目制作心愿单
购买心愿单
心愿单 编辑
[[wsData.name]]

硬件清单

  • [[d.name]]
btnicon
我也要做!
点击进入购买页面
上海智位机器人股份有限公司 沪ICP备09038501号-4

© 2013-2024 Comsenz Inc. Powered by Discuz! X3.4 Licensed

mail