1.5 开始第一幅“码绘”——自定变量与函数,创一招“懵逼表情涂”

引言——想要重复绘制内容应该怎么办?

目前我们已经清晰地理解了如何用代码绘制1个懵逼脸。

现在升级一点难度,考虑一个问题:

如果要在屏幕上不同位置画N个懵逼脸,怎么办?

若采用之前一样的方法,我们可以在绘制完一个脸后,用重复的代码(不同参数)绘制另一个脸,比如:


 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
// 函数setup() : 准备阶段
function setup() {
// 创建画布,宽度640像素,高度480像素
// 画布的坐标系统为,左上角坐标为(0,0),
// x方向水平向右,y方向垂直向下,单位像素
createCanvas(640,480);
}

// 函数draw():作画阶段
function draw() {
// 画第一个脸
fill(255);// 填充白色
// 1 画脸
ellipse(320,240,200,200);// 圆圈
// 2 左眼
ellipse(280,240,50,50);// 另一个圆圈
// 3 右眼
ellipse(360,240,50,50);
// 4 嘴巴
ellipse(320,300,80,40);
fill(0);// 填充黑色
// 5 左眼珠
ellipse(280,240,20,20);
// 6 右眼珠
ellipse(360,240,20,20);
// 7 头发:从左到右画一排竖线
line(260,180,260,100);
line(280,180,280,100);
line(300,180,300,100);
line(320,180,320,100);
line(340,180,340,100);
line(360,180,360,100);
line(380,180,380,100);

// 以下为重复代码,但参数不同
// 画第二个脸
fill(255);// 填充白色
// 1 画脸
ellipse(220,240,200,200);// 圆圈
// 2 左眼
ellipse(180,240,50,50);// 另一个圆圈
// 3 右眼
ellipse(260,240,50,50);
// 4 嘴巴
ellipse(220,300,80,40);
fill(0);// 填充黑色
// 5 左眼珠
ellipse(180,240,20,20);
// 6 右眼珠
ellipse(260,240,20,20);
// 7 头发:从左到右画一排竖线
line(160,180,160,100);
line(180,180,180,100);
line(200,180,200,100);
line(220,180,220,100);
line(240,180,240,100);
line(260,180,260,100);
line(280,180,280,100);
}

这样就能绘制两个脸:

1.5 开始第一幅“码绘”——自定变量与函数,创一招“懵逼表情涂”


但是......我想画20个脸,咋办?????

不嫌累的话可以试试写20遍重复代码......,希望不要勾起被老师罚抄课文的苦难记忆......

可见,这个办法的局限也是相当明显的。

用自定义函数实现功能复用

为了解决这个麻烦,我们需要获得一项新技能:自定义函数

回顾一下1.1节中讲到的“函数”,或者说“招数/方法/行为/职能/功能”,只要有了某个函数定义,也就是概括了某种做事的流程,然后在我们想要的地方便可以“调用”它,或者说是“施放/执行/行使/发招”。例如,我们可以设想已经定义了一个“DrawConfuseFace”的函数,然后,在draw()函数中就可以直接调用这个函数两次,实现画两个懵逼脸。也就是,我们现在期望的draw()函数写法:

// 函数draw():作画阶段
function draw() {
// 在(200,140)位置画第一个脸
drawConfuseFace(200,140);
// 在(320,280)位置画第二个脸
drawConfuseFace(320,280);
}
这当然是可行的!而且实现后效果如下:

1.5 开始第一幅“码绘”——自定变量与函数,创一招“懵逼表情涂”

为了实现这个drawConfuseFace函数,我们要依次搞清楚3项技能: 1.自定义函数;2.自定义变量;3.函数参数

 

自定义函数

在1.1节的讲解中,已经了解到“函数定义”的基本办法,也就是现有代码中函数setup()和draw()的写法即函数定义。

那么,我们仿照draw()函数的写法,在其后新写一个函数,并在draw()函数中直接调用它20遍!希望能画出20个懵逼脸。

代码如下:

// 函数setup() : 准备阶段
function setup() {
// 创建画布,宽度640像素,高度480像素
// 画布的坐标系统为,左上角坐标为(0,0),
// x方向水平向右,y方向垂直向下,单位像素
createCanvas(640,480);
}

// 函数draw():作画阶段
function draw() {
// 调用20遍,希望能画20个脸
drawConfuseFace();
drawConfuseFace();
drawConfuseFace();
drawConfuseFace();
drawConfuseFace();
drawConfuseFace();
drawConfuseFace();
drawConfuseFace();
drawConfuseFace();
drawConfuseFace();
drawConfuseFace();
drawConfuseFace();
drawConfuseFace();
drawConfuseFace();
drawConfuseFace();
drawConfuseFace();
drawConfuseFace();
drawConfuseFace();
drawConfuseFace();
drawConfuseFace();
}

// 自定函数:画懵逼脸
function drawConfuseFace() {
fill(255);// 填充白色
// 1 画脸
ellipse(320,240,200,200);// 圆圈
// 2 左眼
ellipse(280,240,50,50);// 另一个圆圈
// 3 右眼
ellipse(360,240,50,50);
// 4 嘴巴
ellipse(320,300,80,40);
fill(0);// 填充黑色
// 5 左眼珠
ellipse(280,240,20,20);
// 6 右眼珠
ellipse(360,240,20,20);
// 7 头发:从左到右画一排竖线
line(260,180,260,100);
line(280,180,280,100);
line(300,180,300,100);
line(320,180,320,100);
line(340,180,340,100);
line(360,180,360,100);
line(380,180,380,100);
}
然而,却只能画出来1个脸:
1.5 开始第一幅“码绘”——自定变量与函数,创一招“懵逼表情涂”
这是为啥?原来,我们虽然施放了20次drawConfuseFace(),但由于每一次施放中,drawConfuseFace()函数的执行过程用到的参数完全一样,因此,上述代码相当于在相同位置重复画了20次,每一次绘制都覆盖掉前一次绘制的结果,那么最终也只能看到1个脸。
这并非我们想要的函数形态,我们更希望drawConfuseFace()能够在不同位置画脸。
于是,drawConfuseFace()函数需要进行改造,让它能够在释放时输入参数,并且根据参数的具体数值来发挥不同的效果,
例如,我们希望施放该函数时可以用以下形态:
drawConfuseFace(100,200); //在(100,200)位置画懵逼脸
drawConfuseFace(300,50); // 在(300,50)位置画懵逼脸
......

为了实现它,还需要对变量的认识更进一步,即学会“自定义变量”。

自定义变量

在之前实现的用鼠标控制位置的代码中,我们用到了两个变量mouseX和mouseY,它们是p5.js提供的可以在任意时刻任意位置直接访问的变量。为了更加灵活地指定位置,我们可以用自定义变量。

下列代码示例了在程序中自定义变量:

var A,a; // 定义两个变量 A和a, 特别注意,大小写不同的名称对应不同变量!
var B = 1; // 定义变量B,赋值1
var Haha = 100; // 定义变量Haha, 赋值100;
var Jam = B; // 定义变量Jam, 用变量B的值为其赋值;

在定义了上述变量后,便可以在需要的时候使用它们,就如同使用变量mouseX和mouseY一般,例如:

ellipse(B,Haha,100,100); // 在位置(B,Haha)绘制长宽为100的圆形
line(Jam,Haha,Haha,B); // 从(Jam,Haha)到(Haha,B)绘制一条线段
此外,在变量定义中,用到了赋值符"=",要注意,它的作用有点像是“等于”,但其实应该理解为“赋值”,其目的是将右边的表达式的计算结果赋予左边的变量,下面列出一些用法示例:

var A = 1; // 定义变量A并赋值1
var B = 2; // 定义变量B并赋值2
var C = A + 10;// 定义变量C, 计算表达式A+10的值,然后赋值给C
var D = B - 5; // 定义变量D, 计算表达式B-5的值,然后赋值给D
var E = A + B; // 定义变量E, 计算表达式A+B的值,然后赋值给E


掌握了自定义变量,我们对原来的代码进行改造。原始代码为:

  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
// 函数setup() : 准备阶段
function setup() {
// 创建画布,宽度640像素,高度480像素
// 画布的坐标系统为,左上角坐标为(0,0),
// x方向水平向右,y方向垂直向下,单位像素
createCanvas(640,480);
}

