1.7 开始第一幅“码绘”——用时间控制变量,让懵逼脸动起来
引言——码绘的缺点和优点
在之前的程序中,所有懵逼脸的表情都是僵住不动的。
如此一来,相比于传统纸面绘画,码绘技术还体现不出什么优点,可以说还有一系列缺点:
- 编写代码麻烦容易出现笔误,
- 学习编程语言代价高,
- 不够直观,
- 缺乏操纵画笔时的控制感和愉悦感,
- 要用完全逻辑的方式来考虑作画过程,抒情性、即兴性和表意性显得不足。
但是,用编程来作画,可以实现纸面绘画做不出的效果,主要就是两种:动态和交互。
在之前的程序中,交互性已经略有体现,我们已经实现了让懵逼脸可以跟随鼠标移动。
然而由于缺乏动态,表情僵硬,还很难说有”码绘“什么特别的优势。
这一节,我们将让懵逼脸动起来,它不仅可以跟随鼠标移动,自身的尺寸/表情/五官位置还绘随时间而发生变化。
一个简单的动画程序案例
先看一个极简的程序例:
1 2 3 4 5 6 7 8 9 10 11 |
function setup() {
createCanvas(640,480);
}
function draw() {
// 调用函数second(),获得程序运行经过的秒数
var s = second();
// 在屏幕中心画圆圈,长宽等于秒数
ellipse(320,240,s,s);
}
|
代码也示意了简单的动画技术,其要点有二:
- draw()函数随时间反复执行;
- 每一次执行draw()函数,通过调用函数second()获得程序当前经过的秒数,并且在绘制圆形时用获得的时间数值来指定圆形的尺寸。
制造动态的基本方法
从上述简单的动画示例,可以导出制作动态的基本方法,写成一个函数如下:
1 2 3 4 5 |
function LoopFunction()// 这个函数应该被框架程序持续反复调用
{
UpdateEveryThing(); // 更新每一个东西
DrawEveryThing(); // 绘制每一个东西
}
|
可见,有两个要领:1.要定义一个在程序框架中被持续反复调用的函数;2.在该函数中,要更新并绘制一些东西;
再对应到p5.js提供的程序框架,可见其draw()函数符合上述要求,只要能够在draw()函数中更新需要绘制物体的信息并将它们绘制出来,就能产生动画效果。
让懵逼脸移动起来
利用之前写出的drawConfuseFace()函数,即:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 |
// 画懵逼脸
function drawConfuseFace(
posX, posY, // 脸部中心位置
faceSize, // 脸部尺寸
scaleMouth, // 嘴巴尺度比例,相对于脸部尺寸
scaleLEye, // 左眼尺度比例, 相对于脸部尺寸
scaleREye) // 右眼尺度比例, 相对于脸部尺寸
{
// -------------- 1 画脸 ---------------
fill(255);// 填充白色
ellipse(posX,posY,faceSize,faceSize);// 圆圈
// -------------- 2 画眼睛 ---------------
// 2.1 计算眼睛相对于脸中心点的偏移量
var EyeOffsetX = 0.2 * faceSize; // 眼睛横向偏移量为脸部尺寸的0.2倍
var EyeOffsetY = 0 * faceSize; // 眼睛纵向偏移量为脸部尺寸的0倍
// 2.2 计算眼睛尺寸
// 左右眼尺寸
var LEyeSize = faceSize * scaleLEye;
var REyeSize = faceSize * scaleREye;
// 左右眼珠尺寸
var LIrisSize = LEyeSize * 0.4;
var RIrisSize = REyeSize * 0.4;
// 2.2 画出左眼
fill(255);// 填充白色
ellipse(
posX-EyeOffsetX, // 脸的中心位置向左偏移EyeOffsetX
posY+EyeOffsetY, // 脸的中心位置向下偏移EyeOffsetY
LEyeSize,
LEyeSize);
// 2.3 画出右眼
fill(255);// 填充白色
ellipse(
posX+EyeOffsetX,
posY+EyeOffsetY,
REyeSize,
REyeSize);
// 5 左眼珠
fill(0);// 填充黑色
ellipse(
posX-EyeOffsetX, // 位置与左眼一样
posY+EyeOffsetY,
LIrisSize, // 尺寸则采用比左眼小的尺寸
LIrisSize);
// 6 右眼珠
fill(0);// 填充黑色
ellipse(
posX+EyeOffsetX,
posY+EyeOffsetY,
RIrisSize,
RIrisSize);
// -------------- 3 画嘴巴 ---------------
// 3.1 计算嘴巴相对于脸部中心位置的偏移量
var MouthOffsetX = 0.0;
var MouthOffsetY = 0.3*faceSize;
// 3.2 计算嘴巴尺寸
var MouthWidth = faceSize * scaleMouth;
var MouthHeight = MouthWidth/2.0;
// 3.3 画出嘴巴
fill(255); // 填充白色
ellipse(
posX + MouthOffsetX,
posY + MouthOffsetY,
MouthWidth,
MouthHeight);
// -------------- 4 画头发 ---------------
drawOneHair(posX,posY,faceSize,-0.3);
drawOneHair(posX,posY,faceSize,-0.2);
drawOneHair(posX,posY,faceSize,-0.1);
drawOneHair(posX,posY,faceSize,0);
drawOneHair(posX,posY,faceSize,0.1);
drawOneHair(posX,posY,faceSize,0.2);
drawOneHair(posX,posY,faceSize,0.3);
}
// 绘制一根头发
function drawOneHair(
faceX,faceY, // 脸的中心位置
faceSize, // 脸的尺寸
offsetXOnFaceSize) // 头发X坐标的的偏移量,以脸部尺寸为单位尺寸
{
// ------------- 1 计算尺寸和位置 ---------//
// 头发相对脸部中心的Y偏移量
var HairOffsetY = faceSize * 0.3;
// 计算X偏移量
var offsetX = offsetXOnFaceSize * faceSize;
// 头发长度
var HairLength = faceSize * 0.4;
// --------------- 2 画头发 ---------------//
line(
faceX - offsetX,
faceY - HairOffsetY,
faceX - offsetX,
faceY - (HairOffsetY + HairLength) );
}
|
我们重新写一个简单的draw()函数,实现一个持续移动的懵逼脸:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
// 函数draw():作画阶段
function draw() {
// -------- 获取时间 ------------------- //
// 获得毫秒数
var Millis = millis();
// 获得秒数,相比second(),能获得小数点后的部分
var Second = millis()/1000;
// -------- 计算位置 --------------------//
var posX = Second * 80;
posX = posX%width; // 让posX不超过屏幕宽度
var posY = Second * 61;
posY = posY%height; // 让posY不超过屏幕高度
// --------- 绘制懵逼脸------------------//
drawConfuseFace(posX,posY,80,0.3,0.2,0.2);
}
|
这样便实现了移动的懵逼脸,效果如下:
在上述代码中,使用了millis()函数来获取时间,单位为毫秒,然后再通过除法运算转化为秒数,这种方式相比直接调用second()而言,可以获得精度更高的时间值。而用second()函数只能获得整数的读秒计数。
加粗的代码便是实现动态效果的关键。对于posX,首先通过语句“varposX=Second*80;"通过语句让posX的数值正比于秒数Second,
于是,在不同时刻,这几计算出的posX的实际数值也不同;然后,为了让懵逼脸始终位于画布内,用了求余数的算符 %, 让posX对画布宽度width求余数(width是p5.js提供的变量,其数值就是画布宽度:https://p5js.org/reference/#/p5/width )。算符%的作用是求余数,其用法如下列代码:
posY的计算方式于posX完全一样。
于是,在最后调用drawConfuseFace()函数时,由于用随时间发生变化的量posX和posY作为位置,因此画出来的懵逼脸便随着时间移动了。
让懵逼脸的表情动起来
通过随时间发生变化的变量来控制位置,便实现了懵逼脸的移动。
同理,若构造出能够随时间变化的变量来控制懵逼脸的尺寸,便可以实现懵逼脸的表情动画效果。于是,我们尝试将代码进一步改造如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
// 函数draw():作画阶段
function draw() {
// -------- 获取时间 ------------------- //
// 获得毫秒数
var Millis = millis();
// 获得秒数,相比second(),能获得小数点后的部分
var Second = millis()/1000;
// -------- 计算位置 --------------------//
var posX = Second * 80;
posX = posX%width; // 让posX不超过屏幕宽度
var posY = Second * 61;
posY = posY%height; // 让posY不超过屏幕高度
// -------- 计算懵逼脸五官尺寸 ----------//
var Second20 = Second * 20;
var FaceSize = (Second20 + 50)%100;
var MouseScale = ((Second20 + 30)%50 )/100;
var LEyeScale = (((Second20 + 20)%40)/120 ) + 0.1;
var REyeScale = 0.3 - ((Second20 + 20)%40)/150;
// --------- 绘制懵逼脸------------------//
drawConfuseFace(
posX,posY,
FaceSize,
MouseScale,
LEyeScale,
REyeScale);
}
|
这样就实现了让懵逼脸的五官随时间发生变化:
上述代码中,在调用函数drawConfuseFace()时,所有的参数都用随时间变化的变量来指定,于是,每一次调用draw()函数时,由于这些变量的数值都不同,这就实现了在每一次绘制懵逼脸时让五官的位置和尺寸都不同。
这里再截取出关键代码:
// -------- 计算懵逼脸五官尺寸 ----------//
var Second20 = Second * 20;
var FaceSize = (Second20 + 50)%100;
var MouseScale = ((Second20 + 30)%50 )/100;
var LEyeScale = (((Second20 + 20)%40)/120 ) + 0.1;
var REyeScale = 0.3 - ((Second20 + 20)%40)/150;