一、滑块
1. 建立画板,创建一个静止滑块
void setup(){
size(800,600);
background(240);
}
void draw(){
fill(50);
rect(400,300, 80,20);
}
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 --;
此时子弹开始运动起来。
此时,子弹每一帧的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. 检测滑块位置并反弹
首先,要判定子弹的下边缘是否到达滑块的上边缘,也就是判定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;
}
}
此时,全部代码如下:
//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。
此时,我们就需要创建一个烟花的类(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(){
}
}
创建类的步骤:
- 创建class代码块
- 添加属性字段
- 通过构造方法来给属性字段分配值
- 添加方法
首先,创建一个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;
}
}
}