鲤——processing动画交互应用

鲤——processing动画交互应用

前言

鲤鱼在我们的日常生活中是一种非常常见的鱼类,它们中的一部分会成为餐桌上的食物,另一部分——锦鲤,则具有极高的观赏价值。我们在园林、寺庙等景点的水池中经常能看见色彩艳丽的各类锦鲤,其中红色和金色的锦鲤最受欢迎。
我从很久以前就很喜欢锦鲤的形象,不过比起现实中的锦鲤,我更喜欢的是中国风的水墨画或水彩中的锦鲤,无论是细细描绘还是草草勾勒,鱼身的弧度和色泽都显得灵动缥缈,仿佛下一刻这尾鱼儿就会从画中跳出来,一摆尾便消失在视野中。

说到鲤,说不定有人会想起一款叫《鲤》的游戏,我当初是在taptap上下载到这个游戏的,画面风格和游戏方式都很清新休闲,不过现在好像无法下载了(不知道发生了啥)。这次作业主题也是因为回想起了这部游戏才产生的,想要实现一个可以控制鲤来完成一些任务的小游戏。

目录

1.应用设计
2.编写过程
3.总结
4.参考资料
5.应用推荐

应用设计

自己最初的想法虽然好,但在编写过程中遇到的问题太多了,方案也就变得越来越简单,这里就只写最后成型的程序内容吧。
1.首先是最基本的,鼠标移动控制鲤鱼在池塘中自由游动,并伴有一定的动画效果。
2.鼠标按下时,会给鱼一个当前方向的力,让鱼儿加速。
3.鲤鱼游动时会不断吐出泡泡,泡泡受到浮力与重力影响(浮力肯定是要大一点的)向上移动。
4.随机地在池塘中产生鱼食,产生时会有波纹,鱼食经过一段时间后消失,鲤鱼吃到鱼食时会在当前位置动态出现莲花。
5.鲤鱼最初为白色,每吃一次鱼食就会加深颜色直到最终变红。
6.画面上方有文字提醒,当鲤鱼变成最终心态后赋予一个固定的力让鱼游动,游戏结束。
编写过程将按照设计内容来一一解说。

编写过程

1.虽然我也很想自己设计一个完整的鲤鱼动画…不过时间紧迫加上能力限制,最终使用的鲤鱼蓝本来源于一个开源项目(看到这个项目也促使了我做这个主题),我借鉴了其中绘制鲤鱼的一部分代码,是哪个网站找到的我记不得了…
项目运行页面:http://mylifeaquatic.herokuapp.com/
github源码:https://github.com/dasl-/my-life-aquatic
项目源码好像是需要用ruby运行的(不太了解),所以我只看了proccessing文件夹里面的代码进行参考(在public/processing里面),最终借鉴的是三个文件中的部分代码:Boid,Fish和Flagellum。
Boid类(包括我自己的注释):

class Boid {
	public PVector location;
	public PVector velocity;
	private PVector acceleration;
	private float maxForce;
	private float maxSpeed;
	
	public Boid( PVector _location, float _maxSpeed, float _maxForce) {
     createBoid(_location, _maxSpeed, _maxForce);
     velocity= new PVector( random( -maxSpeed, maxSpeed ), random( -maxSpeed, maxSpeed ) );
    }
  private void createBoid(PVector _location, float _maxSpeed, float _maxForce) {
		location 		= _location;
		maxSpeed 		= _maxSpeed;
		maxForce 		= _maxForce;
		acceleration 	= new PVector( 0, 0 );
	}
	
	protected void update() {
		velocity.add(acceleration);
		velocity.limit(maxSpeed*2);
		location.add(velocity);
		acceleration.mult(0);
	}

	private PVector steer( PVector _target, boolean _slowdown ) {
		PVector steer;
		PVector desired = PVector.sub( _target, location );//target minus location now
		float dist = desired.mag();// get vector length
		if ( dist > 0 ) {
			desired.normalize();//danweihua vector,make the length=1
			if ( _slowdown && dist < 60 ) {
				desired.mult( maxSpeed * (dist / 60) );
			}
			else {
				desired.mult( maxSpeed );//direction*speed
			}
			steer = PVector.sub( desired, velocity );
			steer.limit( maxForce );
		}
		else {
			steer =new PVector( 0, 0 );
		}
		return steer;
	}
	
