image.png

image.png

一、滑块

1. 建立画板,创建一个静止滑块

  1. void setup(){
  2. size(800,600);
  3. background(240);
  4. }
  5. void draw(){
  6. fill(50);
  7. rect(400,300, 80,20);
  8. }

image.png

2. 用变量代替数值

要想滑块可以跟随鼠标移动,那么滑块的x坐标是变量。后面,子弹和滑块的交互也会用到滑块的y坐标和宽度以及高度,所以,将这四个变量都设置一个变量名。然后在setup函数内初始化数值。

float barX,barY,barW,barH;

void setup(){
  size(800,600);
  background(240);
  barX = width/2;
  barY = height-60;
  barW = 80;
  barH = 20;
}

void draw(){
  fill(50);
  rect(barX,barY, barW,barH);  
}

3. 让滑块跟随鼠标动起来

这里实现的效果是,鼠标按压并拖动时,移动滑块。你也可以让滑块的x坐标跟随鼠标移动,只需要将滑块的x坐标直接设置成mouseX就可以了。

//滑块跟随鼠标移动
void mouseDragged(){
  int offset = mouseX - pmouseX;
  x+=offset;
  x = constrain(x, 0,width-w);
}

此时发现一个问题,拖动鼠标后,前一帧的画面并没有清除,此时需要在draw函数内添加一个background(240);

4. 完整代码

//滑块相关变量
float x,y,w,h;

void setup(){
  size(800,600);
  background(240);

  //初始化滑块位置
  x = width/2;
  y = height-60;
  w = 80;
  h = 20;   
}

void draw(){
  background(240);

  //显示滑块
  fill(50);
  noStroke();
  rect(x, y, w, h); 
}

//滑块跟随鼠标移动
void mouseDragged(){
  int offset = mouseX - pmouseX;
  x+=offset;
  x = constrain(x, 0,width-w);
}

二、子弹

1. 绘制静态的子弹

子弹出现的位置,在滑块的中间。那么它的初始坐标就可以相对滑块进行设置。首先,先创建控制子弹运动的变量bulletX, bulletY, bulletR。

...
//子弹相关变量
float bulletX, bulletY, bulletR;

void setup(){
  size(800,600);
  ...
  //初始化子弹位置
  bulletR = 20;
  bulletX = x + w/2;
  bulletY = y - bulletR;    
}

void draw(){
  background(240);
  ...
  //显示子弹
  fill(255,100,100);
  ellipse(bulletX, bulletY, bulletR*2,bulletR*2);
}
...

2. 让子弹运动起来

processing动画的原理跟电影一样,通过一秒钟60帧画面的持续播放,产生动态的感觉。
首先,比如,每一帧让子弹的x坐标+1,y坐标-1,子弹就会向左上方运动。
在draw函数的末尾加上:
bulletX ++;
bulletY --;
此时子弹开始运动起来。
image.png
此时,子弹每一帧的x轴的速度为1,y轴为-1。我们可以给这两个速度设置一个变量,那么就可以对速度进行控制了。

3. 设置速度变量

在程序开头,添加变量spx, spy. 并且可以在此时就初始化数值。
float spx = 1, spy = -1;
那么上一步的
bulletX ++;
bulletY --;
就可以写成:
bulletX += spx;
bulletY += spy;

4. 检测边缘并反弹

此时,子弹直接穿过边缘,然后消失掉。 我们想让它在碰到左、上、右面边缘时,反弹。那么就需要在每一帧,对小球的位置进行判断,看它是否接近边缘。对于右面墙壁来讲,就是bulletX是否大于宽度width,但由于子弹有一个半径bulletR,所以应该需要判断子弹的右边缘是否超过width,也就是bulletX-bulletR>=width。当判定结果为真时,子弹向左前方运动,也即,spy不变,spx为-spx。

  //边缘检测,并改变速度值
  if (bulletX > width-bulletR) {
    spx = -spx;
  }

同理,其余两个方向的写法为:

  //边缘检测,并改变速度值
  if (bulletX > width-bulletR || bulletX < bulletR) {
    spx = -spx;
  } else if(bulletY < bulletR){
    spy = -spy;
  }

5. 检测滑块位置并反弹

image.png
首先,要判定子弹的下边缘是否到达滑块的上边缘,也就是判定bulletY - bulletR > y。然后,判定子弹的x坐标是否在滑块的范围内。也即:

   else if (bulletY>=y - bulletR) {
    if (bulletX>=x && bulletX<=x+w) {
      spy = -spy;
    }

此时,所有的边缘判定部分如下:

  //边缘检测,并改变速度值
  if (bulletX > width-bulletR || bulletX < bulletR) {
    spx = -spx;
  } else if (bulletY < bulletR) {
    spy = -spy;
  } else if (bulletY>=y - bulletR) {
    if (bulletX>=x && bulletX<=x+w) {
      spy = -spy;
    }
  }

小球反弹.mov (179.07KB)

此时,全部代码如下:

//9:50

//滑块相关变量
float x, y, w, h;

//子弹相关变量
float bulletX, bulletY, bulletR;
float spx = 3, spy = -2;

void setup() {
  size(800, 600);

  //初始化滑块位置
  x = width/2;
  y = height-60;
  w = 80;
  h = 20;

  //初始化子弹位置
  bulletR = 20;
  bulletX = x + w/2;
  bulletY = y - bulletR;
}

void draw() {
  background(240);

  //显示滑块
  fill(50);
  noStroke();
  rect(x, y, w, h); 

  //显示子弹
  fill(255, 100, 100);
  ellipse(bulletX, bulletY, bulletR*2, bulletR*2);

  //更新子弹位置
  bulletX += spx;
  bulletY += spy;

  //边缘检测,并改变速度值
  if (bulletX > width-bulletR || bulletX < bulletR) {
    spx = -spx;
  } else if (bulletY < bulletR) {
    spy = -spy;
  } else if (bulletY>=y - bulletR) {
    if (bulletX>=x && bulletX<=x+w) {
      spy = -spy;
    }
  }
}

//滑块跟随鼠标移动
void mouseDragged() {
  int offset = mouseX - pmouseX;
  x+=offset;
  x = constrain(x, 0, width-w);
}

三、创建子弹对象

1. 模块化代码

模块化可以增加代码可读性,并且可多次复用。此处将子弹显示、更新、检测边缘写成一个函数的方式,更多的是展示前者。
模块化后,原来的draw函数分解如下:

void draw() {
  background(240);

  //显示滑块
  fill(50);
  noStroke();
  rect(x, y, w, h); 

  display();
  update();
  checkEdge();
}

void display() { 
  //显示子弹
  fill(255, 100, 100);
  ellipse(bulletX, bulletY, bulletR*2, bulletR*2);
}

void update() {
  //更新子弹位置
  bulletX += spx;
  bulletY += spy;
}

void checkEdge() {
  //边缘检测,并改变速度值
  if (bulletX > width-bulletR || bulletX < bulletR) {
    spx = -spx;
  } else if (bulletY < bulletR) {
    spy = -spy;
  } else if (bulletY>=y - bulletR) {
    if (bulletX>=x && bulletX<=x+w) {
      spy = -spy;
    }
  }
}

2. 创建一个子弹类

点击右侧小按钮,选择新建页签并命名为bullet。
image.png
此时,我们就需要创建一个烟花的类(class),然后用这个类来创建对象。

概念:类class = 数据data + 方法method

Processing有预设的class类,比如PImage,PFont。不同于int,float,boolean只能存储一个值的数据类型,class类可以存储多个。另外,还可用方法(method)来处理变量。
以显示生活中的小狗为例,它具有年龄,身高,颜色,品种等属性(变量),可以吠叫,睡觉,还会感到饥饿(方法)。

下侧代码构建了一个“狗”的(class),之后可以使用Dog类型创建具体的对象
对象:对象是类的一个实例,有状态和行为。
:类是一个模板,它描述一类对象的行为和状态。

class Dog{
  String breed;
  int age;
  String color;
  void barking(){
  }

  void hungry(){
  }

  void sleeping(){
  }
}

创建类的步骤:

  1. 创建class代码块
  2. 添加属性字段
  3. 通过构造方法来给属性字段分配值
  4. 添加方法

首先,创建一个class块,然后将子弹的属性字段放入class块内。

注意,关键字class开头小写,而Firework开头大写。添加字段时,可以先对其初始化。也可以在下一步的构造方法中对字段初始化。并添加构造方法,初始化数值。

class Bullet {
  //子弹相关变量
  float bulletX, bulletY, bulletR;
  float spx = 3, spy = -2;

  Bullet() {
    //初始化子弹位置
    bulletR = 20;
    bulletX = x + w/2;
    bulletY = y - bulletR;
  }
}

接下来,就可以添加类的方法啦

方法(method)的创建和函数(function)一样,只是方法是在类的代码块中的,因此每行开头需要缩进。
此时全部代码如下:

class Bullet {
  //子弹相关变量
  float bulletX, bulletY, bulletR;
  float spx = 3, spy = -2;

  Bullet() {
    //初始化子弹位置
    bulletR = 20;
    bulletX = x + w/2;
    bulletY = y - bulletR;
  }

  void display() { 
    //显示子弹
    fill(255, 100, 100);
    ellipse(bulletX, bulletY, bulletR*2, bulletR*2);
  }

  void update() {
    //更新子弹位置
    bulletX += spx;
    bulletY += spy;
  }

  void checkEdge() {
    //边缘检测,并改变速度值
    if (bulletX > width-bulletR || bulletX < bulletR) {
      spx = -spx;
    } else if (bulletY < bulletR) {
      spy = -spy;
    } else if (bulletY>=y - bulletR) {
      if (bulletX>=x && bulletX<=x+w) {
        spy = -spy;
      }
    }
  }
}

3. 在主函数中用子弹类创建对象

在程序开头加入Bullet bullet;
并在setup函数初始化bullet = new Bullet();
在draw函数内进行操作变换。
只需要将之前的

  display();
  update();
  checkEdge();

前面各加上bullet.
也即:

  //操作子弹对象
  bullet.display();
  bullet.update();
  bullet.checkEdge();

此时,主函数全部代码如下:

//滑块相关变量
float x, y, w, h;

//创建子弹
Bullet bullet;

void setup() {
  size(800, 600);

  //初始化滑块位置
  x = width/2;
  y = height-60;
  w = 80;
  h = 20;
  //初始化子弹
  bullet = new Bullet();
}

void draw() {
  background(240);

  //显示滑块
  fill(50);
  noStroke();
  rect(x, y, w, h); 
  //操作子弹对象
  bullet.display();
  bullet.update();
  bullet.checkEdge();
}

//滑块跟随鼠标移动
void mouseDragged() {
  int offset = mouseX - pmouseX;
  x+=offset;
  x = constrain(x, 0, width-w);
}

下面的步骤有些简略,大家可以尝试将代码copy进去看看效果。后面如有需要,我会优化步骤。

四、创建病毒类

1. 创建病毒类

同理,我们创建病毒类如下:

class Virus {
  color c = color(random(160), random(160), random(160), 150);
  float x, y, r;

  Virus(float x_, float y_, float r_) {
    x=x_;
    y=y_;
    r=r_;
  }

  void display() {
    noFill();
    stroke(c);
    strokeWeight(4);
    ellipse(x, y, 2*r, 2*r);
  }

  //判定是否和子弹交叠
  Boolean intersect(Bullet bullet) {
    float d = dist(x, y, bullet.x, bullet.y);
    if (d <= r+bullet.r) {
      return true;
    } else {
      return false;
    }
  }
}

2. 创建病毒对象数组

在程序开头加入Virus [] virus = new Virus[20];
并在setup函数用for函数遍历并初始化

  //初始化病毒数组
  for (int i=0; i<virus.length; i++) {
    virus[i] = new Virus(random(0, width), random(0, height-300), random(10, 40));
  }

在draw函数进行显示

  for (int i=0; i<virus.length; i++) {
    virus[i].display();
  }

3. 判断子弹是否接触病毒

如果子弹打中病毒,子弹需要转变方向。在bullet标签中,添加如下代码

class Bullet {
   //子弹相关变量
   ...
   maxSpeed = 4;

   ...
   ...

   void changeDirection(Virus virus) {
    //向量转化为坐标,有点抽象
    float tempSpx = bulletX-virus.x;
    float tempSpy=bulletY-virus.y;
    spx = tempSpx-spx;
    spy = tempSpy-spy;
    float speed = sqrt(spx*spx+spy*spy);
    if (speed>maxSpeed) {
      float scale = maxSpeed/speed;
      spx = spx*scale;
      spy = spy*scale;
    }
  }  
}

draw函数中添加

  for (int i=0; i<virus.length; i++) {
    if (virus[i].intersect(bullet)) {
      bomb.play();
      bullet.changeDirection(virus[i]);
      //将i元素从数组中移除
      Virus [] tempV1 = (Virus[])subset(virus, 0, i);
      Virus [] tempV2 = (Virus[]) subset(virus, i+1);
      virus=(Virus[])concat(tempV1, tempV2);
    }
  }

目前全部代码如下,向下滑动查看所有。

//滑块相关变量
float x, y, w, h;

//创建子弹
Bullet bullet;

//创建病毒数组
Virus [] virus = new Virus[20];

void setup() {
  size(800, 600);

  //初始化滑块位置
  x = width/2;
  y = height-60;
  w = 80;
  h = 20;
  //初始化子弹
  bullet = new Bullet();
  //初始化病毒数组
  for (int i=0; i<virus.length; i++) {
    virus[i] = new Virus(random(0, width), random(0, height-300), random(10, 40));
  }
}

void draw() {
  background(240);

  //显示滑块
  fill(50);
  noStroke();
  rect(x, y, w, h); 
  //操作子弹对象
  bullet.display();
  bullet.update();
  bullet.checkEdge();

  for (int i=0; i<virus.length; i++) {
    virus[i].display();
  }

  for (int i=0; i<virus.length; i++) {
    if (virus[i].intersect(bullet)) {
      bullet.changeDirection(virus[i]);
      //将i元素从数组中移除
      Virus [] tempV1 = (Virus[])subset(virus, 0, i);
      Virus [] tempV2 = (Virus[]) subset(virus, i+1);
      virus=(Virus[])concat(tempV1, tempV2);
    }
  }
}

//滑块跟随鼠标移动
void mouseDragged() {
  int offset = mouseX - pmouseX;
  x+=offset;
  x = constrain(x, 0, width-w);
}

class Bullet {
  //子弹相关变量
  float bulletX, bulletY, bulletR;
  float spx = 3, spy = -2;
  float maxSpeed = 4;

  Bullet() {
    //初始化子弹位置
    bulletR = 20;
    bulletX = x + w/2;
    bulletY = y - bulletR;
  }

  void display() { 
    //显示子弹
    fill(255, 100, 100);
    ellipse(bulletX, bulletY, bulletR*2, bulletR*2);
  }

  void update() {
    //更新子弹位置
    bulletX += spx;
    bulletY += spy;
  }

  void checkEdge() {
    //边缘检测,并改变速度值
    if (bulletX > width-bulletR || bulletX < bulletR) {
      spx = -spx;
    } else if (bulletY < bulletR) {
      spy = -spy;
    } else if (bulletY>=y - bulletR) {
      if (bulletX>=x && bulletX<=x+w) {
        spy = -spy;
      }
    }
  }

  void changeDirection(Virus virus) {
    //向量转化为坐标,有点抽象
    float tempSpx = bulletX-virus.x;
    float tempSpy=bulletY-virus.y;
    spx = tempSpx-spx;
    spy = tempSpy-spy;
    float speed = sqrt(spx*spx+spy*spy);
    if (speed>maxSpeed) {
      float scale = maxSpeed/speed;
      spx = spx*scale;
      spy = spy*scale;
    }
  }
}

class Virus {
  color c = color(random(160), random(160), random(160), 150);
  float x, y, r;

  Virus(float x_, float y_, float r_) {
    x=x_;
    y=y_;
    r=r_;
  }

  void display() {
    noFill();
    stroke(c);
    strokeWeight(4);
    ellipse(x, y, 2*r, 2*r);
  }

  Boolean intersect(Bullet bullet) {
    float d = dist(x, y, bullet.bulletX, bullet.bulletY);
    if (d <= r+bullet.bulletR) {
      return true;
    } else {
      return false;
    }
  }
}

最终的全部代码

主函数

import processing.sound.*;

SoundFile bomb;
SoundFile fail;
SoundFile pass;

float barX, barY;
float barWidth, barHeight;

Bullet bullet;
Virus [] virus = new Virus[20];

boolean gameover = false;

void setup() {
  size(800, 600);
  pixelDensity(2);    //苹果电脑启用高分辨率

  noStroke();
  barX = width/2;
  barY=height-40;
  barWidth=80;
  barHeight=20;

  bullet = new Bullet(barX, barY, barWidth);
  for (int i=0; i<virus.length; i++) {
    virus[i] = new Virus(random(0, width), random(0, height-300), random(10, 40));
  }

  //加载声音
  bomb = new SoundFile(this, "bomb.mp3");
  fail = new SoundFile(this, "fail.mp3");
  pass = new SoundFile(this, "pass.mp3");
  textAlign(CENTER);
  textSize(50);
}

void draw() {
  background(237);

  //显示反射块
  fill(55);
  noStroke();
  rect(barX, barY, barWidth, barHeight);

  //显示子弹
  bullet.display();
  bullet.update();
  bullet.checkEdge(barX, barY, barWidth);

  for (int i=0; i<virus.length; i++) {
    virus[i].display();
  }

  for (int i=0; i<virus.length; i++) {
    if (virus[i].intersect(bullet)) {
      bomb.play();
      bullet.changeDirection(virus[i]);
      //将i元素从数组中移除
      Virus [] tempV1 = (Virus[])subset(virus, 0, i);
      Virus [] tempV2 = (Virus[]) subset(virus, i+1);
      virus=(Virus[])concat(tempV1, tempV2);
    }
  }

  if (virus.length==0) {
    //显示胜利的绿色界面
    gameover = true;
    background(#2ad26f);
    pass.play();

    fill(255);
    text("Great. You win. ^_^", width/2, height/2);
    noLoop();
  }

  if (bullet.isOut()) {
    //显示红色界面
    gameover = true;
    background(#fc615d);
    fail.play();

    fill(255);
    text("Sorry. You lose. -_-", width/2, height/2);
    noLoop();
  }
}

void mouseDragged() {
  int offset=mouseX-pmouseX;
  barX+=offset;
  barX=constrain(barX, 0, width-barWidth);
}

void mouseClicked() {
  if (gameover) {
    bullet = new Bullet(barX, barY, barWidth);
    virus =(Virus[]) expand(virus, 20);
    for (int i=0; i<virus.length; i++) {
      virus[i] = new Virus(random(0, width), random(0, height-300), random(10, 40));
    }
    gameover = false;
    loop();
  }
}

Bullet

class Bullet {
  float x, y;   //坐标
  int r=15;      //半径
  float maxSpeed = 4;

  float spx=3, spy=-2;   //x和y方向的速度

  Bullet(float x_, float y_, float w_) {
    x = x_ + w_/2;
    y = y_-r;
  }

  void display() {
    fill(0);
    noStroke();
    ellipse(x, y, 2*r, 2*r);
  }

  void update() {
    x+=spx;
    y+=spy;
  }

  void checkEdge(float x_, float y_, float w_) {
    if (x>=width-r) {
      spx=-spx;
    } else if (x<=r) {
      spx=-spx;
    } else if (y<=r) {
      spy=-spy;
    } else if (y>=y_ - r && y<y_-r + spy) {
      if (x>=x_ && x<=x_+w_) {
        spy = -spy;
      }
    }
  }

  void changeDirection(Virus virus) {

    //向量转化为坐标,有点抽象
    float tempSpx = x-virus.x;
    float tempSpy=y-virus.y;
    spx = tempSpx-spx;
    spy = tempSpy-spy;
    float speed = sqrt(spx*spx+spy*spy);
    if (speed>maxSpeed) {
      float scale = maxSpeed/speed;
      spx = spx*scale;
      spy = spy*scale;
    }
  }

  boolean isOut() {
    return y > height+r ? true:false;
  }
}

Virus

class Virus {
  color c = color(random(160), random(160), random(160), 150);
  float x, y, r;

  Virus(float x_, float y_, float r_) {
    x=x_;
    y=y_;
    r=r_;
  }

  void display() {
    noFill();
    stroke(c);
    strokeWeight(4);
    ellipse(x, y, 2*r, 2*r);
  }

  Boolean intersect(Bullet bullet) {
    float d = dist(x, y, bullet.x, bullet.y);
    if (d <= r+bullet.r) {
      return true;
    } else {
      return false;
    }
  }
}