In describing this project, space invaders was mentioned a bunch of times, and I decided that it sounded reasonably fun to work on.
I set up my game as if there would be multiple enemies who would shoot back, though in my current implementation enemies win by reaching the end, and are all simple circles, ship the ship is a triangle.
The main code is very simple, creating a game and having the win logic.
Game game;
float divisions= 50;
void setup(){
size(500,500);
game= new Game();
frameRate(30);
}
void draw(){
game.draw();
if(!game.update()){
int score= game.getScore();
game=null;
background(0);
textSize(32);
text("Game Over",width/7,height/2-50);
text("Score: "+score,width*3/7,height/2+50);
noLoop();
}
}
void keyPressed(){
game.keyPressed(key);
}
The game is also simple, just facilitating the interaction of the enemies, ship, and projectiles, and containing each. Also tracks the larger game logic like score and lives.
import java.util.Iterator;
class Game{
ArrayList<Enemy> enemies;
Ship ship;
ArrayList<Projectile> projectiles;
float timer;
float enemyRate= 500;
float levelTimer;
float levelRate= 30000;
int lives;
int score;
Game(){
enemies= new ArrayList<Enemy>();
ship= new Ship(this);
projectiles= new ArrayList<Projectile>();
timer= millis();
levelTimer= millis();
lives= 10;
}
void keyPressed(int key){
ship.keyPressed(key);
}
boolean update(){
Enemy enemy;
Iterator<Enemy> enemyIterator= enemies.iterator();
while(enemyIterator.hasNext()){
try{
enemy=enemyIterator.next();
}
catch(Exception e){
break;
}
enemy.update();
Projectile projectile;
Iterator<Projectile> projectileIterator= projectiles.iterator();
if(enemy.reachedEnd(ship)){
enemies.remove(enemyIterator);
enemies.remove(enemy);
score-=100;
lives--;
}
while(projectileIterator.hasNext()){
try{
projectile=projectileIterator.next();
}
catch(Exception e){
break;
}
if(projectile.checkImpact(enemy)){
enemies.remove(enemyIterator);
enemies.remove(enemy);
projectiles.remove(projectileIterator);
projectiles.remove(projectile);
score++;
}
}
if(levelTimer+levelRate<millis()){
enemyRate/=1.3;
score*=1.3;
levelTimer=millis();
}
}
ship.update();
for(Projectile projectile: projectiles){
projectile.update();
}
if(timer+enemyRate<millis()){
enemies.add(new Enemy(width/divisions,height/divisions,game));
timer=millis();
}
return lives>0;
}
void draw(){
background(0);
for(Enemy enemy: enemies){
enemy.draw();
}
ship.draw();
for(Projectile projectile: projectiles){
projectile.draw();
}
fill(255);
textSize(8);
text("lives: "+lives,10,10);
text("score: "+score,width-50,10);
}
void addProjectile(Projectile projectile){
projectiles.add(projectile);
}
int getScore(){
return score;
}
}
The first component class is ship, moves according to controls (z,x,’ ‘), mostly just creates projectiles
class Ship{
float x;
float step;
float y;
boolean shooting;
float sWidth;
float sHeight;
float timer;
float fireRate;
Game game;
Ship(Game game){
this.game= game;
x= width/2;
step= width/divisions;
y= width*(divisions-2)/divisions;
sWidth= width/divisions;
sHeight= height/divisions;
timer= millis();
fireRate= 250;
shooting=false;
}
void keyPressed(int key){
if(key=='x'&&x<width-sWidth){
x+= step;
}
else if(key=='z'&&x>sWidth){
x-= step;
}
else if(key==' '){
shooting=!shooting;
}
}
void update(){
if(timer+fireRate<millis()&&shooting){
shoot();
timer= millis();
}
}
void draw(){
triangle(x-sWidth/2,sHeight+y,x,y,x+sWidth/2,sHeight+y);
}
void shoot(){
game.addProjectile(new ShipProjectile(x,y));
}
float getY(){
return y;
}
}
Since in theory projectiles can be made by enemies (though collision detection would need a re-write, mostly an if instance of or separate detection logic for each projectile type.), there is a parent and child version
class Projectile{
float x;
float y;
float xVelocity;
float yVelocity;
Projectile(float x, float y){
this.x=x;
this.y=y;
}
void update(){
x+=xVelocity;
y+=yVelocity;
}
void draw(){
stroke(255);
point(x,y);
}
boolean checkImpact(Enemy enemy){
return((x-enemy.getX())*(x-enemy.getX())+(y-enemy.getY())*(y-enemy.getY()))<enemy.getR()*enemy.getR();
}
}
class ShipProjectile extends Projectile{
ShipProjectile(float x, float y){
super(x,y);
xVelocity=0;
yVelocity=-height/divisions*3/4;
}
}
The logic is the same for each, the child simply gives numbers
Last class is enemy, walks down the screen from side to side.
class Enemy{
float x;
float y;
float r;
float xVelocity;
int direction;
Game game;
Enemy(float x, float y, Game game){
this.game=game;
this.x=x;
this.y=y;
r=sqrt(width*height/(divisions*divisions));
xVelocity=r/2;
direction=1;
}
void update(){
if(r<x&&x<width-r||direction==0){
if(direction==0){
if(x<width/2){
direction=1;
}
else{
direction=-1;
}
}
x+=xVelocity*direction;
}
else if(x<=r&&direction!=0){
y+=2*r;
direction=0;
}
else if(x>=width-r&&direction!=0){
y+=2*r;
direction=0;
}
}
void draw(){
fill(255);
ellipse(x,y,r,r);
}
float getX(){
return x;
}
float getY(){
return y;
}
float getR(){
return r;
}
boolean reachedEnd(Ship ship){
return y>ship.getY();
}
}
End result: