前言
Processing是一种灵活的软件写生本,是一种学习如何在视觉艺术的环境中编写代码的语言。自2001年以来,Processing促进了视觉艺术中的软件素养和技术中的视觉素养。有成千上万的学生、艺术家、设计师、研究人员和业余爱好者使用加工来学习和制作原型。
在之前对processing有了初步的了解之后,根据老师的要求,开始了交互式的码绘创作。接下来来介绍我这次创作的这幅作品。
成果展示
这次,我绘制的对象是《弹丸论破2》里的女主角——七海千秋
那么,首先来看一下最终实现的效果:
主要的功能如下:
1.背景的苹果会不断生成,并且自转同时往右下角匀速移动。
2.会绘制一个Switch游戏机,会跟随鼠标移动而移动。
3.人物的眼睛会鼠标(游戏机)的移动而转动,并对准该方向。
4.按住鼠标时,人物的眼睛会发光,游戏机屏幕上会显示文字(Nintendo Switch),且背景苹果转动和平移的速度将加快。
那么接下来,我就分块讲解一下这些功能是如何实现的。
人物头像
从编程思路的复杂程度而言,头像这个版块应该是最简单的,但实际上实施起来是非常困难的。因为可以看到,组成人物头像的线条不是规则的,是由很多曲线,直线,填充(在Processing里面并没有想象中那么简单)组成的。废话不多说,接下来就来介绍人物头像的制作。
头发线条
头发线条是真的把我折磨得最惨的一个部分了,能成功画完真的是为爱发电,因为我太喜欢这个角色了。。
简单来说,就是画一大堆贝塞尔曲线。
我是把头发分成了三个组——总的外围轮廓,一些细线和刘海。
拿外围轮廓举例。如果要画一个完整的轮廓线,代码的结构应该如下这样的:
beginShape();
vertex(x0,y0);
bezierVertex(x1,y1);
bezierVertex(x2,y2);
……
endShape();
在beginShape()函数和endShape()函数之间,所有画的曲线会被当做是一个图形,这样方便在beginShape()函数之前修改这个图形的线宽,填充颜色等等。
vertex()函数在这里的作用是作为第一条曲线的起点,如果加在中间,就会自动从上一句曲线的终点连接一条直线到该点,并将该点作为下一条曲线的起点。
bezierVertex()函数有六个参数。分别为第一个控制点的x,y坐标,第二个控制点的x,y坐标,以及终点的x,y坐标。如果连续输入bezierVertex()函数,前一个的终点将作为下一条曲线的起点。
然后。。就慢慢调参数吧。。。
我建议大家可以找一些辅助工具,或者用一些方法更方便获取点的坐标,比如如下代码:
text(mouseX+" "+mouseY,mouseX+20,mouseY-20);
这一句可以实时显示鼠标位置,稍微减少工作量。
颜色填充
正如我之前说的,填充在processing里面可不是意见方便事。如果是封闭图形的填充到还好,就如之前的头发轮廓函数内,只需在beginShape()函数前添加一句:
fill(r,g,b);
头发轮廓的填充后效果如下:
如果不是封闭图形,processing会自动把最后一个点和第一个点连起来,这有时候会导致填充范围的错误,我就因为这个问题改了很久的代码。。
所以我推荐是,将图片分层,分散到不同函数里去编写,然后搞清他们的遮挡关系,逐一覆盖,这样效果会好很多。
比如,我的脸其实画的很长:
但是问题不大,前面会再添加刘海图层,会把多出来的部分覆盖:
最后为了还原人物,我还加了一个不是很还原的高光。。
因为工作量实在太大,就没弄渐变,这一层已经很致命了。。
这里高光不需要轮廓,我调用了noStroke()函数,但不要忘记下一步需要轮廓的时候再用stroke()函数修改回来。
鼠标跟随——游戏机
Processing里面鼠标跟随非常方便,直接获取鼠标的x,y坐标——mouseX,mouseY即可,可直接作为参数使用。
这里的游戏机我还是用了分层叠加的方式。
fill(#00FFFD);
rect(mouseX-50,mouseY-25,100,50,10);
fill(#FF4040);
rect(mouseX-50,mouseY-25,30,50,10);
fill(0);
rect(mouseX-30,mouseY-25,60,50);
fill(#C1C1C1);
rect(mouseX-24,mouseY-20,48,40);
这里用了一个技巧,rect带第四个参数表示圆角矩形,最后一个参数表示圆滑程度。
可以看到,上述代码是先画了一个蓝色的圆角矩阵,然后再画一个红色的圆角矩阵和左侧对齐,再在中间画一个黑色的方形矩阵,最后再画一个较小的灰色矩阵表示屏幕。
然后是手柄的位置,也可以同理得出坐标,只需要多多尝试,不断调整位置就行。
眼睛的跟随
眼睛的跟随不同于游戏机,眼睛的位置不能超过眼眶的范围,根据我的测试,将眼眶范围定为了一个正方形,然后根据鼠标位置判断,代码如下:
if(mouseX>449)
{
eyeX=449;
}
else if(mouseX<437)
{
eyeX=437;
}
else eyeX=mouseX;
if(mouseY>207)
{
eyeY=207;
}
else if(mouseY<203)
{
eyeY=203;
}
else eyeY=mouseY;
ellipse(eyeX,eyeY,25,25);
ellipse(eyeX+70,eyeY,25,25);
这个方法虽然简单,思路清晰,但是也有缺陷,就是当鼠标位于眼眶斜对角的位置(比如左下角)时,无论如何动鼠标,眼睛就不会动。但是这没有办法,因为相当于一个圆卡在了矩形的角落里。如果眼眶的范围是一个圆,那就可以计算出眼睛中心和鼠标的x和y的差值,然后单位化,再赋给眼睛坐标,这样就可以平滑移动了。
背景——苹果的平移和转动
这一部分,我主要讲rotate()函数和translate()函数。
首先声明,processing里面所有的旋转平移等变换都是对坐标轴进行变换。
那么有人会问(比如我),那如果我只想让其中一个物体旋转该怎么办呢?
官方文档给出了解决办法:用 pushMatrix()函数和popMatrix()函数。
再画图前,先把要画的内容压入栈,画完后再出栈,这样就能实现部分物体的变换了。代码结构如下所示:
pushMatrix();
translate(x,y);
rotate(angle);
paint();
popMatrix();
但是我在画的时候遇到了问题,就是物体无论如何都不动,这可咋整呢?
我去网上参考了别的使用了rotate函数的代码,发现,他们把rotate函数放在draw函数里调用后,参数angle每次都自加0.01,这样一来角度就能不断变化,就可以实现不断旋转的效果了,平移同理。
但是另一个问题又来了——苹果转的时候的旋转中心是坐标原点,如下图所示:
这个问题我又去查了查,发现大多数人都是修改了载入模式,把它置为CENTER,可是我去官方文档查找,只有例如rectMode,ellipseMode,imageMode等函数,而我这个苹果是自己画的一个函数。用这些没有效果。。
最后我用了一个投机取巧的办法:我把画完的苹果截图,去掉背景,然后再载入,将imageMode设为CENTER,就可以自转了~
在做完平移和旋转后,只要生成很多个苹果,相隔一定距离,就可以产生成品里面的效果了。
按下鼠标事件
processing中的按下鼠标事件也非常简单,本次我主要是用到了以下两个函数:
mousePressed();
mouseReleased();
mousePressed()函数在按下鼠标的一瞬间会被调用,而mouseReleased()函数则是在松开鼠标的一瞬间会被调用。略加思索,我定义了一个bool变量,控制鼠标是否被按下。然后在mousePressed()里将其置为true,在mouseReleased()函数里将其置为false,代码如下:
void mousePressed()
{
isPress=true;
}
void mouseReleased()
{
isPress=false;
}
然后就可以进行一些操作了。。
比如,在背景方面,如果isPress变量为true,则设置速度为两倍;
if(!isPress)
{
angle+=0.01;
posx+=1;
posy+=1;
}
else
{
angle+=0.02;
posx+=2;
posy+=2;
}
在眼睛函数里,如果isPress变量为true,就额外画两个星星(用arc函数)
if(isPress)
{
stroke(#FEFF03);
strokeWeight(3);
//fill(#FEFF03);
noFill();
arc(eyeX-12.5,eyeY-12.5,25,25,0,HALF_PI);
arc(eyeX+12.5,eyeY-12.5,25,25,HALF_PI,PI);
arc(eyeX+12.5,eyeY+12.5,25,25,PI,HALF_PI+PI);
arc(eyeX-12.5,eyeY+12.5,25,25,HALF_PI+PI,2*PI);
arc(eyeX+57.5,eyeY-12.5,25,25,0,HALF_PI);
arc(eyeX+82.5,eyeY-12.5,25,25,HALF_PI,PI);
arc(eyeX+82.5,eyeY+12.5,25,25,PI,HALF_PI+PI);
arc(eyeX+57.5,eyeY+12.5,25,25,HALF_PI+PI,2*PI);
}
在游戏机函数里,如果isPress变量为true,就额外显示一串字符:
if(isPress)
{
fill (0);
textSize(10);
text("Nintendo",mouseX-22,mouseY-5);
text("Switch",mouseX-18,mouseY+5);
}
就是这么简单,但是这样一来交互就变得更加丰富,多样了。
总结
这次码绘虽然有很多重复而繁琐的工作,工作量很大,但是完成的作品还算美观(自我感觉良好),交互方式多样,有趣,成就感还是蛮大的。也学到了不少知识,对processing的一些函数更为熟悉,对代码中逻辑的思维能力也有所加强,算是不小的收获。
最后,附上这次实验的所有代码:
float angle=0;
int posx=0,posy=0;
PImage img;
boolean isPress=false;
void hairOutline()
{
stroke(3);
fill(#CBBFC8);
beginShape();
strokeWeight(3);
vertex(482,74);
bezierVertex(417,65,373,97,374,184);
bezierVertex(366,203,334,215,326,186);
bezierVertex(331,226,361,217,369,220);
bezierVertex(351,227,331,227,319,208);
bezierVertex(329,229,340,233,380,234);
bezierVertex(346,242,329,232,317,221);
bezierVertex(335,243,356,247,389,241);
bezierVertex(367,260,350,260,334,255);
bezierVertex(361,268,380,270,445,244);
vertex(551,249);
bezierVertex(567,257,585,258,606,246);
vertex(582,241);
bezierVertex(589,242,607,242,624,232);
bezierVertex(607,231,590,228,577,220);
bezierVertex(587,227,604,230,621,215);
bezierVertex(602,219,579,214,569,200);
bezierVertex(589,219,613,216,630,189);
bezierVertex(530,219,628,71,475,73);
endShape();
}
void hairLine()
{
//strokeWeight(3);
noFill();
beginShape();
vertex(420,101);
bezierVertex(361,135,411,225,381,234);
endShape();
beginShape();
vertex(538,111);
bezierVertex(579,139,537,191,580,223);
endShape();
beginShape();
vertex(524,113);
bezierVertex(577,175,529,222,581,241);
endShape();
line(369,220,389,212);
}
void hairThinLine()
{
//noFill();
strokeWeight(2);
beginShape();
vertex(391,150);
bezierVertex(395,198,349,232,326,186);
endShape();
beginShape();
vertex(389,212);
bezierVertex(376,225,350,240,319,208);
endShape();
beginShape();
vertex(403,216);
bezierVertex(398,236,388,241,389,241);
endShape();
//right
beginShape();
vertex(572,184);
bezierVertex(582,209,609,212,630,189);
endShape();
beginShape();
vertex(560,216);
bezierVertex(571,232,590,247,618,236);
endShape();
}
void face()
{
strokeWeight(3);
fill(255);
beginShape();
vertex(411,144);
bezierVertex(411,212,420,230,432,242);
bezierVertex(440,254,455,263,475,271);
bezierVertex(498,267,508,257,522,244);
bezierVertex(536,209,531,227,542,144);
endShape();
}
void liuhai()
{
//frontHair left&right
strokeWeight(3);
fill(#CBBFC8);
beginShape();
vertex(417,117);
bezierVertex(369,198,424,316,480,278);
bezierVertex(429,290,417,234,413,194);
endShape();
fill(#CBBFC8);
beginShape();
vertex(522,113);
bezierVertex(593,190,522,299,496,285);
bezierVertex(501,289,539,261,538,194);
line(538,194,530,186);
bezierVertex(527,156,532,175,532,190);
endShape();
//
fill(#CBBFC8);
beginShape();
vertex(420,129);
bezierVertex(406,164,411,177,416,185);
bezierVertex(420,188,425,185,430,184);
bezierVertex(428,155,427,170,432,139);
endShape();
beginShape();
vertex(432,139);
bezierVertex(428,155,427,170,430,184);
bezierVertex(434,183,441,180,446,180);
bezierVertex(446,172,445,164,448,145);
endShape();
beginShape();
vertex(448,145);
bezierVertex(446,172,445,164,446,182);
bezierVertex(452,185,461,185,467,181);
bezierVertex(465,164,466,149,469,134);
endShape();
beginShape();
vertex(469,134);
bezierVertex(465,164,466,149,467,181);
bezierVertex(474,184,481,187,490,180);
bezierVertex(491,152,490,172,493,128);
endShape();
beginShape();
vertex(493,128);
bezierVertex(493,151,502,168,506,180);
bezierVertex(508,182,515,183,517,184);
bezierVertex(511,171,505,157,505,144);
endShape();
beginShape();
vertex(505,144);
bezierVertex(505,157,511,171,519,185);
bezierVertex(520,186,529,188,538,193);
bezierVertex(529,169,529,143,513,130);
endShape();
}
void hairColor()
{
fill(#E3CEDD);
strokeWeight(0);
//noStroke();
beginShape();
vertex(532,85);
bezierVertex(543,97,548,105,537,112);
bezierVertex(547,120,541,129,538,132);
bezierVertex(542,136,524,169,507,165);
bezierVertex(496,155,493,142,495,128);
bezierVertex(490,150,486,168,476,176);
bezierVertex(469,172,465,173,455,177);
bezierVertex(453,179,448,179,443,179);
bezierVertex(431,167,436,148,440,139);
bezierVertex(429,155,427,166,426,179);
bezierVertex(409,174,423,137,437,123);
bezierVertex(366,179,425,294,469,280);
bezierVertex(382,277,388,133,420,119);
bezierVertex(409,128,407,137,399,143);
bezierVertex(405,101,414,114,422,100);
bezierVertex(390,126,403,114,380,153);
bezierVertex(385,81,462,69,476,75);
bezierVertex(472,72,520,71,532,85);
endShape();
}
void eyesLine()
{
stroke(0);
strokeWeight(5);
noFill();
beginShape();
vertex(423,201);
bezierVertex(430,190,457,188,460,198);
endShape();
beginShape();
vertex(423,204);
bezierVertex(425,210,431,214,435,216);
endShape();
strokeWeight(4);
line(423,201,419,201);
strokeWeight(1);
line(426,196,419,190);
line(429,193,422,189);
//眉毛
beginShape();
vertex(411,180);
bezierVertex(434,177,452,179,465,188);
endShape();
//中心477 202
strokeWeight(5);
noFill();
beginShape();
vertex(531,201);
bezierVertex(524,190,497,188,494,198);
endShape();
beginShape();
vertex(531,204);
bezierVertex(529,210,523,214,519,216);
endShape();
strokeWeight(4);
line(531,201,535,201);
strokeWeight(1);
line(528,196,535,190);
line(525,193,532,189);
//眉毛
beginShape();
vertex(543,180);
bezierVertex(520,177,502,179,489,188);
endShape();
}
void mouse_nose()
{
line(474,220,474,229);
line(474,229,477,231);
noStroke();
fill(#FFE3F7);
ellipse(510,225,22,11);
ellipse(444,225,22,11);
//嘴
strokeWeight(0);
fill(#A74A4A);
beginShape();
vertex(462,255);
bezierVertex(461,234,493,235,493,255);
bezierVertex(483,264,469,264,461,254);
endShape();
//舌头
fill(#D36666);
beginShape();
vertex(493,255);
bezierVertex(482,251,470,251,462,255);
bezierVertex(469,264,483,264,493,255);
endShape();
//牙齿
fill(255);
beginShape();
vertex(466,244);
bezierVertex(473,245,481,246,488,243);
bezierVertex(482,239,473,238,466,244);
endShape();
}
void eyeball()
{
int eyeX,eyeY;
strokeWeight(1);
fill(#F0AEC2);
//if(mouseX>449||mouseX<437 && mouseY>207||mouseY<203)
if(mouseX>449)
{
eyeX=449;
}
else if(mouseX<437)
{
eyeX=437;
}
else eyeX=mouseX;
if(mouseY>207)
{
eyeY=207;
}
else if(mouseY<203)
{
eyeY=203;
}
else eyeY=mouseY;
ellipse(eyeX,eyeY,25,25);
ellipse(eyeX+70,eyeY,25,25);
if(isPress)
{
stroke(#FEFF03);
strokeWeight(3);
//fill(#FEFF03);
noFill();
arc(eyeX-12.5,eyeY-12.5,25,25,0,HALF_PI);
arc(eyeX+12.5,eyeY-12.5,25,25,HALF_PI,PI);
arc(eyeX+12.5,eyeY+12.5,25,25,PI,HALF_PI+PI);
arc(eyeX-12.5,eyeY+12.5,25,25,HALF_PI+PI,2*PI);
arc(eyeX+57.5,eyeY-12.5,25,25,0,HALF_PI);
arc(eyeX+82.5,eyeY-12.5,25,25,HALF_PI,PI);
arc(eyeX+82.5,eyeY+12.5,25,25,PI,HALF_PI+PI);
arc(eyeX+57.5,eyeY+12.5,25,25,HALF_PI+PI,2*PI);
}
}
void PSP()
{
fill(#00FFFD);
rect(mouseX-50,mouseY-25,100,50,10);
fill(#FF4040);
rect(mouseX-50,mouseY-25,30,50,10);
fill(0);
rect(mouseX-30,mouseY-25,60,50);
fill(#C1C1C1);
rect(mouseX-24,mouseY-20,48,40);
fill(0);
ellipse(mouseX-40,mouseY-10,10,10);
ellipse(mouseX-40,mouseY+5,5,5);
ellipse(mouseX-40,mouseY+15,5,5);
ellipse(mouseX-45,mouseY+10,5,5);
ellipse(mouseX-35,mouseY+10,5,5);
ellipse(mouseX+40,mouseY+10,10,10);
ellipse(mouseX+40,mouseY-5,5,5);
ellipse(mouseX+40,mouseY-15,5,5);
ellipse(mouseX+45,mouseY-10,5,5);
ellipse(mouseX+35,mouseY-10,5,5);
if(isPress)
{
fill (0);
textSize(10);
text("Nintendo",mouseX-22,mouseY-5);
text("Switch",mouseX-18,mouseY+5);
}
}
void apple()
{
stroke(0);
strokeWeight(3);
fill(#FF3939);
beginShape();
vertex(150,118);
bezierVertex(96,105,125,190,152,177);
bezierVertex(202,183,195,99,159,119);
endShape();
fill(#A0805E);
beginShape();
vertex(159,125);
bezierVertex(168,88,146,83,150,125);
endShape();
fill(#2EFF3A);
beginShape();
vertex(163,107);
bezierVertex(157,94,176,91,186,100);
bezierVertex(175,115,166,111,162,105);
endShape();
noFill();
beginShape();
vertex(182,104);
bezierVertex(174,102,169,103,162,105);
endShape();
beginShape();
vertex(146,123);
bezierVertex(153,125,159,125,164,123);
endShape();
}
void my_background()
{
if(!isPress)
{
angle+=0.01;
posx+=1;
posy+=1;
}
else
{
angle+=0.02;
posx+=2;
posy+=2;
}
for(int i=-10000;i<1000;i+=150)
{
for(int j=-10000;j<1000;j+=150)
{
pushMatrix();
translate(posx,posy);
translate(i,j+50);
rotate(angle);
//apple();
image(img,0,0);
popMatrix();
}
}
}
void mousePressed()
{
isPress=true;
}
void mouseReleased()
{
isPress=false;
}
void setup(){
size(960, 560);
background(#FFD1F2);
img = loadImage("a.png");
imageMode(CENTER);
//hairOutline();
//hairLine();
//hairThinLine();
//face();
//hairColor();
//liuhai();
//eyesLine();
}
void draw()
{
background (255);
my_background();
hairOutline();
hairLine();
hairThinLine();
face();
hairColor();
liuhai();
eyeball();
eyesLine();
mouse_nose();
PSP();
//apple();
}
感谢观看~