运动
一.帧
1.1定义
为了创建流畅的动画,Processing尝试以每秒60帧的速率在draw()函数中运行代码。一帧即draw()函数运行一次,帧频率是每秒钟运行了多少帧。因此,一个程序每秒运行60帧意味着每秒钟draw()函数中的全部代码运行了60次。
1.2观察帧频率
为了确认帧频率,运行这个程序并查看控制台输出的值(frameRate变量持续跟踪程序的速率)。代码如下:
void draw(){
println(frameRate);
}
1.3设置帧频率
frameRate()函数改变程序运行的频率。代码如下:
void setup(){
frameRate(30);//每秒30帧
}
void draw(){
println(frameRate);
}
注意:Processing一般以每秒60帧的速度运行代码,如果运行一个draw()方法的时间超过1/60秒,那么帧频率就会降低。frameRate()函数指定的只是最大帧帧频率。
二.速度和方向
为了创建流畅运动的先例,我们使用一种叫做浮点型的数据类型,这种变量存储带小数点的数,这为运动提供了更强大的表现能力。
2.1移动图形
案例代码如图1
图1
保存运行如图2
图2
注意:运行代码时,我们可以发现图形从左边往右边运动,当x的值比窗口宽度大时,图形从右侧缓慢消失。x的值会持续增加,但看不见了图形。
1.4循环
案例代码如图3
图3
保存并运行如图4
图4
注意:图3中代码比图1多了一个判断。在draw()函数每次运行的时候,代码都检测x的值是否已经超出屏幕的宽度(加上图形半径)。如果超过,我们设置x的值为负,这样他就可以继续增加,从屏幕左侧出现,实现一个循环,而不是像图2运行只出现一次。
1.5折返
当图形撞击到边缘时让它反向,而不是穿过边界重新从左侧出现。为了实现这种效果,我们添加一个新的变量来存储图形的方向。方向值为1的时候图形向右运动,当图形为-1时图形向左运动。案例代码如下:
int radius=40;
float x=110;
float speed=0.5;
int direction=1;
void setup(){
size(240,120);
ellipseMode(RADIUS);
}
void draw(){
background(0);
x+=speed*direction;
if((x>width-radius)||(x<radius)){
direction=-direction; //反转方向
}
if(direction==1){
arc(x,60,radius,radius,0.52,5.76);//面向右边
}
else{
arc(x,60,radius,radius,3.67,8.9);//面向左边
}
}
保存并运行如图5
图5
当图形接触到边缘时,代码通过改变dircetion变量的符号来改变图形方向。比如说当图形接触到一个边缘时,如果direction是正的,代码会将它变成负的。
二.补间动画
有时候我们想让一个图形从屏幕的某处移动到另一个位置。用几行代码可以设置运动的起点和终点,每帧计算两点间的位置。
2.1计算补间位置
为了实现补间动画,我们在顶部创建了一组变量。多运行几次这段代码,然后改变它们的值来看看代码如何用不同的速度从一个点移到另外一个点。改变step变量改变速度。案例代码如下:
int startX=20; //计算x的起始坐标
int stopX=160; //计算x的终止坐标
int startY=30; //计算y的起始坐标
int stopY=80; //计算y的终止坐标
float x=startX;//当前x坐标
float y=startY;//当前y坐标
float step=0.005; //每一步的尺寸(0.0到1.0)
float pct=0.0;//运行过的百分比(0.0到1.0)
void setup(){
size(240,120);
}
void draw(){
background(0);
if(pct<1.0){
x=startX+((stopX-startX)*pct);
y=startY+((stopY-startY)*pct);
pct +=step;
}
ellipse(x,y,20,20);
}
保存并运行如图6
图6
三.随机
3.1生成随机数
我们可以用随机数模拟现实世界中不确定的特征。random()函数计算这样的数值,我们可以在程序中设置一个范围来调整这些随机数。
案例代码如下:
void draw(){
float r=random(0.mouseX);
println(r);
}
上面这个案例可在控制台输出随机数,由鼠标控制它生成的范围。random()函数通常返回一个浮点数的值,因此要保证赋值符号左边的变量也是float类型。
3.2随机绘制
案例代码如图7
图7
保存并运行如图8
图8
当鼠标向左时变化很小,当鼠标向右时变化很大,random()函数生成的值变大,运动会变得更加剧烈。因为random()函数在for循环中,执行时每条线都会有一个新的计算值。
3.3随机移动图形
当移动屏幕中图形时,随机值可以让图形出现的更加自然。
案例代码如图9
图9
保存并运行如图10
图10
图10中我们可以发现之前运行过的轨迹都存在,这是因为没有使用background()函数,如果使用我们可以发现只有一个圆在中。盯着这个案例运行久点可以发现圆形移出来屏幕又运动回来。这是偶然的,我们可以添加一些if结构或者使用constrain()函数来保证圆形不离开屏幕外。constrain()函数可以将一个值限定在一个范围内,这样可以将x和y保持在运行窗口之内。替换draw()函数里面的代码如下:
void draw(){
x+=random(-speed,speed);
y+=random(-speed,speed);
x=constrain(x,0,width);
y=constrain(y,0,height);
ellipse(x,y,diameter,diameter);
}
四.计时器
每一个Processing程序都会计算运行时间。它用以毫秒为单位来计算(1/1000秒)。因此,经过1s后它会记为1000,一分钟以后会记为60000.我们可以用这个计时器在特定的时间点触发动画。millis()函数用来返回计数器的值。
4.1经过时间
查看经过时间代码如下:
void setup(){
size(240,120);
}
void draw(){
int timer=millis();
println(timer);
}
4.2触发时间事件
当与if结合时,从millis()函数中返回的值可以被用于程序中的序列动画和事件。案例代码如图11
图11
保存并运行如图12
图12
当2s过去后,if块中的代码可以触发一个变化。在图11中,变量timer1和timer2决定了什么时候去改变x变量的值。
五.圆周
5.1正弦波形的值
案例代码如下:
float angle=0.0;
void setup(){
size(240,120);
}
void draw(){
float sinval=sin(angle);
println(sinval);
float gray=map(sinval,-1,1,0,255);
background(gray);
angle+=0.1;
}
案例代码中sin()函数的值在角度增加的过程中是从-1~1循环的。使用map()函数后,sinval变量的范围被转变为0~255.这个新的值被用来设置窗口背景的颜色。当代码运行时候我们可以发现颜色不断变化。
5.2正弦波运动
案例代码如图13
图13
保存并运行如图14
图14
5.3圆周运动
当sin()和cos()函数一起使用时,它们可以生成圆周运动。cos()函数的值提供了x坐标,sin()函数的值提供了y坐标。两个值都乘以scalar的变量来改变运动的半径,并使用offset值来设置圆周的中心。
案例代码如图15
图15
保存并运行如图16
图16
5.4螺旋
当在每一帧稍微增加scalar的值会产生螺旋形而不是圆形。案例代码如图17
图17
保存并运行如图18
图18
六.运动案例
机器人代码如下:
float x=180;//x坐标
float y=400;//y坐标
float bodyHeight=153;//身体高度
float neckHeight=56;//脖子高度
float radius=45;//头的半径
float angle=0.0;//运动的角度
void setup(){
size(360,480);
ellipseMode(RADIUS);
background(0,153,204);//蓝色的背景
}
void draw(){
//通过一个小的随机值造成位置的变化
x+=random(-4,4);
y+=random(-1,1);
//改变脖子的高度
neckHeight=80+sin(angle)*30;
angle+=0.05;
//调整头的高度
float ny=y-bodyHeight-neckHeight-radius;
//脖子
stroke(255);
line(x+2,y-bodyHeight,x+2,ny);
line(x+12,y-bodyHeight,x+12,ny);
line(x+22,y-bodyHeight,x+22,ny);
//天线
line(x+12,ny,x-18,ny-43);
line(x+12,ny,x+42,ny-99);
line(x+12,ny,x+78,ny+15);
//身体
noStroke();
fill(255,204,0);
ellipse(x,y-33,33,33);
fill(0);
rect(x-45,y-bodyHeight,90,bodyHeight-33);
fill(255,204,0);
rect(x-45,y-bodyHeight+17,90,6);
//头部
fill(0);
ellipse(x+12,ny,radius,radius);
fill(255);
ellipse(x+24,ny-6,14,14);
fill(0);
ellipse(x+24,ny-6,3,3);
}
保存运行如图19
图19