用p5.js实现一个小动画——故宫橘猫赏秋图
互动媒体第二次作业要求我们手绘一幅动画,再用代码实现出动画。由于时间原因,手绘并没有画动画,而是以插画的形式画了一张,然后p5实现了动画。
这里先放效果图:
板绘插图
码绘效果图
这里强烈建议直接运行代码!!!gif丢帧!!!可怜我的渐变啊啊啊啊!!!
下面附上完整代码:
var Width=600; var Height=700; var pixel=1; var Y_AXIS = 1; var X_AXIS = 2; var skyHeight=190; var wall_Width=600; var wall_Height=300; var wuyan_width=120; var wuyan_height=20; var quad_width=70; var quad_height=30; var center_x=500; var center_y=115; var cat_scale=111; var easing=1; var Time; //face_color=color(180,180,150,0.5*255); function setup() { createCanvas(Width,Height); } function draw() { frameRate(5); drawwall(); drawsky(); push(); translate(10,-5); YinxingTree(); pop(); draw_wallshadow(); if(center_x<-10) center_x=650; center_x-=15*easing; drawcat(cat_scale,center_x,center_y); translate(10,-25); noStroke(); fill(30); rect(Width-10,0,200,Height); push(); YinxingTree(); pop(); } function drawcat(cat_scale,center_x,center_y) { stroke(200,200,240); noStroke(); //肚子 pos1_x=center_x-(cat_scale)/3; pos1_y=center_y+(cat_scale)*2/5-5; pos2_x=center_x+(cat_scale*1/3); pos2_y=center_y+(cat_scale)*2/5; //前体 pos3_x=pos1_x-(cat_scale/5); pos3_y=center_y+(cat_scale)*2/5; pos4_x=pos1_x-(cat_scale/8); pos4_y=center_y+(cat_scale)/15; pos5_x=pos4_x-(cat_scale/8); pos5_y=pos4_y-(cat_scale)/20; //头 pos6_x=pos5_x-(cat_scale/4); pos6_y=pos5_y-(cat_scale)/6; pos7_x=pos5_x-(cat_scale/6); pos7_y=pos5_y-(cat_scale)/30; pos8_x=pos5_x-(cat_scale)*3/8; pos8_y=pos5_y+(cat_scale)/8; pos9_x=pos8_x+(cat_scale)/5; pos9_y=pos8_y+(cat_scale)/5; //屁股 pos10_x=pos2_x-(cat_scale/4)*0; pos10_y=pos2_y-(cat_scale)*1/3; pos11_x=pos10_x+(cat_scale*1/8); pos11_y=pos10_y+(cat_scale)/10; fill(220,200,180); triangle(center_x,center_y,pos1_x,pos1_y,pos2_x,pos2_y); triangle(center_x,center_y,pos1_x,pos1_y,pos3_x,pos3_y); fill(150,70,10); triangle(center_x,center_y,pos3_x,pos3_y,pos4_x,pos4_y); triangle(pos3_x,pos3_y,pos4_x,pos4_y,pos5_x,pos5_y); triangle(pos3_x,pos3_y,pos5_x,pos5_y,pos6_x,pos6_y); fill(150,70,10); triangle(pos3_x,pos3_y,pos7_x,pos7_y,pos8_x,pos8_y); fill(180,100,10); triangle(pos8_x,pos8_y,pos9_x,pos9_y,pos5_x,pos5_y); fill(150,70,10); triangle(center_x,center_y,pos2_x,pos2_y,pos10_x,pos10_y); triangle(pos2_x,pos2_y,pos10_x,pos10_y,pos11_x,pos11_y); fill(180); feetControl(pos1_x-6,pos1_y); feetControl(pos2_x-4,pos2_y); noFill(); weiba(pos11_x,pos11_y); } function weiba(x,y) { push(); strokeWeight(10); stroke(150,70,10); x1=x-20; y1=y; x2=x+20; y2=y-20; x3=x+25; y3=y+5; x4=x+55; y4=y-20; bezier(x1,y1,x2,y2,x3,y3,x4,y4); noStroke(); pop(); } function feetControl(x,y) { if(x%2==0) { rect(x-(cat_scale)/10,y-8,(cat_scale)/10,(cat_scale)*1/3+8); } else { quad(x,y-10, x-(cat_scale)/10,y-10, x-(cat_scale)/10+(cat_scale/10),y+(cat_scale)*1/3, x+(cat_scale/10),y+(cat_scale)*1/3); quad(x,y-15, x-(cat_scale)/10,y-15, x-(cat_scale)/10-(cat_scale/5),y+(cat_scale)*1/3, x-(cat_scale/5),y+(cat_scale)*1/3); } } function segment(trans_x, trans_y, a,segLength) { push(); translate(trans_x, trans_y); rotate(a); rect(); pop(); } function draw_wallshadow() { noStroke(); var c1=color(160,10,0); var c2=color(80,10,80); setGradient(0,600,Width,150,c1,c2,1); noStroke(); fill(160,10,0); for(var i=0;i<Width;i++) { arc(i,600,50,15,PI,0); i=i+80; } } function drawwall() { noStroke(); fill(100,10,0); rect(0, 0, Width, Height); fill(190,70,20); rect(0, Height-wall_Height, wall_Width, wall_Height); drawWuYan1(); drawWuYan2(); drawWuYan3(); drawWuYan4(); } function drawWuYan1() { stroke(20); fill(190,100,10); for(var i=0;i<Width;i++) { rect(i-5,wall_Height+70,wuyan_width,wuyan_height); i=i+wuyan_width; } } function drawWuYan2() { var cwu2_1=color(50,120,30); var cwu2_2=color(60,10,0); for(var j=0;j<Width+80;j++) { setGradient(j-65,wall_Height+35, wuyan_width,wuyan_height+10, cwu2_1,cwu2_2,1); stroke(180,130,20); rect(j-65,wall_Height+36, wuyan_width,wuyan_height+10); j=j+wuyan_width; } var cwu3_1=color(10,20,10); var cwu3_2=color(80,100,20); fill(50,120,30); setGradient(0,wall_Height-15, Width,50,cwu3_1,cwu3_2,1); } function drawWuYan3() { noStroke(); fill(190,150,90); for(var k=0;k<Width;k++) { rect(k,skyHeight,wuyan_width,10); k=k+wuyan_width; } fill(190,100,10); rect(0,skyHeight+15,Width,12); fill(190,110,30); rect(0,skyHeight+35,Width,35); } function drawPIdwon(x_trans) { stroke(90,50,50); push(); translate(x_trans, skyHeight+100); rotate(0.0); fill(140,100,50); arc(0, 0, quad_width, quad_width-15, 0, PI); pop(); } function drawPIdwon_shadow(x_trans,shadow) { noStroke(); push(); translate(x_trans, skyHeight+100); rotate(0.0); fill(10,20,10); arc(0, 0, quad_width+shadow, quad_width+shadow, 0, PI); pop(); } function drawquad(i,j,x_trans) { var c1=color(90,50,50); var c2=color(180,90,50); setGradient(x_trans-(quad_width/2)+i, skyHeight+93-j, quad_width,5,c1,c2,2); } function drawCicle(x_trans,angle,c1,c2,c3,i) { push(); noStroke(); fill(c1,c2,c3); translate(x_trans-i+7,skyHeight+70+i*3); rotate(angle); arc(0,0,50,50, 0, PI/2); pop(); } function drawCicle_all(x_trans) { for(var i=0;i<8;i++) { drawCicle(x_trans+quad_width-8,24.5,100,10,10,i); drawCicle(x_trans+quad_width-8,-2.2,130,110,90,i); drawCicle(x_trans+quad_width-8,1,70,20,10,i); drawCicle(x_trans+quad_width-8,-3.5,200,160,80,i); } stroke(50,10,10); fill(140,100,50); ellipse(x_trans+60,skyHeight+95,50,50); fill(80,60,20); ellipse(x_trans+60,skyHeight+95,35,35); } function drawWuYan4() { for(var x_trans=50;x_trans<Width;x_trans++) { drawPIdwon_shadow(x_trans+10,10); drawPIdwon(x_trans); for(var i=0;i<5;i++) { yp=i*5; drawquad(i,yp,x_trans); } drawCicle_all(x_trans); x_trans=x_trans+120; } } function YinxingTree() { push(); drawtree(220,180,0,-20,20,random(0.6)); drawtree(120,60,0,-100,100,random(0.01)); drawtree(120,60,0,-50,160,random(0.01)); drawtree(180,160,0,40,160,random(0.05)); drawtree(200,100,0,-20,100,random(1)); drawtree(200,160,0,0,120,random(0.5)); drawtree(220,160,0,55,160,random(0.1)); drawtree(240,200,0,50,100,random(0.3)); drawtree(240,200,0,50,180,random(0.3)); drawtree(240,200,0,80,190,random(1)); drawtree(220,180,0,-50,80,random(0.1)); translate(150,90); drawtree(220,180,0,-50,150,random(0.5)); translate(-100,-150); drawtree(240,200,120,-100,100,random(0.01)); pop(); } function drawtree(c1,c2,c3,pos_x,pos_y,pos_angle) { push(); rotate(pos_angle); var trans_x; var trans_y; var trans_angle; fill(c1,c2,c3); for(var i=0;i<20;i++) { trans_x=random(50); trans_y=random(20); trans_angle=random(-0.5); push(); translate(trans_x,trans_y); rotate(trans_angle); drawYinXing(pos_x,pos_y); pop(); } pop(); } function drawYinXing(pos_x,pos_y) { stroke(200,150,60); push(); translate(pos_x, pos_y); rotate(0.0); arc(0, 0, 30, 30, 0, PI/2); pop(); } function drawsky() { var c1 = color(90,150,205); var c2 = color(190,200,220); noStroke(); setGradient(0, 0, Width, skyHeight,c1,c2,1); } function setGradient(x, y, w, h, c1, c2,axis) { noFill(); if (axis == Y_AXIS) { // Top to bottom gradient for (var i = y; i <= y+h; i++) { var inter = map(i, y, y+h, 0, 1); var c = lerpColor(c1, c2, inter); stroke(c); line(x, i, x+w, i); } } else if (axis == X_AXIS) { // Left to right gradient for (var k = x; k <= x+w; k++) { var interk = map(k, x, x+w, 0, 1); var ck = lerpColor(c1, c2, interk); stroke(ck); line(k, y, k, y+h); } } }
代码结构解析
1.背景:
其实画背景还挺简单的,基本物体就是红墙,屋檐,银杏树,天空。
天空是渐变的,用了一个函数,p5官网里面也有:
function drawsky() { var c1 = color(90,150,205); var c2 = color(190,200,220); noStroke(); setGradient(0, 0, Width, skyHeight,c1,c2,1); } function setGradient(x, y, w, h, c1, c2,axis) { noFill(); if (axis == Y_AXIS) { // Top to bottom gradient for (var i = y; i <= y+h; i++) { var inter = map(i, y, y+h, 0, 1); var c = lerpColor(c1, c2, inter); stroke(c); line(x, i, x+w, i); } } else if (axis == X_AXIS) { // Left to right gradient for (var k = x; k <= x+w; k++) { var interk = map(k, x, x+w, 0, 1); var ck = lerpColor(c1, c2, interk); stroke(ck); line(k, y, k, y+h); } } }
红墙就不细说了,直接看屋檐,屋檐还稍微有点东西。观察故宫屋檐结构之后发现,故宫这样的建筑简直太有规律可循了!你只要生成一个基本元,接下来的就只用循环生成就可以。我们主要来看看圆木那一块怎么实现。
圆木那里其实还挺麻烦,主要是有光的影响,圆木被分为三个面:受光面,反光面,阴影面,直接用一个圆肯定解决不了,我想了一个办法,用三个扇形就可以区分三个面。
具体代码:
function drawCicle(x_trans,angle,c1,c2,c3,i) { push(); noStroke(); fill(c1,c2,c3); translate(x_trans-i+7,skyHeight+70+i*3); rotate(angle); arc(0,0,50,50, 0, PI/2); pop(); } function drawCicle_all(x_trans) { for(var i=0;i<8;i++) { drawCicle(x_trans+quad_width-8,24.5,100,10,10,i); drawCicle(x_trans+quad_width-8,-2.2,130,110,90,i); drawCicle(x_trans+quad_width-8,1,70,20,10,i); drawCicle(x_trans+quad_width-8,-3.5,200,160,80,i); } stroke(50,10,10); fill(140,100,50); ellipse(x_trans+60,skyHeight+95,50,50); fill(80,60,20); ellipse(x_trans+60,skyHeight+95,35,35); }
还有瓦片上的阴影,也用了渐变过渡,这里就不贴代码了。
银杏树
一开始对银杏树没什么头绪,观察了好几棵学校里的银杏,在大风刮过之时,金黄树叶在风中颤抖摇晃,我突然有了灵感——色块堆积。我可以不用准准确确的画出这棵树长啥样,我只需要保证它在运动中是符合这棵树的逻辑的,那么这棵树就是成功的。
下面贴上代码:
function YinxingTree() { push(); drawtree(220,180,0,-20,20,random(0.6)); drawtree(120,60,0,-100,100,random(0.01)); drawtree(120,60,0,-50,160,random(0.01)); drawtree(180,160,0,40,160,random(0.05)); drawtree(200,100,0,-20,100,random(1)); drawtree(200,160,0,0,120,random(0.5)); drawtree(220,160,0,55,160,random(0.1)); drawtree(240,200,0,50,100,random(0.3)); drawtree(240,200,0,50,180,random(0.3)); drawtree(240,200,0,80,190,random(1)); drawtree(220,180,0,-50,80,random(0.1)); translate(150,90); drawtree(220,180,0,-50,150,random(0.5)); translate(-100,-150); drawtree(240,200,120,-100,100,random(0.01)); pop(); } function drawtree(c1,c2,c3,pos_x,pos_y,pos_angle) { push(); rotate(pos_angle); var trans_x; var trans_y; var trans_angle; fill(c1,c2,c3); for(var i=0;i<20;i++) { trans_x=random(50); trans_y=random(20); trans_angle=random(-0.5); push(); translate(trans_x,trans_y); rotate(trans_angle); drawYinXing(pos_x,pos_y); pop(); } pop(); } function drawYinXing(pos_x,pos_y) { stroke(200,150,60); push(); translate(pos_x, pos_y); rotate(0.0); arc(0, 0, 30, 30, 0, PI/2); pop(); }
大量使用radom可以让这棵树更自然。
2.动画主角——猫
这里我先对猫进行了一些处理——低多边形处理。
吸取了第一个实验的教训,这次我先设置了一个中心点,然后在根据这个点扩充出有关猫的肢干总共12个点,然后画三角形,形成一个没有四肢,没有尾巴的橘猫。
尾巴用了贝塞尔曲线,坐标也跟中心点关联。
猫的四肢是运动视觉的关键!!!动画之所以能动是因为有承上启下的连续性动作。猫行走从侧面看过去就是两腿相互交叉变换。所以在写动画逻辑之前你需要先画出关键帧状态。
关键帧状态确定了就可开始着手动画逻辑:首先视觉上我们先要营造出猫在原地踏步的感觉。我们有两个关键帧状态,所以可以运用模运算,在运动的中心坐标基础上模2,结果对应两个状态。
附上代码:
function feetControl(x,y) { if(x%2==0) { rect(x-(cat_scale)/10,y-8,(cat_scale)/10,(cat_scale)*1/3+8); } else { quad(x,y-10, x-(cat_scale)/10,y-10, x-(cat_scale)/10+(cat_scale/10),y+(cat_scale)*1/3, x+(cat_scale/10),y+(cat_scale)*1/3); quad(x,y-15, x-(cat_scale)/10,y-15, x-(cat_scale)/10-(cat_scale/5),y+(cat_scale)*1/3, x-(cat_scale/5),y+(cat_scale)*1/3); } }
至此,动画完成。
手绘与码绘的对比
在动画这个应用上,其实两者各有千秋。手绘能做到画面更加精致有更多细节,更能体现质感,但同时,它又太过费时。而码绘在运动这一方面有着得天独厚的优势,它能更平滑的完成动画操作。
发现的问题
码绘在建立场景的过程中,发现对于环境色这一概念,几乎还是一个空白领域。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。
《魔兽世界》大逃杀!60人新游玩模式《强袭风暴》3月21日上线
暴雪近日发布了《魔兽世界》10.2.6 更新内容,新游玩模式《强袭风暴》即将于3月21 日在亚服上线,届时玩家将前往阿拉希高地展开一场 60 人大逃杀对战。
艾泽拉斯的冒险者已经征服了艾泽拉斯的大地及遥远的彼岸。他们在对抗世界上最致命的敌人时展现出过人的手腕,并且成功阻止终结宇宙等级的威胁。当他们在为即将于《魔兽世界》资料片《地心之战》中来袭的萨拉塔斯势力做战斗准备时,他们还需要在熟悉的阿拉希高地面对一个全新的敌人──那就是彼此。在《巨龙崛起》10.2.6 更新的《强袭风暴》中,玩家将会进入一个全新的海盗主题大逃杀式限时活动,其中包含极高的风险和史诗级的奖励。
《强袭风暴》不是普通的战场,作为一个独立于主游戏之外的活动,玩家可以用大逃杀的风格来体验《魔兽世界》,不分职业、不分装备(除了你在赛局中捡到的),光是技巧和战略的强弱之分就能决定出谁才是能坚持到最后的赢家。本次活动将会开放单人和双人模式,玩家在加入海盗主题的预赛大厅区域前,可以从强袭风暴角色画面新增好友。游玩游戏将可以累计名望轨迹,《巨龙崛起》和《魔兽世界:巫妖王之怒 经典版》的玩家都可以获得奖励。
更新日志
- 中国武警男声合唱团《辉煌之声1天路》[DTS-WAV分轨]
- 紫薇《旧曲新韵》[320K/MP3][175.29MB]
- 紫薇《旧曲新韵》[FLAC/分轨][550.18MB]
- 周深《反深代词》[先听版][320K/MP3][72.71MB]
- 李佳薇.2024-会发光的【黑籁音乐】【FLAC分轨】
- 后弦.2012-很有爱【天浩盛世】【WAV+CUE】
- 林俊吉.2012-将你惜命命【美华】【WAV+CUE】
- 晓雅《分享》DTS-WAV
- 黑鸭子2008-飞歌[首版][WAV+CUE]
- 黄乙玲1989-水泼落地难收回[日本天龙版][WAV+CUE]
- 周深《反深代词》[先听版][FLAC/分轨][310.97MB]
- 姜育恒1984《什么时候·串起又散落》台湾复刻版[WAV+CUE][1G]
- 那英《如今》引进版[WAV+CUE][1G]
- 蔡幸娟.1991-真的让我爱你吗【飞碟】【WAV+CUE】
- 群星.2024-好团圆电视剧原声带【TME】【FLAC分轨】