Arraylist and inheritance

I am going to show an example where I am testing the use of inheritance.

There are three classes, one that is a ship that can be moved with the keys and shoot, the other class is in charge of shooting and finally there is the parent class that is in charge of managing an arraylist that will update all the draw methods of the child classes and add a method to remove the objects, also in the constructor it adds itself to the list and the child classes that call super() will be added to the list.

What I would like to know is if the parent class is designed correctly, but if it is not I would like to know how to improve it or design it in another way.

//prueba de veracidad
Jugador jugador1;

void settings(){
  size(640,480);
  noSmooth();
}

void setup(){
  frameRate(30);
  background(85,106,206);
  jugador1 = new Jugador();
}

void draw(){
  background(85,106,206);
  //jugador1.draw();
  Padre.draw_all();
}
//clase jugador--------------------------------------
class Jugador extends Padre{
  private float x,y,velocidad;
  private PImage grafico = loadImage("jugador.png");
  private Disparo disparo1;
  private int contador; 
  
  public Jugador(){
    super();
    imageMode(CENTER);
    this.x = 320;
    this.y = 400;
    this.velocidad = 5;
    this.contador = 0;
  }
  
  public void draw(){
    mover();
    disparar();
    image(grafico,x,y);
  }
  
  private void mover(){
    if(keyPressed && key == CODED && keyCode == LEFT){
      x -= velocidad;
    }else if(keyPressed && key == CODED && keyCode == RIGHT){
      x += velocidad;
    }
    
    if(keyPressed && key == CODED && keyCode == UP){
      y -= velocidad;
    }else if(keyPressed && key == CODED && keyCode == DOWN){
      y += velocidad;
    }
  }
  
  private void disparar(){
    contador++;
    if(keyPressed && key == 'z' && contador > 5){
      disparo1 = new Disparo(x,y); 
      contador = 0;
    }
  }

} //fin clase jugador------------------------------------
//clase disparo-----------------------------------
class Disparo extends Padre{
  private float x,y,velocidad;
  private PImage grafico = loadImage("disparo.png");
  
  public Disparo(float x,float y){
    super();
    imageMode(CENTER);
    this.x = x;
    this.y = y;
    this.velocidad = 3;
  }
  
  public void draw(){
    mover();
    eliminar();
    image(grafico,x,y);
  }
  
  private void mover(){
    y -= velocidad;
  }
  
  private void eliminar(){
    if(y < 64){
      live = false;
      //si padre.kill() esta descomentado en el metodo draw_all
      //de la clase padre este condicional no hace falta
      //solo es necesario pone la variable live en false
      if(live == false){
        kill();
      }
    }
  }
  
} //fin clase disparo---------------------------------
//clase padre-----------------------------------
static class Padre{
  private static ArrayList<Padre> lista = new ArrayList<Padre>();
  public boolean live = true;
  
  public Padre(){
    lista.add(this);
  }
  
  public void draw(){
  }
  
   public static void draw_all(){
    for(int indice = lista.size()-1;indice >= 0;indice--){
      Padre padre = lista.get(indice);
      padre.draw();
      //con esto no hace falta llamar al metodo kill() 
      //en ninguna clase hija solo hay que poner live en false
      //descomentar padre.kill() si quieres que funcione
      //padre.kill(); 
    }
  }
  
  public void kill(){
    for(int indice = lista.size()-1;indice >= 0;indice--){
      Padre padre = lista.get(indice);
      if(padre.live == false){
        lista.remove(indice);
      }
    }
  }

} //fin clase padre------------------------------

Hi @hokuto,

Don’t really want to comment your approach, but I would like to recommend you to do a bit of study on OO Design Patterns…

https://www.oodesign.com/amp/

Cheers
— mnse

2 Likes

There are many design patterns that could be used in this situation but their value depends on your understanding of OO and in particular what OO inheritance means and when to use it.

As far as I can determine from your description the classes Jugador (Player) and Disparo (Shooting) are not specialist versions of the type Padre so inheritance is not appropriate here.

Based on your scenario description I would have the following inheritance hierarchy

               Entity
                  |
             MovingEntity
      -----------------------
      |                     |
     Ship                Projectiles

The Entity class can be any game object that does not move and MovingEntity is any object that can move in the game world. In your scenario you have two types of moving object the ship and projectiles fired from the ship.

So the next part is the controller class which I will call World
This would be responsible for

  • managing a collection of all entities (a composition relationship with Entity not inheritance)
  • updating all entities
  • getting the entities to render themselves
  • collision detection
  • user input

