码绘
手绘
代码实现过程
var numBalls = 30;
var spring = 0.05;
var gravity = 0.01;
var friction = -0.9;
var balls = [];
var dancingWords = [];
var angle1=0;
var angle2=90;
var scalar = 80;
function setup() {
//colorMode(HSB, 360, 100, 100);
createCanvas(600, 500);
rectMode(CENTER);
for (var i = 0; i < numBalls; i++) {
balls[i] = new Ball(
random(width),
random(height),
random(40, 80),
i,
balls
);
}
noStroke();
createP('Lets dance together.Lets dance together.Lets dance together.Lets dance together.').addClass('text');
var texts = selectAll('.text');
for (var i=0; i<texts.length; i++) {
var paragraph = texts[i].html();
var words = paragraph.split(' ');
for (var j=0; j<words.length; j++) {
var spannedWord = createSpan(words[j]);
var dw = new DanceSpan(spannedWord, random(500), random(60));
dancingWords.push(dw);
}
}
}
function draw() {
background(0);
balls.forEach(ball => {
ball.collide();
ball.move();
ball.display();
});
for (var i=0; i<dancingWords.length; i++) {
dancingWords[i].brownian();
}
//画小丑
fill(255,215,0);
quad(204,181,271,210,270,227,196,194);
quad(386,176,319,211,324,231,393,189);
fill(255,248,220);
rect(272,353,17, 40);
rect(323,353,17, 40);
fill(72,118,255);
ellipse(274,300,55,55);
ellipse(320,300,55,55);
quad(265,323,280,323,287,336,254,336);
quad(317,323,332,323,339,336,306,336);
fill(106,90,205);
quad(270,200,325,200,345,290,250,290);
fill(0,139,69);
ellipse(250,170,50,30);
ellipse(253,145,50,30);
ellipse(260,120,50,30);
ellipse(350,170,50,30);
ellipse(344,145,50,30);
ellipse(335,120,50,30);
fill(255,235,205)
ellipse(300,160,100,100);//tou
fill(0,139,69);
ellipse(300,115,35,55);
ellipse(275,124,35,55);
ellipse(325,124,35,55);
fill(255);
ellipse(275,164,25,25);
ellipse(325,164,25,25);
fill(0);
quad(275,144,277,164,275,184,273,164);
quad(325,144,327,164,325,184,323,164);
quad(255,164,275,167,295,164,275,161);
quad(305,164,325,167,345,164,325,161);
fill(255,248,220);
ellipse(196,183,27,27);
ellipse(393,179,27,27);
fill(255,0,0);
rect(272,373,17, 40);
rect(323,373,17, 40);
arc(251,400, 60, 60, radians(180), radians(0));
arc(344,400, 60, 60, radians(180), radians(0));
strokeWeight(1);
beginShape();
vertex(267, 184);
vertex(275, 183);
vertex(282, 189);
vertex(291, 194);
vertex(301, 196);
vertex(316, 191);
vertex(324, 184);
vertex(333,187);
vertex(329,196);
vertex(315,204);
vertex(301,206);
vertex(300,207);
vertex(289,206);
vertex(275,200);
vertex(267,193);
endShape(CLOSE);
var ang1 = radians(angle1);
var ang2 = radians(angle2);
var x1 = width/2+ (scalar * cos(ang1));
var x2 = width/2 + (scalar * cos(ang2));
var y1 = height/3 + (scalar * sin(ang1));
var y2 = height/3 + (scalar * sin(ang2));
fill(25 ,25 ,112);
rect(x1, y1, 50, 50);
rect(x2, y2, 50, 50);
fill(255, 255, 0);
rect(x2, y1, 50, 50);
rect(x1, y2, 50, 50);
angle1 += 3;
angle2 += 2;
}
function Ball(xin, yin, din, idin, oin) {
this.x = xin;
this.y = yin;
var vx = 0;
var vy = 0;
this.diameter = din;
this.id = idin;
this.others = oin;
this.collide = function() {
for (var i = this.id + 1; i < numBalls; i++) {
// console.log(others[i]);
var dx = this.others[i].x - this.x;
var dy = this.others[i].y - this.y;
var distance = sqrt(dx * dx + dy * dy);
var minDist = this.others[i].diameter / 2 + this.diameter / 2;
// console.log(distance);
//console.log(minDist);
if (distance < minDist) {
//console.log("2");
var angle = atan2(dy, dx);
var targetX = this.x + cos(angle) * minDist;
var targetY = this.y + sin(angle) * minDist;
var ax = (targetX - this.others[i].x) * spring;
var ay = (targetY - this.others[i].y) * spring;
vx -= ax;
vy -= ay;
this.others[i].vx += ax;
this.others[i].vy += ay;
}
}
};
this.move = function() {
vy += gravity;
this.x += vx;
this.y += vy;
if (this.x + this.diameter / 2 > width) {
this.x = width - this.diameter / 2;
vx *= friction;
} else if (this.x - this.diameter / 2 < 0) {
this.x = this.diameter / 2;
vx *= friction;
}
if (this.y + this.diameter / 2 > height) {
this.y = height - this.diameter / 2;
vy *= friction;
} else if (this.y - this.diameter / 2 < 0) {
this.y = this.diameter / 2;
vy *= friction;
}
};
this.display = function() {
var h = random(100, 255);
fill(0, h, h,200);
ellipse(this.x, this.y, this.diameter, this.diameter);
};
}
function DanceSpan(element, x, y) {
element.position(x, y);
this.brownian = function() {
x += random(-6, 6);
y += random(-6, 6);
element.position(x, y);
};
}
比较
技法
这次的手绘只是一副表现出了画面中的具体物件的图画,没有表现出直观的动态,因此是比较简单的,没有用到什么特别的技巧,如果要做出动态的画面需要制作多幅图片再做出动画,工作量就比较大了。
本次的码绘主要分为了三个部分,分别是作为背景的有抖动效果的文字,背景闪烁跳动的圆和前面控制小方块运动的小丑。背景产生的圆的位置和大小是随机的,互相会发生碰撞,和画布边界也会碰撞。文字抖动主要是使文字发生快速位移,产生抖动效果。小丑比较简单就是静态描绘,小丑控制的方块则是利用了方块坐标的改变,得到动态的效果。主要参考了两个案例,参考文献给出。
工具
码绘是基于p5.js实现
手绘依旧使用纸笔作画
理念
手绘和码绘的重点始终是在“绘”上,因此首先需要的是对画面的构思,对场景里物体的摆放,但两种方法又给我提供了不同的效果选择,码绘能够更为轻松的实现规则或随机的流畅动态,手绘能更精致更准确地描述出笔者的真实感受。但无论哪一种,都是十分好的表达途径。
创作体验
因为最开始没有一个十分完备的画面构思,因此在进行码绘的时候是十分苦恼的,在每一次新的尝试中常常是不能达到想象的效果但是又有可能出现更加有趣的图像,所以其实是拥有了很多选择但是没有一个画面主题。最后还是先确定了主题手绘画出了想要的画面,再通过码绘进行了动态实现,可以说是一次手绘码绘的合作了,最后的结果还是非常满意的。
呈现效果
手绘的图片可能没有码绘的这么好看,但毕竟是先用手绘确定了基本画面,其实如果用数位板画画并且做出多张图片的话呈现的动画效果应该是和码绘旗鼓相当甚至要更好的,因为笔者可以确定到每一帧地创作修改,但是这样工作量太大了,因此这种并不需要太精细的动态效果还是码绘更高效,码绘呈现的动态效果也非常灵动。
参考文献
https://p5js.org/zh-Hans/examples/motion-bouncy-bubbles.html
https://p5js.org/zh-Hans/examples/dom-modifying-the-dom.html