• fullscreen
  • Body.pde
  • Bullet.pde
  • Particle.pde
  • Ship.pde
  • Space.pde
  • SpaceClasses.pde
  • /* 
       Body class
       by blindfish
       www.blindfish.co.uk
       november 2009
       
       Handles generics of bodies that might float in space - i.e. position velocity etc.
       You can set gravityAffected or frictionAffected to false if you don't want an inheriting
       class to be affected by these.
       
       Note that all bodies are registered with Space.  That way there's a central location to do 
       stuff like collision detection etc. and send messages back to the bodies.
       
       See ActionScript Animation by Keith Peters for a great reference on scripted animation and 
       the simplified physics used here.
    */
    
    
    public class Body {
      float x;
      float y;
      float vx;
      float vy;
      boolean gravityAffected;
      boolean frictionAffected;
      Space space;
       
      Body(float x, float y, Space space) {
        this.x = x;
        this.y = y;
        this.vx = 0;
        this.vy = 0;
        this.space = space;    
        this.gravityAffected = true;
        this.frictionAffected = true;
      }
      
      Body(float x, float y, float vx, float vy, Space space) {
        this.x = x;
        this.y = y;
        this.vx = vx;
        this.vy = vy;
        this.space = space;
        this.gravityAffected = true;
        this.frictionAffected = true;
      }
      
    
      void move() {
        if(space.playing) {
          if(gravityAffected){
            vy += space.gravity;
          }
          if(frictionAffected){
            vx *= space.friction;
            vy *= space.friction;
          }
          
          x += vx;
          y += vy;
          
          checkConstraints();
        }
      }
      
      void checkConstraints() {
        //constraints
          if(x<0) {
            x = width;
          }
          else if(x>width) {
            x = 0;
          }
          if(y<0){
            y=height;
          }
          else if (y>height) {
            y=0;
          }
      }  
      
    }
    
    /* 
       Bullet class
       by blindfish
       www.blindfish.co.uk
       november 2009
       
       Could maybe have re-used (i.e. extended) Particle here...
    */
    
    public class Bullet extends Body {
      float maxLife;  
      int life;
      int speed;
      
      // Should probably provide an overloaded constructor to allow setting of things like speed, life etc.
      Bullet(float x, float y, float rotation, Space space) {
        super(x,y,space);
        maxLife = 45;  // governs the distance a bullet can travel
        life = (int)maxLife;
        speed = 6;
        float angle = radians(rotation);
        vx = cos(angle) * speed;
        vy = sin(angle) * speed;
        frictionAffected = false;
        
        registerDraw(this);
      }
     
     void draw() {
       move();
       if (life > 0) {
         int g = (int)((255/maxLife) * life);
         life --;
         noStroke();
         rectMode(CENTER);
         fill(0,g,0);
         rect(x,y,2,2);
       }
       else {
         removeBullet();
       }
     }
     
     // Could there be a utility method in Space to handle this?
     void removeBullet() {
       unregisterDraw(this);
       int index = space.bullets.indexOf(this);
       space.bullets.remove(index);
     }
     
      
    }
    
    /* 
       Particle class
       by blindfish
       www.blindfish.co.uk
       november 2009
       
       A rather crude system.  In theory I'm sure it would be much better
       to keep an ArrayList of particle objects to be re-used as and when 
       necessary; rather than the rather innefficient method of removing
       old particles and creating new ones as and when necessary adopted here... 
    */
    
    public class Particle extends Body {
      int decay;
      int decayFade;
      int pAlpha;
      int pSize;
      color colour;
      
      Particle(float x, float y, int decay, Space space) {
        super(x,y,space);
        colour = #00ff00;
        this.vx = random(2)-1;
        this.vy = random(2)-1;
        this.decay = decay;
        this.decayFade = 255/decay;
        this.pAlpha = 255;
        this.pSize = 1;
        registerDraw(this);
      }
      
      Particle(float x, float y, int decay, float rotation, Space space) {
        super(x,y,space);
        colour = #00ff00;
        
        float angle = radians(rotation);
        vx = (cos(angle) * -3) + random(0.4)-0.2;
        vy = (sin(angle) * -3) + random(0.4)-0.2;
        
        this.decay = decay;
        this.decayFade = 255/decay;
        this.pAlpha = 255;
        this.pSize = 1;
        registerDraw(this);
      }
      
      Particle(float x, float y, int decay, int pSize, Space space) {
        super(x,y,space);
        colour = #00ff00;
        this.vx = random(2)-1;
        this.vy = random(2)-1;
        this.decay = decay;
        this.decayFade = 255/decay;
        this.pAlpha = 255;
        this.pSize = pSize;
        registerDraw(this);
      }
    
      
      void draw() {
        move();
        if(space.playing){
          if(decay>0){
      
            noStroke();
            rectMode(CENTER);
            fill(0,pAlpha,0);
            pAlpha -= decayFade;
            rect(x,y,pSize,pSize);
            decay --;
          }
          else {
            remove();
          }
        }
      }
      
      // I'm sure it would be much more efficient to keep a store of particle objects 
      // and re-use as necessary; thereby avoiding the removal an addition of lots of objects...
      void remove() {
       unregisterDraw(this);
       int index = space.particles.indexOf(this);
       space.particles.remove(index);
     }
    }
    
    /* 
       Ship class
       by blindfish
       www.blindfish.co.uk
       november 2009
       
       Since development was rather fluid I still need to do more work to make this generic.
       For instance it may be that you prefer to allocate 'lives' and explode the ship each 
       time it is hit, rather than use the 'shields' approach used here.
       
       I'm also wondering whether I should have added ship specific key handling here rather 
       than in space; though there is some logic of doing it all in one place to avoid conflicts.
       
    */
    
    
    public class Ship extends Body {
      
      float rotation;       // current rotation of ship
      float vr;             // speed at which rotation takes place (maybe adjust as ship upgrades)
      float thrust;         // Again could adjust with upgrades...
      float reverseThrust;  
      int coolingRate;      
      int cooling;          // creates a delay between shots being fired.
      
      // for key handling
      boolean left;
      boolean right;
      boolean up;
      boolean down;
      boolean fire;
      
      boolean shieldHit;
      int shieldRadius;
      int shieldRecharge;
      int shieldRechargeRate;
      int shields;
      
      color colour = #00ff00;
      // positions to send jet and bullets from
      PVector base;
      PVector tip;
      
      Ship(float x, float y, Space space) {
        super(x,y,0,0,space);
        registerDraw(this);
        init();
      }
      
      void init() {
        x = width/2;
        y = width/2;
        vx = 0;
        vy = 0;
        vr = 2;
        thrust = 0.05;
        reverseThrust = 0.005;
        coolingRate = 30;
        cooling = coolingRate;
        shields = 2;
        shieldRadius = 26;
        shieldRecharge = 60;
        shieldRechargeRate = shieldRecharge;
        rotation = -90;
        left = false;
        right = false;
        up = false;
        down = false;
        fire = false;
      }
      
      void shipSetupOverride(float vr, float thrust, float reverseThrust, int collingRate) {
        this.vr = vr;
        this.thrust = thrust;
        this.reverseThrust = reverseThrust;
        this.coolingRate = collingRate;
      }
      
      void draw() {
        handleKeys();
        move();
        // The points that make up the ship
        tip = space.rotatePoint(10,0,rotation);
        PVector leftCorner = space.rotatePoint(-10,-5,rotation);
        PVector rightCorner = space.rotatePoint(-10,5,rotation);
        base = space.rotatePoint(-5,0,rotation);
        
        noFill();
        strokeWeight(1);
        stroke(colour);
        
        if(shields >= 0){
          beginShape();
            vertex(x + tip.x,y + tip.y);
            vertex(x + leftCorner.x, y + leftCorner.y);
            vertex(x + base.x, y + base.y);
            vertex(x + rightCorner.x, y + rightCorner.y);
            vertex(x + tip.x,y + tip.y);
          endShape();
        }
    
        if(shieldHit) {
          if(shields>=0){
            ellipseMode(CENTER);
            stroke(colour,shieldRecharge*2);
            ellipse(x,y,shieldRadius,shieldRadius);
          }
          else {
           // unregisterDraw(this);
            space.gameOver = true;
            shieldHit = false;
          }
          // if shield has recharged
          if(shieldRecharge<=0) {
            shieldHit=false;
            shieldRecharge = shieldRechargeRate;
          }
          shieldRecharge --;  
        }
        
        // crude way to limit the rate of fire
        if(cooling > 0) {
          cooling --;
        }
      }// draw() ends...
    // - - - - - - - - - - - - - - - - - - - - - - - - //
      
      void shieldHit() {
        // crude 'bounce'
        vx *= -0.5;
        vy *= -0.5;
        shields --;
        shieldHit = true;
      }
    
      void handleKeys() {
        if(shields>-1){
          if(up) {
            float angle = radians(rotation);
            float ax = cos(angle) * thrust;
            float ay = sin(angle) * thrust;
            vx += ax;
            vy += ay;
            jetStream();        
          }
          else if(down) {
            float angle = radians(rotation);
            float ax = cos(angle) * reverseThrust;
            float ay = sin(angle) * reverseThrust;
            vx -= ax;
            vy -= ay;
          }
          
          if(left) rotation -= vr;
          if(right) rotation += vr;
          
          if(fire) {
            if(cooling <= 0){
              cooling = coolingRate;
              space.bullets.add(new Bullet(x,y,rotation,space));
            }
          }
        }
      }
      
      
      // add some particles :)
      void jetStream() {
        for(int i= 0; i<3; i++) {
          space.particles.add(new Particle(x+base.x,y+base.y,15,rotation,space));
        }
      }
      
    }
    
    /* Space class
       by blindfish
       www.blindfish.co.uk
       november 2009
       
       Space is where everything happens: that includes collision detection, key handling etc.
       Since empty space would be a little boring, a ship is added automatically
       when a Space object is added; but to make a game out of this you'd need to extend 
       Space and add containers for objects that the ship interacts with - e.g. aliens.  You can 
       then iterate through these containers to perform collision detection etc.
       For example with Asteroids I did the following:
       
    public class AsteroidSpace extends Space {
    
      ArrayList asteroids;
      int numAsteroids;
    
      AsteroidSpace() {
        super();
        this.asteroids = new ArrayList();
      }
      // etc... 
    }
    
    Note that in order to register your bodies (e.g. aliens) with Space you then need to create a 
    variable of the extended type of space in the extended body:
    
    public class Asteroid extends Body {
      // PROPERTIES
      AsteroidSpace space;     // MUST override the type of space here since normal space doesn't contain asteroids... (seems like a hack to me)
      // etc...
      
      // CONSTRUCTORS
      Asteroid(float x, float y, float vx, float vy, AsteroidSpace space) {
        super(x,y,vx,vy,space);
        this.space = space;
        // etc...
      }
      // etc
    }
    
    That seems like an ugly hack to me, but it works :)
    
    Note that you SHOULD NOT remove something directly from an ArrayList container when you're iterating over it in Space.
    The approach I took was to find a way to set the Body as 'dead' and check against this in the Body's class
    and remove the object from there (thinking about it I should perhaps have created a property and method specifically for this
    purpose in Body).  So for example here's the unoptimised collision detection for bullets from Asteroids:
    
    void handleBulletCollisions() {
        for(int i=0; i<bullets.size(); i++) {
          // the problem happens here:
          for(int j=0; j<asteroids.size(); j++) {
            Bullet thisBullet = (Bullet) bullets.get(i);
            Asteroid thisAsteroid = (Asteroid) asteroids.get(j);
    
            float dx = thisBullet.x - thisAsteroid.x;
            float dy = thisBullet.y - thisAsteroid.y;
            float distSquared = dx*dx + dy*dy;
            if(distSquared < (thisAsteroid.radius*thisAsteroid.radius)) {
              // rather than directly removing the bullet we flag it to be removed:
              thisBullet.life=0;
              createExplosion(thisAsteroid.x,thisAsteroid.y,(int)(thisAsteroid.radius*0.75));
    
              // the equivalent needs to be done when removing the asteroid... in this case in the explode method
              thisAsteroid.explode();
              score+=10;
            }
          }             
        }
      }
     
    */
    
    public class Space {
      //PROPERTIES
      float gravity;
      float friction;
    
      ArrayList bullets;
      ArrayList particles;
      Ship ship;
    
      boolean playing;
      boolean menu;
      int score;
      boolean gameOver;
      int gameOverPause;
      int pauseBetweenLevels;
      
      PFont font;
      
      // CONSTRUCTOR
      Space() {
        init();
      }
      
      Space(float gravity, float friction) {
        this.gravity = gravity;
        this.friction = 1 - friction;
        init(); 
      }
      
      void init() {
        registerDraw(this);
    
        this.bullets = new ArrayList();
        this.particles = new ArrayList();
        ship = new Ship(width/2,height/2,this);
        playing = false;
        menu = true;
        registerKeyEvent(this);
        
        font = createFont("Arial", 18);
        textFont(font);
      }
    
      void startGame() {
        // useful for resetting variables to default values after previous game has finished
      }
      
      // METHODS
      void draw() {
    
    //    smooth();
    //    noStroke();
    //    fill(0,45);
    //    rectMode(CORNER);
    //    rect(0,0,width,height);  // this looks interesting :)
        
        background(0); // but I suspect this performs better
        
        if (playing) {
          handleBulletCollisions();
          handleShipCollision();
          
          if(gameOver) {
              if(gameOverPause == 240){
                // would be good to get this into the ship class somehow...
                createExplosion(ship.x,ship.y,30);
              }
              if(gameOverPause<0) {
                space.playing = false;
                space.menu = true;
                gameOver = false;
              }
              else {
                gameOverPause --;
              } 
            }
            else {
              drawHUD();
            }  
          checkForEndOfLevel();
        }
        
        else if (!menu) {     // i.e. paused
          drawPauseMenu();
        }
        else {                // display menu
          drawMainMenu();
        }
      }
      
      // - - - - - - IN-GAME METHOD  - - - - - - //
      void    handleBulletCollisions(){
        // will need to be implemeted to check collisions with whatever can be shot...
      }
          
      void handleShipCollision() {
        // will need to be implemeted to check collisions with whatever hit the ship...    
      }
      
      // other collision methods may be necessary
      
      void createExplosion(float x,float y, int numParticles) {
        for(int i=0;i<numParticles; i++) {
          particles.add(new Particle(x,y,45,2,this));
        }
      }
      
      void checkForEndOfLevel(){
        // e.g. are all aliens dead?
      }
      
      void gameOver() {
        playing = false;
        menu = true;
      }
      
      // - - - - - - MENUS - - - - - - //
      void drawHUD() {
        // e.g. lives, score etc...
      }  
      
      void drawPauseMenu() {
        fill(255);
        textAlign(CENTER);
        text("PAUSED", width/2,height/2);
      }
      
      void drawMainMenu() {
        fill(255);
        textAlign(CENTER);
        //text("score: " + score, width/2, height/2-100);
        text("press S to start", width/2,height/2-70); 
      }
      
    
      
      // - - - - - - UTILITIES - - - - - - //
      PVector rotatePoint(float x, float y, float rotation) {
        PVector rotatedPoint = new PVector();
        Float angle  = radians(rotation);
        rotatedPoint.x = cos(angle) * x - sin(angle) * y;
        rotatedPoint.y = cos(angle) * y + sin(angle) * x;
        return rotatedPoint;
      }
      
      void hide(Body body) {
        unregisterDraw(body);
      }
      
    
      public void keyEvent(KeyEvent event){
        if(playing) {
          if(event.getID() == KeyEvent.KEY_PRESSED){
            switch(event.getKeyCode()){
            case KeyEvent.VK_UP:
              if(!ship.up)    ship.up = true;
              break;
            case KeyEvent.VK_DOWN:
              if(!ship.down)  ship.down = true;
              break;
            case KeyEvent.VK_LEFT:
              if(!ship.left)  ship.left = true;
              break;
            case KeyEvent.VK_RIGHT:
              if(!ship.right) ship.right = true;
              break;
            case KeyEvent.VK_CONTROL:
            case KeyEvent.VK_SPACE:
              if(!ship.fire) ship.fire = true;
              break;  
            }
          }
    
          else if(event.getID() == KeyEvent.KEY_RELEASED){
            switch(event.getKeyCode()){
            case KeyEvent.VK_UP:
              if(ship.up) 
                ship.up = false;
              break;
            case KeyEvent.VK_DOWN:
              if(ship.down) 
                ship.down = false;
              break;
            case KeyEvent.VK_LEFT:
              if(ship.left) 
                ship.left = false;
              break;
            case KeyEvent.VK_RIGHT:
              if(ship.right) 
                ship.right = false;
              break;
            case KeyEvent.VK_CONTROL:
            case KeyEvent.VK_SPACE:
              if(ship.fire) ship.fire = false;
              break;  
            case KeyEvent.VK_P:
              playing = false;
              break; 
            }
          }
        }
        // If paused...
        else if (!playing && !menu) {
          if(event.getID() == KeyEvent.KEY_RELEASED){
            switch(event.getKeyCode()) {
            case KeyEvent.VK_P:
              playing = true;
              break;
            }  
          }
        }
        // menu controls
        else {
          if(event.getID() == KeyEvent.KEY_RELEASED){
            switch(event.getKeyCode()) {
            case KeyEvent.VK_S:
              menu = false;
              playing = true;
              startGame();
              break;
            }  
          }
        }
      }
      
      
      
    }
    
    Space space;
    
    void setup() {
      size(400,400);
      space = new Space(0,0);
    //  space = new Space(0.075,0.025); // with added gravity and friction
    //  space.ship.shipSetupOverride(3, 0.3, 0.05, 15); // override defaults to make ship navigable with gravity and friction... and speeded up rate of fire
    }
    
    
    void draw() {
      // nothing to see here...
      // I got a bit carried away implementing everything, including key-handling, in the classes.
      // I suspect it would have been better/simpler to leave some things here...
      // in the meantime Space is probably where you should start looking ;)
    }
    

    code

    tweaks (0)

    about this sketch

    This sketch is running as Java applet, exported from Processing.

    license

    advertisement

    blindfish

    Space game base classes

    Add to Faves Me Likey@! 3
    You must login/register to add this sketch to your favorites.

    A set of classes I used to create an Asteroids game. Far from perfect but in theory could be re-used to make space-based games...

    Arrow keys for control. CTRL or SPACE to fire, P to pause... But you'll need to add some aliens to make it interesting ;)

    Wolfe
    24 Feb 2011
    I don't like that game as I like your clean understandable code.
    By the way in SpaceClasses.pde (I suppose, this is main file) use "size(400,400,P2d);"
    instead of "size(400,400);"
    You need to login/register to comment.