Note: some of these might require addition user defined classes.

This explanation is based on a short scenario description and might require modification given a full description.

5 Likes

Can you show me an example of what you mean.
Basically what I want is a class that is in charge of managing the list where the objects will be added, updating the painting methods of all the objects and providing different methods such as removing objects, movement, collisions, etc.

Hi @hokuto,

you can possibly use something like a command pattern.
This is just a demonstration of how it would work … even though it is a bit overdosed for this show case…
:slight_smile:

Cheers
— mnse

PS: For your case it is almost sufficient to use the Controller and just implement update/draw for your Entities …


//-------------------------------------------
// Commands (interface)
//-------------------------------------------
public interface Command {
    void execute(Entity entity);
}

// Possible Commands to execute (ie update and draw)
class UpdateCommand implements Command {
    @Override
    public void execute(Entity entity) {
        entity.update();
    }
}

class RenderCommand implements Command {
    @Override
    public void execute(Entity entity) {
        entity.render();
    }
}

//-------------------------------------------
// Controller
// Controller for Entities (Manage Entities)
//-------------------------------------------
class Controller {
    private ArrayList<Entity> entities = new ArrayList<>();

    public void add(Entity entity) {
        entities.add(entity);
    }

    public void remove(Entity entity) {
        entities.remove(entity);
    }

    public void execute(Command command) {
        for (Entity entity : entities) {
            command.execute(entity);
        }
    }
}

//-----------------------------------------------------------
// Entity Interface (needs update and render)
public interface Entity {
    void update();
    void render();
}

//-----------------------------------------------------------
// Example Entity: Ball
// implements update and draw
class Ball implements Entity {
  PVector pos;
  PVector vel;
  float   radius;
  public Ball() {
    pos = new PVector(width/2,height/2);
    vel = new PVector(1+random(3),1+random(3));
    radius = 20;
  }
  @Override
  public void update() {
    pos.add(vel);
    if (pos.x < radius || pos.x+radius > width)
      vel.x *= -1;
    if (pos.y < radius || pos.y+radius > height)
      vel.y *= -1;
  }  
  @Override
  public void render() {
    pushStyle();
    noStroke();
    fill(0,255,0);
    ellipseMode(RADIUS);
    ellipse(pos.x,pos.y,radius,radius);
    popStyle();
  }  
}

//-----------------------------------------------------------
// Example Entity: RotateThing
// implements update and draw
class RotateThing implements Entity {
  PVector pos;
  float   angle;
  float   size;
  public RotateThing() {
    pos = new PVector(width/2,height/2);
    angle = 0;
    size  = 200;
  }

  @Override  
  public void update() {
    angle += 0.1;
  }  

  @Override
  public void render() {
    pushMatrix();
    pushStyle();    
    noStroke();
    fill(255);
    rectMode(CENTER);
    translate(pos.x,pos.y);
    rotate(angle);
    rect(0,0,size,10);
    rect(0,0,10,size);    
    popStyle();
    popMatrix();
  }  
}

//-------------------------------------------
// Main Sketch
//-------------------------------------------
Controller ctrl;
Command[] commands = {new UpdateCommand(), new RenderCommand()};

void setup() {
	size(500,500);
  ctrl = new Controller();
  // add some Balls
  for (int i=0;i<5;i++) {
    ctrl.add(new Ball());
  }
  // Add the Rotate thing
  ctrl.add(new RotateThing());
}

void draw() {
  background(0);
  // loop through possible Commands and apply it to the entities
  for (Command c : commands) {
    ctrl.execute(c);
  }  
}

ezgif.com-optimize

4 Likes

Thank you mnse.
I am going to study and transfer this code to my example to see if I can get it to work for me, possibly I will ask a question later. Greetings :slightly_smiling_face:

1 Like

If you know more ways to do it, I would like to see it to continue practicing and trying new things, I really liked the example of mnse. Greetings :slightly_smiling_face:

Difficult to make a short example but I have done my best to create a mini game that demonstrates how you might use OO in this type of program.

Here is the game in action, it is not complete as there is no game ‘end’ condition so it goes on forever. The sketch code is below and runs to ~330 lines of code.

Stage stage;
Player p;
float prevTime;

float alienDelay = 20; // The delay between alien waves in seconds
float alienTime = alienDelay;

public void setup() {
  size(600, 440);
  // Create the player in the centre of the screen
  p = new Player(this, width/2, height/2);
  // Create the stage (manager) for this players game
  stage = new Stage(this, p);
  // Add some mines to protect the player
  addMines();
  // Add the first wave of aliens
  addAlienShips(4);
  // Start the game clock
  prevTime = millis();
}


