简介
本文介绍的弹幕游戏是参考《代码本色》一书中的前四章,运用其中的向量、力以及粒子系统得以实现。
开发工具
processing3
主要流程
弹幕游戏主要分为敌方(enemy)、玩家(player)以及弹幕机制,由于本文设计的游戏移除了射击元素,并且为玩家的移动加上了物理机制,躲避弹幕会变得难以操作,同时屏幕中会有两个旋涡对玩家的移动进行干扰,玩家撞到边界或陨石后速度会发生变化并扣血,hp为0游戏结束。玩家可以通过WASD四键来对飞船进行移动,移动需要消耗燃料槽,燃料槽在玩家没有操作的时候会自行补充。玩家存活的时间越长,得分越高。
飞船(玩家操控)
飞船由SpaceShip类实现,其中包含的固有属性有:质量(mass)、位置(pos)、加速度(acc)、速度(velocity)、受到推进器的推力(force)、受到旋涡的引力(totalGforce)。和现实中的物理系统一样,质量和位置决定飞船受到的引力,玩家的操纵决定飞船受到的推进力,力之和决定飞船的加速度,加速度改变速度,速度来改变位置,这样我们就可以获得一个在真空状态下,模拟真实物理系统而运动的飞船,同时我们给飞船加一个血条和燃料槽,飞船碰到边界和陨石时,飞船的速度会发生改变(方向大小之类的,这个可以自定义),血条会减少,血条为0游戏就结束。
// SpaceShip
class SpaceShip{
float mass;
PVector pos;
PVector force;
PVector velocity;
PVector acc;
PVector totalGforce;
SpaceShip(float m,int x,int y){
‘’’初始化
}
void setPosition(PVector np){
‘’’设定位置
}
void addThrust(PVector thrust){
‘’’添加推力
}
void addTotalGforce(PVector gforce){
‘’‘添加引力
}
void moveSS(int[] a){
‘’‘移动飞船
}
void boarderCollider(int[] a){
‘’’判断是否越界
}
void showSS(){
‘’’画出飞船
}
void drawShip(){
‘’’定义飞船样式
}
}
陨石
陨石的构成比较简单,初始位置(pos),速度(v)以及贴图就可以了,陨石的运动路径可以自定义,这里我直接简单的设置为直线下落。同时陨石中要有一个判断与飞船是否相撞的方法。
// Stone
class Stone{
PVector pos;
PVector v;
PImage pic;
Stone(PImage p){
‘’’初始化
}
void update(){
‘’‘更新位置
}
void showStone(){
‘’‘显示陨石
}
boolean isOut(){
‘’’判断是否出界
}
boolean isColid(PVector spos){
‘’’判断是否与飞船相撞
}
}
旋涡
旋涡是用粒子系统来实现的,原理是用长扁的椭圆绕着圆心旋转,椭圆有一定的偏转,旋转过程中椭圆的旋转半径越来越小,小到一定程度时,旧椭圆湮灭,生成一个新椭圆,约400个椭圆能到达不错的效果,但由于processing的渲染器很菜,实际过程中会导致掉帧等情况,在游戏中减少为200个。
// Particle2椭圆粒子
PVector pos=new PVector(0,0);
PVector center=new PVector(0,0);
float distance;
float od;
float theta;
float ot;
float radius1;
float radius2;
boolean first=true;
Particle2(float t,float d,PVector c){
‘’’初始化
}
void update(){
‘’’更新位置
}
void showP(){
‘’’显示粒子
}
boolean isDead(){
‘’’判断是否消亡
}
// Bhole 粒子组成旋涡
class Bhole{
int p_num=200;
PVector center;
int dist;
ArrayList<Particle2> plist=new ArrayList<Particle2>();
Bhole(PVector c,int d){
‘’’初始化
}
void show(){
‘’’显示旋涡
}
}
//定义旋涡的位置、质量、半径等。
class Planet{
float mass;
PVector pos;
int radius;
Bhole bh;
Planet(float m,int r,int x,int y){
‘’’初始化
}
void showP(){
‘’’显示旋涡
}
}
喷焰
喷焰同样使用粒子系统生成,将粒子的运动轨迹改为直线即可,同时我们使用高斯随机数来产生粒子,使得整个粒子系统呈现出的效果具有火焰的感觉。
// Particle
class Particle{
PVector pos;
PVector velocity;
PVector acc;
PVector direction;
float lifeSpan;
Particle(PVector origin,PVector vDirection){
‘’’初始化
}
void run(){
‘’’运行
}
void update(){
‘’’更新位置
}
void showParticle(){
‘’’显示粒子
}
boolean isDead(){
‘’’判断是否消亡
}
演示效果
总结
整个游戏最初的目标是做成飞船穿越在星际间的那种样式,但由于种种原因未能实现,比较遗憾。代码编写过程中比较好玩的部分就是粒子系统,我们可以通过调节粒子的运动轨迹来重现各种各样的效果,比如我在上文中实现的火焰和旋涡。
源码
// SpaceShip
class SpaceShip{
float mass;
PVector pos;
PVector force;
PVector velocity;
PVector acc;
PVector totalGforce;
SpaceShip(float m,int x,int y){
mass=m;
pos=new PVector(x,y);
force=new PVector(0,0);
velocity=new PVector(0,0);
acc=new PVector(0,0);
totalGforce=new PVector(0,0);
}
void setPosition(PVector np){
pos.x=np.x;
pos.y=np.y;
}
void addThrust(PVector thrust){
force.set(thrust);
}
void addTotalGforce(PVector gforce){
totalGforce.set(gforce);
}
void moveSS(int[] a){
PVector joinForce=PVector.add(force,totalGforce);
acc.x=joinForce.x/mass;
acc.y=joinForce.y/mass;
velocity.add(acc);
pos.add(velocity);
boarderCollider(a);
}
void boarderCollider(int[] a){
if(pos.x<0||pos.x>width||pos.y<0||pos.y>height){
velocity.x*=-1;
velocity.y*=-1;
a[0]-=10;
}
}
void showSS(){
drawShip();
}
void drawShip(){
//beginShape();
rectMode(CENTER);
noStroke();
fill(200);
triangle(pos.x,pos.y,pos.x-30,pos.y+15,pos.x-30,pos.y-15);
triangle(pos.x,pos.y,pos.x-15,pos.y+30,pos.x+15,pos.y+30);
triangle(pos.x,pos.y,pos.x-15,pos.y-30,pos.x+15,pos.y-30);
triangle(pos.x,pos.y,pos.x+30,pos.y+15,pos.x+30,pos.y-15);
fill(255);
rect(pos.x,pos.y,30,30);
fill(100);
rect(pos.x,pos.y,20,20);
fill(200);
//endShape();
}
}
// Stone
class Stone{
PVector pos;
PVector v;
PImage pic;
Stone(PImage p){
pos= new PVector(random(0,600),0);
v=new PVector(0,random(1,5));
pic=p;
}
void update(){
pos.add(v);
}
void showStone(){
fill(255);
image(pic,pos.x,pos.y);
update();
}
boolean isOut(){
if(pos.y>height)
return true;
else
return false;
}
boolean isColid(PVector spos){
PVector drect=new PVector(pos.x-spos.x,pos.y-spos.y);
float distance=drect.mag();
if(distance<50)
return true;
else
return false;
}
}
// Particle2椭圆粒子
class Particle2{
PVector pos=new PVector(0,0);
PVector center=new PVector(0,0);
float distance;
float od;
float theta;
float ot;
float radius1;
float radius2;
boolean first=true;
Particle2(float t,float d,PVector c){
theta=t;
ot=t;
distance=randomGaussian()*d;
od=distance;
pos.x=c.x+distance*cos(theta);
pos.y=c.y+distance*sin(theta);
center.set(c);
radius1=40;
radius2=8;
}
void update(){
distance-=0.5;
theta+=0.03;
pos.x=center.x+distance*cos(theta);
pos.y=center.y+distance*sin(theta);
if(distance<0){
radius1=5;
radius2=5;
}
}
void showP(){
pushStyle();
pushMatrix();
noStroke();
translate(pos.x,pos.y);
if(first){
fill(distance+100,0,distance+150,0);
first=false;}
else
fill(distance+50,0,distance+150,100-distance);
rotate(theta-PI/3);
ellipse(0,0,radius1,radius2);
popStyle();
popMatrix();
update();
}
boolean isDead(){
if(distance<15)
return true;
else
return false;
}
}
// Bhole 粒子组成旋涡
class Bhole{
int p_num=200;
PVector center;
int dist;
ArrayList<Particle2> plist=new ArrayList<Particle2>();
Bhole(PVector c,int d){
dist=d;
center=new PVector(c.x,c.y);
for(int i=1;i<p_num+1;i++){
float theta=random(0,2*PI);
plist.add(new Particle2(theta,dist,center));
}
}
void show(){
for(int i =0;i<plist.size();i++){
Particle2 p=plist.get(i);
p.showP();
if(p.isDead()){
float theta=random(0,2*PI);
plist.set(i,new Particle2(theta,dist,center));
}
}
}
}
//定义旋涡的位置、质量、半径等。
class Planet{
float mass;
PVector pos;
int radius;
Bhole bh;
Planet(float m,int r,int x,int y){
mass=m;
radius=r;
pos=new PVector(x,y);
bh=new Bhole(pos,radius);
}
void showP(){
fill(255);
ellipse(pos.x,pos.y,radius+50,radius+50);
fill(0);
ellipse(pos.x,pos.y,radius+47,radius+47);
bh.show();
}
}
// Particle
class Particle{
PVector pos;
PVector velocity;
PVector acc;
PVector direction;
float lifeSpan;
Particle(PVector origin,PVector vDirection){
pos=new PVector(origin.x,origin.y);
direction=new PVector(vDirection.x,vDirection.y);
if(vDirection.x==0)
acc=new PVector(1/30*vDirection.y,vDirection.y/100);
else{
acc=new PVector(vDirection.x/100,1/30*vDirection.x*randomGaussian());
}
velocity=new PVector(vDirection.x,vDirection.y);
lifeSpan=40;
}
void run(){
update();
showParticle();
}
void update(){
velocity.add(acc);
pos.add(velocity);
lifeSpan-=2.0;
}
void showParticle(){
noStroke();
float pc=random(0,1);
int yel=0;
if(pc>0.7)
yel=180;
if(direction.x==0){
fill(255,yel,0,lifeSpan/1.5);
ellipse(pos.x,pos.y,8,16);
}
else{
fill(255,yel,0,lifeSpan/1.5);
ellipse(pos.x,pos.y,16,8);
}
}
boolean isDead(){
if(lifeSpan<=0)
return true;
else
return false;
}
}
// Escape
import java.util.Iterator;
SpaceShip ss;
Planet p1;
Planet p3;
ArrayList<Stone> stones=new ArrayList<Stone>();
float G=6.6;
int p_num=40;
int s_num=5;
ArrayList<Particle> plist=new ArrayList<Particle>();
int[] bg_len=new int[2];
PVector sf=new PVector(0,10);
boolean gamemode=false;
boolean isOver=false;
PImage bg;
PImage yunshi;
int score=0;
void setup(){
size(601,600);
bg=loadImage("bg.png");
yunshi=loadImage("s.png");
ss=new SpaceShip(width/2,height/2,200);
p1=new Planet(50,100,400,400);
p3=new Planet(70,100,200,200);
for(int i=0;i<s_num;i++){
stones.add(new Stone(yunshi));
}
bg_len[0]=150;
bg_len[1]=150;
}
void draw(){
if(gamemode==true){
background(0);
if(!keyPressed){
ss.addThrust(new PVector(0,0));
if(bg_len[1]<150)
bg_len[1]+=1;
}
PVector tf=new PVector(0,0);
float dist1=spDist(ss.pos,p1.pos,p1.radius);
float dist2=spDist(ss.pos,p3.pos,p3.radius);
if(dist1<100)
tf.add(ug(ss.pos,p1.pos,ss.mass,p1.mass,p1.radius));
if(dist2<100)
tf.add(ug(ss.pos,p3.pos,ss.mass,p3.mass,p3.radius));
ss.addTotalGforce(tf);
ss.moveSS(bg_len);
p1.showP();
p3.showP();
Iterator<Particle> it=plist.iterator();
while(it.hasNext()){
Particle p=it.next();
p.run();
if(p.isDead()){
it.remove();
}
}
Iterator<Stone> it2=stones.iterator();
int count=0;
while(it2.hasNext()){
Stone s=it2.next();
if(s.isOut()){
it2.remove();
count++;
}
else{
if(s.isColid(ss.pos)){
bg_len[0]-=10;
ss.velocity.x*=-0.2;
ss.velocity.y=3;
it2.remove();
count++;
}else
s.showStone();
}
}
for(int i=0;i<count;i++)
stones.add(new Stone(yunshi));
ss.showSS();
if(bg_len[0]>0){
rectMode(CORNER);
fill(255);
rect(30,30,160,40);
fill(255,0,0);
rect(35,35,bg_len[0],30);
fill(255);
rect(430,30,160,40);
fill(255,100,0);
rect(435,35,bg_len[1],30);
score=int(frameRate);
}else{
gamemode=false;
isOver=true;
}
}
if(gamemode==false&&isOver==false){
background(bg);
}
if(gamemode==false&&isOver==true){
beginM();
}
}
void beginM(){
rectMode(CENTER);
noStroke();
fill(255);
textSize(32);
textAlign(CENTER);
text("Your Score is "+score, width/2, height/2+10);
textSize(64);
text("Game Over", width/2, height/2-50);
}
void keyPressed() {
switch(key) {
case 'w':
if(bg_len[1]>0){
bg_len[1]-=1;
ss.addThrust(new PVector(0,-10));
for(int i=0;i<p_num;i++){
PVector ppos = new PVector(random(ss.pos.x-10,ss.pos.x+10),ss.pos.y+30);
plist.add(new Particle( ppos,new PVector(0,(10-i%5)/5)));
}
}
break;
case 'a':
if(bg_len[1]>0){
bg_len[1]-=1;
ss.addThrust(new PVector(-10,0));
for(int i=0;i<p_num;i++){
PVector ppos = new PVector(ss.pos.x+30,random(ss.pos.y-10,ss.pos.y+10));
plist.add(new Particle( ppos,new PVector((10-i%5)/5,0)));
}
}
break;
case 's':
if(bg_len[1]>0){
bg_len[1]-=1;
ss.addThrust(new PVector(0,10));
for(int i=0;i<p_num;i++){
PVector ppos = new PVector(random(ss.pos.x-10,ss.pos.x+10),ss.pos.y-30);
plist.add(new Particle( ppos,new PVector(0,-(10-i%5)/5)));
}
}
break;
case 'd':
if(bg_len[1]>0){
bg_len[1]-=1;
ss.addThrust(new PVector(10,0));
for(int i=0;i<p_num;i++){
PVector ppos = new PVector(ss.pos.x-30,random(ss.pos.y-10,ss.pos.y+10));
plist.add(new Particle( ppos,new PVector(-(10-i%5)/5,0)));
}
}
break;
case 'r':
gamemode=true;
isOver=false;
ss=new SpaceShip(width/2,height/2,200);
bg_len[0]=150;
bg_len[1]=150;
default:
break;
}
}
void mousePressed(){
if(mouseX>=(width/2-100)&&mouseX<=(width/2+100)&&mouseY>=(height/2-25)&&mouseY<=(height/2+25)){
gamemode=true;
}
}
float spDist(PVector pos1,PVector pos2,float r){
PVector drect=new PVector(pos2.x-pos1.x,pos2.y-pos1.y);
float distance=drect.mag();
return distance;
}
PVector ug(PVector pos1,PVector pos2,float m1,float m2,float r){
PVector drect=new PVector(pos2.x-pos1.x,pos2.y-pos1.y);
float distance=drect.mag();
if(distance>10){
drect.normalize();
float gForce=(G*m1*m2)/(distance*distance);
drect.mult(gForce);
return drect;
}else
return new PVector(0,0);
}