From Bird Flock to Class Hierarchy

edited October 2013 in Programming Questions

Hi all... I am working on an algorithm to simulate a flock of birds (in 2D). Technical matters are an issue, but most importantly, for the time being, I want to conclude to a fine class-structure, so as to be able to develop further without draw-backs. The entities the algorithm includes so far, are:

1) Bird_animation class: a class designed to take care of the animation of a single bird (wings flopping, head moving, and so on...) This class could be totally replaced by a single arrow shape, but I like being fancy.

2) Bird class Does the basic calculation, according to external and internal stimuli, affecting the movement of the bird.

3) Flock class Passes data from a total of objects to each one of them. More specifically, an arraylist of Birds exists in the Flock class. So in the Flock there is a double loop that causes all the interactions between Birds, and passes the data accordingly, in which case the Flock class should no more be called a Flock but more something like "Reactions" or "Environment".

So far, the program consists of Birds that have the same characteristics, and react to each other in the same way. But later on, I might want to add some different types of Birds, in a prey - predator game for example.

I was looking for some advice on the class hierarchy for something like that, or some advice so this whole thing could be easily extendable. If anyone has any suggestion, I would be glad to receive feedback.

Thanks in advance, Kostas.

Answers

  • Answer ✓

    Here is a suggested class hierarchy.

    uml

    The Game class would be the main class in managing any entity (e.g. bird) to appear in the game.

    All entities would ultimately inherit from the abstract class BaseEntity, this would have information common to all entities e.g. position. The MovingEntity class (inherits from BaseEntity) and would be the base class for any entity that is required to move in the game, so would have information about velocity etc. The next class, Vehicle (inherits from MovingEntity) is the base class for any entity using AI techniques (such as flocking) to control its movement.

    Every entity could have a Renderer object which is responsible for displaying the entity on screen. This class could also be the head of another inheritance hierarchy for different types of renderer.

    The final class on the diagram is the AI_Behaviour class and this houses all the steering behaviour code.

    Since the birds are flocking (a steering behaviour) they would inherit from Vehicle. Any stationary objects would inherit from BaseEntity and any moving entities not subject to steering behaviours (e.g. clouds) would inherit from MovingEntity.

    The BaseEntity class (and all child classes) would have two methods

    update(elapsed_time_since_last_update) which is used to update the entites position, speed etc.

    display() use the renderer object to display the entity.

    The Game class would have a continuous loop that would as a minimum -

    (1) loop through all the entities calling its update method

    (2) loop through all the entities to display the entity

  • I need some time to work on your answer. I will be back soon...

    thanks for your time (so far) :)

  • I think it is time to share some more details of how I have modelled the Bird class.

    Each bird has these main parameters: PVector Position; | PVector Speed; | PVector Force; | PVector Acceleration; | float Mass; | float View_distance;

    By (Force=Mass*Acceleration) I get an ever increasing speed.

    But then I introduce air Friction, which is analogous to speed, by something like (Friction=Speed*Friction_factor) but it has an opposite direction. So a steady Force finally gives a steady Speed.

    So: If a bird has no external reactions at all, it will follow a straight line, with an ever increasing speed. Friction is an external, immediate reaction (collisions could be in the same category), affecting an aspect of the bird directly.

    After that, A bird gets some other external stimuli, such as its distance from neighbouring birds, or their speeds' measure and direction. These stimuli affect the bird in a more indirect way, making it turn the vector of the Force towards the left or right.

    And that's about it as far as the mechanics are concerned. I am still experimenting on how a bird's relation to its neighbours creates attractors that affect the Force vector, but that's another story.

    After the model you suggested, I could have something like that: A BaseEntity class which holds [Position]. | A MovingEntity class which extends BaseEntity and holds [speed]. | A Vehicle class which holds [Force] and [Acceleration]. | A bird class which holds the bird type, [Mass], [Size] and some other stuff.

    I am a bit confused about the AI_Behaviour class. How can the Bird class AND the AI_Behaviour class affect the Vehicle class on the same level?

    I am a bit confused, because I used to do half the AI stuff inside the bird class, and the other half within the Flock class (which is the equivalent of Game class now).

    To be more specific, Lets say I have an arrayList of birds, within the Game class. I loop through it, gathering information about which bird can see which other bird, then I get data such as positions and speeds, then calculate the attractors based on these data and pass the attractors to the Bird class. Finally the Bird class alters its Force vector, based on these Attractors.

    This is what I was doing up to now. But it seems there might be some way to do it better.

    could you explain that abit more, based on the above example?

  • Answer ✓

    I have to confess that the design is based on my own AI for 2D Games library and is designed to be extensible. The AI_Behaviour class is equivalent to the library's AutoPilot class and is responsible for gathering information about the environment / other entities and depending on Vehicle's steering behaviours, calculate a force vector to be applied to the entity. I suggest that you look here for more information about steering behaviours.

    So how does it work assume that the method signature for the update method is

    public void update(Game game, float elapsedTime)

    and this method is in the BaseEntity, MovingEntity, Vehicle classes and possibly the Bird class. There is not a lot to do in the BaseEntity class, and in the MovingEntity class it would be simple updating the velocity and position.

    The Vehicle class will have a reference to an AI_Behaviour object. Now this class will also have a calculate method which will have a signature

    public PVector calculateForce(Game game, Vehicle entity, float elapsedTime)

    this method can use the game parameter to fetch info about the environment e.g. a list of entities close to entity and then calculate and return the force to be applied on this update cycle.

    The Vehicle update method would call the calculate method and then cap the size of the force to something sensible and use it to update its velocity and position.

    Now we come to the Bird class. This class may or may not have an update method, if it doesn't then it will use the one in Vehicle. If it does then it needs to do the flocking steering behaviour stuff in the Vehicle update then do its own thing. The update method in the Bird class would look something like this

    public void update(Game game, float elapsedTime) { super(game, elapsedTime); // execute the Vehicle update method // now anything specific to the Bird class

    It means that all the flocking calculations are done in ONE class, and that class gets any information it needs from game and the vehicle entity.

    HTH

  • Answer ✓

    Just noticed a mistake in last bit should be

    public void update(Game game, float elapsedTime) { super.update(game, elapsedTime); // execute the Vehicle update method // now anything specific to the Bird class

    Sorry about that :\">

  • edited October 2013

    Allright, here is what I have prepared, based on your directions

    class BaseEntity{ //anything that has a position
      PVector Position;
      BaseEntity(PVector inPosition){
        Position = inPosition;
      }
    }
    
    class MovingEntity extends BaseEntity{ //anything that moves
      PVector Speed;
      MovingEntity(PVector Position, PVector inSpeed){
        super(Position);
        Speed = inSpeed;
      }
    }
    
    class Vehicle extends MovingEntity{ //anything that has mass and Force
      PVector Force;
      PVector Acceleration;
      float mass;
      Vehicle(PVector Position, PVector Speed, PVector inForce, float inMass){
        super(Position, Speed);
        Force = inForce;
        mass = inMass;
      }
      void update(PVector inForce){
        Force = inForce;
        Acceleration = PVector.div(Force,mass);
        Speed.add(Acceleration);
        Position.add(Speed);
      }
    }
    
    class Bird extends Vehicle{
      Bird(PVector Position, PVector Speed, PVector Force, float Mass){
        super(Position, Speed, Force, Mass);
      }
      void show(){
        //draw bird here 
      }
    }
    

    Does that seem right so far?

    Next step is the Game class... with an arraylist of Birds.

    I am still abit confused as to how the AI is connected to the Bird and the Game, but I think it will solve itself while writing it down. :)>-

  • Answer ✓

    I have made a few changes to your code. In Java only class names start with an uppercase letter, I know Java does not enforce this, but it is a convention used by all Java developers. It does mean that if you see a word with the first letter capitalised then it represents a class. BTW constants are by convention written in all uppercase.

    I have changed velocity for speedin your code. Velocity is a directional vector i.e. points in the direction of travel, speed is the magnitude of the velocity vector. It is a small thing but it could save confusion later because at some point you will probably want to cap the bird's speed, after all its not a jet plane ;))

    You will also notice that PVector objects are created for the acceleration, velocity, position and force attributes when an entity is created and that I use v0.set(v1) rather than v0=v1. The reason v0 and v1 are PVector object references and an object reference is used by the JVM to find the location in RAM where the object is stored. In the statement v0=v1 we are copying the object reference so after this statement both v0 and v1 reference the same object, so changing v0 will change v1 (bit like having 2 handles on the same saucepan). The statement v0.set(v1) on the other-hand copies the contents (attributes) from the v1 object into the v0 object. The two-handled saucepan can cause many difficult to find logical errors, so best to avoid it. This approach will also reduce the number of PVector objects being created - always desirable because excessive object creation can reduce the applications performance.

    For instance you had

    Acceleration = PVector.div(Force,mass);

    this will create a new PVector object and assign its object reference to Acceleration - replacing the previous object reference resulting in the old Acceleration object having to be garbage collected (another costly operation). I have replaced this with

    acceleration.set(force); acceleration.div(mass);

    Any way here is the code

      class BaseEntity { //anything that has a position
        PVector position = new PVector();
    
        BaseEntity(PVector inPosition) {
          position.set(inPosition);
        }
      }
    
      class MovingEntity extends BaseEntity { //anything that moves
        PVector velocity = new PVector();
    
        MovingEntity(PVector position, PVector inVelocity) {
          super(position);
          velocity.set(inVelocity);
        }
      }
    
      class Vehicle extends MovingEntity { //anything that has mass and Force
        PVector force = new PVector();
        PVector acceleration = new PVector();
        float mass;
    
        Vehicle(PVector position, PVector velocity, PVector inForce, float inMass) {
          super(position, velocity);
          force.set(inForce);
          mass = inMass;
        }
    
        void update(PVector inForce) {
          force.set(inForce);
          acceleration.set(force);
          acceleration.div(mass);
          velocity.add(acceleration);
          position.add(velocity);
        }
      }
    
      class Bird extends Vehicle {
        Bird(PVector position, PVector velocity, PVector force, float mass) {
          super(position, velocity, force, mass);
        }
    
        void show() {
          //draw bird here
        }
      }
    
Sign In or Register to comment.