// 函数draw():作画阶段
function draw() {
fill(255);// 填充白色
// 1 画脸
ellipse(mouseX,mouseY,200,200);// 圆圈
// 2 左眼
ellipse(mouseX-40,mouseY,50,50);// 另一个圆圈
// 3 右眼
ellipse(mouseX+40,mouseY,50,50);
// 4 嘴巴
ellipse(mouseX,mouseY+60,80,40);
fill(0);// 填充黑色
// 5 左眼珠
ellipse(mouseX-40,mouseY,20,20);
// 6 右眼珠
ellipse(mouseX+40,mouseY,20,20);
// 7 头发:从左到右画一排竖线
line(mouseX-60,mouseY-60,mouseX-60,mouseY-140);
line(mouseX-40,mouseY-60,mouseX-40,mouseY-140);
line(mouseX-20,mouseY-60,mouseX-20,mouseY-140);
line(mouseX ,mouseY-60,mouseX ,mouseY-140);
line(mouseX+20,mouseY-60,mouseX+20,mouseY-140);
line(mouseX+40,mouseY-60,mouseX+40,mouseY-140);
line(mouseX+60,mouseY-60,mouseX+60,mouseY-140);
}

用自定义变量对其进行改造:

  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
// 函数setup() : 准备阶段
function setup() {
// 创建画布,宽度640像素,高度480像素
// 画布的坐标系统为,左上角坐标为(0,0),
// x方向水平向右,y方向垂直向下,单位像素
createCanvas(640,480);
}

// 函数draw():作画阶段
function draw() {
// 定义两个变量,并对其赋值为当前鼠标坐标
// 之后的所有语句都用这两个变量来设定绘制位置
var centerX = mouseX;
var centerY = mouseY;

fill(255);// 填充白色
// 1 画脸
ellipse(centerX,centerY,200,200);// 圆圈
// 2 左眼
ellipse(centerX-40,centerY,50,50);// 另一个圆圈
// 3 右眼
ellipse(centerX+40,centerY,50,50);
// 4 嘴巴
ellipse(centerX,centerY+60,80,40);
fill(0);// 填充黑色
// 5 左眼珠
ellipse(centerX-40,centerY,20,20);
// 6 右眼珠
ellipse(centerX+40,centerY,20,20);
// 7 头发:从左到右画一排竖线
line(centerX-60,centerY-60,centerX-60,centerY-140);
line(centerX-40,centerY-60,centerX-40,centerY-140);
line(centerX-20,centerY-60,centerX-20,centerY-140);
line(centerX ,centerY-60,centerX ,centerY-140);
line(centerX+20,centerY-60,centerX+20,centerY-140);
line(centerX+40,centerY-60,centerX+40,centerY-140);
line(centerX+60,centerY-60,centerX+60,centerY-140);
}

如此改造后,程序运行的结果并未变化,也就是绘制一个跟随鼠标运动的懵逼脸。

但这样改造仍然有两个好处,一方面是具备了更佳的可读性,因为我们定义的变量名称centerX和centerY本身就使用了具有明确含义的词汇center,其名称就说明了它们是绘制的懵逼脸的“中心”位置;另一方面,代码具有了更好的可扩展性:这两个变量的数值可以在后续过程中任意时刻发生变化,而不会像mouseX和mouseY那样始终是鼠标位置;并且,重点是,这种改造便于我们将这一段代码改造为带参数的函数。(●ˇ∀ˇ●)


函数参数

函数在定义时,可以定义任意数量的参数,例如,我们可以将drawConfuseFace()函数定义为带参数形态:

function drawConfuseFace(posX, posY
// 定义了两个形式参数posX,posY,它们也是函数中的两个变量
// 函数执行过程中所有绘图语句都基于变量posX和posY来指定位置
{
fill(255);// 填充白色
// 1 画脸
ellipse(posX,posY,200,200);// 圆圈
// 2 左眼
ellipse(posX-40,posY,50,50);// 另一个圆圈
// 3 右眼
ellipse(posX+40,posY,50,50);
// 4 嘴巴
ellipse(posX,posY+60,80,40);
fill(0);// 填充黑色
// 5 左眼珠
ellipse(posX-40,posY,20,20);
// 6 右眼珠
ellipse(posX+40,posY,20,20);
// 7 头发:从左到右画一排竖线
line(posX-60,posY-60,posX-60,posY-140);
line(posX-40,posY-60,posX-40,posY-140);
line(posX-20,posY-60,posX-20,posY-140);
line(posX ,posY-60,posX ,posY-140);
line(posX+20,posY-60,posX+20,posY-140);
line(posX+40,posY-60,posX+40,posY-140);
line(posX+60,posY-60,posX+60,posY-140);
}
如此一来,我们便可以在draw()函数中以带参数的形态调用drawConfuseFace()了:

// 函数draw():作画阶段
function draw() {
// 在(200,140)位置画第一个脸
drawConfuseFace(200,140);
// 在(320,280)位置画第二个脸
drawConfuseFace(320,280);
}
并且,drawConfuseFace()可以反复以调用并输入不同数值的参数,从而在不同位置画出懵逼脸了。
现在反过来对drawConfuseFace()函数的带参数形式进行解释:
其定义"function drawConfuseFace(posX,posY)"中, posX和posY是函数的形式参数,简称形参。这里相当于是说:有一个行为叫做drawConfuseFace,它能够以某个位置(posX,posY)为中心画一个懵逼脸,并且在实际执行这个行为的时候必须对位置(posX,posY)指定具体数值,而具体指定的数值就叫做函数调用时的实际参数,简称实参
有了形式参数posX和posY,它们便成为了在该函数的执行过程中(也就是大括号{}中的代码)可以访问的变量。例如,函数中的语句”ellipse(posX,posY,200,200);"便是以(posX,posY)为中心绘制椭圆,而且其后所有的绘图语句都是基于这两个变量来指定位置。
为了进一步理解函数的形式参数和实际参数,我们可以类比生活中的一些例子。例如,手机有一个功能叫做“拨号”,这个行为在手机中已经有一套完整的执行流程,其中需要用到一个”号码”的形式参数,并且在人们实际使用手机执行该功能时,就需要具体指定“号码”的数值,也就是指定的实际参数。再例如,有一种行为叫做“睡觉”,这个行为有一套标准的执行过程,相当予“睡觉”行为的函数定义,它在未实际执行时,睡多长时间时一个不确定的量,也即一个形式参数,而在某人实际执行”睡觉”行为时,就需要具体化睡眠时间,例如8小时,也就是指定了实际参数为8小时。
有了这个自定义的“招数” drawConfuseFace(posX,posY),我们便可以尝试直接调用它,从而方便地画出多个懵逼脸,例如,将draw()函数中的代码写为:
  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
// 函数setup() : 准备阶段
function setup() {
// 创建画布,宽度640像素,高度480像素
// 画布的坐标系统为,左上角坐标为(0,0),
// x方向水平向右,y方向垂直向下,单位像素
createCanvas(640,480);
}

// 函数draw():作画阶段
function draw() {
// 在鼠标位置及其上下左右绘制5个懵逼脸
drawConfuseFace(mouseX-100,mouseY);
drawConfuseFace(mouseX+100,mouseY);
drawConfuseFace(mouseX,mouseY-100);
drawConfuseFace(mouseX,mouseY+100);
drawConfuseFace(mouseX,mouseY);
}

function drawConfuseFace(posX, posY)
{
fill(255);// 填充白色
// 1 画脸
ellipse(posX,posY,200,200);// 圆圈
// 2 左眼
ellipse(posX-40,posY,50,50);// 另一个圆圈
// 3 右眼
ellipse(posX+40,posY,50,50);
// 4 嘴巴
ellipse(posX,posY+60,80,40);
fill(0);// 填充黑色
// 5 左眼珠
ellipse(posX-40,posY,20,20);
// 6 右眼珠
ellipse(posX+40,posY,20,20);
// 7 头发:从左到右画一排竖线
line(posX-60,posY-60,posX-60,posY-140);
line(posX-40,posY-60,posX-40,posY-140);
line(posX-20,posY-60,posX-20,posY-140);
line(posX ,posY-60,posX ,posY-140);
line(posX+20,posY-60,posX+20,posY-140);
line(posX+40,posY-60,posX+40,posY-140);
line(posX+60,posY-60,posX+60,posY-140);
}

其效果如下:

1.5 开始第一幅“码绘”——自定变量与函数,创一招“懵逼表情涂”


补充:变量的作用域和生命周期

为了用好变量,还需要了理解它的作用域生命周期

一个变量的作用域就是指在程序代码的哪些部分能够访问到这个变量,从下图中可以看出作用域的几种典型情况:

1.5 开始第一幅“码绘”——自定变量与函数,创一招“懵逼表情涂”

执行效果如下:

1.5 开始第一幅“码绘”——自定变量与函数,创一招“懵逼表情涂”


有关作用域,可以查看这里的解释:http://www.runoob.com/js/js-scope.html



知识点

函数:http://www.runoob.com/js/js-functions.html

作用域:http://www.runoob.com/js/js-scope.html


相关资源

教程示例程序:https://github.com/magicbrush/DrawingByCodingTutorialDemos/