	protected void seek(PVector _target) {
		acceleration.add( steer( _target, false ) );
	}
}

Boid主要掌管鲤鱼的运动,其中的update和代码本色中的Mover类很相似,由位置,速度,加速度和力的层层累加完成当前时刻鲤鱼位置的计算,不同的是鲤鱼的速度有最大上限,此处最大上限为定义的最大速度(maxspeed)的两倍,原因在下面再解释。这个类被创建时,会定义最初位置,最大速度与最大力并给予一个随机的初始速度。
下面的seek与steer函数则是控制鱼类向鼠标移动的关键,steer函数接收目标位置target(slowdown参数没用到不用管),用target减去当前位置location得到运动需要的向量desired,单位化这个向量,之后将它乘以初始最大速度,就得到了所需速度(包括方向),用所需速度减去目前速度,得到所需加速度,因为此处不计重量因此用最大力来约束这个加速度后,就可以得到最终的所需加速度。使用seek函数接收目标即可更新当前加速度,成功完成导向。seek函数与代码本色中的自制智能体有关。

Fish类继承了Boid类,而Flagellum类主要用于确定每一时刻的鲤鱼各个部分的位置与形状,在Fish类中定义了五个Flagellum类:身体,两半尾巴和两个鱼鳍,Fish类中的render函数负责绘制整个鲤鱼,这些部分我粗略看了之后没怎看懂,后来也没有更多时间 细细看了,有兴趣的朋友可以去下了源码研究研究。Fish的update函数中这一部分承接上文实现鲤鱼的游动:

if(mousePosition.x != mousePositionOld.x ||mousePosition.y != mousePositionOld.y)
             seek(mousePosition);//move to the mouse but some bugs...
       mousePositionOld = mousePosition;

一般鼠标停留在某处不动后鲤鱼就会朝一个固定方向游动,游出画面后经由checkboders函数再从另一边游出,不过我发现如果鼠标快速在一个位置附近晃动就会出现鲤鱼停住的bug。

最后在主文件test中创建Fish类就可以实现第一个功能了。

Fish fish;
PImage p;
public void setup() {
  smooth();
  size(1400, 900);
  fish=new Fish(new PVector(700,50),3,5);
  frameRate(45); 
  p=loadImage("1.png");
}

void drawBackground()
{
  image(p,0,0);
}

public void draw() {
  drawBackground();
  fish.render();
  fish.update();

}

public void mouseMoved() {
    PVector mousePosition = new PVector(mouseX, mouseY);
    fish.setMousePosition(mousePosition);
}

2.鼠标点击后施加力做起来还是挺简单的,在Boid里加入两个函数:

	public void applyForce() {
    PVector f = velocity;
    f.normalize();
    f.mult(10);
    acceleration.add(f);
  }
  
  public void deleteForce() {
     velocity.normalize();
     velocity.mult(maxSpeed);
  }

在test的draw中加入以下:

  if(mousePressed)
  {
    fish.applyForce();
    PVector mousePosition = new PVector(mouseX, mouseY);
    fish.setMousePosition(mousePosition);
  }
  else
  {
    fish.deleteForce();
  }

鼠标点击后施加力,由于赋予鲤鱼的初始速度为maxspeed,所以上文中曾提到在Boid的update中给速度增加了限制,为两倍的maxspeed,施加力后鲤鱼加速到最大速度后就会匀速游动,松开鼠标则速度变回初始速度。

3.这一部分要用到粒子系统,基础的粒子类在代码本色里是有的:

class Particle {
  PVector position;
  PVector velocity;
  PVector acceleration;
  float lifespan;

  Particle(PVector l) {
    acceleration = new PVector(0, 0.05);
    velocity = new PVector(random(-1, 1), random(-1, 0));
    position = new PVector (l.x, l.y);
    lifespan = 255.0;
  }
  void run() {
    update();
    display();
  }
  void update() {
    velocity.add(acceleration);
    position.add(velocity);
    acceleration.mult(0);
  }
  void display() {
  }
  boolean isDead() {
    if (lifespan < 0.0) {
      return true;
    } 
    else {
      return false;
    }
  }
}

不过我虽然最后用到的三类粒子都继承了这个初类,但基本都重写一遍了所有变量和函数…这个就权当参考。
泡泡类:

class Particle1 extends Particle
{
  final static float MAX_SPEED = 0.1;
  PVector vel = new PVector(random(-MAX_SPEED, MAX_SPEED), random(-MAX_SPEED, MAX_SPEED));
  PVector acc = new PVector(0, 0);
  PVector pos;
  float mass = random(1, 2);
  float size = mass*random(3,4);
  int lifespan = 255;
  color c;
  float wei;
  Particle1(PVector p)
  {
    super(p);
    pos = new PVector (p.x, p.y);
    acc = new PVector (random(0.1, 1.5), 0);
    if(random(0,1)<0.5)
       c=#ffffff;
    else
       c=#1E90FF;
    wei=random(1,3);
  }
  public void update()
  {
    vel.add(acc); 
    pos.add(vel); 
    acc.mult(0);
    size += 0.05; 
    lifespan=lifespan-2;
  }
  public void applyForce(PVector force) 
  {
    PVector f = PVector.div(force, mass);
    acc.add(f);
  }
  public void display()
  {
    pushMatrix();
    noFill();
    strokeWeight(wei);
    stroke(c,lifespan);
    ellipse(pos.x, pos.y, size * 4, size * 4);
    popMatrix();
  }
  
}

其中比较重要的是随机定义泡泡的质量和大小,大小部分由质量决定,随时间泡泡大小会慢慢变大。Update函数更新位置,display函数绘制粒子,之后就是粒子系统的类:

import java.util.Iterator;
class ParticleSystem1
{
  ArrayList<Particle1> particles = new ArrayList<Particle1>();
  void update()
  {
    Iterator<Particle1> i = particles.iterator();
    while (i.hasNext()) {
      Particle1 p = i.next();
      if (p.pos.x > width || p.pos.x < 0) {
        i.remove();
        continue;
      } else if (p.pos.y > height || p.pos.y < 0) {
        i.remove();
        continue;
      }
      p.applyForce(new PVector(random(-1,1),0));
      p.applyForce(new PVector(0,(p.mass-p.size/3)/3));
      p.update();
      if (p.isDead()) {
        i.remove();  
      } else {
        p.display();
      }
    }
  }
}

这其中重要的是力的施加,一个是随机的左右力让泡泡晃动,另一个就是模拟浮力减去重力,当然为了实现比较良好的效果,力的设计就不太严谨,其次还有当泡泡运动到画面之外或生命到期时就从系统中除去这个粒子。
在test中实现泡泡效果也很简单:

ParticleSystem1 system = new ParticleSystem1();
system.particles.add(new Particle1(new PVector(fish.location.x-5*fish.velocity.x, fish.location.y-5*fish.velocity.y)));
system.update();

4.出现鱼食也是利用粒子系统,基本架构和前面一样我就不解释贴代码了,鱼食是一个个出现的,同一时间只有一个,鱼食出现的地方会有简单的波纹,鱼食在原地一段时间后会移动并缩小。

之后吃到鱼食绘制花朵这个想法来源与我对游戏《鲤》里莲花的印象,最后选择绘制花朵张开的动态效果。同样是利用粒子系统,其中比较重要的部分就是display绘制函数。

pushMatrix();
  fill(255);
  stroke(255,192,200);
  strokeWeight(5);
  translate(pos.x, pos.y);
  scale(0.5);
  ellipse(0,0,120, 120);
  rotate(2*angle);
  for (int i=0; i<16; i++) {
  pushMatrix();
  float i0=30;
  rotate(i* TWO_PI/16);
  translate(0, i0);
  ellipse(0, 0, 30, 30);
  rotate(radians(angle));
  noFill();
  stroke(255,192,200);
  beginShape();
  vertex(0,0);
  bezierVertex(10,50,30,90,100,100);
  bezierVertex(90,30,50,10,0,0);
  endShape();
  popMatrix();  
  }
  if(angle<150*PI)
     angle+=speed;
  else
     ;
  popMatrix();

其中angle的初始大小和speed的大小以及第一个rotate用于控制整朵花的动画幅度与速度(参数都是改来改去调到合适为止没多大深意),第一个ellipse绘制花心的圆,之后的循环用来绘制围绕花心的十六片花瓣,每一片是用自定义图形设计的花瓣形状,使用循环每次绘制后装过一定角度绘制下一片,循环中的ellipse在每片花瓣靠近花心的位置画一个圆,其中i0是指每次将坐标轴转到一片花瓣在花心的位置时,在离y轴30的距离处开始绘制花瓣,rotate(radians(angle))用来控制花瓣总体的转向,会形成花瓣张开的效果。
在test中简单地判断鲤鱼是否碰到鱼食,出现花并清空鱼食的寿命:

if((Math.abs(fish.location.x-foodnow.pos.x)<20)&&(Math.abs(fish.location.y-foodnow.pos.y)<20)&&(foodnow.lifespan>0))
  {
    foodnow.lifespan=0;
    flower.particles.add(new Particle3(new PVector(foodnow.pos.x, foodnow.pos.y)));
  }

5.鱼的颜色加深实现起来很简单,每次判断吃到鱼食时改变鱼的颜色就行。

6.最后是一个简单的文字提示和特效,鱼儿最终变为红色时会改变最上方的文字,初始文字和最后文字都有一点简单的动画效果。
text类:

class Text {
  char text;
  float homex, homey; 
  float x, y;
  float theta;
  Text(float x1, float y1, char text1) {
    homex = x = x1;
    homey = y = y1;
    text = text1;
    x = random(width);
    y = random(height);
    theta = random(TWO_PI);
  }

  void display() {
    textAlign(LEFT);
    pushMatrix();
    translate(x, y);
    rotate(theta);
    text(text, 0, 0);
    popMatrix();
  }
  void home() { 
    x = lerp(x, homex, 0.04);
    y = lerp(y, homey, 0.04);
    theta = lerp(theta, 0, 0.04);
  }
}

这个类使用了lerp函数来实现有趣的动画效果,首先赋予x,y和theta一个随机值,每次绘制字体将坐标轴转换到(x,y)处,且进行旋转,lerp函数使得x和y不断向homex和homey(也就是字体最终应该在的位置)靠近,角度也从随机值向0靠近,lerp函数最后一个参数决定每次靠近多少。

总结

程序参考了代码本色中一、二、四、六章的功能,原本想要实现很多游戏方法,但是最后只实现了一点点,而且还有很多细节没有完善,不过花了很多时间来研究借鉴的代码与代码本色上的内容,也做过很多尝试,有失败也有成功,算是勉强符合了作业要求,学到了一些东西。以后若有机会我还会把这个小游戏完善的更好,之后再上传到网站上展示,目前还是过于简单。

参考资料

1.鲤鱼游动:
项目运行页面:http://mylifeaquatic.herokuapp.com/
github源码:https://github.com/dasl-/my-life-aquatic
2.openprocessing:https://www.openprocessing.org/

应用推荐

1.【融入动画技术的交互应用】——PlayWithThePicture
https://blog.csdn.net/qq_41616984/article/details/89737428

与图片交互的程序具有多种新奇有趣的功能,flow功能可以选取图像的一部分并让其自由流动,papercut功能使得静止的二维图像变成一条条不断飘动剪纸,赋予了图像生命。操作界面清晰易懂,制作者将多种技术相互融合,实现了自己追求的效果,让读者不由期待更加完善的功能。

2.交互应用 | 音乐坊
https://mp.weixin.qq.com/s?__biz=MzU4ODc2OTUyNQ==&mid=2247483745&idx=1&sn=510050b6b4438482fdb25e41c0cdadbb&chksm=fdd6f0fbcaa179edd0ab7aa13b1922d4ff02b290b0871caad79a6df7306c603ff8112a3c1b5b&mpshare=1&scene=23&srcid=#rd

音乐可视化应用,点击屏幕不同位置可以产生多种多样的不同形状音符,同时发出不同类的声音,创建平板和小球相互碰撞后会发出鼓点的声音。程序胜在交互方式自由度高,使用者可以随心所欲创建音符,而且画面清新优美,看了介绍就忍不住想去试一试。

3.用Processing制作一个「生态瓶」
https://zhuanlan.zhihu.com/p/64726213

推荐这一篇主要是因为应用的创意非常有趣,使用者可以主动设计一个简单的生态系统,控制系统中三个物种的数量,然后观察最终得到的结果。我也曾经在生物课上制作过简易的生态瓶,因此觉得这个作品不仅仅具有娱乐价值,加以完善后会有很好的教育作用。

©️2022 CSDN 皮肤主题:大白 设计师:CSDN官方博客 返回首页

打赏作者

雁三白

你的鼓励将是我创作的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值