public void draw() {
  // Calculate the elapsed time since last frame in seconds
  float currTime = millis();
  float etime = (currTime - prevTime)/1000;
  prevTime = currTime; // Remember current time for next frame
  // Count down alien timer
  alienTime -= etime;
  if (alienTime < 0) { // create alien wave
    alienDelay *= 0.95f; // reduce time till next alien wave
    alienTime = alienDelay;
    addAlienShips(4);  // add a new wave
  }
  // Fire missile when possible (slight delay between firings)
  if (mousePressed) stage.fireWeapon(p);
  // Ready to update and render
  background(32);
  stage.update(etime);// Update everything
  stage.render();// render everything
  //text(""+stage.actors.size(), 20, 20);
}

public void addMines() {
  int[] n = {4, 5, 7};
  float[] od = {40, 60, 80}, av = {0.25f, -0.33f, 0.213f};
  for (int i = 0; i < n.length; i++) {
    float da = PApplet.TAU / n[i];
    for (int j = 0; j < n[i]; j++) {
      Mine m = new Mine(this, p.pos.x, p.pos.y );
      m.setOrbit( od[i], j* da, av[i]);
      stage.addActor(m);
    }
  }
}

public void addAlienShips(int n) {
  float s0, ax, vx, ay;
  for (int i = 0; i < n; i++) {
    s0 = random(1) > 0.5f ? -1 : 1;
    ay = (0.5f + random(0.05f, 0.4f) * s0) * height;
    if (random(1) > 0.5f) {
      ax = -50;
      vx = random(20, 60);
    } else {
      ax = width + 50;
      vx = -random(20, 80);
    }
    AlienShip as = new AlienShip(this, ax, ay);
    as.setVel(vx, 0);
    stage.addActor(as);
  }
}
// This class manages the game
static class Stage {
  protected PApplet pa; // the Processing sketch
  // List of all game entities
  protected ArrayList<Entity> actors = new ArrayList<Entity>();
  // the player controlled object
  protected Player player;

  public Stage(PApplet papplet, Player plyr) {
    pa = papplet;
    player = plyr;
    actors.add(player);
  }

  // Main update function called once every frame
  public void update(float etime) {
    // Perform collision detection
    collisionDetect();
    // Remove dead actors (spaceships and mines)
    // Iterate from the last entity added so that we can safely
    // use the remove method
    for (int i = actors.size() - 1; i >= 0; i--)
      if (!actors.get(i).alive) actors.remove(i);
    // Update all actors
    for (Entity e : actors) e.update(etime);
  }

  // Adds a game entity
  public void addActor(Entity a) {
    actors.add(a);
  }

  // Perform collision detection
  protected void collisionDetect() {
    int s = 1; // 1 = ignore player  ; 0 = include player
    int nbr = actors.size();
    // Standard double loop to check every entity against every other just once
    for (int i = s; i < nbr-1; i++ )
      for (int j = i + 1; j < nbr; j++) {
        Entity ei = actors.get(i), ej = actors.get(j);
        if (ei.alive && ej.alive && collide(ei, ej)) {
          ei.alive = false; // mark any entities that collise as dead
          ej.alive = false;
        }
      }
  }

  // Returns true if these two entities collide
  protected boolean collide(Entity e0, Entity e1) {
    float dx = e0.pos.x - e1.pos.x, dy = e0.pos.y - e1.pos.y;
    return dx*dx + dy*dy < e0.cr2 + e1.cr2;
  }

  // if the player can fire then create a missile
  public boolean fireWeapon(Player p) {
    if (p.canFire()) {
      Missile m = new Missile(pa, p.pos.x, p.pos.y);
      float a = PApplet.atan2(pa.mouseY - p.pos.y, pa.mouseX - p.pos.x);
      m.setAngle(a);
      m.setVel(PApplet.cos(a) * 120, PApplet.sin(a) * 120);
      actors.add(m);
      return true;
    }
    return false;
  }

  // Get all entities to render themselves.
  public void render() {
    for (Entity e : actors) e.render();
  }
}

// Base class for all entities
static abstract public class Entity {
  protected PApplet pa;
  protected PVector pos = new PVector();
  protected float cr2 = 0;     // estimated collision radius squared
  protected float angle = 0;     // rotation angle used when rendering
  protected boolean alive = true; // false would remove the entity from the game;

  public Entity(PApplet papplet) {
    this.pa = papplet;
  }

  public Entity(PApplet papplet, float x, float y) {
    this.pa = papplet;
    pos.set(x, y);
  }

  abstract public void update(float etime);
  abstract public void render();

  public void setPos(float x, float y) {
    pos.set(x, y);
  }

  public void setAngle(float ang) {
    angle = ang;
  }
}

// base class for all entities that move
static public class MovingEntity extends Entity {
  protected PVector vel = new PVector();

  public MovingEntity(PApplet papplet, float x, float y) {
    super(papplet, x, y);
  }

  public void render() {
  }

  public void update(float etime) {
    pos.set(pos.x + vel.x * etime, pos.y + vel.y * etime);
  }

  public void setVel(float x, float y) {
    vel.set(x, y);
  }
}

// The missile class
static class Missile extends MovingEntity {

  public Missile(PApplet papplet, float x, float y) {
    super(papplet, x, y);
    cr2 = 2*2;
  }

  public void update(float etime) {
    alive = (pos.x > -10 && pos.x < pa.width + 10 && pos.y > -10 && pos.y < pa.height + 10);
    super.update(etime);
  }

  public void render() {
    pa.push();
    pa.translate(pos.x, pos.y);
    pa.rotate(angle);
    pa.stroke(220, 64, 64);
    pa.strokeWeight(3);
    pa.line(-3, 0, 3, 0);
    pa.pop();
  }
}


static public class Player extends Entity {

  float timeToFire = 1; // Time before we can fire the weapon in seconds

  public Player(PApplet papplet, float x, float y) {
    super(papplet, x, y);
    cr2 = 15*15;
  }

  // Rotate the players gun to point towards mouse pointer and
  public void update(float etime) {
    angle = PApplet.atan2(pa.mouseY - pos.y, pa.mouseX - pos.x);
    timeToFire -= etime;
  }

  public boolean canFire() {
    boolean canFire = timeToFire < 0;
    if (canFire) timeToFire = 0.5f;
    return canFire;
  }

  public void render() {
    pa.push();
    pa.translate(pos.x, pos.y);
    pa.rotate(angle);
    pa.fill(180, 180, 48);
    pa.stroke(220, 220, 64);
    pa.strokeWeight(6);
    pa.line(0, 0, 16, 0);
    pa.strokeWeight(3);
    pa.ellipse(0, 0, 15, 15);
    pa.pop();
  }
}

static public class AlienShip extends MovingEntity {

  public AlienShip(PApplet papplet, float x, float y) {
    super(papplet, x, y);
    cr2 = 10*10;
  }

  public void update(float etime) {
    super.update(etime);
    alive = pos.x > -100 && pos.x < pa.width + 100;
  }

  public void render() {
    pa.push();
    pa.translate(pos.x, pos.y);
    pa.fill(210, 210, 192);
    pa.stroke(160, 0, 160);
    pa.strokeWeight(1.2f);
    pa.ellipse(0, -10, 18, 18);
    pa.fill(160, 0, 160);
    pa.ellipse(0, 0, 40, 18);
    pa.pop();
  }
}

static public class Mine extends Entity {
  // These define the angular position of the mine
  PVector orbitCenter = new PVector();
  float angPos, angVel;
  float orbitDist;

  public Mine(PApplet papplet, float x, float y) {
    super(papplet);
    orbitCenter.set(x, y);
    cr2 = 9*9;
  }

  public void setOrbit(float od, float ang, float av) {
    orbitDist = od;
    angPos = ang;
    angVel = av;
    calcCartCoords();
  }

  // Update the angular position of the mine
  public void update(float etime) {
    angPos += angVel * etime;
    calcCartCoords();
  }

  // Calculate the cartesian coordinates for the mine, they will
  // be used when rendering the mine
  private void calcCartCoords() {
    pos.x = orbitCenter.x + orbitDist * PApplet.cos(angPos);
    pos.y = orbitCenter.y + orbitDist * PApplet.sin(angPos);
  }

  public void render() {
    pa.push();
    pa.translate(pos.x, pos.y);
    pa.stroke(64, 220, 220);
    pa.strokeWeight(1.2f);
    for (int i = 0; i < 5; i++) {
      pa.rotate(PApplet.PI / 5);
      pa.line(-7, 0, 7, 0);
    }
    pa.fill(64, 180, 180);
    pa.strokeWeight(1.5f);
    pa.ellipse(0, 0, 10, 10);
    pa.pop();
  }
}
4 Likes

Thank you very much quark, an impressive example. With this I have plenty to learn everything I need, thank you very much for the help. :+1: