简介

本文介绍的弹幕游戏是参考《代码本色》一书中的前四章,运用其中的向量、力以及粒子系统得以实现。

开发工具

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);
}

相关文章: